From 67f4d5cb7925313e9216ddffaf1def84e0858d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 1 Apr 2020 21:39:36 +0200 Subject: [PATCH] Work around compressing empty stream on PHP 7+ --- README.md | 1 - src/Compressor.php | 2 ++ src/ZlibFilterStream.php | 15 +++++++++++++++ tests/ZlibFilterDeflateCompressorTest.php | 2 -- tests/ZlibFilterGzipCompressorTest.php | 2 -- tests/ZlibFilterZlibCompressorTest.php | 2 -- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7d2536b..939dafa 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,6 @@ As such, we've spotted several inconsistencies (or *bugs*) between different PHP These inconsistencies exist in the underlying PHP engines and there's little we can do about this in this library. * All Zend PHP versions: Decompressing invalid data does not emit any data (and does not raise an error) -* PHP 7 only: Compressing an empty string does not emit any data (not a valid compression stream) * HHVM only: does not currently support the GZIP and ZLIB format at all (and does not raise an error) * HHVM only: The [`zlib.deflate` filter function](https://github.com/facebook/hhvm/blob/fee8ae39ce395c7b9b8910dfde6f22a7745aea83/hphp/system/php/stream/default-filters.php#L77) buffers the whole string. This means that compressing a stream of 100 MB actually stores the whole string in memory before invoking the underlying compression algorithm. * PHP 5.3 only: Tends to SEGFAULT occasionally on shutdown? diff --git a/src/Compressor.php b/src/Compressor.php index 8ed49c9..22b518f 100644 --- a/src/Compressor.php +++ b/src/Compressor.php @@ -46,5 +46,7 @@ public function __construct($encoding, $level = -1) parent::__construct( Filter\fun('zlib.deflate', array('window' => $encoding, 'level' => $level)) ); + + $this->emptyWrite = $encoding; } } diff --git a/src/ZlibFilterStream.php b/src/ZlibFilterStream.php index 95cd4bd..45dae7b 100644 --- a/src/ZlibFilterStream.php +++ b/src/ZlibFilterStream.php @@ -74,6 +74,13 @@ public static function createZlibDecompressor() private $filter; + /** + * @var int|null + * @see Compressor + * @internal + */ + protected $emptyWrite; + public function __construct($filter) { $this->filter = $filter; @@ -85,6 +92,7 @@ protected function transformData($chunk) $ret = $filter($chunk); if ($ret !== '') { + $this->emptyWrite = null; $this->forwardData($ret); } } @@ -94,6 +102,13 @@ protected function transformEnd($chunk) $filter = $this->filter; $ret = $filter($chunk) . $filter(); + // Stream ends successfully and did not emit any data whatsoever? + // This happens when compressing an empty stream with PHP 7 only. + // Bypass filter and manually compress/encode empty string. + if ($this->emptyWrite !== null && $ret === '') { + $ret = \zlib_encode('', $this->emptyWrite); + } + if ($ret !== '') { $this->forwardData($ret); } diff --git a/tests/ZlibFilterDeflateCompressorTest.php b/tests/ZlibFilterDeflateCompressorTest.php index 2aed4ba..2d4aa26 100644 --- a/tests/ZlibFilterDeflateCompressorTest.php +++ b/tests/ZlibFilterDeflateCompressorTest.php @@ -13,8 +13,6 @@ public function setUp() public function testDeflateEmpty() { - if (PHP_VERSION >= 7) $this->markTestSkipped('Not supported on PHP 7 (empty chunk will not be emitted)'); - $this->compressor->on('data', $this->expectCallableOnceWith("\x03\x00")); $this->compressor->on('end', $this->expectCallableOnce()); diff --git a/tests/ZlibFilterGzipCompressorTest.php b/tests/ZlibFilterGzipCompressorTest.php index ac54887..8a86a27 100644 --- a/tests/ZlibFilterGzipCompressorTest.php +++ b/tests/ZlibFilterGzipCompressorTest.php @@ -15,8 +15,6 @@ public function setUp() public function testCompressEmpty() { - if (PHP_VERSION >= 7) $this->markTestSkipped('Not supported on PHP 7 (empty chunk will not be emitted)'); - $os = "\x03"; // UNIX (0x03) or UNKNOWN (0xFF) $this->compressor->on('data', $this->expectCallableOnceWith("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00" . $os . "\x03\x00" . "\x00\x00\x00\x00\x00\x00\x00\x00")); $this->compressor->on('end', $this->expectCallableOnce()); diff --git a/tests/ZlibFilterZlibCompressorTest.php b/tests/ZlibFilterZlibCompressorTest.php index aa047d6..b927f8b 100644 --- a/tests/ZlibFilterZlibCompressorTest.php +++ b/tests/ZlibFilterZlibCompressorTest.php @@ -15,8 +15,6 @@ public function setUp() public function testCompressEmpty() { - if (PHP_VERSION >= 7) $this->markTestSkipped('Not supported on PHP 7 (empty chunk will not be emitted)'); - $this->compressor->on('data', $this->expectCallableOnceWith("\x78\x9c" . "\x03\x00" . "\x00\x00\x00\x01")); $this->compressor->on('end', $this->expectCallableOnce());