想要强制下载资源而不是直接在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);

其他回答

我在所有主流浏览器中测试了以下代码,包括老式的explorer(通过兼容模式),它在任何地方都能正常工作:

$filename = $_GET['file']; //this string from $_GET is already decoded
if (strstr($_SERVER['HTTP_USER_AGENT'],"MSIE"))
  $filename = rawurlencode($filename);
header('Content-Disposition: attachment; filename="'.$filename.'"');

我们在一个web应用程序中遇到了类似的问题,最后从HTML <input type="file">中读取文件名,并在一个新的HTML <input type="hidden">中以url编码的形式设置它。当然,我们必须删除一些浏览器返回的“C:\fakepath\”这样的路径。

当然,这并不能直接回答OPs的问题,但可能是其他人的解决方案。

在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

以下文件链接自Jim在回答中提到的RFC草案,进一步解决了这个问题,在这里值得直接注意:

HTTP内容处理头和rfc2231 /2047编码的测试用例

库类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;
  }