browser - http如何编码在filename参数的ContentDisposition头?

  显示原文与译文双语对照的内容

web应用程序想要迫使一个资源下载而不是直接呈现在web浏览器发出 Content-Disposition http响应头的形式:

Content-Disposition: attachment; filename=FILENAME

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

当前 [RFC 2045 ] 语法将参数值( 因此Content-Disposition文件名) 限制为 US-ASCII 。 我们认识到在文件名中允许任意字符集的非常可取,但是定义必要的机制超出了本文的范围。

经验证据,然而,似乎今天最流行的web浏览器允许non-US-ASCII人物然而( 因为缺乏标准) 不同意文件的编码方案和字符集规范名称。

为了这个问题,的流行浏览器

  • yf_terminology_Firefox@#@#@#Firefox_yf_terminology
  • yf_terminology_Internet Explorer@#@#@#IE_yf_terminology
  • Safari
  • yf_terminology_Google Chrome@#@#@#Google Chrome_yf_terminology
  • yf_terminology_Opera@#@#@#Opera_yf_terminology
时间:

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

RFC 2183表明这些标题应该编码根据 RFC 2184,废弃的RFC 2231,草案的rfc。

我知道这是个旧帖子,但仍然很相关。 我发现现代浏览器支持 rfc5987,它允许utf-8编码,百分比编码( url-encoded ) 。


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

Safari ( 5 ) 并不supprt这个和你在代替使用safari的写作标准文件名直接utf-8编码的头:


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

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


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

上面的测试在 IE7-11,Chrome 32,Opera 12,FF25,Safari 6中测试,使用这里文件名下载:

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

@TomZ: 我在IE7和IE8中测试过,结果发现我不需要转义撇号('。 你是否有一个失败的示例?

@Dave Van den Eynde: 根据RFC6266和 IE7+8,在一行中结合两个文件名,除了安卓和,我已经更新了代码以反映这一点。 感谢你的建议。

@Thilo: 不了解GoodReader或者任何其他 non-browser 。 使用Android方法可能会有一些运气。

有一个简单且非常健壮的替代方案: 使用一个包含你想要的文件名 url。

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

这里技巧适用:


/real_script.php/fake_filename.doc

如果你的服务器支持URL重写( 例如。 然后你就可以完全隐藏脚本部分了。

url中的字符应该是 UTF-8,urlencoded byte-by-byte:


/mot%C3%B6rhead # motörhead

RFC 6266 描述" Content-Disposition的使用超文本传输协议(http头字段) 。 引用:

6.国际化注意事项

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

并且在他们的示例部分中:

这里示例与上面的示例相同,但添加"文件名"参数以与不实现 RFC 5987的用户代理兼容:


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

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

附录中,还提供了一个长列表来增加互操作性。 它还指向一个站点,它比较了的实现。 适用于常见文件名的当前all-pass测试包括:

  • attwithisofnplain: 带双引号和不带编码的纯 ISO-8859-1 文件名。 这需要一个文件名,它是所有 ISO-8859-1 并且不包含百分号,至少不在十六进制数字前面。
  • attfnboth: 上面描述的两个参数。 虽然IE8将使用" filename"参数,但大多数浏览器都应该使用大多数文件名。

RFC 5987 引用 RFC 2231,它描述实际格式。 2231主要用于邮件,5987告诉我们哪些部分可以用于HTTP报头。 请不要将这与mime标头使用http multipart/form-data 体内,这是由 RFC 2388 ( 节 4.4特定的 ) 和 HTML 5草案。

下面的文档链接草案提到的rfc吉姆他回答进一步解决的问题和直接绝对值得注意:

为http Content-Disposition 测试用例标题和rfc 2231/2047 编码

在 ASP.NET mvc2中,我使用类似这样的东西:


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

如果你不使用 mvc(2),你可以使用


HttpUtility.UrlPathEncode(fileName)

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


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

我使用下面的代码Fragment进行编码( 假设文件名包含文件的文件名和扩展名,换句话说: 测试。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 +""");

我发现了解决方案,它适用于所有浏览器( IE 。 我安装的所有浏览器- IE8,FF16,Opera 12,Chrome 22.

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

我的解决方案基于事实,浏览器如何从 filename 参数读取值。 如果 filename 参数( 例如 filename*=utf-8''test.xml ) 浏览器中没有指定字符集,浏览器期望该值在浏览器编码的本地编码。

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

因此,如果我们将值放入 filename 目录中,根据用户的浏览器进行 utf-8/win-1250编码,它应该工作。 至少它对我有用。

简而言之,如果我们有名为 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示例是 ,上面提到过。

在一个web应用程序,我们有一个类似的问题,最终通过阅读从html <input type="file"> 文件名,并设置在一个新的html <input type="hidden"> url-encoded形式。 当然,我们必须删除一些浏览器返回的像"fakepath:"这样的路径。

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

...