Bug #71396 [NEW]: zlib.compress may generate unexpected ZLIB Compress data depending on a paramer
| From: | salsi at icosaedro dot it | Date: | Sun, 17 Jan 2016 10:58:35 +0000 |
| Subject: | Bug #71396 [NEW]: zlib.compress may generate unexpected ZLIB Compress data depending on a paramer | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-198730@lists.php.net to get a copy of this message | ||
From: salsi at icosaedro dot it
Operating system:
PHP version: master-Git-2016-01-17 (Git)
Package: Streams related
Bug Type: Bug
Bug description:zlib.compress may generate unexpected ZLIB Compress data depending on a paramer
Description:
------------
The zlib.compress stream filter may generate 2 completely different
streams of compressed data depending on the presence of the 'window'
parameter, possibly generating unexpected formatted data.
To be more specific:
- If the 'window' parameter is missing, the resulting compressed stream
is perfectly compliant with RFC 1951 DEFLATE and can be decompressed
succesfully with the gzinflate() function (gzuncompress() would trigger
E_WARNING instead).
- On the contrary, if the 'window' parameter is set, the resulting
compressed stream is compliant with RFC 1950 ZLIB Compress and can be
succesfully decompressed with the gzuncompress() function (gzinflate()
would trigger E_WARNING instead). The resulting stream has then the
following structure:
ZLIB_COMPRESS = HEADER(2bytes) DEFLATE ADLER32(4bytes)
Note how the DEFLATE data are contained inside a ZLIB Compress data, but
the 2 formats are quite different.
The following script tests all the cases with any combination of the
parameters. The resulting compressed data are checked for compliance
with the specifications (see comments in the isZlibCompress()
function).
The page http://php.net/manual/it/filters.compression.php
does not
clarify very much what is happening here and what the zlib.compress
filter should do, but certainly if there are 2 sets of functions for 2
quite different compressed formats, there should also be 2 stream
filters with different names. For example (its only an idea):
- deflate.compress and deflate.uncompress implementing RFC 1951
(DEFLATE).
- zlib.compress and zlib.uncompress implementing RFC 1950 (ZLIB
Compress).
See also bug #68556 about the zlib.inflate filter.
Test script:
---------------
<?php
/*
* The zlib.compress filter writes DEFLATE (RFC 1951) if no
* 'window' parameter, and writes ZLIB Compress (RFC 1950) if that
* parameter is set. Either to be documented, or better having 2
* different filters for 2 different formats.
* Tested on: PHP 5.6.3 and PHP 7.1.0-dev from git 2016-01-17.
* See also: zlib.compress fails on empty data,
* https://bugs.php.net/bug.php?id=71395
*/
// Set a safe test environment:
error_reporting(-1);
// maps errors to ErrorException:
function my_error_handler($errno, $message)
{ throw new ErrorException($message); }
set_error_handler("my_error_handler");
/**
* Detect if the passed data is a possible ZLIB Compress stream
* of bytes.
* References: RFC1950 2.2, see check bits field.
* @param string $data Random bytes to test. If less that 7,
* always return FALSE.
* @return boolean TRUE if the $data is a possible ZLIB Compress
* stream, FALSE means it is certainly NOT a ZLIB Compress stream
* or less than 7 byte were passed.
*/
function isZlibCompress($data) {
// HEADER (2), DEFLATE (1+), ADLER32 (4) >= 7 bytes:
if( strlen($data) < 7 )
return FALSE;
// First 2 B big-endian must be multiple of 31:
$CMF = ord($data[0]);
$FLG = ord($data[1]);
if( ($CMF * 256 + $FLG) % 31 != 0 )
return FALSE;
// Compression method = 2, window size <= 7:
$CM = $CMF & 0xf;
$CINFO = $CMF >> 4;
if( !( $CM == 8 && $CINFO <= 7) )
return FALSE;
return TRUE;
}
/**
* Test zlib.compress filter. The plain data are written to file
* using the zlib.compress stream filter and the specified
* parameters, then the file is read back and compared with the
* gzdeflate() and the gzcompress() functions counterparts. Also
* tryes to detect the resulting actual encoding generated
* by the filter.
* Reference: {@link http://php.net/manual/it/filters.compression.php}
* @param string $plain Plain data.
* @param int[string] $params Compression parameters: level, window,
memory.
* @throws ErrorException
*/
function testZlibFilter($plain, $params = array()) {
echo "\nTesting data: ", rawurlencode($plain), ", params = ",
var_export($params), ":\n";
$fn = "test.deflate";
// Compress with zlib.compress filter:
$f = fopen($fn, "wb");
stream_filter_append($f, 'zlib.deflate', STREAM_FILTER_WRITE,
$params);
fwrite($f, $plain);
fclose($f);
// Read back the compressed file:
$compressed_with_filter = file_get_contents($fn);
echo " compressed with zlib.deflate: ",
rawurlencode($compressed_with_filter), "\n";
// Detect actual algo used and compare with compression functions:
$is_zlib_compress = isZlibCompress($compressed_with_filter);
if( $is_zlib_compress ){
echo " detected ZLIB COMPRESS data\n";
// Compare with gzcompress():
$compressed_with_gzcompress = gzcompress($plain);
echo " compressed with gzcompress(): ",
rawurlencode($compressed_with_gzcompress), "\n";
if( $compressed_with_gzcompress !== $compressed_with_filter )
echo " ERROR: compressed differ!\n";
// Check decompression of the ZLIB Compress data:
$plain2 = gzuncompress($compressed_with_filter);
if( $plain !== $plain2 )
echo " ERROR: decompression: ", rawurlencode($plain2), "\n";
} else {
echo " certainly NOT ZLIB Compress data, assuming DEFLATE data\n";
$compressed_with_gzdeflate = gzdeflate($plain);
echo " compressed with gzdeflate(): ",
rawurlencode($compressed_with_gzdeflate), "\n";
if( $compressed_with_gzdeflate !== $compressed_with_filter )
echo " ERROR: compressed differ!\n";
// Check decompression of the DEFLATE data:
$plain2 = gzinflate($compressed_with_filter);
if( $plain !== $plain2 )
echo " ERROR: decompression: ", rawurlencode($plain2), "\n";
}
}
// Testing the "abc" string with all the possible combination of
parametrs, and
// testing if the resultin compressed file is or is not ZLIB Compress:
// It seems that the presence of the 'window' param triggers ZLIB
COMPRESS,
// its absence triggers DEFLATE:
testZlibFilter("abc", array(
)); // DEFLATE
testZlibFilter("abc", array('level' => -1
)); // DEFLATE
testZlibFilter("abc", array( 'memory' =>
9)); // DEFLATE
testZlibFilter("abc", array('level' => -1, 'memory'
=>
9)); // DEFLATE
testZlibFilter("abc", array('level' => -1, 'window' => 15,
'memory' =>
9)); // ZLIB COMPRESS
testZlibFilter("abc", array( 'window' => 15
)); // ZLIB COMPRESS
testZlibFilter("abc", array('level' => -1, 'window' => 15
)); // ZLIB COMPRESS
testZlibFilter("abc", array( 'window' => 15, 'memory'
=>
9)); // ZLIB COMPRESS
//testZlibFilter(""); // <-- crashes on PHP 7.1, see bug 71395
?>
Expected result:
----------------
(the detected format should always be DEFLATE or ZLIB Compress,
depending on the exact meaning of the zlib.compress filter).
Actual result:
--------------
Testing data: abc, params = array (
):
compressed with zlib.deflate: KLJ%06%00
certainly NOT ZLIB Compress data, assuming DEFLATE data
compressed with gzdeflate(): KLJ%06%00
Testing data: abc, params = array (
'level' => -1,
):
compressed with zlib.deflate: KLJ%06%00
certainly NOT ZLIB Compress data, assuming DEFLATE data
compressed with gzdeflate(): KLJ%06%00
Testing data: abc, params = array (
'memory' => 9,
):
compressed with zlib.deflate: KLJ%06%00
certainly NOT ZLIB Compress data, assuming DEFLATE data
compressed with gzdeflate(): KLJ%06%00
Testing data: abc, params = array (
'level' => -1,
'memory' => 9,
):
compressed with zlib.deflate: KLJ%06%00
certainly NOT ZLIB Compress data, assuming DEFLATE data
compressed with gzdeflate(): KLJ%06%00
Testing data: abc, params = array (
'level' => -1,
'window' => 15,
'memory' => 9,
):
compressed with zlib.deflate: x%9CKLJ%06%00%02M%01%27
detected ZLIB COMPRESS data
compressed with gzcompress(): x%9CKLJ%06%00%02M%01%27
Testing data: abc, params = array (
'window' => 15,
):
compressed with zlib.deflate: x%9CKLJ%06%00%02M%01%27
detected ZLIB COMPRESS data
compressed with gzcompress(): x%9CKLJ%06%00%02M%01%27
Testing data: abc, params = array (
'level' => -1,
'window' => 15,
):
compressed with zlib.deflate: x%9CKLJ%06%00%02M%01%27
detected ZLIB COMPRESS data
compressed with gzcompress(): x%9CKLJ%06%00%02M%01%27
Testing data: abc, params = array (
'window' => 15,
'memory' => 9,
):
compressed with zlib.deflate: x%9CKLJ%06%00%02M%01%27
detected ZLIB COMPRESS data
compressed with gzcompress(): x%9CKLJ%06%00%02M%01%27
--
Edit bug report at https://bugs.php.net/bug.php?id=71396&edit=1
--
Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=71396&r=trysnapshot54
Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=71396&r=trysnapshot55
Try a snapshot (trunk): https://bugs.php.net/fix.php?id=71396&r=trysnapshottrunk
Fixed in SVN: https://bugs.php.net/fix.php?id=71396&r=fixed
Fixed in release: https://bugs.php.net/fix.php?id=71396&r=alreadyfixed
Need backtrace: https://bugs.php.net/fix.php?id=71396&r=needtrace
Need Reproduce Script: https://bugs.php.net/fix.php?id=71396&r=needscript
Try newer version: https://bugs.php.net/fix.php?id=71396&r=oldversion
Not developer issue: https://bugs.php.net/fix.php?id=71396&r=support
Expected behavior: https://bugs.php.net/fix.php?id=71396&r=notwrong
Not enough info: https://bugs.php.net/fix.php?id=71396&r=notenoughinfo
Submitted twice: https://bugs.php.net/fix.php?id=71396&r=submittedtwice
register_globals: https://bugs.php.net/fix.php?id=71396&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=71396&r=php4
Daylight Savings: https://bugs.php.net/fix.php?id=71396&r=dst
IIS Stability: https://bugs.php.net/fix.php?id=71396&r=isapi
Install GNU Sed: https://bugs.php.net/fix.php?id=71396&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=71396&r=float
No Zend Extensions: https://bugs.php.net/fix.php?id=71396&r=nozend
MySQL Configuration Error: https://bugs.php.net/fix.php?id=71396&r=mysqlcfg