Skip to content

Commit a7aa204

Browse files
authored
Merge commit from fork
[1.x] Enforce client response max headers size
2 parents c0865b4 + 2558622 commit a7aa204

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

@@ -163,6 +165,19 @@ public function handleData($data)
163165
// buffer until double CRLF (or double LF for compatibility with legacy servers)
164166
$eom = \strpos($this->buffer, "\r\n\r\n");
165167
$eomLegacy = \strpos($this->buffer, "\n\n");
168+
$eomMaxHeaderSize = $eom;
169+
if ($eomLegacy !== false && ($eom === false || $eomLegacy < $eom)) {
170+
$eomMaxHeaderSize = $eomLegacy;
171+
}
172+
173+
// reject response if buffer size is exceeded
174+
if ($eomMaxHeaderSize > $this->maxHeaderSize || ($eomMaxHeaderSize === false && isset($this->buffer[$this->maxHeaderSize]))) {
175+
$this->closeError(
176+
new \OverflowException('Maximum response header size of ' . $this->maxHeaderSize . ' bytes exceeded.')
177+
);
178+
return;
179+
}
180+
166181
if ($eom !== false || $eomLegacy !== false) {
167182
try {
168183
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
@@ -180,6 +180,43 @@ public function requestShouldEmitErrorIfRequestParserThrowsException()
180180
$request->handleData("\r\n\r\n");
181181
}
182182

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

0 commit comments

Comments
 (0)