diff --git a/src/Http/Request.php b/src/Http/Request.php index 491cfaf..eace73b 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -166,13 +166,7 @@ private function parseMultipartFormData($stream, $boundary) { } elseif ($fieldType === 'file' && $filename) { $tmpPath = tempnam($this->getUploadDir(), 'fastcgi_upload'); $err = file_put_contents($tmpPath, $buffer); - $files[$fieldName] = [ - 'type' => $mimeType ?: 'application/octet-stream', - 'name' => $filename, - 'tmp_name' => $tmpPath, - 'error' => ($err === false) ? true : 0, - 'size' => filesize($tmpPath), - ]; + $this->addFile($files, $fieldName, $filename, $tmpPath, $mimeType, false === $err); $filename = $mimeType = null; } $fieldName = $fieldType = null; @@ -248,10 +242,8 @@ public function getServerRequest() $uri = ServerRequestFactory::marshalUriFromServer($server, $headers); $method = ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET'); - $files = []; - foreach ($this->uploadedFiles as $file) { - $files[] = createUploadedFile($file); - } + $files = $this->uploadedFiles; + $this->preparePsr7UploadedFiles($files); $request = new ServerRequest($server, $files, $uri, $method, $this->stdin, $headers); @@ -276,4 +268,50 @@ public function getHttpFoundationRequest() return new HttpFoundationRequest($query, $post, [], $cookies, $this->uploadedFiles, $this->params, $this->stdin); } + + /** + * Add a file to the $files array + */ + private function addFile(array &$files, string $fieldName, string $filename, string $tmpPath, string $mimeType, bool $err): void + { + $data = [ + 'type' => $mimeType ?: 'application/octet-stream', + 'name' => $filename, + 'tmp_name' => $tmpPath, + 'error' => $err ? UPLOAD_ERR_CANT_WRITE : UPLOAD_ERR_OK, + 'size' => filesize($tmpPath), + ]; + + $parts = preg_split('|(\[[^\]]*\])|', $fieldName, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $count = count($parts); + if (1 === $count) { + $files[$fieldName] = $data; + } else { + $current = &$files; + foreach ($parts as $i => $part) { + if ($part === '[]') { + $current[] = $data; + continue; + } + + $trimmedMatch = trim($part, '[]'); + if ($i === $count -1) { + $current[$trimmedMatch] = $data; + } else { + $current = &$current[$trimmedMatch]; + } + } + } + } + + private function preparePsr7UploadedFiles(array &$files) + { + if (isset($files['tmp_name'])) { + $files = createUploadedFile($files); + } else { + foreach ($files as &$file) { + $this->preparePsr7UploadedFiles($file); + } + } + } } diff --git a/test/Http/RequestTest.php b/test/Http/RequestTest.php index 8b960c0..49ce967 100644 --- a/test/Http/RequestTest.php +++ b/test/Http/RequestTest.php @@ -125,4 +125,135 @@ public function testMultipartContent() $this->assertCount(1, $httpFoundationRequest->files->all()); $this->assertEquals($content, $httpFoundationRequest->getContent()); } + + public function testMultipartContentWithMultipleFiles() + { + $expectedPost = ['foo' => 'A normal stream', 'baz' => 'string']; + + // Set up FastCGI params and content + $params = [ + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_METHOD' => 'POST', + 'content_type' => 'multipart/form-data; boundary="578de3b0e3c46.2334ba3"', + 'REQUEST_URI' => '/my-page', + ]; + + // Set up the FastCGI stdin data stream resource + $content = <<assertEquals($expectedPost, $request->getPost()); + $this->assertEquals($stream, $request->getStdin()); + + // Check the PSR server request + rewind($stream); + $serverRequest = $request->getServerRequest(); + $this->assertEquals($expectedPost, $serverRequest->getParsedBody()); + $files = $serverRequest->getUploadedFiles(); + $this->assertNotEmpty($files['one']); + $this->assertCount(2, $files['one']); + + $this->assertNotEmpty($files['two']); + $this->assertCount(1, $files['two']); + $this->assertNotEmpty($files['two']['item-a']); + + $this->assertNotEmpty($files['three']); + $this->assertCount(1, $files['three']); + $this->assertCount(2, $files['three']['item']); + + $this->assertNotEmpty($files['four']); + $this->assertNotEmpty($files['four']['item_a']); + $this->assertNotEmpty($files['four']['item_a']['item_b']); + $this->assertCount(2, $files['four']['item_a']['item_b']); + + // Check the HttpFoundation request + rewind($stream); + $httpFoundationRequest = $request->getHttpFoundationRequest(); + $this->assertEquals($expectedPost, $httpFoundationRequest->request->all()); + $this->assertCount(4, $httpFoundationRequest->files->all()); + + } }