如何在HTTP中编码Content-Disposition头的filename参数?

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

Content-Disposition: attachment; filename= FILENAME

filename参数可以用来为浏览器下载资源的文件build议一个名称。 但是RFC 2183 (内容处置)在2.3节 (文件名参数)中声明文件名只能使用US-ASCII字符:

目前[RFC 2045]语法将参数值(以及Content-Disposition文件名)限制为US-ASCII。 我们认识到允许在文件名中使用任意字符集的强烈愿望,但是定义必要的机制超出了本文档的范围。

然而,有经validation据表明,目前大多数stream行的Web浏览器似乎还允许非US-ASCII字符(对于缺乏标准)对文件名的编码scheme和字符集规范持不同意见。 问题是,如果需要将文件名“naïvefile”(不带引号且第三个字母是U + 00EF)编码到Content-Disposition头中,那么常用浏览器使用的各种scheme和编码是什么?

针对这个问题, stream行的浏览器是:

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

在RFC 5987 “用于超文本传输​​协议(HTTP)头字段参数的字符集和语言编码”中讨论了这个问题,包括浏览器testing和向后兼容性的链接。

RFC 2183指出,这样的头文件应该根据RFC 2184进行编码,而RFC 2184已经被RFC 2231所废弃,这已经被上面的RFC草案所涵盖。

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

 Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt 

Safari(5)不支持这一点。 相反,您应该使用Safari标准直接在UTF-8编docker中编写文件名:

 Content-Disposition: attachment; filename=Naïve file.txt 

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

 Content-Disposition: attachment; filename=Na%C3%AFve%20file.txt 

在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); 

我使用IE7,IE8,IE9,Chrome 13,Opera 11,FF5,Safari 5testing了上述内容。

2013年11月更新

这是我目前使用的代码。 我仍然需要支持IE8,所以我不能摆脱第一部分。 事实certificate,Android上的浏览器使用内置的Android下载pipe理器,它不能可靠地parsing文件名称的标准方式。

 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); 

上面现在已经在IE7-11,Chrome 32,Opera 12,FF25,Safari 6上testing过了,使用这个文件名进行下载:你好,下载地址:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=110&extra=page%3D1&page=1 ^〜'-_,;。TXT

在IE7上它适用于一些字符,但不是全部。 但是现在谁在乎IE7?

这是我用来为Android生成安全文件名的function。 请注意,我不知道Android上支持哪些字符,但是我已经testing了这些工作是肯定的:

 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); } 

@TomZ:我在IE7和IE8testing,事实certificate,我不需要撇号(')。 你有一个失败的例子吗?

@Dave Van den Eynde:根据RFC6266在一行上结合两个文件名,除Android和IE7 + 8外,我更新了代码以反映这一点。 谢谢你的build议。

@Thilo:对GoodReader或任何其他非浏览器不了解。 使用Android方法可能会有一些运气。

@ Alex Zhukovskiy:我不知道为什么,但正如Connect上所讨论的那样,这似乎不太好。

  • 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,按字节逐字:

 /mot%C3%B6rhead # motörhead 

RFC 6266描述了“ 在超文本传输​​协议(HTTP)中使用内容处置报头字段 ”。 引用:

6.国际化考虑

使用[ RFC5987 ]中定义的编码的“ filename* ”参数( 第4.3节 )允许服务器传送ISO-8859-1字符集以外的字符,还可以select指定正在使用的语言。

在他们的例子部分 :

此示例与上面的示例相同,但添加了“filename”参数以与不执行RFC 5987的用户代理兼容:

 Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''%e2%82%ac%20rates 

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

在附录D中,还有一长串的提高互操作性的build议。 它也指向一个比较实现的网站 。 目前适用于常见文件名的全通过testing包括:

  • 简单地说 :ISO-8859-1文件名带有双引号,不带编码。 这需要一个全是ISO-8859-1的文件名,并且不包含百分号,至less不在hex数字之前。
  • attfnboth :以上述顺序的两个参数。 应该适用于大多数浏览器上的大多数文件名,尽pipeIE8将使用“ filename ”参数。

RFC 5987反过来引用RFC 2231 ,它描述了实际的格式。 2231主要用于邮件,5987告诉我们哪些部分可能用于HTTP头。 不要将它与在RFC 2388 (特别是第4.4节 )和HTML 5草案中pipe理的multipart/form-data HTTP 正文中使用的MIME头混淆。

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

HTTP Content-Disposition标题和RFC 2231/2047编码的testing用例

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

 return File( tempFile , "application/octet-stream" , HttpUtility.UrlPathEncode(fileName) ); 

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

 HttpUtility.UrlPathEncode(fileName) 

我使用下面的代码片段进行编码(假设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 ) ); } 

Java的:

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

在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; } } 

IE 9不固定
IE 9固定

把你的文件名用双引号。 为我解决了这个问题。 喜欢这个:

 Content-Disposition: attachment; filename="My Report.doc" 

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

我在所有主stream浏览器(包括较老的浏览器(通过兼容模式))中testing了以下代码,并且在任何地方都能正常运行:

 $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.'"'); 

如果您使用的是nodejs后端,您可以使用我在这里find的以下代码

 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). // ie, %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); } 

我结束了在我的“download.php”脚本中的以下代码(基于这个博客和这些testing用例 )。

 $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) )); 

只要使用iso-latin1和“safe”字符,就使用filename =“…”的标准方式; 如果没有的话,它会添加文件名* = UTF-8的url编码方式。 根据这个具体的testing案例 ,应该从MSIE9起,最近的FF,Chrome,Safari; 在较低的MSIE版本上,它应该提供包含ISO8859-1版本的文件名的文件名,下划线不在这个编码中。

最后说明:最大。 每个头字段的大小在apache上是8190字节。 UTF-8每个字符最多可以有四个字节; 在rawurlencode之后,每个字符是x3 = 12个字节。 相当低效,但在文件名中应该有600多个“笑”%F0%9F%98%81。

在PHP中,这对我来说(假设文件名是UTF8编码):

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

testing了IE8-11,Firefox和Chrome。
如果浏览器可以解释文件名* = utf-8 ,它将使用UTF8版本的文件名,否则将使用解码后的文件名。 如果您的文件名包含无法用ISO-8859-1表示的字符,则可以考虑使用iconv

经典ASP解决scheme

大多数现代浏览器都支持将Filename传递为UTF-8但是与我使用基于FreeASPUpload.Net的file upload解决scheme(站点不再存在,链接到archive.org的链接)一样,二进制文件的parsing依赖于读取单个字节的ASCII编码的string,当你传递UTF-8编码的数据时,它们工作正常,直到你得到字符ASCII不支持。

但是我能find一个解决scheme来获取代码读取和parsing二进制文件为UTF-8。

 Public Function BytesToString(bytes) 'UTF-8.. Dim bslen Dim i, k , N Dim b , count Dim str bslen = LenB(bytes) str="" i = 0 Do While i < bslen b = AscB(MidB(bytes,i+1,1)) If (b And &HFC) = &HFC Then count = 6 N = b And &H1 ElseIf (b And &HF8) = &HF8 Then count = 5 N = b And &H3 ElseIf (b And &HF0) = &HF0 Then count = 4 N = b And &H7 ElseIf (b And &HE0) = &HE0 Then count = 3 N = b And &HF ElseIf (b And &HC0) = &HC0 Then count = 2 N = b And &H1F Else count = 1 str = str & Chr(b) End If If i + count - 1 > bslen Then str = str&"?" Exit Do End If If count>1 then For k = 1 To count - 1 b = AscB(MidB(bytes,i+k+1,1)) N = N * &H40 + (b And &H3F) Next str = str & ChrW(N) End If i = i + count Loop BytesToString = str End Function 

通过在我自己的代码中实现来自include_aspuploader.aspBytesToString()函数,我们可以得到纯ASPfile upload 。我能够获得UTF-8文件名的工作方式。


有用的链接

  • ASP Classic应用程序中的Multipart / form-data和UTF-8

  • Unicode,UTF,ASCII,ANSI格式的区别

我们在Web应用程序中遇到了类似的问题,最后通过从HTML <input type="file">读取文件名,并以url编码的forms将其设置为新的HTML <input type="hidden"> 。 当然,我们必须删除某些浏览器返回的path,如“C:\ fakepath \”。

当然这不直接回答OP的问题,但可能是其他人的解决scheme。

我通常使用URL编码(使用%xx)文件名,而且它似乎可以在所有浏览器中使用。 无论如何,你可能要做一些testing。

我find了解决scheme,适用于我的所有浏览器(即我已经安装的所有浏览器 – IE8,FF16,Opera 12,Chrome 22)。

我的解决scheme在其他线程中描述: Java servlet下载文件名特殊字符

我的解决scheme是基于这样一个事实,即浏览器如何尝试从filename参数中读取值。 如果filename参数中没有指定字符集(例如filename*=utf-8''test.xml ),浏览器希望该值使用浏览器的本机编码进行编码。

不同的浏览器期望不同的本机编码。 通常浏览器的本地编码是utf-8(FireFox,Opera,Chrome)。 但IE的本机编码是Win-1250。 (我对其他浏览器一无所知。)

因此,如果我们把值放到filename参数中,那么根据用户的浏览器,它是由utf-8 / win-1250编码的,它应该工作。 至less,它对我有用。

总之,如果我们有名为omáčka.xml文件,
对于FireFox,Opera和Chrome,我回应了这个标题(用utf-8编码):

 Content-Disposition: attachment; filename="omáčka.xml" 

对于IE我反应这个头(在win-1250编码):

 Content-Disposition: attachment; filename="omáèka.jpg" 

Java的例子是在我上面提到的职位 。