Skip to content

Commit 4fb60b4

Browse files
committed
Add support for multiple file uploads
1 parent 23237ad commit 4fb60b4

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 = $this->getUploadDir().'/'.substr(md5(rand().time()), 0, 16);
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
@@ -125,4 +125,134 @@ public function testMultipartContent()
125125
$this->assertEquals($content, $httpFoundationRequest->getContent());
126126

127127
}
128+
129+
public function testMultipartContentWithMultipleFiles()
130+
{
131+
$expectedPost = ['foo' => 'A normal stream', 'baz' => 'string'];
132+
133+
// Set up FastCGI params and content
134+
$params = [
135+
'SERVER_PROTOCOL' => 'HTTP/1.1',
136+
'REQUEST_METHOD' => 'POST',
137+
'content_type' => 'multipart/form-data; boundary="578de3b0e3c46.2334ba3"',
138+
'REQUEST_URI' => '/my-page',
139+
];
140+
141+
// Set up the FastCGI stdin data stream resource
142+
$content = <<<HTTP
143+
--578de3b0e3c46.2334ba3
144+
Content-Disposition: form-data; name="foo"
145+
Content-Length: 15
146+
147+
A normal stream
148+
--578de3b0e3c46.2334ba3
149+
Content-Disposition: form-data; name="one[]"; filename="foo.png"
150+
Content-Length: 71
151+
Content-Type: image/png
152+
153+
?PNG
154+

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)