Skip to content

Commit 65f5573

Browse files
committed
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.
1 parent bd093ad commit 65f5573

File tree

7 files changed

+148
-2
lines changed

7 files changed

+148
-2
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ PHP NEWS
1313
. Fixed bug #80393 (Build of PHP extension fails due to configuration gap
1414
with libtool). (kir dot morozov at gmail dot com)
1515
. Fixed bug #80402 (configure filtering out -lpthread). (Nikita)
16+
. Fixed bug #77069 (stream filter loses final block of data). (cmb)
1617

1718
- Fileinfo:
1819
. Fixed bug #77961 (finfo_open crafted magic parsing SIGABRT). (cmb)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
Bug #77069 (stream filter loses final block of data)
3+
--FILE--
4+
<?php
5+
class MyFilter extends php_user_filter {
6+
private $data = '';
7+
8+
public function filter($in, $out, &$consumed, $closing) {
9+
$return = PSFS_FEED_ME;
10+
11+
// While input data is available, continue to read it.
12+
while ($bucket_in = stream_bucket_make_writeable($in)) {
13+
$this->data .= $bucket_in->data;
14+
$consumed += $bucket_in->datalen;
15+
16+
// Process whole lines.
17+
while (preg_match('/(.*?)[\r\n]+(.*)/s', $this->data, $match) === 1) {
18+
list(, $data, $this->data) = $match;
19+
// Send this record output.
20+
$data = strrev($data) . PHP_EOL;
21+
$bucket_out = stream_bucket_new($this->stream, $data);
22+
$return = PSFS_PASS_ON;
23+
stream_bucket_append($out, $bucket_out);
24+
}
25+
}
26+
27+
// Process the final line.
28+
if ($closing && $this->data !== '') {
29+
$data = strrev($this->data) . PHP_EOL;
30+
$bucket_out = stream_bucket_new($this->stream, $data);
31+
$return = PSFS_PASS_ON;
32+
stream_bucket_append($out, $bucket_out);
33+
}
34+
35+
return $return;
36+
}
37+
}
38+
39+
stream_filter_register('my-filter', 'MyFilter');
40+
41+
$input = "Line one\nLine two\nLine three";
42+
43+
$stream = fopen('data://text/plain,' . $input, 'r');
44+
stream_filter_append($stream, 'my-filter');
45+
46+
$output = '';
47+
while (!feof($stream)) {
48+
$output .= fread($stream, 16);
49+
}
50+
fclose($stream);
51+
52+
echo $output;
53+
?>
54+
--EXPECT--
55+
eno eniL
56+
owt eniL
57+
eerht eniL
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Bug #77080 (Deflate not working)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('zlib')) die('skip zlib extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$string = str_repeat("0123456789", 100);
10+
$stream = fopen('data://text/plain,' . $string,'r');
11+
stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_READ, 6);
12+
$compressed = stream_get_contents($stream);
13+
var_dump(gzinflate($compressed) === $string);
14+
?>
15+
--EXPECT--
16+
bool(true)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
Bug #79984 (Stream filter is not called with closing arg)
3+
--FILE--
4+
<?php
5+
6+
class F extends php_user_filter
7+
{
8+
public function onCreate()
9+
{
10+
echo 'filter onCreate' . PHP_EOL;
11+
return true;
12+
}
13+
14+
public function onClose()
15+
{
16+
echo 'filter onClose' . PHP_EOL;
17+
}
18+
19+
public function filter($in, $out, &$consumed, $closing)
20+
{
21+
while ($bucket = stream_bucket_make_writeable($in)) {
22+
$bucket->data = strtoupper($bucket->data);
23+
$consumed += $bucket->datalen;
24+
stream_bucket_append($out, $bucket);
25+
}
26+
echo 'filtered ' . ($consumed ? $consumed : 0) . ' bytes';
27+
if ($closing) {
28+
echo ' and closing.';
29+
} else {
30+
echo '.';
31+
}
32+
if (feof($this->stream)) {
33+
echo ' Stream has reached end-of-file.';
34+
}
35+
echo PHP_EOL;
36+
return PSFS_PASS_ON;
37+
}
38+
}
39+
40+
stream_filter_register('f', 'F');
41+
42+
$str = str_repeat('a', 8320);
43+
44+
$f2 = fopen('php://temp', 'r+b');
45+
fwrite($f2, $str);
46+
fseek($f2, 0, SEEK_SET);
47+
stream_filter_append($f2, 'f', STREAM_FILTER_READ);
48+
var_dump(strlen(stream_get_contents($f2)));
49+
fclose($f2);
50+
51+
?>
52+
--EXPECT--
53+
filter onCreate
54+
filtered 8192 bytes.
55+
filtered 128 bytes and closing.
56+
int(8320)
57+
filter onClose

ext/zlib/tests/bug48725_2.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Bug #48725 (Support for flushing in zlib stream)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('zlib')) die('skip zlib extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$stream = fopen('data://text/plain;base64,' . base64_encode('Foo bar baz'),
10+
'r');
11+
stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_READ);
12+
print bin2hex(stream_get_contents($stream));
13+
?>
14+
--EXPECT--
15+
72cbcf57484a2c02e22a00000000ffff0300

ext/zlib/zlib_filter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ static php_stream_filter_status_t php_zlib_inflate_filter(
9090
inflateEnd(&(data->strm));
9191
data->finished = '\1';
9292
exit_status = PSFS_PASS_ON;
93-
} else if (status != Z_OK) {
93+
} else if (status != Z_OK && status != Z_BUF_ERROR) {
9494
/* Something bad happened */
9595
php_stream_bucket_delref(bucket);
9696
/* reset these because despite the error the filter may be used again */

main/streams/streams.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ PHPAPI int _php_stream_fill_read_buffer(php_stream *stream, size_t size)
567567
/* after this call, bucket is owned by the brigade */
568568
php_stream_bucket_append(brig_inp, bucket);
569569

570-
flags = PSFS_FLAG_NORMAL;
570+
flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_NORMAL;
571571
} else {
572572
flags = stream->eof ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC;
573573
}

0 commit comments

Comments
 (0)