Skip to content

Commit 350788d

Browse files
committed
Support malformed multipart body
For example body containing broken array keys like `key0[key1][key2][`
1 parent 2ace51c commit 350788d

File tree

4 files changed

+40
-27
lines changed

4 files changed

+40
-27
lines changed

src/Event/Http/Psr7Bridge.php

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -93,36 +93,49 @@ public static function convertResponse(ResponseInterface $response): HttpRespons
9393
return new HttpResponse($body, $response->getHeaders(), $response->getStatusCode());
9494
}
9595

96+
/**
97+
* @return array{0: array<string, UploadedFile>, 1: array<string, mixed>|null}
98+
*/
9699
private static function parseBodyAndUploadedFiles(HttpRequestEvent $event): array
97100
{
98-
$bodyString = $event->getBody();
99-
$files = [];
100-
$parsedBody = null;
101101
$contentType = $event->getContentType();
102-
if ($contentType !== null && $event->getMethod() === 'POST') {
103-
if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
104-
parse_str($bodyString, $parsedBody);
105-
} else {
106-
$document = new Part("Content-type: $contentType\r\n\r\n" . $bodyString);
107-
if ($document->isMultiPart()) {
108-
$parsedBody = [];
109-
foreach ($document->getParts() as $part) {
110-
if ($part->isFile()) {
102+
if ($contentType === null || $event->getMethod() !== 'POST') {
103+
return [[], null];
104+
}
105+
106+
if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
107+
$parsedBody = [];
108+
parse_str($event->getBody(), $parsedBody);
109+
return [[], $parsedBody];
110+
}
111+
112+
// Parse the body as multipart/form-data
113+
$document = new Part("Content-type: $contentType\r\n\r\n" . $event->getBody());
114+
if (!$document->isMultiPart()) {
115+
return [[], null];
116+
}
117+
$files = [];
118+
$queryString = '';
119+
foreach ($document->getParts() as $part) {
120+
if ($part->isFile()) {
111121
$tmpPath = tempnam(sys_get_temp_dir(), self::UPLOADED_FILES_PREFIX);
112-
if ($tmpPath === false) {
113-
throw new RuntimeException('Unable to create a temporary directory');
114-
}
115-
file_put_contents($tmpPath, $part->getBody());
116-
$file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());
117-
118-
self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
119-
} else {
120-
self::parseKeyAndInsertValueInArray($parsedBody, $part->getName(), $part->getBody());
121-
}
122-
}
122+
if ($tmpPath === false) {
123+
throw new RuntimeException('Unable to create a temporary directory');
123124
}
125+
file_put_contents($tmpPath, $part->getBody());
126+
$file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());
127+
self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
128+
} else {
129+
// Temporarily store as a query string so that we can use PHP's native parse_str function to parse keys
130+
$queryString .= urlencode($part->getName()) . '=' . urlencode($part->getBody()) . '&';
124131
}
125132
}
133+
if ($queryString !== '') {
134+
$parsedBody = [];
135+
parse_str($queryString, $parsedBody);
136+
} else {
137+
$parsedBody = null;
138+
}
126139
return [$files, $parsedBody];
127140
}
128141

tests/Event/Http/CommonHttpTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ public function test POST request with multipart file uploads(int $version
401401
--testBoundary--\r
402402
";
403403
$this->assertBody($body);
404-
$this->assertParsedBody([]);
404+
$this->assertParsedBody(null);
405405
$this->assertUploadedFile(
406406
'foo',
407407
'lorem.txt',
@@ -554,7 +554,7 @@ abstract protected function assertUri(string $expected): void;
554554

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

557-
abstract protected function assertParsedBody(array $expected): void;
557+
abstract protected function assertParsedBody(array|null $expected): void;
558558

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

tests/Event/Http/HttpRequestEventTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected function assertSourceIp(string $expected): void
112112
$this->assertEquals($expected, $this->event->getSourceIp());
113113
}
114114

115-
protected function assertParsedBody(array $expected): void
115+
protected function assertParsedBody(array|null $expected): void
116116
{
117117
// Not applicable here since the class doesn't parse the body
118118
}

tests/Event/Http/Psr7BridgeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ protected function assertHasMultiHeader(bool $expected): void
123123
// Not applicable here
124124
}
125125

126-
protected function assertParsedBody(array $expected): void
126+
protected function assertParsedBody(array|null $expected): void
127127
{
128128
$this->assertEquals($expected, $this->request->getParsedBody());
129129
}

0 commit comments

Comments
 (0)