Skip to content

Commit d7fe9b3

Browse files
committed
Add support for multiple file uploads
1 parent 35d6f68 commit d7fe9b3

File tree

2 files changed

+177
-11
lines changed

2 files changed

+177
-11
lines changed

src/Http/Request.php

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,7 @@ private function parseMultipartFormData($stream, $boundary) {
166166
} elseif ($fieldType === 'file' && $filename) {
167167
$tmpPath = tempnam($this->getUploadDir(), 'fastcgi_upload');
168168
$err = file_put_contents($tmpPath, $buffer);
169-
$files[$fieldName] = [
170-
'type' => $mimeType ?: 'application/octet-stream',
171-
'name' => $filename,
172-
'tmp_name' => $tmpPath,
173-
'error' => ($err === false) ? true : 0,
174-
'size' => filesize($tmpPath),
175-
];
169+
$this->addFile($files, $fieldName, $filename, $tmpPath, $mimeType, false === $err);
176170
$filename = $mimeType = null;
177171
}
178172
$fieldName = $fieldType = null;
@@ -248,10 +242,8 @@ public function getServerRequest()
248242
$uri = ServerRequestFactory::marshalUriFromServer($server, $headers);
249243
$method = ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET');
250244

251-
$files = [];
252-
foreach ($this->uploadedFiles as $file) {
253-
$files[] = createUploadedFile($file);
254-
}
245+
$files = $this->uploadedFiles;
246+
$this->preparePsr7UploadedFiles($files);
255247

256248
$request = new ServerRequest($server, $files, $uri, $method, $this->stdin, $headers);
257249

@@ -276,4 +268,48 @@ public function getHttpFoundationRequest()
276268

277269
return new HttpFoundationRequest($query, $post, [], $cookies, $this->uploadedFiles, $this->params, $this->stdin);
278270
}
271+
272+
/**
273+
* Add a file to the $files array
274+
*/
275+
private function addFile(array &$files, string $fieldName, string $filename, string $tmpPath, string $mimeType, bool $err): void
276+
{
277+
$data = [
278+
'type' => $mimeType ?: 'application/octet-stream',
279+
'name' => $filename,
280+
'tmp_name' => $tmpPath,
281+
'error' => $err ? UPLOAD_ERR_CANT_WRITE : UPLOAD_ERR_OK,
282+
'size' => filesize($tmpPath),
283+
];
284+
285+
if (!preg_match('|([^\[]+)(?:\[([^\]]+)\])*(\[\])?|', $fieldName, $matches)) {
286+
$files[$fieldName] = $data;
287+
} else {
288+
$current = &$files;
289+
$count = count($matches);
290+
for ($i = 1; $i < $count; $i++) {
291+
if (empty($matches[$i])) {
292+
continue;
293+
}
294+
if ($matches[$i] === '[]') {
295+
$current[] = $data;
296+
} elseif ($i === $count -1) {
297+
$current[$matches[$i]] = $data;
298+
} else {
299+
$current = &$current[$matches[$i]];
300+
}
301+
}
302+
}
303+
}
304+
305+
private function preparePsr7UploadedFiles(array &$files)
306+
{
307+
if (isset($files['tmp_name'])) {
308+
$files = createUploadedFile($files);
309+
} else {
310+
foreach ($files as &$file) {
311+
$this->preparePsr7UploadedFiles($file);
312+
}
313+
}
314+
}
279315
}

test/Http/RequestTest.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,134 @@ public function testMultipartContent()
124124
$this->assertCount(1, $httpFoundationRequest->files->all());
125125
$this->assertEquals($content, $httpFoundationRequest->getContent());
126126
}
127+
128+
public function testMultipartContentWithMultipleFiles()
129+
{
130+
$expectedPost = ['foo' => 'A normal stream', 'baz' => 'string'];
131+
132+
// Set up FastCGI params and content
133+
$params = [
134+
'SERVER_PROTOCOL' => 'HTTP/1.1',
135+
'REQUEST_METHOD' => 'POST',
136+
'content_type' => 'multipart/form-data; boundary="578de3b0e3c46.2334ba3"',
137+
'REQUEST_URI' => '/my-page',
138+
];
139+
140+
// Set up the FastCGI stdin data stream resource
141+
$content = <<<HTTP
142+
--578de3b0e3c46.2334ba3
143+
Content-Disposition: form-data; name="foo"
144+
Content-Length: 15
145+
146+
A normal stream
147+
--578de3b0e3c46.2334ba3
148+
Content-Disposition: form-data; name="one[]"; filename="foo.png"
149+
Content-Length: 71
150+
Content-Type: image/png
151+
152+
?PNG
153+

154+
???
155+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
156+
--578de3b0e3c46.2334ba3
157+
--578de3b0e3c46.2334ba3
158+
Content-Disposition: form-data; name="one[]"; filename="bar.png"
159+
Content-Length: 71
160+
Content-Type: image/png
161+
162+
?PNG
163+

164+
???
165+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
166+
--578de3b0e3c46.2334ba3
167+
Content-Disposition: form-data; name="two[item-a]"; filename="bar.png"
168+
Content-Length: 71
169+
Content-Type: image/png
170+
171+
?PNG
172+

173+
???
174+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
175+
--578de3b0e3c46.2334ba3
176+
Content-Disposition: form-data; name="three[item][]"; filename="foo.png"
177+
Content-Length: 71
178+
Content-Type: image/png
179+
180+
?PNG
181+

182+
???
183+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
184+
--578de3b0e3c46.2334ba3
185+
Content-Disposition: form-data; name="three[item][]"; filename="bar.png"
186+
Content-Length: 71
187+
Content-Type: image/png
188+
189+
?PNG
190+

191+
???
192+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
193+
--578de3b0e3c46.2334ba3
194+
Content-Disposition: form-data; name="four[item_a][item_b[]"; filename="foo.png"
195+
Content-Length: 71
196+
Content-Type: image/png
197+
198+
?PNG
199+

200+
???
201+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
202+
Content-Disposition: form-data; name="four[item_a][item_b[]"; filename="bar.png"
203+
Content-Length: 71
204+
Content-Type: image/png
205+
206+
?PNG
207+

208+
???
209+
IHDR??? ??? ?????? ???? IDATxc???51?)?:??????IEND?B`?
210+
--578de3b0e3c46.2334ba3
211+
Content-Type: text/plain
212+
Content-Disposition: form-data; name="baz"
213+
Content-Length: 6
214+
215+
string
216+
--578de3b0e3c46.2334ba3--
217+
HTTP;
218+
219+
$stream = fopen('php://memory', 'r+');
220+
fwrite($stream, $content);
221+
222+
// Create the request
223+
$request = new Request($params, $stream);
224+
225+
// Check request object
226+
$this->assertEquals($expectedPost, $request->getPost());
227+
$this->assertEquals($stream, $request->getStdin());
228+
229+
// Check the PSR server request
230+
rewind($stream);
231+
$serverRequest = $request->getServerRequest();
232+
$this->assertEquals($expectedPost, $serverRequest->getParsedBody());
233+
$files = $serverRequest->getUploadedFiles();
234+
$this->assertNotEmpty($files['one']);
235+
$this->assertCount(2, $files['one']);
236+
237+
$this->assertNotEmpty($files['two']);
238+
$this->assertCount(1, $files['two']);
239+
$this->assertNotEmpty($files['two']['item-a']);
240+
241+
$this->assertNotEmpty($files['three']);
242+
$this->assertCount(1, $files['three']);
243+
$this->assertCount(2, $files['three']['item']);
244+
245+
$this->assertNotEmpty($files['four']);
246+
$this->assertNotEmpty($files['four']['item_a']);
247+
$this->assertNotEmpty($files['four']['item_a']['item_b']);
248+
$this->assertCount(2, $files['three']['item_a']['item_b']);
249+
250+
// Check the HttpFoundation request
251+
rewind($stream);
252+
$httpFoundationRequest = $request->getHttpFoundationRequest();
253+
$this->assertEquals($expectedPost, $httpFoundationRequest->request->all());
254+
$this->assertCount(7, $httpFoundationRequest->files->all());
255+
256+
}
127257
}

0 commit comments

Comments
 (0)