代码之家  ›  专栏  ›  技术社区  ›  Atif Aziz

如何在HTTP中对内容处置头的文件名参数进行编码?

  •  471
  • Atif Aziz  · 技术社区  · 17 年前

    希望强制资源 下载的 而不是直接 提供 在Web浏览器中,问题A Content-Disposition 表单的HTTP响应中的头:

    Content-Disposition: attachment; filename= FILENAME

    这个 filename 参数可用于建议浏览器将资源下载到的文件的名称。 RFC 2183 (内容处置),但是,在 section 2.3 (文件名参数)文件名只能使用US-ASCII字符:

    当前[RFC 2045]语法限制 参数值(因此 内容处置文件名)至 美国ASCII。我们认识伟大的 任性的可取性 文件名中的字符集,但它是 超出本文件范围 定义必要的机制。

    然而,有经验的证据表明,当今最流行的网络浏览器似乎允许非美国的ASCII字符(由于缺乏标准)对文件名的编码方案和字符集规范存在分歧。问题是,如果需要将文件名__na_/vefile_(不带引号,第三个字母是u+00ef)编码到内容处置头段中,那么流行浏览器使用的各种方案和编码是什么?

    就这个问题而言, 流行的浏览器 存在:

    • 火狐
    • Internet Explorer
    • 游猎
    • 谷歌浏览器
    • 歌剧
    16 回复  |  直到 17 年前
        1
  •  84
  •   Charles    12 年前

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

    RFC 2183 指示应根据 RFC 2184 ,被 RFC 2231 ,包含在上面的RFC草案中。

        2
  •  327
  •   Martin Ørding-Thomsen    9 年前

    我知道这是一个古老的职位,但它仍然很相关。我发现现代浏览器支持Rfc5987,它允许UTF-8编码,百分比编码(URL编码)。然后na_ve file.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、Chrome13、Opera11、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、Chrom32、Opera12、FF25、Safari6、Opera12、FF25、Safari6中进行,使用此文件名下载:160160229\160\\\160\\\\195;\195\195\195\195\195\\\\\\\160\\\\194;½_§!#¥%&()=`@_$,[]+_¨^ ~'-,;.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);
    }
    

    @汤兹:我在IE7和IE8中进行了测试,结果发现我不需要摆脱撇号(“)。你有失败的例子吗?

    @戴夫·范登埃因德:根据RFC6266的工作原理,将两个文件名合并在一行,除了Android和IE7+8,我已经更新了代码来反映这一点。谢谢你的建议。

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

    @亚历克斯·朱可夫斯基:我不知道为什么,但正如上面所讨论的 Connect 它似乎工作得不太好。

        3
  •  156
  •   Kornel    11 年前

    有一个简单而强大的替代方案: 使用包含所需文件名的URL .

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

    这个技巧有效:

    /real_script.php/fake_filename.doc
    

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

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

    /mot%C3%B6rhead   # motörhead
    
        4
  •  61
  •   MvG    12 年前

    RFC 6266 描述了__ 在超文本传输协议(HTTP)中使用内容配置头字段 艾斯。引用:

    6. Internationalization Considerations

    艾斯 filename* _157;参数( 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 编码 忽略AY 文件名* _157;发生在__之后 filename 艾斯。

    Appendix D 此外,还有一长串提高互操作性的建议。它还指向 a site which compares implementations . 当前适用于通用文件名的所有通过测试包括:

    • attwithisofnplain :纯ISO-8859-1文件名,带双引号,不带编码。这需要一个文件名,该文件名全部是ISO-8859-1,不包含百分号,至少不在十六进制数字前面。
    • attfnboth :按上述顺序排列的两个参数。应该适用于大多数浏览器上的大多数文件名,尽管IE8将使用__ 文件名 _157;参数。

    RFC 5987 依次引用 RFC 2231 ,它描述了实际的格式。2231主要用于邮件,5987告诉我们哪些部分也可以用于HTTP头。不要将此与在 multipart/form-data 超文本传输协议 身体 ,由 RFC 2388 ( section 4.4 尤其是)和 HTML 5 draft .

        6
  •  11
  •   Tim Cooper    14 年前

    在ASP.NET MVC2中,我使用类似的方法:

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

    我猜如果你不使用MVC(2),你可以用

    HttpUtility.UrlPathEncode(fileName)
    
        7
  •  9
  •   Dmitry Kaigorodov    11 年前

    将文件名用双引号括起来。帮我解决了这个问题。这样地:

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

    http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

        8
  •  8
  •   Kornel    11 年前

    我使用以下代码片段进行编码(假设 文件名 包含文件名和扩展名,即: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 + "\"");
    
        9
  •  8
  •   martinoss    11 年前

    在ASP.NET Web API中,i 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

        10
  •  5
  •   Stano    14 年前

    我在所有主要浏览器中测试了以下代码,包括旧的探索者(通过兼容模式),它在任何地方都很好地工作:

    $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.'"');
    
        11
  •  5
  •   Emanuele Spatola    10 年前

    如果您使用的是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);
    }
    
        12
  •  4
  •   renergy    11 年前

    我在“download.php”脚本(基于 this blogpost these 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”字符,就使用标准的文件名“…”;否则,它会添加文件名*=utf-8“”的URL编码方式。根据 this specific test case ,它应该从msie9向上运行,在最近的ff、chrome、safari上运行;在较低的msie版本上,它应该提供包含文件名的iso8859-1版本的文件名,并在不在此编码中的字符上加下划线。

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

        13
  •  3
  •   Gustav    10 年前

    在PHP中,这是为我做的(假定文件名是utf8编码的):

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

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

        14
  •  1
  •   Community Mohan Dere    9 年前

    经典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
    

    信贷流向 Pure ASP File Upload 通过实施 BytesToString() 函数从 include_aspuploader.asp 在我自己的代码里,我能够 UTF-8 文件名正在工作。


    有用的链接

        15
  •  -1
  •   V G    11 年前

    我们在一个Web应用程序中遇到了类似的问题,最终从HTML中读取了文件名。 <input type="file"> ,并以URL编码的形式将其设置为新的HTML <input type="hidden"> . 当然,我们必须删除一些浏览器返回的路径,比如“c:\fakepath”。

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

        16
  •  -2
  •   Dario Solera    17 年前

    我通常使用url对文件名进行编码(使用%xx),它似乎在所有浏览器中都能工作。无论如何,您可能需要做一些测试。

    推荐文章