Skip to content

Commit 7447fbf

Browse files
committed
Handle excessive data structures for multipart/form-data
1 parent 21fd349 commit 7447fbf

2 files changed

Lines changed: 118 additions & 2 deletions

File tree

src/Io/MultipartParser.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@ final class MultipartParser
2727
*/
2828
protected $maxFileSize;
2929

30+
/**
31+
* ini setting "max_input_vars"
32+
*
33+
* Does not exist in PHP < 5.3.9 or HHVM, so assume PHP's default 1000 here.
34+
*
35+
* @var int
36+
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-vars
37+
*/
38+
private $maxInputVars = 1000;
39+
40+
/**
41+
* ini setting "max_input_nesting_level"
42+
*
43+
* Does not exist in HHVM, but assumes hard coded to 64 (PHP's default).
44+
*
45+
* @var int
46+
* @link http://php.net/manual/en/info.configuration.php#ini.max-input-nesting-level
47+
*/
48+
private $maxInputNestingLevel = 64;
49+
50+
private $postCount = 0;
51+
3052
public static function parseRequest(ServerRequestInterface $request)
3153
{
3254
$parser = new self($request);
@@ -36,6 +58,15 @@ public static function parseRequest(ServerRequestInterface $request)
3658
private function __construct(ServerRequestInterface $request)
3759
{
3860
$this->request = $request;
61+
62+
$var = ini_get('max_input_vars');
63+
if ($var !== false) {
64+
$this->maxInputVars = (int)$var;
65+
}
66+
$var = ini_get('max_input_nesting_level');
67+
if ($var !== false) {
68+
$this->maxInputNestingLevel = (int)$var;
69+
}
3970
}
4071

4172
private function parse()
@@ -150,6 +181,11 @@ private function parseUploadedFile($filename, $contentType, $contents)
150181

151182
private function parsePost($name, $value)
152183
{
184+
// ignore excessive number of post fields
185+
if (++$this->postCount > $this->maxInputVars) {
186+
return;
187+
}
188+
153189
$this->request = $this->request->withParsedBody($this->extractPost(
154190
$this->request->getParsedBody(),
155191
$name,
@@ -203,6 +239,11 @@ private function extractPost($postFields, $key, $value)
203239
return $postFields;
204240
}
205241

242+
// ignore this key if maximum nesting level is exceeded
243+
if (isset($chunks[$this->maxInputNestingLevel])) {
244+
return $postFields;
245+
}
246+
206247
$chunkKey = rtrim($chunks[0], ']');
207248
$parent = &$postFields;
208249
for ($i = 1; isset($chunks[$i]); $i++) {

tests/Middleware/RequestBodyParserMiddlewareTest.php

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,7 @@ public function testMultipartFormDataParsing()
215215
$data .= "Content-Disposition: form-data; name=\"users[two]\"\r\n";
216216
$data .= "\r\n";
217217
$data .= "second\r\n";
218-
$data .= "--$boundary\r\n";
219-
218+
$data .= "--$boundary--\r\n";
220219

221220
$request = new ServerRequest('POST', 'http://example.com/', array(
222221
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
@@ -241,4 +240,80 @@ function (ServerRequestInterface $request) {
241240
);
242241
$this->assertSame($data, (string)$parsedRequest->getBody());
243242
}
243+
244+
public function testMultipartFormDataIgnoresFieldWithExcessiveNesting()
245+
{
246+
// supported in all Zend PHP versions and HHVM
247+
// ini setting does exist everywhere but HHVM: https://3v4l.org/hXLiK
248+
// HHVM limits to 64 and otherwise returns an empty array structure
249+
$allowed = (int)ini_get('max_input_nesting_level');
250+
if ($allowed === 0) {
251+
$allowed = 64;
252+
}
253+
254+
$middleware = new RequestBodyParserMiddleware();
255+
256+
$boundary = "---------------------------12758086162038677464950549563";
257+
258+
$data = "--$boundary\r\n";
259+
$data .= "Content-Disposition: form-data; name=\"hello" . str_repeat("[]", $allowed + 1) . "\"\r\n";
260+
$data .= "\r\n";
261+
$data .= "world\r\n";
262+
$data .= "--$boundary--\r\n";
263+
264+
$request = new ServerRequest('POST', 'http://example.com/', array(
265+
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
266+
), $data, 1.1);
267+
268+
/** @var ServerRequestInterface $parsedRequest */
269+
$parsedRequest = $middleware(
270+
$request,
271+
function (ServerRequestInterface $request) {
272+
return $request;
273+
}
274+
);
275+
276+
$this->assertEmpty($parsedRequest->getParsedBody());
277+
}
278+
279+
public function testMultipartFormDataTruncatesBodyWithExcessiveLength()
280+
{
281+
// ini setting exists in PHP 5.3.9, not in HHVM: https://3v4l.org/VF6oV
282+
// otherwise default to 1000 as implemented within
283+
$allowed = (int)ini_get('max_input_vars');
284+
if ($allowed === 0) {
285+
$allowed = 1000;
286+
}
287+
288+
$middleware = new RequestBodyParserMiddleware();
289+
290+
$boundary = "---------------------------12758086162038677464950549563";
291+
292+
$data = "";
293+
for ($i = 0; $i < $allowed + 1; ++$i) {
294+
$data .= "--$boundary\r\n";
295+
$data .= "Content-Disposition: form-data; name=\"a[]\"\r\n";
296+
$data .= "\r\n";
297+
$data .= "b\r\n";
298+
}
299+
$data .= "--$boundary--\r\n";
300+
301+
$request = new ServerRequest('POST', 'http://example.com/', array(
302+
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
303+
), $data, 1.1);
304+
305+
/** @var ServerRequestInterface $parsedRequest */
306+
$parsedRequest = $middleware(
307+
$request,
308+
function (ServerRequestInterface $request) {
309+
return $request;
310+
}
311+
);
312+
313+
$body = $parsedRequest->getParsedBody();
314+
315+
$this->assertCount(1, $body);
316+
$this->assertTrue(isset($body['a']));
317+
$this->assertCount($allowed, $body['a']);
318+
}
244319
}

0 commit comments

Comments
 (0)