]> granicus.if.org Git - php/commitdiff
Fix #77069: stream filter loses final block of data
authorChristoph M. Becker <cmbecker69@gmx.de>
Tue, 22 Sep 2020 14:02:01 +0000 (16:02 +0200)
committerChristoph M. Becker <cmbecker69@gmx.de>
Tue, 8 Dec 2020 10:47:49 +0000 (11:47 +0100)
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.

NEWS
ext/standard/tests/streams/bug77069.phpt [new file with mode: 0644]
ext/standard/tests/streams/bug77080.phpt [new file with mode: 0644]
ext/standard/tests/streams/bug79984.phpt [new file with mode: 0644]
ext/zlib/tests/bug48725_2.phpt [new file with mode: 0644]
ext/zlib/zlib_filter.c
main/streams/streams.c

diff --git a/NEWS b/NEWS
index b242d304154fbc437899ffa2d17dfc2168648066..0de562eeaff04c92ce9a023bac3ffc1081875090 100644 (file)
--- 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 (file)
index 0000000..ec78ac2
--- /dev/null
@@ -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 (file)
index 0000000..feb2065
--- /dev/null
@@ -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 (file)
index 0000000..7126458
--- /dev/null
@@ -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 (file)
index 0000000..168afd8
--- /dev/null
@@ -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
index 3654a140fd5a66c528ebddf3368d9a6869efe07a..3c19d380169a1f8b94d75c62910811729ba5f7e3 100644 (file)
@@ -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 */
index cf411a1dd375185a462a7fea35a44fa65fc48c74..ab413872e0bd41a5a22ad474ea1265669ff96adf 100644 (file)
@@ -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;
                        }