8000 [BrowserKit] Nested file array prevents uploading file · symfony/browser-kit@090ce40 · GitHub
[go: up one dir, main page]

Skip to content

Commit 090ce40

Browse files
afilinanicolas-grekas
authored andcommitted
[BrowserKit] Nested file array prevents uploading file
1 parent 6a0e997 commit 090ce40

File tree

2 files changed

+114
-15
lines changed

2 files changed

+114
-15
lines changed

HttpBrowser.php

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,9 @@ private function getBodyAndExtraHeaders(Request $request): array
7373
}
7474

7575
$fields = $request->getParameters();
76-
$hasFile = false;
77-
foreach ($request->getFiles() as $name => $file) {
78-
if (!isset($file['tmp_name'])) {
79-
continue;
80-
}
81-
82-
$hasFile = true;
83-
$fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
84-
}
8576

86-
if ($hasFile) {
87-
$part = new FormDataPart($fields);
77+
if ($uploadedFiles = $this->getUploadedFiles($request->getFiles())) {
78+
$part = new FormDataPart($uploadedFiles);
8879

8980
return [$part->bodyToIterable(), $part->getPreparedHeaders()->toArray()];
9081
}
@@ -119,4 +110,26 @@ private function getHeaders(Request $request): array
119110

120111
return $headers;
121112
}
113+
114+
/**
115+
* Recursively go through the list. If the file has a tmp_name, convert it to a DataPart.
116+
* Keep the original hierarchy.
117+
*/
118+
private function getUploadedFiles(array $files): array
119+
{
120+
$uploadedFiles = [];
121+
foreach ($files as $name => $file) {
122+
if (!\is_array($file)) {
123+
return $uploadedFiles;
124+
}
125+
if (!isset($file['tmp_name'])) {
126+
$uploadedFiles[$name] = $this->getUploadedFiles($file);
127+
}
128+
if (isset($file['tmp_name'])) {
129+
$uploadedFiles[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
130+
}
131+
}
132+
133+
return $uploadedFiles;
134+
}
122135
}

Tests/HttpBrowserTest.php

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ public function getBrowser(array $server = [], History $history = null, CookieJa
2727
/**
2828
* @dataProvider validContentTypes
2929
*/
30-
public function testRequestHeaders(array $request, array $exepectedCall)
30+
public function testRequestHeaders(array $requestArguments, array $expectedArguments)
3131
{
3232
$client = $this->createMock(HttpClientInterface::class);
3333
$client
3434
->expects($this->once())
3535
->method('request')
36-
->with(...$exepectedCall)
36+
->with(...$expectedArguments)
3737
->willReturn($this->createMock(ResponseInterface::class));
3838

3939
$browser = new HttpBrowser($client);
40-
$browser->request(...$request);
40+
$browser->request(...$requestArguments);
4141
}
4242

4343
public function validContentTypes()
@@ -61,7 +61,7 @@ public function validContentTypes()
6161
];
6262
}
6363

64-
public function testMultiPartRequest()
64+
public function testMultiPartRequestWithSingleFile()
6565
{
6666
$client = $this->createMock(HttpClientInterface::class);
6767
$client
@@ -81,4 +81,90 @@ public function testMultiPartRequest()
8181
file_put_contents($path, 'my_file');
8282
$browser->request('POST', 'http://example.com/', [], ['file' => ['tmp_name' => $path, 'name' => 'foo']]);
8383
}
84+
85+
public function testMultiPartRequestWithNormalFlatArray()
86+
{
87+
$client = $this->createMock(HttpClientInterface::class);
88+
$this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']);
89+
90+
$browser = new HttpBrowser($client);
91+
$browser->request('POST', 'http://example.com/', [], [
92+
'file1' => $this->getUploadedFile('file1'),
93+
'file2' => $this->getUploadedFile('file2'),
94+
]);
95+
}
96+
97+
public function testMultiPartRequestWithNormalNestedArray()
98+
{
99+
$client = $this->createMock(HttpClientInterface::class);
100+
$this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']);
101+
102+
$browser = new HttpBrowser($client);
103+
$browser->request('POST', 'http://example.com/', [], [
104+
'level1' => [
105+
'level2' => [
106+
'file1' => $this->getUploadedFile('file1'),
107+
'file2' => $this->getUploadedFile('file2'),
108+
],
109+
],
110+
]);
111+
}
112+
113+
public function testMultiPartRequestWithBracketedArray()
114+
{
115+
$client = $this->createMock(HttpClientInterface::class);
116+
$this->expectClientToSendRequestWithFiles($client, ['file1_content', 'file2_content']);
117+
118+
$browser = new HttpBrowser($client);
119+
$browser->request('POST', 'http://example.com/', [], [
120+
'form[file1]' => $this->getUploadedFile('file1'),
121+
'form[file2]' => $this->getUploadedFile('file2'),
122+
]);
123+
}
124+
125+
public function testMultiPartRequestWithInvalidItem()
126+
{
127+
$client = $this->createMock(HttpClientInterface::class);
128+
$this->expectClientToSendRequestWithFiles($client, ['file1_content']);
129+
130+
$browser = new HttpBrowser($client);
131+
$browser->request('POST', 'http://example.com/', [], [
132+
'file1' => $this->getUploadedFile('file1'),
133+
'file2' => 'INVALID',
134+
]);
135+
}
136+
137+
private function uploadFile(string $data): string
138+
{
139+
$path = tempnam(sys_get_temp_dir(), 'http');
140+
file_put_contents($path, $data);
141+
142+
return $path;
143+
}
144+
145+
private function getUploadedFile(string $name): array
146+
{
147+
return [
148+
'tmp_name' => $this->uploadFile($name.'_content'),
149+
'name' => $name.'_name',
150+
];
151+
}
152+
153+
protected function expectClientToSendRequestWithFiles(HttpClientInterface $client, $fileContents)
154+
{
155+
$client
156+
->expects($this->once())
157+
->method('request')
158+
->with('POST', 'http://example.com/', $this->callback(function ($options) use ($fileContents) {
159+
$this->assertStringContainsString('Content-Type: multipart/form-data', implode('', $options['headers']));
160+
$this->assertInstanceOf('\Generator', $options['body']);
161+
$body = implode('', iterator_to_array($options['body'], false));
162+
foreach ($fileContents as $content) {
163+
$this->assertStringContainsString($content, $body);
164+
}
165+
166+
return true;
167+
}))
168+
->willReturn($this->createMock(ResponseInterface::class));
169+
}
84170
}

0 commit comments

Comments
 (0)
0