From 65f5573bc82108bbaf2727ffa11575f3292d736f Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 22 Sep 2020 16:02:01 +0200 Subject: 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. --- NEWS | 1 + ext/standard/tests/streams/bug77069.phpt | 57 ++++++++++++++++++++++++++++++++ ext/standard/tests/streams/bug77080.phpt | 16 +++++++++ ext/standard/tests/streams/bug79984.phpt | 57 ++++++++++++++++++++++++++++++++ ext/zlib/tests/bug48725_2.phpt | 15 +++++++++ ext/zlib/zlib_filter.c | 2 +- main/streams/streams.c | 2 +- 7 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/streams/bug77069.phpt create mode 100644 ext/standard/tests/streams/bug77080.phpt create mode 100644 ext/standard/tests/streams/bug79984.phpt create mode 100644 ext/zlib/tests/bug48725_2.phpt 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-- +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-- + +--FILE-- + +--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-- +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-- + +--FILE-- + +--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; } -- cgit v1.2.1