Skip to content

Commit 963e50c

Browse files
committed
Fix #75776: Flushing streams with compression filter is broken
First, the `bzip2.compress` filter has the same issue as `zlib.deflate` so we port the respective fix[1] to ext/bz2. Second, there is still an issue, if a stream with an attached compression filter is flushed before it is closed, without any writes in between. In that case, the compression is never finalized. We fix this by enforcing a `_php_stream_flush()` with the `closing` flag set in `_php_stream_free()`, whenever a write filter is attached. This call is superfluous for most write filters, but does not hurt, even when it is unnecessary. [1] <http://git.php.net/?p=php-src.git;a=commit;h=20e75329f2adb11dd231852c061926d0e4080929> Closes GH-6703.
1 parent 073b6ea commit 963e50c

File tree

5 files changed

+68
-4
lines changed

5 files changed

+68
-4
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ PHP NEWS
55
- Core:
66
. Fixed bug #80781 (Error handler that throws ErrorException infinite loop).
77
(Nikita)
8+
. Fixed bug #75776 (Flushing streams with compression filter is broken). (cmb)
89

910
- Intl:
1011
. Fixed bug #80763 (msgfmt_format() does not accept DateTime references).

ext/bz2/bz2_filter.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef struct _php_bz2_filter_data {
4141
enum strm_status status; /* Decompress option */
4242
unsigned int small_footprint : 1; /* Decompress option */
4343
unsigned int expect_concatenated : 1; /* Decompress option */
44+
unsigned int is_flushed : 1; /* only for compression */
4445

4546
int persistent;
4647
} php_bz2_filter_data;
@@ -228,14 +229,18 @@ static php_stream_filter_status_t php_bz2_compress_filter(
228229
bucket = php_stream_bucket_make_writeable(buckets_in->head);
229230

230231
while (bin < bucket->buflen) {
232+
int flush_mode;
233+
231234
desired = bucket->buflen - bin;
232235
if (desired > data->inbuf_len) {
233236
desired = data->inbuf_len;
234237
}
235238
memcpy(data->strm.next_in, bucket->buf + bin, desired);
236239
data->strm.avail_in = desired;
237240

238-
status = BZ2_bzCompress(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : (flags & PSFS_FLAG_FLUSH_INC ? BZ_FLUSH : BZ_RUN));
241+
flush_mode = flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : (flags & PSFS_FLAG_FLUSH_INC ? BZ_FLUSH : BZ_RUN);
242+
data->is_flushed = flush_mode != BZ_RUN;
243+
status = BZ2_bzCompress(&(data->strm), flush_mode);
239244
if (status != BZ_RUN_OK && status != BZ_FLUSH_OK && status != BZ_FINISH_OK) {
240245
/* Something bad happened */
241246
php_stream_bucket_delref(bucket);
@@ -261,11 +266,12 @@ static php_stream_filter_status_t php_bz2_compress_filter(
261266
php_stream_bucket_delref(bucket);
262267
}
263268

264-
if (flags & PSFS_FLAG_FLUSH_CLOSE) {
269+
if (flags & PSFS_FLAG_FLUSH_CLOSE || ((flags & PSFS_FLAG_FLUSH_INC) && !data->is_flushed)) {
265270
/* Spit it out! */
266271
status = BZ_FINISH_OK;
267272
while (status == BZ_FINISH_OK) {
268-
status = BZ2_bzCompress(&(data->strm), BZ_FINISH);
273+
status = BZ2_bzCompress(&(data->strm), (flags & PSFS_FLAG_FLUSH_CLOSE ? BZ_FINISH : BZ_FLUSH));
274+
data->is_flushed = 1;
269275
if (data->strm.avail_out < data->outbuf_len) {
270276
size_t bucketlen = data->outbuf_len - data->strm.avail_out;
271277

@@ -381,6 +387,7 @@ static php_stream_filter *php_bz2_filter_create(const char *filtername, zval *fi
381387
}
382388

383389
status = BZ2_bzCompressInit(&(data->strm), blockSize100k, 0, workFactor);
390+
data->is_flushed = 1;
384391
fops = &php_bz2_compress_ops;
385392
} else {
386393
status = BZ_DATA_ERROR;

ext/bz2/tests/bug75776.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Bug #75776 (Flushing streams with compression filter is broken)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('bz2')) die('skip bz2 extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$text = str_repeat('0123456789abcdef', 1000);
10+
11+
$temp = fopen('php://temp', 'r+');
12+
stream_filter_append($temp, 'bzip2.compress', STREAM_FILTER_WRITE);
13+
fwrite($temp, $text);
14+
15+
rewind($temp);
16+
17+
var_dump(bin2hex(stream_get_contents($temp)));
18+
var_dump(ftell($temp));
19+
20+
fclose($temp);
21+
?>
22+
--EXPECT--
23+
string(144) "425a68343141592653599fe7bbbf0001f389007fe03f002000902980026826aa80003ea9061520c6a41954833a9069520d6a41b54837a9071520e6a41d5483ba9079520f6a41f548"
24+
int(72)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Bug #75776 (Flushing streams with compression filter is broken)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('zlib')) die('skip zlib extension not available');
6+
if (!extension_loaded('bz2')) die('skip bz2 extension not available');
7+
?>
8+
--FILE--
9+
<?php
10+
$compression = [
11+
'gz' => ['zlib.deflate', 'gzinflate'],
12+
'bz2' => ['bzip2.compress', 'bzdecompress']
13+
];
14+
foreach ($compression as $ext => [$filter, $function]) {
15+
$stream = fopen(__DIR__ . "/75776.$ext", 'w');
16+
stream_filter_append($stream, $filter);
17+
fwrite($stream,"sdfgdfg");
18+
fflush($stream);
19+
fclose($stream);
20+
21+
$compressed = file_get_contents(__DIR__ . "/75776.$ext");
22+
var_dump($function($compressed));
23+
}
24+
?>
25+
--EXPECT--
26+
string(7) "sdfgdfg"
27+
string(7) "sdfgdfg"
28+
--CLEAN--
29+
<?php
30+
@unlink(__DIR__ . "/75776.gz");
31+
@unlink(__DIR__ . "/75776.bz2");
32+
?>

main/streams/streams.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ fprintf(stderr, "stream_free: %s:%p[%s] preserve_handle=%d release_cast=%d remov
445445
(close_options & PHP_STREAM_FREE_RSRC_DTOR) == 0);
446446
#endif
447447

448-
if (stream->flags & PHP_STREAM_FLAG_WAS_WRITTEN) {
448+
if (stream->flags & PHP_STREAM_FLAG_WAS_WRITTEN || stream->writefilters.head) {
449449
/* make sure everything is saved */
450450
_php_stream_flush(stream, 1);
451451
}

0 commit comments

Comments
 (0)