Skip to content

Commit

Permalink
Fix #77069: stream filter loses final block of data
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cmb69 committed Dec 8, 2020
1 parent bd093ad commit 65f5573
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 2 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
57 changes: 57 additions & 0 deletions ext/standard/tests/streams/bug77069.phpt
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions ext/standard/tests/streams/bug77080.phpt
Original file line number Diff line number Diff line change
@@ -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)
57 changes: 57 additions & 0 deletions ext/standard/tests/streams/bug79984.phpt
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions ext/zlib/tests/bug48725_2.phpt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion ext/zlib/zlib_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 65f5573

Please sign in to comment.