如何为HTTP标头编码UTF8文件名? (Python,Django)

时间:2009-09-01 10:00:31

标签: python django http http-headers escaping

我有HTTP标头的问题,它们是用ASCII编码的,我想提供一个视图,用于下载名称可以是非ASCII的文件。

response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), )

我不想使用静态文件来处理与非ASCII文件名相同的问题,但在这种情况下,文件系统及其文件名编码会出现问题。 (我不知道目标操作系统。)

我已经尝试过urllib.quote(),但它引发了KeyError异常。

可能我做错了,但也许这是不可能的。

6 个答案:

答案 0 :(得分:36)

这是常见问题。

没有可互操作的方法来做到这一点。一些浏览器实现专有扩展(IE,Chrome),其他实现RFC 2231(Firefox,Opera)。

请参阅http://greenbytes.de/tech/tc2231/上的测试用例。

更新:截至2012年11月,所有当前的桌面浏览器都支持RFC 6266和RFC 5987中定义的编码(Safari> = 6,IE> = 9,Chrome,Firefox,Opera,Konqueror)。

答案 1 :(得分:31)

不要在Content-Disposition中发送文件名。没有办法使非ASCII标头参数跨浏览器(*)工作。

相反,只发送“Content-Disposition:attachment”,并将文件名作为URL编码的UTF-8字符串保留在URL的尾随(PATH_INFO)部分,以便浏览器默认选择并使用。浏览器处理UTF-8 URL比使用Content-Disposition更可靠。

(*:实际上,由于RFC 2616,2231和2047之间的关系非常不正常,因此甚至没有一个当前标准说明应该如何完成,这是Julian试图清除的事情在一个规范级别。一致的浏览器支持是在遥远的未来。)

答案 2 :(得分:28)

请注意,在2011年,RFC 6266(特别是附录D)对此问题进行了权衡,并提出了具体建议。

即,您可以发出仅包含ASCII字符的filename,然后发送filename*,其中包含RFC 5987格式的文件名,供那些理解它的代理使用。

通常这看起来像filename="my-resume.pdf"; filename*=UTF-8''My%20R%C3%A9sum%C3%A9.pdf,其中Unicode文件名(“我的Résumé.pdf”)被编码为UTF-8然后进行百分比编码(注意,不要将+用于空格)。

请实际阅读RFC 6266和RFC 5987(或使用一个强大且经过测试的库,为您提取此内容),因为我的摘要缺乏重要细节。

答案 3 :(得分:5)

从2018年开始, Django 2.1 中提供了一个解决方案(以open ticket贬值了七年之久)。您可以使用FileResponse中内置的as_attachment参数。例如,要以HTTP响应返回文件类型为output_file的文件output_mime_type作为HTTP响应:

response = FileResponse(open(output_file, 'rb'), as_attachment=True, content_type=output_mime_type)
return response

或者,如果您不能使用FileResponse,则可以使用其来源中的相关部分来更直接地更改Content-Disposition。  这是该来源当前的样子:

from urllib.parse import quote
try:
    document.file_name.encode('ascii')
    file_expr = 'filename="{}"'.format(filename)
except UnicodeEncodeError:
    # Handle a non-ASCII filename
    file_expr = "filename*=utf-8''{}".format(quote(filename))
response['Content-Disposition'] = 'attachment; {}'.format(file_expr)

答案 4 :(得分:4)

我可以说我已经成功使用较新的(RFC 5987)格式来指定使用电子邮件格式(RFC 2231)编码的标头。我提出了以下解决方案,该解决方案基于django-sendfile项目的代码。

import unicodedata
from django.utils.http import urlquote

def rfc5987_content_disposition(file_name):
    ascii_name = unicodedata.normalize('NFKD', file_name).encode('ascii','ignore').decode()
    header = 'attachment; filename="{}"'.format(ascii_name)
    if ascii_name != file_name:
        quoted_name = urlquote(file_name)
        header += '; filename*=UTF-8\'\'{}'.format(quoted_name)

    return header

# e.g.
  # request['Content-Disposition'] = rfc5987_content_disposition(file_name)

我只使用 Django 1.8 Python 3.4 上测试了我的代码。所以类似的solution in django-sendfile可能会让你更好。

Django的跟踪器中有一个long standing ticket,它确认了这一点,但尚未提出补丁。所以不幸的是,我已经找到了一个强大的测试库,如果有更好的解决方案,请告诉我。

答案 5 :(得分:-1)

黑客攻击:

if (Request.UserAgent.Contains("IE"))
{
  // IE will accept URL encoding, but spaces don't need to be, and since they're so common..
  filename = filename.Replace("%", "%25").Replace(";", "%3B").Replace("#", "%23").Replace("&", "%26");
}