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

如何为HTTP头编码UTF8文件名?(Python,Django),python,django,http,http-headers,escaping,Python,Django,Http,Http Headers,Escaping,我对HTTP头有问题,它们是用ASCII编码的,我想提供一个下载文件的视图,文件名可以是非ASCII的 response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), ) 我不想使用静态文件来处理非ASCII文件名的相同问题,但在这种情况下,文件系统和文件名编码会出现问题。(我不知道目标操作系统。) 我已经尝试了urllib.quote(),但它引发了

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

response['Content-Disposition'] = 'attachment; filename="%s"' % (vo.filename.encode("ASCII","replace"), )
我不想使用静态文件来处理非ASCII文件名的相同问题,但在这种情况下,文件系统和文件名编码会出现问题。(我不知道目标操作系统。)

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

可能我做错了什么,但可能是不可能的。

这是常见问题解答

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

请参阅中的测试用例


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

在内容配置中不发送文件名。无法使非ASCII标头参数跨浏览器(*)工作

相反,只发送“contentdisposition:attachment”,并将文件名作为URL编码的UTF-8字符串保留在URL的尾部(PATH_INFO),以便浏览器在默认情况下拾取和使用。浏览器处理UTF-8 URL比处理内容配置更可靠

(*:实际上,由于RFC 2616、2231和2047之间的关系非常不正常,朱利安正试图在规范层面上澄清这一点。一致的浏览器支持在遥远的将来才会出现。)

a hack:

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");
}
请注意,2011年,(特别是附录D)对此问题进行了权衡,并提出了具体建议

也就是说,您可以为理解它的代理发出一个仅包含ASCII字符的
文件名
,后跟带有RFC 5987格式文件名的
文件名*

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


请确实阅读RFC 6266和RFC 5987(或者使用一个健壮且经过测试的库为您进行摘要),因为我在这里的总结缺少重要的细节。

我可以说,我已经成功地使用了新的()格式,即指定用电子邮件表单()编码的头。我根据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)
我只在Python3.4上用Django 1.8测试了我的代码。所以类似的可能更适合你


Django的追踪器中有一个补丁承认这一点,但目前还没有针对afaict提出任何补丁。不幸的是,这与我所能找到的健壮的测试库非常接近,请告诉我是否有更好的解决方案。

从Django 2.1开始,您可以使用,它将正确设置附件的
内容配置
标题。从Django 3.0(发行版)开始,它还将为
内联文件正确设置它

例如,要返回名为
my_img.jpg
且MIME类型为
image/jpeg
的文件作为HTTP响应:

response = FileResponse(open("my_img.jpg", 'rb'), as_attachment=True, content_type="image/jpeg")
return response
或者,如果无法使用
FileResponse
,则可以使用中的相关部分自行设置
内容配置
标题。以下是该源当前的外观:

from urllib.parse import quote

disposition = 'attachment' if as_attachment else 'inline'
try:
    filename.encode('ascii')
    file_expr = 'filename="{}"'.format(filename)
except UnicodeEncodeError:
    file_expr = "filename*=utf-8''{}".format(quote(filename))
response.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)

谢谢最容易的事最难找到;)最近,Julian为了这个目的整理了RFC2231的配置文件:这是否适用于多部分/表单数据支持,因为现在我可以在ChromeJame中从表单上传文件时看到在“filename”参数中发送的原始UTF-8字节:否。请参见顶部答案中已废弃的RFC 5987包含一些重要信息,但你实际上已经解决了这个问题。谢谢自从这个答案出来后,已经发布了关于这个主题的RFC。值得注意的是
filename*=
结构,只有较新的浏览器才支持该结构,并保证允许您使用UTF-8,其编码方式与RFC 5987相同。我意识到我晚了很多年,但是。。。KeyError异常真的让我很烦。我的意思不仅仅是“每隔一段时间我就会遇到这个问题”,我的意思是,我在几年前向Python提交了一个补丁来解决这个问题,争论了一段时间,然后决定他们不想更改Python 2。我确实在Python3中解决了这个问题,但他们从未接受我在Python2中的补丁。解决的办法是先进行.encode('utf-8'),然后使用urllib.quote。但这是针对URL编码的,而URL编码并不是将其放入头中的标准方式。用户代理嗅探通常很糟糕,并且负责许多tc2231/rfc6266测试用例。这就是我在Django项目中的文件下载端点所需要的。非常感谢。令人惊叹的!这就是我们需要的!注意:如果
as_attachment=False
(如果
Content Disposition
inline
)它在Django 2.1版或Django 2.2版中都不可用,现在(2019年5月21日)它在Django开发版本中,因此对于
inline
,我使用手动版本。有关@don_vanchos评论的更多信息,请参阅。