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


533

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

Content-Disposition: attachment; filename=FILENAME

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

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

但是,有经验证据表明,当今大多数流行的Web浏览器似乎都允许使用非US-ASCII字符,但是(由于缺乏标准)在文件名的编码方案和字符集规范上存在分歧。那么问题是,如果文件名“naïvefile”(不带引号,并且第三个字母为U + 00EF)需要编码到Content-Disposition标头中,那么流行的浏览器采用了哪些不同的方案和编码?

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

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

使它适用于Mobile Safari(@MartinØrding-Thomsen建议使用原始utf-8),但不适用于同一设备上的GoodReader。有任何想法吗?
Thilo 2012年


1
如果您可以设置路径的最后一段,则Kornel的答案被证明是阻力最小的路径。加上这个Content-Disposition: attachment
Antti Haapala'9

Answers:


94

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

RFC 2183指出,此类标头应按照RFC 2184编码,而RFC 2231已弃用该RFC,而RFC草案已将其覆盖。


5
另请注意,互联网草案(不是“ RFC草案”)已经完成,最终文档为RFC 5987(greenbytes.de/tech/webdav/rfc5987.html
朱利安·

11
与此相关的是,我发现Firefox(包括版本4至9)在文件名中包含逗号(,)时会中断Content-Disposition: filename="foo, bar.pdf"。结果是firefox正确下载了文件,但保留了.part扩展名(例如foo,bar.pdf-1.part)。然后,由于应用程序未与关联,因此该文件当然无法正确打开.part。其他ASCII字符似乎可以正常工作。
catchdave 2012年

3
欲了解更多有关IE浏览器的行为,请参阅blogs.msdn.com/b/ieinternals/archive/2010/06/07/...
EricLaw

5
@catchdave:您忘记了“附件”;部分。
ChristofferHammarström,2014年

6
总而言之,这仅是带有74个投票的仅链接答案。
Antti Haapala'9

364

我知道这是一个旧帖子,但它仍然非常重要。我发现现代浏览器支持rfc5987,它允许utf-8编码,百分比编码(URL编码)。然后,朴素的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,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:除了android和IE7 + 8以外,按照RFC6266将两个文件名组合在一起的做法是可行的,我已经更新了代码以反映这一点。感谢您的建议。

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

@Alex Zhukovskiy:我不知道为什么,但是正如在Connect上讨论的那样,它似乎无法很好地工作。


1
使它适用于Mobile Safari(如上建议的原始utf-8),但不适用于同一设备上的GoodReader。有任何想法吗?
Thilo 2012年

1
IE7和8也需要转义撇号:.Replace(“'”,Uri.HexEscape('\'))
TomZ 2012年

1
直接编写UTF-8字符似乎适用于Firefox,Chrome和Opera的当前版本。没有测试Safari和IE。
Martin Tournoij 2013年

20
为什么不将它们组合在一起,Content-Disposition: attachment; filename*=UTF-8''Na%C3%AFve%20file.txt; filename=Na%C3%AFve%20file.txt而跳过浏览器嗅探呢?那行得通吗?
Dave Van den Eynde

9
fastmail的好心人发现了另一个解决方法:blog.fastmail.com/2011/06/24/download-non-english-filenames Content-Disposition:附件;filename =“ foo-%c3%a4.html”; filename * = UTF-8''foo-%c3%a4.html两次指定fileName(一次不使用UTF-8前缀,一次不使用),使其可以在IE8-11,Edge,Chrome,Firefox和Safari(似乎像苹果固定的野生动物园,所以它现在
也在

169
  • 没有可互操作的方式来编码中的非ASCII名称Content-Disposition浏览器兼容性是一团糟

  • 理论上讲,使用UTF-8的语法Content-Disposition非常奇怪:(filename*=UTF-8''foo%c3%a4是的,这是一个星号,除了中间的空单引号外没有其他引号)

  • 此标头不是某种标准(HTTP / 1.1规范承认其存在,但不需要客户端支持它)。

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

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

此技巧有效:

/real_script.php/fake_filename.doc

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

URL中的字符应采用UTF-8(逐字节urlencoded):

/mot%C3%B6rhead   # motörhead

3
试试GetAttachment.aspx / fake_filename.doc?id = 34(尽管这可能是仅Apache的怪癖)
Kornel

2
这是一个很棒的解决方案;确实对我有很大帮助。谢谢。
kristopolous 2011年

6
我沿着兔子路走,尝试了其他一些解决方案。尝试嗅出正确的浏览器和版本以正确设置标题是一场噩梦。Chrome浏览器错误地将其标识为Safari,它的行为根本不一样(如果编码不正确,逗号会中断)。省去麻烦,使用此解决方案,并根据需要对URL进行别名。
mpen 2013年

3
/:id/:filename方法非常简单且有效,谢谢!
卡·斯蒂布

2
一千次“是”。您将以此赢得时间。更均匀的是-一些Android浏览器会完全忽略Content-Disposition而是创建非常有趣的文件名(它们将从您的路径中生成)。因此,保持理智的唯一解决方案是设置Content-Disposition: attachment并传递所需的文件名作为最后的路径组成部分:
Julik,2016年

73

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

6.国际化注意事项

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

在他们的示例部分

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

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

注意:当不支持RFC 5987编码的用户代理filename*会在“ filename” 之后出现“ ”。

附录D中,还列出了一长串提高互操作性的建议。它还指向一个比较实现的站点。当前适用于通用文件名的全通测试包括:

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

RFC 5987又参考RFC 2231,它描述了实际的格式。2231主要用于邮件,5987告诉我们哪些部分也可以用于HTTP标头。请勿将其与multipart/form-dataHTTP 正文中使用的MIME头混淆,HTTP 正文RFC 2388(特别是4.4节)和HTML 5草案的约束。


1
我在Safari中遇到了麻烦。下载带有俄语名称的文件时,收到错误且不可读的字符。该解决方案有所帮助。但是我们需要在一行中发送标头(!!!)。
evtuhovdo '16

16

以下是Jim在回答中提到的RFC草案中链接的以下文档,它进一步解决了这个问题,绝对值得在此处直接注明:

HTTP Content-Disposition标头和RFC 2231/2047编码的测试用例


请注意,可以同时提供两种编码filename参数的方式,并且它们似乎可以在旧浏览器和新浏览器(在这种情况下为MSIE8和Safari)中正常工作。检查attfnboth由@AtifAziz提到的报告。
巴勃罗·蒙蒂利亚

11

在asp.net mvc2中,我使用如下所示的内容:

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

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

HttpUtility.UrlPathEncode(fileName)

2
用于文件名编码的网址编码无效,浏览器不应对该网址进行网址解码。
SerialSeb

IE 11绝对不会在此字段中解码url编码。

但是,在需要的时候,浏览器是Chrome或IE浏览器,其他如FF,Safari和Opera做工精细待url编码与编码出来
礼萨

10

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

正确,因为在RFC 6266-> RFC 5987中使用过,所以它rawurlencode至少应在PHP中用于filename*=处置标头(请参阅tools.ietf.org/html/rfc6266#section-4.1tools.ietf.org/html/rfc5987#section -3.2.1)不允许在不进行百分比转义的情况下使用空间(另一方面,似乎可以在不进行转义的情况下允许空间,尽管此处仅应存在ASCII)。不必严格按照rawurlencode进行编码,因此可以忽略一些字符:gist.github.com/brettz9/8752120value-charsext-valuefilename=
Brett

10

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

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

http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download

我已经测试了多个选项。浏览器不支持规范,并且行为不同,我相信双引号是最好的选择。


3
遗憾的是,这并不能解决以上答案中解释的所有问题。
卡·斯蒂布

2
这将允许您用空格返回一个文件名,&%#等优点,解决了。
唐·奇德尔

如果文件名包含双引号(是的,可能会发生),该怎么办?如RFC 6266中所述,文件名是一个“带引号的字符串”,而如RFC 2616中所指定,带引号的字符串中的双引号应以反斜杠转义。
Christophe Roussy

9

在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固定


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

5

我在“ download.php”脚本中获得了以下代码(基于此博客文章这些测试用例)。

$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编码的方式。根据这个特定的测试用例,它应该从MSIE9开始运行,并且可以在最近的FF,Chrome,Safari上运行;在较低的MSIE版本上,它应提供包含文件名的ISO8859-1版本的文件名,并在非此编码的字符上加下划线。

最后说明:最大 在Apache上,每个标头字段的大小为8190字节。UTF-8最多每个字符四个字节;在rawurlencode之后,每一个字符x3 = 12个字节。效率很低,但是从理论上说,文件名中的%F0%9F%98%81仍应有600个以上的“微笑”。


...但是最大可传输文件名长度也取决于客户端。刚刚发现,最多[89 smiles] .pdf文件名通过MSIE11获得。在Firefox37中,最大为[111x😁] .pdf。Chrome41在第110个微笑处将文件名截断。有趣的是,后缀已确定。
apurkrt 2015年

5

如果您使用的是Node.js后端,则可以使用我在这里找到的以下代码

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

1
更好地使用encodeURI(str)。例如文件名称中的日期:encodeURIComponent('"Kornél Kovács 1/1/2016')=>“KornélKovács1%2F1%2F2016” vs. encodeURI('"Kornél Kovács 1/1/2016')=>“KornélKovács1/1/2016”
gdibble

4

在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改用。


3
尽管此代码可以回答问题,但提供有关其为什么和/或如何回答问题的其他上下文将显着提高其长期价值。请编辑您的答案以添加一些说明。
Toby Speight

2
哎呀,上面所有仅代码的答案都没有被这样否定或批评。我也发现了为什么已经被很好地回答了:IE不能解释filename * = utf-8,但是需要该脚本提供的ISO8859-1版本的文件名。只想给懒惰者一个适用于PHP的简单代码。
古斯塔夫

我认为这被否决了,因为问题不是特定于语言,而是有关实现标头编码时应遵循的RFC。但是,对于此答案,对于PHP,此代码使我的烦恼不再了。
j4k3

谢谢。这个答案可能没有严格回答问题,但这正是我在寻找并帮助我解决Python问题的方法。
Lyndsy Simon

1
我非常确定,如果用户可以控制文件名,则可以将此代码用作攻击媒介。
Antti Haapala'9

3

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

  • 除了为日语配置的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的一个功能。

这是一个很好的更新。您能否详细说明您尝试过的特定测试?
布拉德,

3

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

1

经典ASP解决方案

大多数现代浏览器都支持传递FilenameUTF-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 UploadBytesToString()include_aspuploader.aspUTF-8


有用的链接


-1

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

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


1
完全不同的问题。问题是关于下载,您的答复是关于上传
Oskar Berggren

-3

我通常使用URL编码(用%xx编码)文件名,并且似乎可以在所有浏览器中使用。您可能还是要进行一些测试。


10
我做了几次测试,但在所有浏览器中都无法正常运行,因此是一个问题。:)
Atif Aziz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.