Skip to content

Commit bb151a7

Browse files
authored
Merge commit from fork
[3.x] Enforce client response max headers size
2 parents 335ca4f + d256704 commit bb151a7

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

src/Io/ClientRequestStream.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class ClientRequestStream extends EventEmitter implements WritableStreamInterfac
2323
const STATE_HEAD_WRITTEN = 2;
2424
const STATE_END = 3;
2525

26+
private $maxHeaderSize = 65536;
27+
2628
/** @var ClientConnectionManager */
2729
private $connectionManager;
2830

@@ -156,6 +158,19 @@ public function handleData($data)
156158
// buffer until double CRLF (or double LF for compatibility with legacy servers)
157159
$eom = \strpos($this->buffer, "\r\n\r\n");
158160
$eomLegacy = \strpos($this->buffer, "\n\n");
161+
$eomMaxHeaderSize = $eom;
162+
if ($eomLegacy !== false && ($eom === false || $eomLegacy < $eom)) {
163+
$eomMaxHeaderSize = $eomLegacy;
164+
}
165+
166+
// reject response if buffer size is exceeded
167+
if ($eomMaxHeaderSize > $this->maxHeaderSize || ($eomMaxHeaderSize === false && isset($this->buffer[$this->maxHeaderSize]))) {
168+
$this->closeError(
169+
new \OverflowException('Maximum response header size of ' . $this->maxHeaderSize . ' bytes exceeded.')
170+
);
171+
return;
172+
}
173+
159174
if ($eom !== false || $eomLegacy !== false) {
160175
try {
161176
if ($eom !== false && ($eomLegacy === false || $eom < $eomLegacy)) {

tests/Io/ClientRequestStreamTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,43 @@ public function requestShouldEmitErrorIfRequestParserThrowsException()
184184
$request->handleData("\r\n\r\n");
185185
}
186186

187+
public static function provideResponseHeaderOverflow()
188+
{
189+
$data = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nX-Data: ";
190+
$data .= str_repeat('A', 65537 - strlen($data)) . "\r\n\r\n";
191+
192+
$legacy = "HTTP/1.1 200 OK\r\n";
193+
$legacy .= str_repeat('A', 65537 - strlen($legacy)) . "\n\n";
194+
195+
return [
196+
'without end of message' => [str_repeat('A', 65537)],
197+
'with end of message' => [$data],
198+
'with legacy end of message' => [$legacy],
199+
];
200+
}
201+
202+
/**
203+
* @dataProvider provideResponseHeaderOverflow
204+
* @param string $data
205+
*/
206+
public function testRequestShouldEmitErrorWhenResponseHeadersExceedMaximumSize($data)
207+
{
208+
$connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
209+
210+
$connectionManager = $this->getMockBuilder('React\Http\Io\ClientConnectionManager')->disableOriginalConstructor()->getMock();
211+
$connectionManager->expects($this->once())->method('connect')->willReturn(\React\Promise\resolve($connection));
212+
213+
$requestData = new Request('GET', 'http://www.example.com');
214+
$request = new ClientRequestStream($connectionManager, $requestData);
215+
216+
$request->on('response', $this->expectCallableNever());
217+
$request->on('error', $this->expectCallableOnceWith($this->isInstanceOf('OverflowException')));
218+
$request->on('close', $this->expectCallableOnce());
219+
220+
$request->end();
221+
$request->handleData($data);
222+
}
223+
187224
/** @test */
188225
public function getRequestShouldSendAGetRequest()
189226
{

0 commit comments

Comments
 (0)