Skip to content

Commit 85fd8f9

Browse files
committed
Add parameter to control maximum number of file uploads per request
1 parent 73569a9 commit 85fd8f9

5 files changed

Lines changed: 75 additions & 5 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,18 @@ constructor like this:
796796
new RequestBodyParserMiddleware(8 * 1024 * 1024); // 8 MiB limit per file
797797
```
798798

799+
By default, this middleware respects the
800+
[`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads)
801+
(default `20`) ini setting.
802+
If you upload more files in a single request, additional files will be ignored
803+
and the `getUploadedFiles()` method returns a truncated array.
804+
You can control the maximum number of file uploads per request by explicitly
805+
passing the second parameter to the constructor like this:
806+
807+
```php
808+
new RequestBodyParserMiddleware(10 * 1024, 100); // 100 files with 10 KiB each
809+
```
810+
799811
> Note that this middleware handler simply parses everything that is already
800812
buffered in the request body.
801813
It is imperative that the request body is buffered by a prior middleware

examples/12-upload.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
// buffer and parse HTTP request body before running our request handler
123123
$server = new StreamingServer(new MiddlewareRunner(array(
124124
new RequestBodyBufferMiddleware(8 * 1024 * 1024), // 8 MiB max, ignore body otherwise
125-
new RequestBodyParserMiddleware(100 * 1024), // 100 KiB max, reject upload otherwise
125+
new RequestBodyParserMiddleware(100 * 1024, 1), // 1 file with 100 KiB max, reject upload otherwise
126126
$handler
127127
)));
128128

src/Io/MultipartParser.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,21 @@ final class MultipartParser
5454
*/
5555
private $uploadMaxFilesize;
5656

57+
/**
58+
* ini setting "max_file_uploads"
59+
*
60+
* @var int
61+
*/
62+
private $maxFileUploads;
63+
5764
private $postCount = 0;
65+
private $filesCount = 0;
5866

5967
/**
6068
* @param int|null $uploadMaxFilesize
69+
* @param int|null $maxFileUploads
6170
*/
62-
public function __construct($uploadMaxFilesize = null)
71+
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
6372
{
6473
$var = ini_get('max_input_vars');
6574
if ($var !== false) {
@@ -71,6 +80,7 @@ public function __construct($uploadMaxFilesize = null)
7180
}
7281

7382
$this->uploadMaxFilesize = $uploadMaxFilesize === null ? $this->iniUploadMaxFilesize() : (int)$uploadMaxFilesize;
83+
$this->maxFileUploads = $maxFileUploads === null ? (int)ini_get('max_file_uploads') : (int)$maxFileUploads;
7484
}
7585

7686
public function parse(ServerRequestInterface $request)
@@ -86,6 +96,7 @@ public function parse(ServerRequestInterface $request)
8696
$request = $this->request;
8797
$this->request = null;
8898
$this->postCount = 0;
99+
$this->filesCount = 0;
89100
$this->maxFileSize = null;
90101

91102
return $request;
@@ -147,15 +158,25 @@ private function parsePart($chunk)
147158

148159
private function parseFile($name, $filename, $contentType, $contents)
149160
{
161+
$file = $this->parseUploadedFile($filename, $contentType, $contents);
162+
if ($file === null) {
163+
return;
164+
}
165+
150166
$this->request = $this->request->withUploadedFiles($this->extractPost(
151167
$this->request->getUploadedFiles(),
152168
$name,
153-
$this->parseUploadedFile($filename, $contentType, $contents)
169+
$file
154170
));
155171
}
156172

157173
private function parseUploadedFile($filename, $contentType, $contents)
158174
{
175+
// ignore excessive number of file uploads
176+
if (++$this->filesCount > $this->maxFileUploads) {
177+
return;
178+
}
179+
159180
$size = strlen($contents);
160181

161182
// no file selected (zero size and empty filename)

src/Middleware/RequestBodyParserMiddleware.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ final class RequestBodyParserMiddleware
1111

1212
/**
1313
* @param int|null $uploadMaxFilesize
14+
* @param int|null $maxFileUploads
1415
*/
15-
public function __construct($uploadMaxFilesize = null)
16+
public function __construct($uploadMaxFilesize = null, $maxFileUploads = null)
1617
{
17-
$this->multipart = new MultipartParser($uploadMaxFilesize);
18+
$this->multipart = new MultipartParser($uploadMaxFilesize, $maxFileUploads);
1819
}
1920

2021
public function __invoke(ServerRequestInterface $request, $next)

tests/Io/MultipartParserTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,42 @@ public function testUploadNoFile()
754754
$this->assertSame(UPLOAD_ERR_NO_FILE, $file->getError());
755755
}
756756

757+
public function testUploadTooManyFilesReturnsTruncatedList()
758+
{
759+
$boundary = "---------------------------12758086162038677464950549563";
760+
761+
$data = "--$boundary\r\n";
762+
$data .= "Content-Disposition: form-data; name=\"first\"; filename=\"first\"\r\n";
763+
$data .= "Content-type: text/plain\r\n";
764+
$data .= "\r\n";
765+
$data .= "hello\r\n";
766+
$data .= "--$boundary\r\n";
767+
$data .= "Content-Disposition: form-data; name=\"second\"; filename=\"second\"\r\n";
768+
$data .= "Content-type: text/plain\r\n";
769+
$data .= "\r\n";
770+
$data .= "world\r\n";
771+
$data .= "--$boundary--\r\n";
772+
773+
$request = new ServerRequest('POST', 'http://example.com/', array(
774+
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
775+
), $data, 1.1);
776+
777+
$parser = new MultipartParser(100, 1);
778+
$parsedRequest = $parser->parse($request);
779+
780+
$files = $parsedRequest->getUploadedFiles();
781+
782+
$this->assertCount(1, $files);
783+
$this->assertTrue(isset($files['first']));
784+
785+
$file = $files['first'];
786+
$this->assertSame('first', $file->getClientFilename());
787+
$this->assertSame('text/plain', $file->getClientMediaType());
788+
$this->assertSame(5, $file->getSize());
789+
$this->assertSame(UPLOAD_ERR_OK, $file->getError());
790+
$this->assertSame('hello', (string)$file->getStream());
791+
}
792+
757793
public function testPostMaxFileSize()
758794
{
759795
$boundary = "---------------------------12758086162038677464950549563";

0 commit comments

Comments
 (0)