如何在HTTP中编码Content-Disposition头文件名参数?

Ati*_*ziz 509 browser specifications http-headers

想要强制下载资源而不是直接在Web浏览器中呈现资源的Web应用程序Content-Disposition在表单的HTTP响应中发出标头:

Content-Disposition: attachment; filename=FILENAME

filename参数可用于建议浏览器下载资源的文件的名称.但是,RFC 2183(Content-Disposition)在2.3节(文件名参数)中指出文件名只能使用US-ASCII字符:

当前[RFC 2045]语法将参数值(以及因此内容处理文件名)限制为US-ASCII.我们认识到允许在文件名中使用任意字符集的巨大愿望,但是定义必要的机制超出了本文档的范围.

然而,有经验证据表明,当今大多数流行的Web浏览器似乎都允许非US-ASCII字符(缺乏标准)对编码方案和文件名的字符集规范不同意.问题是,如果文件名"naïvefile"(没有引号,第三个字母是U + 00EF)需要编码到Content-Disposition标题中,那么流行浏览器采用的各种方案和编码是什么?

出于这个问题的目的,流行的浏览器是:

  • 火狐
  • IE浏览器
  • 苹果浏览器
  • 谷歌浏览器
  • 歌剧

Mar*_*sen 350

我知道这是一个老帖子,但它仍然非常相关.我发现现代浏览器支持rfc5987,它允许utf-8编码,百分比编码(url编码).然后Naïvefile.txt变成:

Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt
Run Code Online (Sandbox Code Playgroud)

Safari(5)不支持此功能.相反,您应该使用直接在utf-8编码标头中编写文件名的Safari标准:

Content-Disposition: attachment; filename=Naïve file.txt
Run Code Online (Sandbox Code Playgroud)

IE8及更早版本也不支持它,你需要使用utf-8编码的IE标准,百分比编码:

Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt
Run Code Online (Sandbox Code Playgroud)

在ASP.Net中,我使用以下代码:

string contentDisposition;
if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
    contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
else if (Request.Browser.Browser == "Safari")
    contentDisposition = "attachment; filename=" + fileName;
else
    contentDisposition = "attachment; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
Response.AddHeader("Content-Disposition", contentDisposition);
Run Code Online (Sandbox Code Playgroud)

我使用IE7,IE8,IE9,Chrome 13,Opera 11,FF5,Safari 5测试了上述内容.

2013年11月更新:

这是我目前使用的代码.我仍然需要支持IE8,所以我无法摆脱第一部分.事实证明Android上的浏览器使用内置的Android下载管理器,它无法以标准方式可靠地解析文件名.

string contentDisposition;
if (Request.Browser.Browser == "IE" && (Request.Browser.Version == "7.0" || Request.Browser.Version == "8.0"))
    contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName);
else if (Request.UserAgent != null && Request.UserAgent.ToLowerInvariant().Contains("android")) // android built-in download manager (all browsers on android)
    contentDisposition = "attachment; filename=\"" + MakeAndroidSafeFileName(fileName) + "\"";
else
    contentDisposition = "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + Uri.EscapeDataString(fileName);
Response.AddHeader("Content-Disposition", contentDisposition);
Run Code Online (Sandbox Code Playgroud)

以上现在在IE7-11,Chrome 32,Opera 12,FF25,Safari 6中测试,使用此文件名下载:你好abcABCæøåÆØÅäöüïëêîâéíáóúýñ½§!#¤%&()=`@£$€{[]} +' ^〜'-_,;.TXT

在IE7上,它适用于某些字符但不是全部.但是谁现在关心IE7呢?

这是我用来为Android生成安全文件名的函数.请注意,我不知道Android上支持哪些字符,但我已经测试了这些字符的确有效:

private static readonly Dictionary<char, char> AndroidAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+,@£$€!½§~'=()[]{}0123456789".ToDictionary(c => c);
private string MakeAndroidSafeFileName(string fileName)
{
    char[] newFileName = fileName.ToCharArray();
    for (int i = 0; i < newFileName.Length; i++)
    {
        if (!AndroidAllowedChars.ContainsKey(newFileName[i]))
            newFileName[i] = '_';
    }
    return new string(newFileName);
}
Run Code Online (Sandbox Code Playgroud)

@TomZ:我在IE7和IE8中测试过,结果发现我不需要转义撇号(').你有失败的例子吗?

@Dave Van den Eynde:根据RFC6266将两个文件名组合在一行上除了Android和IE7 + 8之外,我已更新代码以反映这一点.感谢您的建议.

@Thilo:不知道GoodReader或任何其他非浏览器.使用Android方法可能会有一些运气.

@Alex Zhukovskiy:我不知道为什么,但正如在Connect上所讨论的那样,它看起来效果不是很好.

  • 为什么不将它们结合起来,如"内容 - 处置:附件"; 文件名*= UTF-8''Na%C3%AFve%20file.txt; filename = Na%C3%AFve%20file.txt`并跳过浏览器嗅探?那会有用吗? (19认同)
  • fastmail的好心人找到了另一种解决方法:https://blog.fastmail.com/2011/06/24/download-non-english-filenames/ Content-Disposition:attachment; 文件名= "foo-%C3%a4.html"; filename*= UTF-8''foo-%c3%a4.html指定fileName两次(一次没有UTF-8前缀,一次使用)使其在IE8-11,Edge,Chrome,Firefox和Safari中工作(看起来像苹果固定的野生动物园,所以它现在也在这里工作) (8认同)

Kor*_*nel 167

有一个简单且非常强大的替代方法:使用包含所需文件名的URL.

当最后一个斜杠后面的名称是你想要的名字时,你不需要任何额外的标题!

这个技巧有效:

/real_script.php/fake_filename.doc
Run Code Online (Sandbox Code Playgroud)

如果您的服务器支持URL重写(例如mod_rewrite在Apache中),那么您可以完全隐藏脚本部分.

URL中的字符应为UTF-8,逐字节urlencoded:

/mot%C3%B6rhead   # motörhead
Run Code Online (Sandbox Code Playgroud)

  • 我走下了兔子的路,尝试了其他一些解决方案; 试图嗅出正确的浏览器和版本来正​​确设置标题太噩梦了.Chrome错误地识别为Safari,它的行为完全不同(如果编码不正确,则会在逗号上中断).省去麻烦,使用此解决方案并根据需要为URL添加别名. (6认同)
  • 试试GetAttachment.aspx/fake_filename.doc?id = 34(虽然它可能只是Apache的怪癖) (3认同)
  • `/:id /:filename`方法非常简单,有效,谢谢! (3认同)
  • 这是一个很棒的解决方案; 真的帮助了我很多.谢谢. (2认同)
  • 一千次"是".你会认真地赢得时间.更加均匀 - 一些Android浏览器会使_ignore_中的`Content-Disposition`变得平坦,并创建非常有趣的文件名(它们将从您的路径生成).所以保持一个人的理智的唯一解决方案就是设置`Content-Disposition:attachment`并将所需的文件名作为最后一个路径组件传递: (2认同)
  • 这是一个很好的解决方案(让我觉得有点愚蠢)在相关的注释,记住如果文件名来自用户变量,你仍然必须确保它已准备好文件系统.如果你没有,并且文件有类似`/`的东西,你会得到*非常奇怪的浏览器错误.以[这个答案](http://superuser.com/a/358861)作为参考我使用`s.replace(/ [\ 000-\031 \\\ /:*?"<>\|]/g ,'_')` (2认同)
  • @GuneyOzsan 保存的文件名是由网络浏览器推断出来的,浏览器不了解服务器端发生的事情,所以他们不了解也不关心服务器如何解释 URL。浏览器只采用 URL 路径中最后一个斜杠之后的任何内容,有时还会尝试根据 `Content-Type` 更正文件扩展名。 (2认同)

Jim*_*Jim 94

在提出的RFC 5987 "用于超文本传输​​协议(HTTP)标头字段参数的字符集和语言编码"中讨论了这一点,包括浏览器测试和向后兼容性的链接.

RFC 2183表明此类标头应根据RFC 2184进行编码,RFC 2184已被上述RFC草案所涵盖.

  • 与此相关,我发现如果文件名中有逗号(,),则Firefox(版本4-9包含)会中断,例如`Content-Disposition:filename ="foo,bar.pdf"`.结果是firefox正确下载文件但保留了`.part`扩展名(例如`foo,bar.pdf-1.part`).然后,当然文件将无法正确打开,因为该应用程序与`.part`无关.其他ASCII字符似乎工作正常. (10认同)
  • 另请注意,互联网草案(不是"草案RFC")已经完成,最终文件是RFC 5987(http://greenbytes.de/tech/webdav/rfc5987.html) (5认同)
  • @catchdave:你忘了"附件"; 部分. (5认同)
  • 总而言之,这只是一个仅有链接的答案,有74个赞成票. (4认同)
  • 有关IE行为的更多信息,请参阅http://blogs.msdn.com/b/ieinternals/archive/2010/06/07/content-disposition-attachment-and-international-unicode-characters.aspx (3认同)
  • @MatthewSchinckel例如http://kbyanc.blogspot.hk/2010/07/serving-file-downloads-with-non-ascii.html和http://www.digiblog.de/2011/04/android-and-the -download文件,头/ (2认同)

MvG*_*MvG 67

RFC 6266描述了" 在超文本传输​​协议(HTTP)中使用内容 - 处置标头字段 ".引用:

6.国际化考虑因素

使用[ RFC5987 ]中定义的编码的" filename*"参数(第4.3节)允许服务器传输ISO-8859-1字符集之​​外的字符,还可以选择指定正在使用的语言.

在他们的示例部分中:

此示例与上面的示例相同,但添加"filename"参数是为了与未实现RFC 5987的用户代理兼容 :

Content-Disposition: attachment;
                     filename="EURO rates";
                     filename*=utf-8''%e2%82%ac%20rates
Run Code Online (Sandbox Code Playgroud)

注意:那些不支持RFC 5987编码的用户代理filename*在" filename" 之后发生时忽略" ".

附录D中,还有一长串建议可以提高互操作性.它还指向一个比较实现的站点.适用于常见文件名的当前全通测试包括:

  • attwithisofnplain:带有双引号且无编码的普通ISO-8859-1文件名.这需要一个文件名,该文件名都是ISO-8859-1,并且不包含百分号,至少不在十六进制数字前面.
  • attfnboth:上述顺序中的两个参数.应该适用于大多数浏览器上的大多数文件名,尽管IE8将使用" filename"参数.

RFC 5987又引用了RFC 2231,它描述了实际的格式.2231主要用于邮件,5987告诉我们哪些部分也可用于HTTP标头.不要将此与multipart/form-dataHTTP 主体内使用的MIME标头混淆,后者由RFC 2388(特别是第4.4节)和HTML 5草案管理.


Ati*_*ziz 16

以下文件与Jim在他的回答中提到的RFC草案相关联,进一步解决了这个问题,这里绝对值得直接注意:

HTTP Content-Disposition标头和RFC 2231/2047编码的测试用例


Elm*_*mer 11

在asp.net mvc2我使用这样的东西:

return File(
    tempFile
    , "application/octet-stream"
    , HttpUtility.UrlPathEncode(fileName)
    );
Run Code Online (Sandbox Code Playgroud)

我想如果你不使用mvc(2)你可以使用编码文件名

HttpUtility.UrlPathEncode(fileName)
Run Code Online (Sandbox Code Playgroud)

  • 文件名编码的URL编码无效,浏览器不应该对这些编码进行url解码. (2认同)

Vas*_*hev 9

我使用以下代码片段进行编码(假设fileName包含文件的文件名和扩展名,即:test.txt):


PHP:

if ( strpos ( $_SERVER [ 'HTTP_USER_AGENT' ], "MSIE" ) > 0 )
{
     header ( 'Content-Disposition: attachment; filename="' . rawurlencode ( $fileName ) . '"' );
}
else
{
     header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode ( $fileName ) );
}
Run Code Online (Sandbox Code Playgroud)

Java的:

fileName = request.getHeader ( "user-agent" ).contains ( "MSIE" ) ? URLEncoder.encode ( fileName, "utf-8") : MimeUtility.encodeWord ( fileName );
response.setHeader ( "Content-disposition", "attachment; filename=\"" + fileName + "\"");
Run Code Online (Sandbox Code Playgroud)


mar*_*oss 8

在ASP.NET Web API中,我url编码文件名:

public static class HttpRequestMessageExtensions
{
    public static HttpResponseMessage CreateFileResponse(this HttpRequestMessage request, byte[] data, string filename, string mediaType)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        var stream = new MemoryStream(data);
        stream.Position = 0;

        response.Content = new StreamContent(stream);

        response.Content.Headers.ContentType = 
            new MediaTypeHeaderValue(mediaType);

        // URL-Encode filename
        // Fixes behavior in IE, that filenames with non US-ASCII characters
        // stay correct (not "_utf-8_.......=_=").
        var encodedFilename = HttpUtility.UrlEncode(filename, Encoding.UTF8);

        response.Content.Headers.ContentDisposition =
            new ContentDispositionHeaderValue("attachment") { FileName = encodedFilename };
        return response;
    }
}
Run Code Online (Sandbox Code Playgroud)

IE 9不固定
IE 9已修复


Dmi*_*dov 8

将文件名放在双引号中.解决了我的问题.像这样:

Content-Disposition: attachment; filename="My Report.doc"
Run Code Online (Sandbox Code Playgroud)

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

  • 遗憾的是,这并没有解决上述答案中解释的所有问题. (3认同)
  • 这将允许您返回带有空格的文件名,`&`,`%`,`#`等等.所以它解决了这个问题. (2认同)
  • 如果文件名包含双引号怎么办(是的,这可能会发生),根据 RFC 6266 中的规定,文件名是“带引号的字符串”,并且根据 RFC 2616 中的规定,带引号的字符串中的双引号应使用反斜杠转义。 (2认同)

Gus*_*tav 8

在 PHP 中,这是为我完成的(假设文件名是 UTF8 编码的):

header('Content-Disposition: attachment;'
    . 'filename="' . addslashes(utf8_decode($filename)) . '";'
    . 'filename*=utf-8\'\'' . rawurlencode($filename));
Run Code Online (Sandbox Code Playgroud)

针对 IE8-11、Firefox 和 Chrome 进行了测试。
如果浏览器可以解释filename*=utf-8它将使用文件名的 UTF8 版本,否则它将使用解码后的文件名。如果您的文件名包含无法在 ISO-8859-1 中表示的字符,您可能需要考虑iconv改用。

  • 尽管此代码可以回答问题,但提供有关_为什么_和/或_如何_回答问题的附加上下文将显着提高其长期价值。请[编辑]您的答案以添加一些解释。 (3认同)
  • 哇,上述纯代码答案都没有像这样被低估或批评。此外,我发现 _why_ 已经得到了很好的回答:IE 不解释文件名*=utf-8,但需要 ISO8859-1 版本的文件名,该脚本确实提供了该版本。只想给懒人一个简单的 PHP 代码。 (2认同)

Bja*_*ted 7

从 .NET 4.5(和 Core 1.0)开始,您可以使用ContentDispositionHeaderValue为您进行格式化。

\n
var fileName = "Na\xc3\xafve file.txt";\nvar h = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");\nh.FileNameStar = fileName;\nh.FileName = "fallback-ascii-name.txt";\n\nResponse.Headers.Add("Content-Disposition", h.ToString());\n
Run Code Online (Sandbox Code Playgroud)\n

h.ToString()将导致:

\n
attachment; filename*=utf-8\'\'Na%C3%AFve%20file.txt; filename=fallback-ascii-name.txt\n
Run Code Online (Sandbox Code Playgroud)\n

  • 我将其与 /sf/answers/3975829721/ 中的“ASCII 折叠”结合起来生成 h.FileName 注意:h.FileName 不得包含引号字符(来自 ContentDispositionHeaderValue 来源:“仅允许使用边界引号”) (2认同)

Sta*_*ano 5

我在所有主流浏览器中测试了以下代码,包括较旧的浏览器(通过兼容模式),它适用于所有地方:

$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.'"');
Run Code Online (Sandbox Code Playgroud)


apu*_*krt 5

我最终在我的“download.php”脚本中得到了以下代码(基于这篇博文这些测试用例)。

$il1_filename = utf8_decode($filename);
$to_underscore = "\"\\#*;:|<>/?";
$safe_filename = strtr($il1_filename, $to_underscore, str_repeat("_", strlen($to_underscore)));

header("Content-Disposition: attachment; filename=\"$safe_filename\""
.( $safe_filename === $filename ? "" : "; filename*=UTF-8''".rawurlencode($filename) ));
Run Code Online (Sandbox Code Playgroud)

只要只使用iso-latin1和“safe”字符,就使用文件名=“...”的标准方式;如果没有,它会添加 filename*=UTF-8'' url-encoded 方式。根据这个特定的测试用例,它应该可以从 MSIE9 开始运行,并且可以在最近的 FF、Chrome、Safari 上运行;在较低的 MSIE 版本上,它应该提供包含文件名的 ISO8859-1 版本的文件名,下划线不在此编码中的字符上。

最后一点:最大值。apache 上每个头字段的大小为 8190 字节。UTF-8 每个字符最多可以有四个字节;在 rawurlencode 之后,每个字符 x3 = 12 个字节。效率很低,但理论上文件名中仍有超过 600 个“微笑”%F0%9F%98%81 是可能的。


Ema*_*ola 5

如果您使用的是nodejs后端,则可以使用我在此处找到的以下代码

var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''" 
             + encodeRFC5987ValueChars(fileName);

function encodeRFC5987ValueChars (str) {
    return encodeURIComponent(str).
        // Note that although RFC3986 reserves "!", RFC5987 does not,
        // so we do not need to escape it
        replace(/['()]/g, escape). // i.e., %27 %28 %29
        replace(/\*/g, '%2A').
            // The following are not required for percent-encoding per RFC5987, 
            // so we can allow for a little better readability over the wire: |`^
            replace(/%(?:7C|60|5E)/g, unescape);
}
Run Code Online (Sandbox Code Playgroud)


use*_*043 5

只是更新,因为我今天正在尝试所有这些东西以响应客户问题

  • 除了为日语配置的 Safari 之外,我们的客户测试的所有浏览器都使用 filename=text.pdf 效果最佳 - 其中 text 是由 ASP.Net/IIS 以 utf-8 序列化的客户值,没有 url 编码。出于某种原因,为英语配置的 Safari 会接受并正确保存带有 utf-8 日语名称的文件,但为日语配置的相同浏览器会以未解释的 utf-8 字符保存文件。所有其他经过测试的浏览器似乎都运行得最好/很好(无论语言配置如何),文件名 utf-8 编码而没有 url 编码。
  • 我无法找到一个单一的浏览器实现Rfc5987 / 8187可言。我使用最新的 Chrome、Firefox 版本以及 IE 11 和 Edge 进行了测试。我尝试只用 filename*=utf-8''texturlencoded.pdf 设置标题,用 filename=text.pdf 设置它;文件名*=utf-8''texturlencoded.pdf。Rfc5987/8187 的任何一个特性似乎都没有在上述任何一个中得到正确处理。

  • 这是一个很好的更新。您能详细说明一下您尝试过的具体测试吗? (2认同)