download($path, $safeName, $headers); $response->headers->set('Content-Disposition', static::contentDisposition($downloadName)); return $response; } /** * Build RFC 5987 compatible Content-Disposition value. */ public static function contentDisposition( string $downloadName, string $type = 'attachment' ): string { $safeOriginalName = static::sanitizeHeaderFilename($downloadName); $safeFallback = addcslashes(static::asciiFallbackFilename($safeOriginalName), "\"\\"); $encoded = rawurlencode($safeOriginalName); return "{$type}; filename=\"{$safeFallback}\"; filename*=UTF-8''{$encoded}"; } /** * Generate ASCII fallback filename for old clients. */ public static function asciiFallbackFilename(string $filename): string { $filename = static::sanitizeHeaderFilename($filename); $extension = pathinfo($filename, PATHINFO_EXTENSION); $basename = pathinfo($filename, PATHINFO_FILENAME); $asciiBase = Str::ascii($basename); $asciiBase = preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) $asciiBase); $asciiBase = trim((string) $asciiBase, '._-'); if ($asciiBase === '') { $asciiBase = 'download'; } $asciiExtension = Str::ascii((string) $extension); $asciiExtension = preg_replace('/[^A-Za-z0-9]+/', '', (string) $asciiExtension); return $asciiExtension === '' ? $asciiBase : "{$asciiBase}.{$asciiExtension}"; } private static function sanitizeHeaderFilename(string $filename): string { $filename = str_replace(["\r", "\n"], '', trim($filename)); return $filename !== '' ? $filename : 'download'; } }