Skip to content

Commit 3c04e99

Browse files
committed
Change Decompressor to use more efficient inflate_init() context
Among others, this also supports error reporting when decompressing an invalid data stream and fixes all known inconsistencies.
1 parent fc2816f commit 3c04e99

File tree

6 files changed

+76
-22
lines changed

6 files changed

+76
-22
lines changed

README.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ supporting compression and decompression of GZIP, ZLIB and raw DEFLATE formats.
1515
* [Usage](#usage)
1616
* [Compressor](#compressor)
1717
* [Decompressor](#decompressor)
18-
* [Inconsistencies](#inconsistencies)
1918
* [Install](#install)
2019
* [Tests](#tests)
2120
* [License](#license)
@@ -162,17 +161,6 @@ $input->pipe($decompressor)->pipe($filterBadWords)->pipe($output);
162161
For more details, see ReactPHP's
163162
[`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
164163

165-
### Inconsistencies
166-
167-
The stream compression filters are not exactly the most commonly used features of PHP.
168-
As such, we've spotted some inconsistencies (or *bugs*) in different PHP versions.
169-
These inconsistencies exist in the underlying PHP engines and there's little we can do about this in this library.
170-
171-
* All PHP versions: Decompressing invalid data does not emit any data (and does not raise an error)
172-
173-
Our test suite contains several test cases that exhibit these issues.
174-
If you feel some test case is missing or outdated, we're happy to accept PRs! :)
175-
176164
## Install
177165

178166
The recommended way to install this library is [through Composer](https://getcomposer.org).

src/Decompressor.php

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,49 @@
3131
* For more details, see ReactPHP's
3232
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
3333
*/
34-
final class Decompressor extends ZlibFilterStream
34+
final class Decompressor extends TransformStream
3535
{
36+
/** @var ?resource */
37+
private $context;
38+
3639
/**
3740
* @param int $encoding ZLIB_ENCODING_GZIP, ZLIB_ENCODING_RAW or ZLIB_ENCODING_DEFLATE
3841
*/
3942
public function __construct($encoding)
4043
{
41-
parent::__construct(
42-
Filter\fun('zlib.inflate', array('window' => $encoding))
43-
);
44+
$context = @inflate_init($encoding);
45+
if ($context === false) {
46+
throw new \InvalidArgumentException('Unable to initialize decompressor' . strstr(error_get_last()['message'], ':'));
47+
}
48+
49+
$this->context = $context;
50+
}
51+
52+
protected function transformData($chunk)
53+
{
54+
$ret = @inflate_add($this->context, $chunk);
55+
if ($ret === false) {
56+
throw new \RuntimeException('Unable to decompress' . strstr(error_get_last()['message'], ':'));
57+
}
58+
59+
if ($ret !== '') {
60+
$this->forwardData($ret);
61+
}
62+
}
63+
64+
protected function transformEnd($chunk)
65+
{
66+
$ret = @inflate_add($this->context, $chunk, ZLIB_FINISH);
67+
$this->context = null;
68+
69+
if ($ret === false) {
70+
throw new \RuntimeException('Unable to decompress' . strstr(error_get_last()['message'], ':'));
71+
}
72+
73+
if ($ret !== '') {
74+
$this->forwardData($ret);
75+
}
76+
77+
$this->forwardEnd();
4478
}
4579
}

tests/DecompressorTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
use Clue\React\Zlib\Decompressor;
4+
5+
class DecompressorTest extends TestCase
6+
{
7+
/**
8+
* @expectedException InvalidArgumentException
9+
*/
10+
public function testCtorThrowsForInvalidEncoding()
11+
{
12+
new Decompressor(0);
13+
}
14+
}

tests/DeflateDecompressorTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,16 @@ public function testInflateBig()
4848
$this->assertEquals($data, $buffered);
4949
}
5050

51-
public function testInflateInvalid()
51+
public function testDecompressInvalidDataEmitsError()
5252
{
53-
$this->markTestSkipped('Not supported by any PHP version (neither does reject invalid data)');
53+
$this->decompressor->on('data', $this->expectCallableNever());
54+
$this->decompressor->on('error', $this->expectCallableOnce());
5455

56+
$this->decompressor->write('invalid');
57+
}
58+
59+
public function testDecompressInvalidOnEndEmitsError()
60+
{
5561
$this->decompressor->on('data', $this->expectCallableNever());
5662
$this->decompressor->on('error', $this->expectCallableOnce());
5763

tests/GzipDecompressorTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,16 @@ public function testDecompressBig()
4848
$this->assertEquals($data, $buffered);
4949
}
5050

51-
public function testDecompressInvalid()
51+
public function testDecompressInvalidDataEmitsError()
5252
{
53-
$this->markTestSkipped('Not supported by any PHP version (neither does reject invalid data)');
53+
$this->decompressor->on('data', $this->expectCallableNever());
54+
$this->decompressor->on('error', $this->expectCallableOnce());
5455

56+
$this->decompressor->write('invalid');
57+
}
58+
59+
public function testDecompressInvalidOnEndEmitsError()
60+
{
5561
$this->decompressor->on('data', $this->expectCallableNever());
5662
$this->decompressor->on('error', $this->expectCallableOnce());
5763

tests/ZlibDecompressorTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,16 @@ public function testDecompressBig()
4848
$this->assertEquals($data, $buffered);
4949
}
5050

51-
public function testDecompressInvalid()
51+
public function testDecompressInvalidDataEmitsError()
5252
{
53-
$this->markTestSkipped('Not supported by any PHP version (neither does reject invalid data)');
53+
$this->decompressor->on('data', $this->expectCallableNever());
54+
$this->decompressor->on('error', $this->expectCallableOnce());
5455

56+
$this->decompressor->write('invalid');
57+
}
58+
59+
public function testDecompressInvalidOnEndEmitsError()
60+
{
5561
$this->decompressor->on('data', $this->expectCallableNever());
5662
$this->decompressor->on('error', $this->expectCallableOnce());
5763

0 commit comments

Comments
 (0)