Skip to content

Commit

Permalink
Improve multipart limits test
Browse files Browse the repository at this point in the history
The PR introducing this test assumed time would be enough to accurately predict behavior. This commit changes it to using introspection to check the exact state of the parser and expected state once finished parsing a multipart request body. To accomplish that it was required to make the cursor in the file an object property, so it can be inspected using reflection.
  • Loading branch information
WyriHaximus committed Mar 2, 2023
1 parent 9681f76 commit 2942434
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 13 deletions.
14 changes: 8 additions & 6 deletions src/Io/MultipartParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ final class MultipartParser
private $postCount = 0;
private $filesCount = 0;
private $emptyCount = 0;
private $cursor = 0;

/**
* @param int|string|null $uploadMaxFilesize
Expand Down Expand Up @@ -112,6 +113,7 @@ public function parse(ServerRequestInterface $request)
$request = $this->request;
$this->request = null;
$this->multipartBodyPartCount = 0;
$this->cursor = 0;
$this->postCount = 0;
$this->filesCount = 0;
$this->emptyCount = 0;
Expand All @@ -125,20 +127,20 @@ private function parseBody($boundary, $buffer)
$len = \strlen($boundary);

// ignore everything before initial boundary (SHOULD be empty)
$start = \strpos($buffer, $boundary . "\r\n");
$this->cursor = \strpos($buffer, $boundary . "\r\n");

while ($start !== false) {
while ($this->cursor !== false) {
// search following boundary (preceded by newline)
// ignore last if not followed by boundary (SHOULD end with "--")
$start += $len + 2;
$end = \strpos($buffer, "\r\n" . $boundary, $start);
$this->cursor += $len + 2;
$end = \strpos($buffer, "\r\n" . $boundary, $this->cursor);
if ($end === false) {
break;
}

// parse one part and continue searching for next
$this->parsePart(\substr($buffer, $start, $end - $start));
$start = $end;
$this->parsePart(\substr($buffer, $this->cursor, $end - $this->cursor));
$this->cursor = $end;

if (++$this->multipartBodyPartCount > $this->maxMultipartBodyParts) {
break;
Expand Down
29 changes: 22 additions & 7 deletions tests/Io/MultipartParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1029,26 +1029,41 @@ public function testPostMaxFileSizeIgnoredByFilesComingBeforeIt()

public function testWeOnlyParseTheAmountOfMultiPartChunksWeConfigured()
{
$chunkCount = 5000000;
$boundary = "---------------------------12758086162038677464950549563";

$chunk = "--$boundary\r\n";
$chunk .= "Content-Disposition: form-data; name=\"f\"\r\n";
$chunk .= "\r\n";
$chunk .= "u\r\n";
$data = '';
for ($i = 0; $i < 5000000; $i++) {
$data .= $chunk;
}
$data .= str_repeat($chunk, $chunkCount);
$data .= "--$boundary--\r\n";

$request = new ServerRequest('POST', 'http://example.com/', array(
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
), $data, 1.1);

$parser = new MultipartParser();
$startTime = microtime(true);
$parser->parse($request);
$runTime = microtime(true) - $startTime;
$this->assertLessThan(1, $runTime);

$reflectecClass = new \ReflectionClass('\React\Http\Io\MultipartParser');
$requestProperty = $reflectecClass->getProperty('request');
$requestProperty->setAccessible(true);
$cursorProperty = $reflectecClass->getProperty('cursor');
$cursorProperty->setAccessible(true);
$multipartBodyPartCountProperty = $reflectecClass->getProperty('multipartBodyPartCount');
$multipartBodyPartCountProperty->setAccessible(true);
$maxMultipartBodyPartsProperty = $reflectecClass->getProperty('maxMultipartBodyParts');
$maxMultipartBodyPartsProperty->setAccessible(true);
$parseBodyMethod = $reflectecClass->getMethod('parseBody');
$parseBodyMethod->setAccessible(true);

$this->assertSame(0, $cursorProperty->getValue($parser));

$requestProperty->setValue($parser, $request);
$parseBodyMethod->invoke($parser, '--' . $boundary, $data);

$this->assertSame(strlen(str_repeat($chunk, $multipartBodyPartCountProperty->getValue($parser))), $cursorProperty->getValue($parser) + 2);
$this->assertSame($multipartBodyPartCountProperty->getValue($parser), $maxMultipartBodyPartsProperty->getValue($parser) + 1);
}
}

0 comments on commit 2942434

Please sign in to comment.