首页 文章

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

提问于
浏览
463

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

Content-Disposition: attachment; filename=FILENAME

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

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

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

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

  • Firefox

  • Internet Explorer

  • Safari

  • 谷歌浏览器

  • 歌剧

17 回答

  • 16

    有人对此进行了讨论,包括浏览器测试和向后兼容性的链接,提议RFC 5987,"Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters."

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

  • 1

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

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

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

    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测试了上述内容 .

    Update 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所讨论的那样,它看起来效果不是很好 .

  • -3

    有一个简单而强大的替代方案: use a URL that contains the filename you want .

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

    这个技巧有效:

    /real_script.php/fake_filename.doc
    

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

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

    /mot%C3%B6rhead   # motörhead
    
  • 4

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

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

    并在他们的examples section

    此示例与上面的示例相同,但添加“filename”参数是为了与未实现RFC 5987的用户代理兼容:Content-Disposition:attachment;
    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)和HTML 5 draft控制 .

  • 11

    JimJim中提到的以下文件在他的答复中进一步解决了这个问题,这里绝对值得直接注意:

    Test Cases for HTTP Content-Disposition header and RFC 2231/2047 Encoding

  • 5

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

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

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

    HttpUtility.UrlPathEncode(fileName)
    
  • 61

    我使用以下代码片段进行编码(假设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 + "\"");
    
  • -2

    在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 Not fixed

    IE 9 Fixed

  • 84

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

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

    http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

  • 8

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

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

    如果你使用的是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);
    }
    
  • 152

    我最终在我的"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) ));
    

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

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

  • 8

    在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 .

  • 5

    经典ASP解决方案

    大多数现代浏览器都支持将 Filename 现在作为 UTF-8 传递,但是我使用的文件上传解决方案基于FreeASPUpload.Net(网站不再存在,链接指向archive.org),它不支持't work as the parsing of the binary relied on reading single byte ASCII encoded strings, which worked fine when you passed UTF-8 encoded data until you get to characters ASCII doesn' .

    但是我能够找到一个解决方案来获取代码来读取和解析二进制文件为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() 函数,可以获得Pure ASP File Upload,我可以使 UTF-8 文件名工作 .


    有用的链接

  • 324

    我们在Web应用程序中遇到了类似的问题,最后通过阅读来自HTML <input type="file"> 的文件名,并在新的HTML <input type="hidden"> 中以url编码的形式设置 . 当然,我们必须删除某些浏览器返回的"C:\fakepath"之类的路径 .

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

  • -1

    我通常使用URL编码(使用%xx)文件名,它似乎适用于所有浏览器 . 无论如何,您可能想要进行一些测试 .

  • 8

    我找到了解决方案,适用于我的所有浏览器(即我安装的所有浏览器 - IE8,FF16,Opera 12,Chrome 22) .

    我的解决方案在其他主题中描述:Java servlet download filename special characters

    我的解决方案基于以下事实:浏览器如何尝试从 filename 参数读取值 . 如果 filename 参数中没有指定字符集(例如 filename*=utf-8''test.xml ),则浏览器希望该值以浏览器的本机编码进行编码 .

    不同的浏览器需要不同的本机编码 . 通常浏览器的本机编码是utf-8(FireFox,Opera,Chrome) . 但IE的原生编码是Win-1250 . (我对其他浏览器一无所知 . )

    因此,如果我们将值放入 filename parametr,即根据用户的浏览器由utf-8 / win-1250编码,它应该可以工作 . 至少,它对我有用 .

    简而言之,如果我们有一个名为 omáčka.xml 的文件,
    对于FireFox,Opera和Chrome我会响应此 Headers (以utf-8编码):

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

    对于IE我响应这个 Headers (以win-1250编码):

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

    Java示例是in my post,如上所述 .

相关问题