Skip to content

Commit cabdec6

Browse files
authored
Merge pull request #318 from clue-labs/smuggle
Reject requests that contain both Content-Length and Transfer-Encoding
2 parents c3be43d + 59b4715 commit cabdec6

File tree

3 files changed

+30
-62
lines changed

3 files changed

+30
-62
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,14 +411,14 @@ message boundaries.
411411
This value may be `0` if the request message does not contain a request body
412412
(such as a simple `GET` request).
413413
Note that this value may be `null` if the request body size is unknown in
414-
advance because the request message uses chunked transfer encoding.
414+
advance because the request message uses `Transfer-Encoding: chunked`.
415415

416416
```php
417417
$server = new StreamingServer(function (ServerRequestInterface $request) {
418418
$size = $request->getBody()->getSize();
419419
if ($size === null) {
420420
$body = 'The request does not contain an explicit length.';
421-
$body .= 'This server does not accept chunked transfer encoding.';
421+
$body .= 'This example does not accept chunked transfer encoding.';
422422

423423
return new Response(
424424
411,

src/StreamingServer.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,14 @@ public function handleRequest(ConnectionInterface $conn, ServerRequestInterface
203203
return $this->writeError($conn, 501, $request);
204204
}
205205

206-
$stream = new ChunkedDecoder($stream);
207-
$request = $request->withoutHeader('Content-Length');
206+
// Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
207+
// as per https://tools.ietf.org/html/rfc7230#section-3.3.3
208+
if ($request->hasHeader('Content-Length')) {
209+
$this->emit('error', array(new \InvalidArgumentException('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed')));
210+
return $this->writeError($conn, 400, $request);
211+
}
208212

213+
$stream = new ChunkedDecoder($stream);
209214
$contentLength = null;
210215
} elseif ($request->hasHeader('Content-Length')) {
211216
$string = $request->getHeaderLine('Content-Length');

tests/StreamingServerTest.php

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,79 +1773,42 @@ public function testRequestZeroContentLengthWillEmitEndAndAdditionalDataWillBeIg
17731773
$this->connection->emit('data', array($data));
17741774
}
17751775

1776-
public function testRequestContentLengthWillBeIgnoredIfTransferEncodingIsSet()
1776+
public function testRequestWithBothContentLengthAndTransferEncodingWillEmitServerErrorAndSendResponse()
17771777
{
1778-
$dataEvent = $this->expectCallableOnceWith('hello');
1779-
$endEvent = $this->expectCallableOnce();
1780-
$closeEvent = $this->expectCallableOnce();
1781-
$errorEvent = $this->expectCallableNever();
1782-
1783-
$requestValidation = null;
1784-
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) {
1785-
$request->getBody()->on('data', $dataEvent);
1786-
$request->getBody()->on('end', $endEvent);
1787-
$request->getBody()->on('close', $closeEvent);
1788-
$request->getBody()->on('error', $errorEvent);
1789-
$requestValidation = $request;
1778+
$error = null;
1779+
$server = new StreamingServer($this->expectCallableNever());
1780+
$server->on('error', function ($message) use (&$error) {
1781+
$error = $message;
17901782
});
17911783

1792-
$server->listen($this->socket);
1793-
$this->socket->emit('connection', array($this->connection));
1794-
1795-
$data = "GET / HTTP/1.1\r\n";
1796-
$data .= "Host: example.com:80\r\n";
1797-
$data .= "Connection: close\r\n";
1798-
$data .= "Content-Length: 4\r\n";
1799-
$data .= "Transfer-Encoding: chunked\r\n";
1800-
$data .= "\r\n";
1801-
1802-
$this->connection->emit('data', array($data));
1803-
1804-
$data = "5\r\nhello\r\n";
1805-
$data .= "0\r\n\r\n";
1806-
1807-
$this->connection->emit('data', array($data));
1808-
1809-
$this->assertFalse($requestValidation->hasHeader('Content-Length'));
1810-
$this->assertEquals('chunked', $requestValidation->getHeaderLine('Transfer-Encoding'));
1811-
}
1812-
1813-
public function testRequestInvalidContentLengthWillBeIgnoreddIfTransferEncodingIsSet()
1814-
{
1815-
$dataEvent = $this->expectCallableOnceWith('hello');
1816-
$endEvent = $this->expectCallableOnce();
1817-
$closeEvent = $this->expectCallableOnce();
1818-
$errorEvent = $this->expectCallableNever();
1819-
1820-
$requestValidation = null;
1821-
$server = new StreamingServer(function (ServerRequestInterface $request) use ($dataEvent, $endEvent, $closeEvent, $errorEvent, &$requestValidation) {
1822-
$request->getBody()->on('data', $dataEvent);
1823-
$request->getBody()->on('end', $endEvent);
1824-
$request->getBody()->on('close', $closeEvent);
1825-
$request->getBody()->on('error', $errorEvent);
1826-
$requestValidation = $request;
1827-
});
1784+
$buffer = '';
1785+
$this->connection
1786+
->expects($this->any())
1787+
->method('write')
1788+
->will(
1789+
$this->returnCallback(
1790+
function ($data) use (&$buffer) {
1791+
$buffer .= $data;
1792+
}
1793+
)
1794+
);
18281795

18291796
$server->listen($this->socket);
18301797
$this->socket->emit('connection', array($this->connection));
18311798

18321799
$data = "GET / HTTP/1.1\r\n";
18331800
$data .= "Host: example.com:80\r\n";
18341801
$data .= "Connection: close\r\n";
1835-
// this is valid behavior according to: https://www.ietf.org/rfc/rfc2616.txt chapter 4.4
1836-
$data .= "Content-Length: hello world\r\n";
1802+
$data .= "Content-Length: 5\r\n";
18371803
$data .= "Transfer-Encoding: chunked\r\n";
18381804
$data .= "\r\n";
1805+
$data .= "hello";
18391806

18401807
$this->connection->emit('data', array($data));
18411808

1842-
$data = "5\r\nhello\r\n";
1843-
$data .= "0\r\n\r\n";
1844-
1845-
$this->connection->emit('data', array($data));
1846-
1847-
$this->assertFalse($requestValidation->hasHeader('Content-Length'));
1848-
$this->assertEquals('chunked', $requestValidation->getHeaderLine('Transfer-Encoding'));
1809+
$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
1810+
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
1811+
$this->assertInstanceOf('InvalidArgumentException', $error);
18491812
}
18501813

18511814
public function testRequestInvalidNonIntegerContentLengthWillEmitServerErrorAndSendResponse()

0 commit comments

Comments
 (0)