Skip to content

Commit c0865b4

Browse files
authored
Merge commit from fork
[1.x] fix cases where a malformed HTTP request with 'Transfer-Encoding: chunked' could lead to ReactPHP looping forever
2 parents cf55239 + 58bc906 commit c0865b4

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

‎src/Io/ChunkedDecoder.php‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,20 @@ public function handleData($data)
156156
$this->transferredSize = 0;
157157
$this->buffer = (string)\substr($this->buffer, 2);
158158
} elseif ($this->chunkSize === 0) {
159+
if ($positionCrlf === false) {
160+
// end chunk received, but trailer is incomplete
161+
// trailer shouldn't be bigger than 1024 bytes
162+
if (isset($this->buffer[static::MAX_CHUNK_HEADER_SIZE])) {
163+
$this->handleError(new Exception('Trailer size bigger than ' . static::MAX_CHUNK_HEADER_SIZE . ' bytes'));
164+
}
165+
return;
166+
}
159167
// end chunk received, skip all trailer data
160168
$this->buffer = (string)\substr($this->buffer, $positionCrlf);
161169
}
162170

163-
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) > 2) {
164-
// the first 2 characters are not CRLF, send error event
171+
if ($positionCrlf !== 0 && $this->chunkSize !== 0 && $this->chunkSize === $this->transferredSize && \strlen($this->buffer) >= 2) {
172+
// chunk is completely transferred, but the following two bytes are not a CRLF, send error event
165173
$this->handleError(new Exception('Chunk does not end with a CRLF'));
166174
return;
167175
}

‎tests/Io/ChunkedDecoderTest.php‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,57 @@ public function testEndChunkWithMultipleTrailersWillBeIgnored()
500500
$this->input->emit('data', array("0\r\nFoo: a\r\nBar: b\r\nBaz: c\r\n\r\n"));
501501
}
502502

503+
public function testEndChunkWithIncompleteTrailerWithoutCrlfWillWaitForAdditionalDataAndNotCauseInfiniteLoop()
504+
{
505+
$this->parser->on('data', $this->expectCallableNever());
506+
$this->parser->on('error', $this->expectCallableNever());
507+
$this->parser->on('end', $this->expectCallableNever());
508+
$this->parser->on('close', $this->expectCallableNever());
509+
510+
// malformed end chunk with trailing data but without a terminating CRLF
511+
// must not loop forever, but wait for additional data
512+
$this->input->emit('data', array("0\r\nab"));
513+
}
514+
515+
public function testEndChunkWithIncompleteTrailerWillEndOnceTrailerIsCompleted()
516+
{
517+
$this->parser->on('data', $this->expectCallableNever());
518+
$this->parser->on('error', $this->expectCallableNever());
519+
$this->parser->on('end', $this->expectCallableOnce());
520+
$this->parser->on('close', $this->expectCallableOnce());
521+
522+
$this->input->emit('data', array("0\r\nab"));
523+
$this->input->emit('data', array("\r\n\r\n"));
524+
}
525+
526+
public function testEndChunkWithIncompleteTrailerIsTooBig()
527+
{
528+
$this->parser->on('data', $this->expectCallableNever());
529+
$this->parser->on('close', $this->expectCallableOnce());
530+
$this->parser->on('end', $this->expectCallableNever());
531+
$this->parser->on('error', $this->expectCallableOnce());
532+
533+
$data = '';
534+
for ($i = 0; $i < 1025; $i++) {
535+
$data .= 'a';
536+
}
537+
538+
// incomplete trailer must not be buffered without any limit
539+
$this->input->emit('data', array("0\r\n" . $data));
540+
}
541+
542+
public function testChunkFollowedByExactlyTwoNonCrlfBytesWillErrorAndNotCauseInfiniteLoop()
543+
{
544+
$this->parser->on('data', $this->expectCallableOnceWith('ab'));
545+
$this->parser->on('error', $this->expectCallableOnce());
546+
$this->parser->on('end', $this->expectCallableNever());
547+
$this->parser->on('close', $this->expectCallableOnce());
548+
549+
// completed chunk followed by exactly two bytes that are not a CRLF must not
550+
// loop forever, but report an invalid chunk terminator
551+
$this->input->emit('data', array("2\r\nabXY"));
552+
}
553+
503554
public function testLeadingZerosInInvalidChunk()
504555
{
505556
$this->parser->on('data', $this->expectCallableNever());

0 commit comments

Comments
 (0)