Skip to content

Commit bca58a0

Browse files
committed
Fix non-selected files
- Converted the non-selected files into nulls.
1 parent f801c33 commit bca58a0

File tree

5 files changed

+88
-48
lines changed

5 files changed

+88
-48
lines changed

src/Drivers/LaravelHttpServer.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Illuminate\Contracts\Debug\ExceptionHandler;
1616
use Illuminate\Contracts\Http\Kernel as HttpKernel;
1717
use Illuminate\Foundation\Testing\Concerns\WithoutExceptionHandlingHandler;
18-
use Illuminate\Http\Concerns\InteractsWithInput;
1918
use Illuminate\Http\Request;
2019
use Illuminate\Http\UploadedFile;
2120
use Illuminate\Routing\UrlGenerator;
@@ -26,7 +25,6 @@
2625
use Pest\Browser\GlobalState;
2726
use Pest\Browser\Http\RequestBodyParser;
2827
use Psr\Log\NullLogger;
29-
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;
3028
use Symfony\Component\Mime\MimeTypes;
3129
use Throwable;
3230

@@ -302,20 +300,22 @@ private function handleRequest(AmpRequest $request): Response
302300
}
303301

304302
/**
305-
* Taken from Laravel because we can't manipulate the test flag
306-
*
307-
* @param array<SymfonyUploadedFile[]|SymfonyUploadedFile> $files
308-
* @return array<UploadedFile[]|UploadedFile>
309-
*
310-
* @see InteractsWithInput
303+
* Convert the array to a Laravel file, to keep the test flag.
304+
* If the file is empty, we return the original array, so Laravel
305+
* would convert it to a null.
311306
*/
307+
// @phpstan-ignore-next-line
312308
private function convertUploadedFiles(array $files): array
313309
{
314310
// @phpstan-ignore-next-line
315-
return array_map(function (array|SymfonyUploadedFile $file) {
316-
return is_array($file)
311+
return array_map(function (array $file) {
312+
if (isset($file['error']) && $file['error'] === UPLOAD_ERR_NO_FILE) {
313+
return $file;
314+
}
315+
316+
return array_is_list($file)
317317
? $this->convertUploadedFiles($file)
318-
: UploadedFile::createFromBase($file, true);
318+
: new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], true);
319319
}, $files);
320320
}
321321

src/Http/MultipartParser.php

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use Amp\Http\Server\Request as AmpRequest;
88
use InvalidArgumentException;
9-
use Symfony\Component\HttpFoundation\File\UploadedFile;
109

1110
/**
1211
* Derived work from the MultipartParser in ReactPHP.
@@ -19,7 +18,7 @@
1918
final class MultipartParser
2019
{
2120
/**
22-
* @var array{"$_POST": array<array-key, mixed[]|string>, "$_FILES": array<array-key, UploadedFile[]|UploadedFile>}
21+
* @var array{"$_POST": array<array-key, mixed[]|string>, "$_FILES": array<array-key, array{tmp_file: string, error: int, name: string, type: string, size: int}[]|array{tmp_file: string, error: int, name: string, type: string, size: int}>}
2322
*/
2423
private array $superglobals = ['$_POST' => [], '$_FILES' => []];
2524

@@ -88,7 +87,7 @@ public function __construct(
8887
}
8988

9089
/**
91-
* @return array{array<mixed[]|string>, array<UploadedFile[]|UploadedFile>}
90+
* @return array{array<mixed[]|string>, array<array{tmp_file: string, error: int, name: string, type: string, size: int}[]|array{tmp_file: string, error: int, name: string, type: string, size: int}>}
9291
*/
9392
public function parse(AmpRequest $request, string $body): array
9493
{
@@ -219,8 +218,12 @@ private function parseFile(string $name, string $filename, ?string $contentType,
219218
);
220219
}
221220

222-
private function parseUploadedFile(string $filename, ?string $contentType, string $contents): ?UploadedFile
221+
/**
222+
* @return array{tmp_file: string, error: int, name: string, type: string, size: int}|null
223+
*/
224+
private function parseUploadedFile(string $filename, ?string $contentType, string $contents): array|null
223225
{
226+
$contentType ??= 'application/octet-stream';
224227
$size = strlen($contents);
225228

226229
// no file selected (zero size and empty filename)
@@ -230,13 +233,13 @@ private function parseUploadedFile(string $filename, ?string $contentType, strin
230233
return null;
231234
}
232235

233-
return new UploadedFile(
234-
'',
235-
$filename,
236-
$contentType,
237-
UPLOAD_ERR_NO_FILE,
238-
true,
239-
);
236+
return [
237+
'tmp_name' => '',
238+
'error' => UPLOAD_ERR_NO_FILE,
239+
'name' => $filename,
240+
'type' => $contentType,
241+
'size' => $size,
242+
];
240243
}
241244

242245
// ignore excessive number of file uploads
@@ -246,36 +249,36 @@ private function parseUploadedFile(string $filename, ?string $contentType, strin
246249

247250
// file exceeds "upload_max_filesize" ini setting
248251
if ($size > $this->uploadMaxFilesize) {
249-
return new UploadedFile(
250-
'',
251-
$filename,
252-
$contentType,
253-
UPLOAD_ERR_INI_SIZE,
254-
true,
255-
);
252+
return [
253+
'tmp_name' => '',
254+
'error' => UPLOAD_ERR_INI_SIZE,
255+
'name' => $filename,
256+
'type' => $contentType,
257+
'size' => $size,
258+
];
256259
}
257260

258261
// file exceeds MAX_FILE_SIZE value
259262
if ($this->maxFileSize !== null && $size > $this->maxFileSize) {
260-
return new UploadedFile(
261-
'',
262-
$filename,
263-
$contentType,
264-
UPLOAD_ERR_FORM_SIZE,
265-
true,
266-
);
263+
return [
264+
'tmp_name' => '',
265+
'error' => UPLOAD_ERR_FORM_SIZE,
266+
'name' => $filename,
267+
'type' => $contentType,
268+
'size' => $size,
269+
];
267270
}
268271

269272
$tempFileName = tempnam(sys_get_temp_dir(), 'php');
270273
file_put_contents($tempFileName, $contents);
271274

272-
return new UploadedFile(
273-
$tempFileName,
274-
$filename,
275-
$contentType,
276-
UPLOAD_ERR_OK,
277-
true,
278-
);
275+
return [
276+
'tmp_name' => $tempFileName,
277+
'error' => UPLOAD_ERR_OK,
278+
'name' => $filename,
279+
'type' => $contentType,
280+
'size' => $size,
281+
];
279282
}
280283

281284
private function parsePost(string $name, string $value): void

tests/Fixtures/example.pdf

45.7 KB
Binary file not shown.

tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
});
5353

5454
it('parse a multipart body with files', function (): void {
55+
Route::get('favicon.ico', static fn (): string => '');
5556
Route::get('/', static fn (): string => "
5657
<html>
5758
<head></head>
@@ -60,8 +61,14 @@
6061
<label for='name'>Your name</label>
6162
<input id='name' type='text' name='name'>
6263
63-
<label for='file'>Your file</label>
64-
<input id='file' type='file' name='file'>
64+
<label for='file1'>Your text file</label>
65+
<input id='file1' type='file' name='file1'>
66+
67+
<label for='file2'>Your binary file</label>
68+
<input id='file2' type='file' name='file2'>
69+
70+
<label for='file3'>Your empty file</label>
71+
<input id='file3' type='file' name='file3'>
6572
6673
<button type='submit'>Send</button>
6774
</form>
@@ -73,7 +80,9 @@
7380
<head></head>
7481
<body>
7582
<h1>Hello {$request->post('name')}</h1>
76-
<p>Uploaded file: {$request->file('file')->getClientOriginalName()}</p>
83+
<p>Text file: {$request->file('file1')?->getClientOriginalName()}</p>
84+
<p>Binary file: {$request->file('file2')?->getClientOriginalName()}</p>
85+
<p>Empty file: {$request->file('file3')?->getClientOriginalName()}</p>
7786
</body>
7887
</html>
7988
");
@@ -82,11 +91,14 @@
8291
$page->assertSee('Your name');
8392

8493
$page->fill('Your name', 'World');
85-
$page->attach('Your file', fixture('lorem-ipsum.txt'));
94+
$page->attach('Your text file', fixture('lorem-ipsum.txt'));
95+
$page->attach('Your binary file', fixture('example.pdf'));
8696
$page->submit();
8797

8898
$page->assertSee('Hello World');
89-
$page->assertSee('Uploaded file: lorem-ipsum.txt');
99+
$page->assertSee('Text file: lorem-ipsum.txt');
100+
$page->assertSee('Binary file: example.pdf');
101+
$page->assertSee('Empty file: ');
90102
});
91103

92104
it('parse a multipart body with nested fields', function (): void {

tests/Unit/Http/MultipartParserTest.php

Lines changed: 25 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)