想要强制下载资源而不是直接在Web浏览器中呈现资源的Web应用程序在表单的HTTP响应中发出Content-Disposition报头:
Content-Disposition:附件;filename = filename
filename参数可用于建议浏览器将资源下载到的文件的名称。然而,RFC 2183 (Content-Disposition)在2.3节(文件名参数)中规定文件名只能使用US-ASCII字符:
当前[RFC 2045]语法限制
参数值(因此
内容-处置文件名)到
us - ascii。我们认可伟大的
允许任意的可取性
文件名中的字符集,但它是
超出了本文档的范围
定义必要的机制。
然而,有经验证据表明,目前大多数流行的Web浏览器似乎允许非us - ascii字符,但(由于缺乏标准)在文件名的编码方案和字符集规范上存在分歧。问题是,如果文件名“naïvefile”(不带引号,第三个字母是U+00EF)需要编码到Content-Disposition报头中,那么流行的浏览器采用了哪些不同的方案和编码?
为了解决这个问题,流行的浏览器是:
谷歌Chrome
Safari
Internet Explorer或Edge
火狐
歌剧
PHP框架Symfony 4在HeaderUtils::makeDisposition中有$filenameFallback。
您可以查看这个函数的详细信息-它与上面的答案类似。
使用的例子:
$filenameFallback = preg_replace('#^.*\.#', md5($filename) . '.', $filename);
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename, $filenameFallback);
$response->headers->set('Content-Disposition', $disposition);
在Content-Disposition中没有可互操作的方法来编码非ascii名称。浏览器兼容性是一团糟。
在Content-Disposition中使用UTF-8的理论上正确的语法是非常奇怪的:filename*=UTF-8 " foo%c3%a4(是的,这是一个星号,没有引号,除了中间的一个空单引号)
这个报头有点不太标准(HTTP/1.1规范承认它的存在,但不要求客户端支持它)。
有一种简单而可靠的替代方法:使用包含所需文件名的URL。
当最后一个斜杠后面的名称是您想要的名称时,您不需要任何额外的头文件!
这个技巧很管用:
/real_script.php/fake_filename.doc
如果你的服务器支持URL重写(例如Apache中的mod_rewrite),那么你可以完全隐藏脚本部分。
url中的字符应该是UTF-8,逐字节url编码:
/mot%C3%B6rhead # motörhead
库类Unicode中的方法mimeHeaderEncode($string)可以完成这项工作。
$file_name= Unicode::mimeHeaderEncode($file_name);
drupal/php中的例子:
https://github.com/drupal/core-utility/blob/8.8.x/Unicode.php
/**
* Encodes MIME/HTTP headers that contain incorrectly encoded characters.
*
* For example, Unicode::mimeHeaderEncode('tést.txt') returns
* "=?UTF-8?B?dMOpc3QudHh0?=".
*
* See http://www.rfc-editor.org/rfc/rfc2047.txt for more information.
*
* Notes:
* - Only encode strings that contain non-ASCII characters.
* - We progressively cut-off a chunk with self::truncateBytes(). This ensures
* each chunk starts and ends on a character boundary.
* - Using \n as the chunk separator may cause problems on some systems and
* may have to be changed to \r\n or \r.
*
* @param string $string
* The header to encode.
* @param bool $shorten
* If TRUE, only return the first chunk of a multi-chunk encoded string.
*
* @return string
* The mime-encoded header.
*/
public static function mimeHeaderEncode($string, $shorten = FALSE) {
if (preg_match('/[^\x20-\x7E]/', $string)) {
// floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
$chunk_size = 47;
$len = strlen($string);
$output = '';
while ($len > 0) {
$chunk = static::truncateBytes($string, $chunk_size);
$output .= ' =?UTF-8?B?' . base64_encode($chunk) . "?=\n";
if ($shorten) {
break;
}
$c = strlen($chunk);
$string = substr($string, $c);
$len -= $c;
}
return trim($output);
}
return $string;
}