Skip to content

Commit

Permalink
Merge pull request #22 from clue-labs/php7-empty
Browse files Browse the repository at this point in the history
Work around compressing empty stream on PHP 7+
  • Loading branch information
clue authored Apr 2, 2020
2 parents ccee450 + 67f4d5c commit 361682d
Show file tree
Hide file tree
Showing 6 changed files with 17 additions and 7 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
2 changes: 2 additions & 0 deletions src/Compressor.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@ public function __construct($encoding, $level = -1)
parent::__construct(
Filter\fun('zlib.deflate', array('window' => $encoding, 'level' => $level))
);

$this->emptyWrite = $encoding;
}
}
15 changes: 15 additions & 0 deletions src/ZlibFilterStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -85,6 +92,7 @@ protected function transformData($chunk)
$ret = $filter($chunk);

if ($ret !== '') {
$this->emptyWrite = null;
$this->forwardData($ret);
}
}
Expand All @@ -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);
}
Expand Down
2 changes: 0 additions & 2 deletions tests/ZlibFilterDeflateCompressorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
2 changes: 0 additions & 2 deletions tests/ZlibFilterGzipCompressorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 0 additions & 2 deletions tests/ZlibFilterZlibCompressorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down

0 comments on commit 361682d

Please sign in to comment.