From 4041e0aa5208169abc5697cb587b995af09f0862 Mon Sep 17 00:00:00 2001 From: Stephen Harris Date: Fri, 26 Aug 2016 12:06:33 +0000 Subject: [PATCH 1/2] Fixes bug in rfc2616 #3.6.1 implementation. A chunk size (hexidecimal) must be followed by either an optional chunk extension (which begins with a semicolon) or \r\n. @link https://tools.ietf.org/html/rfc2616#section-3.6.1 --- library/Requests.php | 4 ++-- tests/ChunkedEncoding.php | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/library/Requests.php b/library/Requests.php index 5a6257a8a..79e8d7304 100644 --- a/library/Requests.php +++ b/library/Requests.php @@ -749,7 +749,7 @@ public static function parse_multiple(&$response, $request) { * @return string Decoded body */ protected static function decode_chunked($data) { - if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) { + if (!preg_match('/^([0-9a-f]+)(?:;[^\r\n]*)*\r\n/i', trim($data))) { return $data; } @@ -757,7 +757,7 @@ protected static function decode_chunked($data) { $encoded = $data; while (true) { - $is_chunked = (bool) preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches); + $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;[^\r\n]*)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; diff --git a/tests/ChunkedEncoding.php b/tests/ChunkedEncoding.php index fa8753826..47b046741 100644 --- a/tests/ChunkedEncoding.php +++ b/tests/ChunkedEncoding.php @@ -15,6 +15,10 @@ public static function chunkedProvider() { "02\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n", "abra\ncadabra\nall we got\n" ), + array( + "02;foo=bar;hello=world\r\nab\r\n04;foo=baz\r\nra\nc\r\n06;justfoo\r\nadabra\r\n0c\r\n\nall we got\n", + "abra\ncadabra\nall we got\n" + ), ); } @@ -39,7 +43,7 @@ public function testChunked($body, $expected){ */ public function testNotActuallyChunked() { $transport = new MockTransport(); - $transport->body = 'Hello! This is a non-chunked response!'; + $transport->body = "Believe me\r\nthis looks chunked, but it isn't."; $transport->chunked = true; $options = array( @@ -50,6 +54,7 @@ public function testNotActuallyChunked() { $this->assertEquals($transport->body, $response->body); } + /** * Response says it's chunked and starts looking like it is, but turns out * that they're lying to us From 2f1b5dc03c2cd8772847ab6fc661940f400c460c Mon Sep 17 00:00:00 2001 From: Stephen Harris Date: Tue, 30 Aug 2016 15:48:19 +0000 Subject: [PATCH 2/2] Chunk extension names cannot contain CTLs, spaces or (){}|<>@,;:\"/[]?=. Chunk extension values can, provided they are quoted. Ref #236 --- library/Requests.php | 6 ++++-- tests/ChunkedEncoding.php | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/library/Requests.php b/library/Requests.php index 79e8d7304..bb266189c 100644 --- a/library/Requests.php +++ b/library/Requests.php @@ -749,15 +749,17 @@ public static function parse_multiple(&$response, $request) { * @return string Decoded body */ protected static function decode_chunked($data) { - if (!preg_match('/^([0-9a-f]+)(?:;[^\r\n]*)*\r\n/i', trim($data))) { + if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } + + $decoded = ''; $encoded = $data; while (true) { - $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;[^\r\n]*)*\r\n/i', $encoded, $matches); + $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; diff --git a/tests/ChunkedEncoding.php b/tests/ChunkedEncoding.php index 47b046741..32ff92ffb 100644 --- a/tests/ChunkedEncoding.php +++ b/tests/ChunkedEncoding.php @@ -19,6 +19,14 @@ public static function chunkedProvider() { "02;foo=bar;hello=world\r\nab\r\n04;foo=baz\r\nra\nc\r\n06;justfoo\r\nadabra\r\n0c\r\n\nall we got\n", "abra\ncadabra\nall we got\n" ), + array( + "02;foo=\"quoted value\"\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n", + "abra\ncadabra\nall we got\n" + ), + array( + "02;foo-bar=baz\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n", + "abra\ncadabra\nall we got\n" + ), ); } @@ -38,12 +46,24 @@ public function testChunked($body, $expected){ $this->assertEquals($expected, $response->body); } + public static function notChunkedProvider() { + return array( + 'invalid chunk size' => array( 'Hello! This is a non-chunked response!' ), + 'invalid chunk extension' => array( '1BNot chunked\r\nLooks chunked but it is not\r\n' ), + 'unquoted chunk-ext-val with space' => array( "02;foo=unquoted with space\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n" ), + 'unquoted chunk-ext-val with forbidden character' => array( "02;foo={unquoted}\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n" ), + 'invalid chunk-ext-name' => array( "02;{foo}=bar\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n" ), + 'incomplete quote for chunk-ext-value' => array( "02;foo=\"no end quote\r\nab\r\n04\r\nra\nc\r\n06\r\nadabra\r\n0c\r\n\nall we got\n" ), + ); + } + /** * Response says it's chunked, but actually isn't + * @dataProvider notChunkedProvider */ - public function testNotActuallyChunked() { + public function testNotActuallyChunked($body) { $transport = new MockTransport(); - $transport->body = "Believe me\r\nthis looks chunked, but it isn't."; + $transport->body = $body; $transport->chunked = true; $options = array(