summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph M. Becker <cmbecker69@gmx.de>2020-09-22 16:02:01 +0200
committerChristoph M. Becker <cmbecker69@gmx.de>2020-12-08 11:47:49 +0100
commit65f5573bc82108bbaf2727ffa11575f3292d736f (patch)
tree6dc2ff7610878ca2d1518d33da67e73ee6951af1
parentbd093ad8615267ae4ff1a237e6285dc182a9ff57 (diff)
downloadphp-git-65f5573bc82108bbaf2727ffa11575f3292d736f.tar.gz
Fix #77069: stream filter loses final block of data
Reading from a stream may return greater than zero, but nonetheless the stream's EOF flag may have been set. We have to cater to this condition by setting the close flag for filters. We also have to cater to that change in the zlib.inflate filter: If `inflate()` is called with flush mode `Z_FINISH`, but the output buffer is not large enough to inflate all available data, it fails with `Z_BUF_ERROR`. However, `Z_BUF_ERROR` is not fatal; in fact, the zlib manual states: "If deflate returns with Z_OK or Z_BUF_ERROR, this function must be called again with Z_FINISH and more output space (updated avail_out) but no more input data, until it returns with Z_STREAM_END or an error." Hence, we do so. Closes GH-6001.
-rw-r--r--NEWS1
-rw-r--r--ext/standard/tests/streams/bug77069.phpt57
-rw-r--r--ext/standard/tests/streams/bug77080.phpt16
-rw-r--r--ext/standard/tests/streams/bug79984.phpt57
-rw-r--r--ext/zlib/tests/bug48725_2.phpt15
-rw-r--r--ext/zlib/zlib_filter.c2
-rw-r--r--main/streams/streams.c2
7 files changed, 148 insertions, 2 deletions
diff --git a/NEWS b/NEWS
index b242d30415..0de562eeaf 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,7 @@ PHP NEWS
. Fixed bug #80393 (Build of PHP extension fails due to configuration gap
with libtool). (kir dot morozov at gmail dot com)
. Fixed bug #80402 (configure filtering out -lpthread). (Nikita)
+ . Fixed bug #77069 (stream filter loses final block of data). (cmb)
- Fileinfo:
. Fixed bug #77961 (finfo_open crafted magic parsing SIGABRT). (cmb)
diff --git a/ext/standard/tests/streams/bug77069.phpt b/ext/standard/tests/streams/bug77069.phpt
new file mode 100644
index 0000000000..ec78ac25c5
--- /dev/null
+++ b/ext/standard/tests/streams/bug77069.phpt
@@ -0,0 +1,57 @@
+--TEST--
+Bug #77069 (stream filter loses final block of data)
+--FILE--
+<?php
+class MyFilter extends php_user_filter {
+ private $data = '';
+
+ public function filter($in, $out, &$consumed, $closing) {
+ $return = PSFS_FEED_ME;
+
+ // While input data is available, continue to read it.
+ while ($bucket_in = stream_bucket_make_writeable($in)) {
+ $this->data .= $bucket_in->data;
+ $consumed += $bucket_in->datalen;
+
+ // Process whole lines.
+ while (preg_match('/(.*?)[\r\n]+(.*)/s', $this->data, $match) === 1) {
+ list(, $data, $this->data) = $match;
+ // Send this record output.
+ $data = strrev($data) . PHP_EOL;
+ $bucket_out = stream_bucket_new($this->stream, $data);
+ $return = PSFS_PASS_ON;
+ stream_bucket_append($out, $bucket_out);
+ }
+ }
+
+ // Process the final line.
+ if ($closing && $this->data !== '') {
+ $data = strrev($this->data) . PHP_EOL;
+ $bucket_out = stream_bucket_new($this->stream, $data);
+ $return = PSFS_PASS_ON;
+ stream_bucket_append($out, $bucket_out);
+ }
+
+ return $return;
+ }
+}
+
+stream_filter_register('my-filter', 'MyFilter');
+
+$input = "Line one\nLine two\nLine three";
+
+$stream = fopen('data://text/plain,' . $input, 'r');
+stream_filter_append($stream, 'my-filter');
+
+$output = '';
+while (!feof($stream)) {
+ $output .= fread($stream, 16);
+}
+fclose($stream);
+
+echo $output;
+?>
+--EXPECT--
+eno eniL
+owt eniL
+eerht eniL
diff --git a/ext/standard/tests/streams/bug77080.phpt b/ext/standard/tests/streams/bug77080.phpt
new file mode 100644
index 0000000000..feb20656d6
--- /dev/null
+++ b/ext/standard/tests/streams/bug77080.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Bug #77080 (Deflate not working)
+--SKIPIF--
+<?php
+if (!extension_loaded('zlib')) die('skip zlib extension not available');
+?>
+--FILE--
+<?php
+$string = str_repeat("0123456789", 100);
+$stream = fopen('data://text/plain,' . $string,'r');
+stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_READ, 6);
+$compressed = stream_get_contents($stream);
+var_dump(gzinflate($compressed) === $string);
+?>
+--EXPECT--
+bool(true)
diff --git a/ext/standard/tests/streams/bug79984.phpt b/ext/standard/tests/streams/bug79984.phpt
new file mode 100644
index 0000000000..7126458fff
--- /dev/null
+++ b/ext/standard/tests/streams/bug79984.phpt
@@ -0,0 +1,57 @@
+--TEST--
+Bug #79984 (Stream filter is not called with closing arg)
+--FILE--
+<?php
+
+class F extends php_user_filter
+{
+ public function onCreate()
+ {
+ echo 'filter onCreate' . PHP_EOL;
+ return true;
+ }
+
+ public function onClose()
+ {
+ echo 'filter onClose' . PHP_EOL;
+ }
+
+ public function filter($in, $out, &$consumed, $closing)
+ {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ $bucket->data = strtoupper($bucket->data);
+ $consumed += $bucket->datalen;
+ stream_bucket_append($out, $bucket);
+ }
+ echo 'filtered ' . ($consumed ? $consumed : 0) . ' bytes';
+ if ($closing) {
+ echo ' and closing.';
+ } else {
+ echo '.';
+ }
+ if (feof($this->stream)) {
+ echo ' Stream has reached end-of-file.';
+ }
+ echo PHP_EOL;
+ return PSFS_PASS_ON;
+ }
+}
+
+stream_filter_register('f', 'F');
+
+$str = str_repeat('a', 8320);
+
+$f2 = fopen('php://temp', 'r+b');
+fwrite($f2, $str);
+fseek($f2, 0, SEEK_SET);
+stream_filter_append($f2, 'f', STREAM_FILTER_READ);
+var_dump(strlen(stream_get_contents($f2)));
+fclose($f2);
+
+?>
+--EXPECT--
+filter onCreate
+filtered 8192 bytes.
+filtered 128 bytes and closing.
+int(8320)
+filter onClose
diff --git a/ext/zlib/tests/bug48725_2.phpt b/ext/zlib/tests/bug48725_2.phpt
new file mode 100644
index 0000000000..168afd8d3d
--- /dev/null
+++ b/ext/zlib/tests/bug48725_2.phpt
@@ -0,0 +1,15 @@
+--TEST--
+Bug #48725 (Support for flushing in zlib stream)
+--SKIPIF--
+<?php
+if (!extension_loaded('zlib')) die('skip zlib extension not available');
+?>
+--FILE--
+<?php
+$stream = fopen('data://text/plain;base64,' . base64_encode('Foo bar baz'),
+'r');
+stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_READ);
+print bin2hex(stream_get_contents($stream));
+?>
+--EXPECT--
+72cbcf57484a2c02e22a00000000ffff0300
diff --git a/ext/zlib/zlib_filter.c b/ext/zlib/zlib_filter.c
index 3654a140fd..3c19d38016 100644
--- a/ext/zlib/zlib_filter.c
+++ b/ext/zlib/zlib_filter.c
@@ -90,7 +90,7 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
inflateEnd(&(data->strm));
data->finished = '\1';
exit_status = PSFS_PASS_ON;
- } else if (status != Z_OK) {
+ } else if (status != Z_OK && status != Z_BUF_ERROR) {
/* Something bad happened */
php_stream_bucket_delref(bucket);
/* reset these because despite the error the filter may be used again */
diff --git a/main/streams/streams.c b/main/streams/streams.c
index cf411a1dd3..ab413872e0 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -567,7 +567,7 @@ PHPAPI int _php_stream_fill_read_buffer(php_stream *stream, size_t size)
/* after this call, bucket is owned by the brigade */
php_stream_bucket_append(brig_inp, bucket);
- flags = PSFS_FLAG_NORMAL;
+ flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_NORMAL;
} else {
flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
}