77use Amp \ByteStream \ReadableResourceStream ;
88use Amp \Http \Cookie \RequestCookie ;
99use Amp \Http \Server \DefaultErrorHandler ;
10- use Amp \Http \Server \FormParser \BufferedFile ;
11- use Amp \Http \Server \FormParser \Form ;
1210use Amp \Http \Server \HttpServer as AmpHttpServer ;
1311use Amp \Http \Server \HttpServerStatus ;
1412use Amp \Http \Server \Request as AmpRequest ;
1917use Illuminate \Contracts \Http \Kernel as HttpKernel ;
2018use Illuminate \Foundation \Testing \Concerns \WithoutExceptionHandlingHandler ;
2119use Illuminate \Http \Request ;
22- use Illuminate \Http \UploadedFile ;
2320use Illuminate \Routing \UrlGenerator ;
2421use Illuminate \Support \Uri ;
2522use Pest \Browser \Contracts \HttpServer ;
2623use Pest \Browser \Exceptions \ServerNotFoundException ;
2724use Pest \Browser \Execution ;
2825use Pest \Browser \GlobalState ;
26+ use Pest \Browser \Http \ExtendedFormParser ;
2927use Pest \Browser \Playwright \Playwright ;
3028use Psr \Log \NullLogger ;
3129use Symfony \Component \Mime \MimeTypes ;
@@ -53,14 +51,19 @@ final class LaravelHttpServer implements HttpServer
5351 */
5452 private ?Throwable $ lastThrowable = null ;
5553
54+ /**
55+ * The multipart parser wrapper with upload validation behavior.
56+ */
57+ private ExtendedFormParser $ extendedFormParser ;
58+
5659 /**
5760 * Creates a new laravel http server instance.
5861 */
5962 public function __construct (
6063 public readonly string $ host ,
6164 public readonly int $ port ,
6265 ) {
63- //
66+ $ this -> extendedFormParser = ExtendedFormParser:: fromIni ();
6467 }
6568
6669 /**
@@ -72,6 +75,14 @@ public function __destruct()
7275 // $this->stop();
7376 }
7477
78+ /**
79+ * Overrides the multipart parser instance.
80+ */
81+ public function setExtendedFormParser (ExtendedFormParser $ extendedFormParser ): void
82+ {
83+ $ this ->extendedFormParser = $ extendedFormParser ;
84+ }
85+
7586 /**
7687 * Rewrite the given URL to match the server's host and port.
7788 */
@@ -383,170 +394,6 @@ private function rewriteAssetUrl(string $content): string
383394 */
384395 private function parseMultipartFormData (AmpRequest $ request ): array
385396 {
386- $ form = Form::fromRequest ($ request );
387-
388- $ values = $ form ->getValues ();
389- $ files = $ form ->getFiles ();
390-
391- return [
392- $ this ->normalizeMultipartParameters ($ values ),
393- $ this ->normalizeMultipartFiles ($ files ),
394- ];
395- }
396-
397- /**
398- * Normalize multipart field values to a Symfony request-compatible array.
399- *
400- * @param array<string, list<string>> $fields
401- * @return array<int|string, mixed>
402- */
403- private function normalizeMultipartParameters (array $ fields ): array
404- {
405- $ normalized = [];
406-
407- foreach ($ fields as $ field => $ values ) {
408- foreach ($ values as $ value ) {
409- $ this ->setFieldValue ($ normalized , $ field , $ value );
410- }
411- }
412-
413- return $ normalized ;
414- }
415-
416- /**
417- * Normalize multipart files to a Symfony request-compatible files array.
418- *
419- * @param array<string, list<BufferedFile>> $files
420- * @return array<int|string, mixed>
421- */
422- private function normalizeMultipartFiles (array $ files ): array
423- {
424- $ normalized = [];
425-
426- foreach ($ files as $ field => $ fileEntries ) {
427- foreach ($ fileEntries as $ fileEntry ) {
428- $ this ->setFieldValue ($ normalized , $ field , $ this ->createUploadedFile ($ fileEntry ));
429- }
430- }
431-
432- return $ normalized ;
433- }
434-
435- /**
436- * @param array<int|string, mixed> $target
437- */
438- private function setFieldValue (array &$ target , string $ field , string |UploadedFile $ value ): void
439- {
440- $ segments = $ this ->fieldSegments ($ field );
441-
442- if ($ segments === []) {
443- return ;
444- }
445-
446- $ this ->setNestedFieldValue ($ target , $ segments , $ value );
447- }
448-
449- /**
450- * @return list<string>
451- */
452- private function fieldSegments (string $ field ): array
453- {
454- if (! str_contains ($ field , '[ ' )) {
455- return [$ field ];
456- }
457-
458- $ segments = [];
459- $ head = mb_strstr ($ field , '[ ' , true );
460-
461- if ($ head !== false && $ head !== '' ) {
462- $ segments [] = $ head ;
463- }
464-
465- preg_match_all ('/\[([^\]]*)\]/ ' , $ field , $ matches );
466-
467- foreach ($ matches [1 ] as $ segment ) {
468- $ segments [] = $ segment ;
469- }
470-
471- return $ segments ;
472- }
473-
474- /**
475- * @param array<int|string, mixed> $target
476- * @param list<string> $segments
477- */
478- private function setNestedFieldValue (array &$ target , array $ segments , string |UploadedFile $ value ): void
479- {
480- $ segment = array_shift ($ segments );
481-
482- if ($ segment === null ) {
483- return ;
484- }
485-
486- if ($ segments === []) {
487- if ($ segment === '' ) {
488- $ target [] = $ value ;
489-
490- return ;
491- }
492-
493- if (! array_key_exists ($ segment , $ target )) {
494- $ target [$ segment ] = $ value ;
495-
496- return ;
497- }
498-
499- if (! is_array ($ target [$ segment ])) {
500- $ target [$ segment ] = [$ target [$ segment ]];
501- }
502-
503- $ target [$ segment ][] = $ value ;
504-
505- return ;
506- }
507-
508- if ($ segment === '' ) {
509- $ target [] = [];
510-
511- $ lastKey = array_key_last ($ target );
512- if (! is_array ($ target [$ lastKey ])) {
513- return ;
514- }
515-
516- $ this ->setNestedFieldValue ($ target [$ lastKey ], $ segments , $ value );
517-
518- return ;
519- }
520-
521- if (! isset ($ target [$ segment ]) || ! is_array ($ target [$ segment ])) {
522- $ target [$ segment ] = [];
523- }
524-
525- $ this ->setNestedFieldValue ($ target [$ segment ], $ segments , $ value );
526- }
527-
528- private function createUploadedFile (object $ fileEntry ): UploadedFile
529- {
530- $ tempPath = tempnam (sys_get_temp_dir (), 'pest-browser-upload- ' );
531- assert ($ tempPath !== false , 'Failed to create temporary upload file. ' );
532-
533- $ contents = method_exists ($ fileEntry , 'getContents ' ) ? $ fileEntry ->getContents () : '' ;
534- $ contents = is_string ($ contents ) ? $ contents : '' ;
535-
536- file_put_contents ($ tempPath , $ contents );
537-
538- $ clientFilename = method_exists ($ fileEntry , 'getName ' ) ? $ fileEntry ->getName () : 'upload ' ;
539- $ clientFilename = is_string ($ clientFilename ) && $ clientFilename !== '' ? $ clientFilename : 'upload ' ;
540-
541- $ mimeType = method_exists ($ fileEntry , 'getMimeType ' ) ? $ fileEntry ->getMimeType () : 'application/octet-stream ' ;
542- $ mimeType = is_string ($ mimeType ) && $ mimeType !== '' ? $ mimeType : 'application/octet-stream ' ;
543-
544- return new UploadedFile (
545- $ tempPath ,
546- $ clientFilename ,
547- $ mimeType ,
548- UPLOAD_ERR_OK ,
549- true ,
550- );
397+ return $ this ->extendedFormParser ->parseMultipart ($ request );
551398 }
552399}
0 commit comments