@@ -473,9 +473,15 @@ public function download(int $id, Request $r, ResourceFileHelper $resourceFileHe
473473 fclose ($ stream );
474474 });
475475
476+ $ downloadName = str_replace (["\r" , "\n" , "\0" ], '' , $ downloadName );
477+ $ downloadName = trim ($ downloadName );
478+
479+ $ fallbackName = $ this ->buildAsciiFilenameFallback ($ downloadName );
480+
476481 $ disposition = $ response ->headers ->makeDisposition (
477482 ResponseHeaderBag::DISPOSITION_ATTACHMENT ,
478- $ downloadName
483+ $ downloadName ,
484+ $ fallbackName
479485 );
480486
481487 $ response ->headers ->set ('Content-Type ' , $ mime );
@@ -743,4 +749,37 @@ private function userFullName(int $userId): string
743749
744750 return $ this ->userNameCache [$ userId ] = $ name ;
745751 }
752+
753+ private function buildAsciiFilenameFallback (string $ filename ): string
754+ {
755+ // Prevent header injection / invalid control chars
756+ $ filename = str_replace (["\r" , "\n" , "\0" ], '' , $ filename );
757+ $ filename = trim ($ filename );
758+
759+ $ ext = (string ) pathinfo ($ filename , PATHINFO_EXTENSION );
760+ $ base = (string ) pathinfo ($ filename , PATHINFO_FILENAME );
761+
762+ // Slugify base name (should become ASCII with the default Symfony slugger)
763+ $ baseAscii = (string ) $ this ->slugger ->slug ($ base , '_ ' );
764+ $ baseAscii = strtolower ($ baseAscii );
765+
766+ // Force strict ASCII-only fallback
767+ $ baseAscii = preg_replace ('/[^A-Za-z0-9._-]+/ ' , '_ ' , $ baseAscii ) ?? '' ;
768+ $ baseAscii = preg_replace ('/_+/ ' , '_ ' , $ baseAscii ) ?? '' ;
769+ $ baseAscii = trim ($ baseAscii , "._- " );
770+
771+ if ('' === $ baseAscii ) {
772+ $ baseAscii = 'download ' ;
773+ }
774+
775+ if ('' !== $ ext ) {
776+ $ ext = strtolower ($ ext );
777+ $ ext = preg_replace ('/[^A-Za-z0-9]+/ ' , '' , $ ext ) ?? '' ;
778+ if ('' !== $ ext ) {
779+ return $ baseAscii .'. ' .$ ext ;
780+ }
781+ }
782+
783+ return $ baseAscii ;
784+ }
746785}
0 commit comments