Skip to content

Commit

Permalink
Handle compressed response body
Browse files Browse the repository at this point in the history
  • Loading branch information
Jérôme Parmentier committed Jul 27, 2017
1 parent 1371c8a commit 757de9e
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 24 deletions.
109 changes: 86 additions & 23 deletions lib/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function __construct(
$version = '1.1',
$message = null
) {
$this->status = intval($status);
$this->status = (int) $status;
$this->body = $body;
$this->version = $version;
$this->message = $message;
Expand Down Expand Up @@ -165,19 +165,21 @@ public function getMessage()
*/
public function getBody()
{
// Decode the body if it was transfer-encoded
switch (strtolower($this->getHeader('transfer-encoding'))) {
// Handle chunked body
case 'chunked':
return self::decodeChunkedBody($this->body);
break;

// No transfer encoding, or unknown encoding extension:
// return body as is
default:
return $this->body;
break;
$body = $this->body;

if ('chunked' === strtolower($this->getHeader('transfer-encoding'))) {
$body = self::decodeChunkedBody($body);
}

$contentEncoding = strtolower($this->getHeader('content-encoding'));

if ('gzip' === $contentEncoding) {
$body = self::decodeGzip($body);
} elseif ('deflate' === $contentEncoding) {
$body = self::decodeDeflate($body);
}

return $body;
}

/**
Expand Down Expand Up @@ -225,9 +227,9 @@ public function getHeader($header)
$header = ucwords(strtolower($header));
if (array_key_exists($header, $this->headers)) {
return $this->headers[$header];
} else {
return null;
}

return null;
}

/**
Expand Down Expand Up @@ -272,7 +274,7 @@ public static function fromString($responseStr)
{
// First, split body and headers
$matches = preg_split('|(?:\r?\n){2}|m', $responseStr, 2);
if ($matches and sizeof($matches) == 2) {
if ($matches and 2 === count($matches)) {
list ($headerLines, $body) = $matches;
} else {
throw new Exception(
Expand Down Expand Up @@ -321,28 +323,89 @@ public static function fromString($responseStr)
* @param string $body
*
* @throws \EasyRdf\Exception
*
* @return string
*/
public static function decodeChunkedBody($body)
{
$decBody = '';

while (trim($body)) {
if (preg_match('/^([\da-fA-F]+)[^\r\n]*\r\n/sm', $body, $m)) {
$length = hexdec(trim($m[1]));
$cut = strlen($m[0]);
$decBody .= substr($body, $cut, $length);
$body = substr($body, $cut + $length + 2);
} else {
if (!preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
throw new Exception(
"Failed to decode chunked body in HTTP response."
"Error parsing body - doesn't seem to be a chunked message"
);
}
$length = hexdec(trim($m[1]));
$cut = strlen($m[0]);
$decBody .= substr($body, $cut, $length);
$body = substr($body, $cut + $length + 2);
}

return $decBody;
}

/**
* Decode a gzip encoded message (when Content-encoding = gzip)
*
* Currently requires PHP with zlib support
*
* @param string $body
*
* @throws Exception
*
* @return string
*/
public static function decodeGzip($body)
{
if (!function_exists('gzinflate')) {
throw new Exception(
'zlib extension is required in order to decode "gzip" encoding'
);
}

return gzinflate(substr($body, 10));
}

/**
* Decode a zlib deflated message (when Content-encoding = deflate)
*
* Currently requires PHP with zlib support
*
* @param string $body
*
* @throws Exception
*
* @return string
*/
public static function decodeDeflate($body)
{
if (!function_exists('gzuncompress')) {
throw new Exception(
'zlib extension is required in order to decode "deflate" encoding'
);
}

/**
* Some servers (IIS ?) send a broken deflate response, without the
* RFC-required zlib header.
*
* We try to detect the zlib header, and if it does not exist we
* teat the body is plain DEFLATE content.
*
* This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
*
* @link http://framework.zend.com/issues/browse/ZF-6040
*/
$zlibHeader = unpack('n', substr($body, 0, 2));

if ($zlibHeader[1] % 31 === 0) {
return gzuncompress($body);
}

return gzinflate($body);
}


/**
* Get the entire response as string
Expand Down
40 changes: 39 additions & 1 deletion test/EasyRdf/Http/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,44 @@ public function testInvalidStatusLine()
);
}

public function testGzipResponse()
{
$res = Response::fromString(readFixture('response_gzip'));

$this->assertEquals('gzip', strtolower($res->getHeader('content-encoding')));
$this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody()));
$this->assertEquals('f24dd075ba2ebfb3bf21270e3fdc5303', md5($res->getRawBody()));
}

public function testDeflateResponse()
{
$res = Response::fromString(readFixture('response_deflate'));

$this->assertEquals('deflate', strtolower($res->getHeader('content-encoding')));
$this->assertEquals('0b13cb193de9450aa70a6403e2c9902f', md5($res->getBody()));
$this->assertEquals('ad62c21c3aa77b6a6f39600f6dd553b8', md5($res->getRawBody()));
}

/**
* Make sure wer can handle non-RFC complient "deflate" responses.
*
* Unlike stanrdard 'deflate' response, those do not contain the zlib header
* and trailer. Unfortunately some buggy servers (read: IIS) send those and
* we need to support them.
*
* @link http://framework.zend.com/issues/browse/ZF-6040
*/
public function testNonStandardDeflateResponseZF6040()
{
$this->markTestSkipped('Not correctly handling non-RFC complient "deflate" responses');

$res = Response::fromString(readFixture('response_deflate_iis'));

$this->assertEquals('deflate', strtolower($res->getHeader('content-encoding')));
$this->assertEquals('d82c87e3d5888db0193a3fb12396e616', md5($res->getBody()));
$this->assertEquals('c830dd74bb502443cf12514c185ff174', md5($res->getRawBody()));
}

public function testGetBodyChunked()
{
$response = Response::fromString(
Expand All @@ -115,7 +153,7 @@ public function testInvalidChunkedBody()
{
$this->setExpectedException(
'EasyRdf\Exception',
'Failed to decode chunked body in HTTP response.'
'Error parsing body - doesn\'t seem to be a chunked message'
);
$response = Response::fromString(
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\nINVALID"
Expand Down
Binary file added test/fixtures/response_deflate
Binary file not shown.
Binary file added test/fixtures/response_deflate_iis
Binary file not shown.
Binary file added test/fixtures/response_gzip
Binary file not shown.

0 comments on commit 757de9e

Please sign in to comment.