Skip to content

Commit

Permalink
Support malformed multipart body
Browse files Browse the repository at this point in the history
For example body containing broken array keys like `key0[key1][key2][`
  • Loading branch information
mnapoli committed Jan 31, 2024
1 parent 2ace51c commit 350788d
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 27 deletions.
59 changes: 36 additions & 23 deletions src/Event/Http/Psr7Bridge.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,36 +93,49 @@ public static function convertResponse(ResponseInterface $response): HttpRespons
return new HttpResponse($body, $response->getHeaders(), $response->getStatusCode());
}

/**
* @return array{0: array<string, UploadedFile>, 1: array<string, mixed>|null}
*/
private static function parseBodyAndUploadedFiles(HttpRequestEvent $event): array
{
$bodyString = $event->getBody();
$files = [];
$parsedBody = null;
$contentType = $event->getContentType();
if ($contentType !== null && $event->getMethod() === 'POST') {
if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
parse_str($bodyString, $parsedBody);
} else {
$document = new Part("Content-type: $contentType\r\n\r\n" . $bodyString);
if ($document->isMultiPart()) {
$parsedBody = [];
foreach ($document->getParts() as $part) {
if ($part->isFile()) {
if ($contentType === null || $event->getMethod() !== 'POST') {
return [[], null];
}

if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
$parsedBody = [];
parse_str($event->getBody(), $parsedBody);
return [[], $parsedBody];
}

// Parse the body as multipart/form-data
$document = new Part("Content-type: $contentType\r\n\r\n" . $event->getBody());
if (!$document->isMultiPart()) {

Check failure on line 114 in src/Event/Http/Psr7Bridge.php

View workflow job for this annotation

GitHub Actions / PHP CodeSniffer

Expected 1 space after NOT operator; 0 found
return [[], null];
}
$files = [];
$queryString = '';
foreach ($document->getParts() as $part) {
if ($part->isFile()) {
$tmpPath = tempnam(sys_get_temp_dir(), self::UPLOADED_FILES_PREFIX);
if ($tmpPath === false) {
throw new RuntimeException('Unable to create a temporary directory');
}
file_put_contents($tmpPath, $part->getBody());
$file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());

self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
} else {
self::parseKeyAndInsertValueInArray($parsedBody, $part->getName(), $part->getBody());
}
}
if ($tmpPath === false) {
throw new RuntimeException('Unable to create a temporary directory');
}
file_put_contents($tmpPath, $part->getBody());
$file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());
self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
} else {
// Temporarily store as a query string so that we can use PHP's native parse_str function to parse keys
$queryString .= urlencode($part->getName()) . '=' . urlencode($part->getBody()) . '&';
}
}
if ($queryString !== '') {
$parsedBody = [];
parse_str($queryString, $parsedBody);
} else {
$parsedBody = null;
}
return [$files, $parsedBody];
}

Expand Down
4 changes: 2 additions & 2 deletions tests/Event/Http/CommonHttpTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ public function test POST request with multipart file uploads(int $version
--testBoundary--\r
";
$this->assertBody($body);
$this->assertParsedBody([]);
$this->assertParsedBody(null);
$this->assertUploadedFile(
'foo',
'lorem.txt',
Expand Down Expand Up @@ -554,7 +554,7 @@ abstract protected function assertUri(string $expected): void;

abstract protected function assertHasMultiHeader(bool $expected): void;

abstract protected function assertParsedBody(array $expected): void;
abstract protected function assertParsedBody(array|null $expected): void;

Check failure on line 557 in tests/Event/Http/CommonHttpTest.php

View workflow job for this annotation

GitHub Actions / PHP CodeSniffer

There must be exactly one space between parameter type hint and parameter $expected.

abstract protected function assertSourceIp(string $expected): void;

Expand Down
2 changes: 1 addition & 1 deletion tests/Event/Http/HttpRequestEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ protected function assertSourceIp(string $expected): void
$this->assertEquals($expected, $this->event->getSourceIp());
}

protected function assertParsedBody(array $expected): void
protected function assertParsedBody(array|null $expected): void

Check failure on line 115 in tests/Event/Http/HttpRequestEventTest.php

View workflow job for this annotation

GitHub Actions / PHP CodeSniffer

There must be exactly one space between parameter type hint and parameter $expected.
{
// Not applicable here since the class doesn't parse the body
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Event/Http/Psr7BridgeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected function assertHasMultiHeader(bool $expected): void
// Not applicable here
}

protected function assertParsedBody(array $expected): void
protected function assertParsedBody(array|null $expected): void

Check failure on line 126 in tests/Event/Http/Psr7BridgeTest.php

View workflow job for this annotation

GitHub Actions / PHP CodeSniffer

There must be exactly one space between parameter type hint and parameter $expected.
{
$this->assertEquals($expected, $this->request->getParsedBody());
}
Expand Down

0 comments on commit 350788d

Please sign in to comment.