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

时间:2008-09-18 15:25:07

标签: browser http-headers specifications

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

Content-Disposition: attachment; filename=FILENAME

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

  

当前[RFC 2045]语法限制   参数值(因此   Content-Disposition文件名)   US-ASCII。我们认识到伟大的   允许任意的   文件名中的字符集,但它是   超出本文档的范围   确定必要的机制。

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

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

  • Firefox
  • Internet Explorer
  • Safari浏览器
  • Google Chrome

19 个答案:

答案 0 :(得分:340)

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

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

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

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 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中进行测试,使用此文件名下载:你好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);
}

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

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

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

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

答案 1 :(得分:161)

有一个简单且非常强大的替代方法:使用包含您想要的文件名的网址

如果最后一个斜杠后的名称是您想要的名称,则不需要任何额外的标题!

这个技巧有效:

/real_script.php/fake_filename.doc

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

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

/mot%C3%B6rhead   # motörhead

答案 2 :(得分:89)

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

RFC 2183表示此类标头应根据RFC 2184进行编码,RFC 2231已被上文RFC草案所涵盖的{{3}}淘汰。

答案 3 :(得分:64)

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

  

6. Internationalization Considerations

     

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

examples section

  

此示例与上面的示例相同,但添加了“文件名”   与未实现的用户代理兼容的参数   RFC 5987

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

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

Appendix D中,还有一长串建议可以提高互操作性。它也指向a site which compares implementations。适用于常见文件名的当前全通测试包括:

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

RFC 5987依次引用RFC 2231,其中描述了实际格式。 2231主要用于邮件,5987告诉我们哪些部分也可用于HTTP标头。不要将此与multipart/form-data HTTP 正文中使用的MIME标头混淆,后者由RFC 2388(特别是section 4.4)和{{3}管理}。

答案 4 :(得分:16)

the draft RFCJim提及的Test Cases for HTTP Content-Disposition header and RFC 2231/2047 Encoding中链接的以下文档进一步解决了这个问题,绝对值得直接注意:

{{3}}

答案 5 :(得分:10)

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

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

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

编码文件名
HttpUtility.UrlPathEncode(fileName)

答案 6 :(得分:10)

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

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

我测试了多个选项。浏览器不支持规范并采取不同的行为,我认为双引号是最好的选择。

答案 7 :(得分: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 ) );
}

爪哇:

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

答案 8 :(得分:8)

在ASP.NET Web API中,我对文件名进行了编码:

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 Not fixed
IE 9 Fixed

答案 9 :(得分: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.'"');

答案 10 :(得分:5)

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

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

答案 11 :(得分:4)

我在“download.php”脚本中找到了以下代码(基于this blogpostthese test cases)。

$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-encoded方式。根据{{​​3}},它可以在MSIE9上运行,也可以在最近的FF,Chrome,Safari上运行;在较低的MSIE版本上,它应该提供包含文件名的ISO8859-1版本的文件名,并在不包含此编码的字符上加下划线。

最后说明:最多apache上每个头字段的大小为8190字节。 UTF-8每个字符最多可包含四个字节;在rawurlencode之后,每个字符x3 = 12个字节。相当低效,但理论上仍然可以在文件名中包含超过600个“微笑”%F0%9F%98%81。

答案 12 :(得分:3)

在PHP中,它为我做了(假设文件名是UTF8编码的):

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

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

答案 13 :(得分:1)

经典ASP解决方案

大多数现代浏览器支持现在将Filename作为UTF-8传递,但我使用的文件上传解决方案基于FreeASPUpload.Net (网站不再存在) ,链接指向archive.org它不会起作用,因为二进制文件的解析依赖于读取单字节ASCII编码字符串,当你传递UTF-8编码数据直到你得到它时工作正常字符ASCII不支持。

但是我能够找到一个解决方案来获取代码来读取和解析二进制文件为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

通过在我自己的代码中BytesToString() include_aspuploader.asp实施UTF-8函数,我可以获得Pure ASP File Uploadlong start = System.currentTimeMillis(); // stuff you want to time System.println("Time take: " + ((System.currentTimeMillis() - start) / 1000) + "s"); 文件名可以正常运行。

有用的链接

答案 14 :(得分:1)

自从我今天尝试所有这些东西以响应客户问题以来,这只是一个更新

  • 除了为日语配置的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设置它; filename * = utf-8''texturlencoded.pdf。在上述任何一项中,似乎都没有正确处理Rfc5987 / 8187的一项功能。

答案 15 :(得分:0)

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

var fileName = "Naïve file.txt";
var h = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
h.FileNameStar = fileName;
h.FileName = "fallback-ascii-name.txt";

Response.Headers.Add("Content-Disposition", h.ToString());

h.ToString() 将导致:

attachment; filename*=utf-8''Na%C3%AFve%20file.txt; filename=fallback-ascii-name.txt

答案 16 :(得分:0)

PHP框架Symfony 4在$filenameFallback中有HeaderUtils::makeDisposition。 您可以查看此功能的详细信息-与上面的答案类似。

用法示例:

$filenameFallback = preg_replace('#^.*\.#', md5($filename) . '.', $filename);
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename, $filenameFallback);
$response->headers->set('Content-Disposition', $disposition);

答案 17 :(得分:-1)

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

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

答案 18 :(得分:-2)

我通常使用URL编码(使用%xx)文件名,它似乎适用于所有浏览器。你可能还想做一些测试。