【问题标题】:How to encode UTF8 filename for HTTP headers? (Python, Django)如何为 HTTP 标头编码 UTF8 文件名? (Python,Django)
【发布时间】:2010-11-24 14:23:08
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我意识到我迟到了几年,但是...... KeyError 异常真的让我很烦恼。我的意思不仅仅是“每隔一段时间我都会遇到这个问题”,我是说,我在几年前向 Python 提交了一个补丁来修复,争论了一段时间,然后决定他们不想更改 Python 2。我确实在 Python 3 中解决了这个问题,但他们从未接受我在 Python 2 中的补丁。解决方法是先使用 .encode('utf-8'),然后使用 urllib.quote。但这是针对 URL 编码的,这不是将这些放在标头中的标准方式。

标签: python django http http-headers escaping


【解决方案1】:

这是一个常见问题解答。

没有可互操作的方法来做到这一点。一些浏览器实现专有扩展(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)。

【讨论】:

  • 谢谢!最简单的东西最难找到;)
  • 最近,Julian 为此目的整理了一份 RFC2231 配置文件:datatracker.ietf.org/doc/draft-reschke-rfc2231-in-http
  • 这是否适用于 multipart/form-data 支持,因为现在我可以看到从 Chrome 中的表单上传文件时在“文件名”参数中发送的原始 UTF-8 字节
  • jaime:没有。见 greenbytes.de/tech/webdav/rfc6266.html#rfc.section.1.p.4>。
  • RFC 5987 已被 RFC 8187 废弃
【解决方案2】:

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

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

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

【讨论】:

  • 最佳答案包含一些重要信息,但您实际上已经解决了问题。谢谢!
  • 自从这个答案出来以来,已经发布了关于这个主题的 RFC。值得注意的是 filename*= 构造,它只有较新的浏览器支持并保证让您使用 UTF-8,编码为 RFC 5987。tools.ietf.org/html/rfc6266#appendix-D
【解决方案3】:

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

也就是说,您可以发出仅包含 ASCII 字符的 filename,然后是带有 RFC 5987 格式文件名的 filename*,以便那些理解它的代理。

通常这看起来像 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 项目中文件下载端点所需要的。谢谢!
【解决方案4】:

Django 2.1 开始(参见问题#16470),您可以使用FileResponse,它将正确设置附件的Content-Disposition 标头。从 Django 3.0(问题 #30196)开始,它还将为 inline 文件正确设置。

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

response = FileResponse(open("my_img.jpg", 'rb'), as_attachment=True, content_type="image/jpeg")
return response

或者,如果您不能使用FileResponse,您可以使用FileResponse's source 中的相关部分自行设置Content-Disposition 标头。以下是该来源目前的样子:

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)

【讨论】:

  • 注意:如果as_attachment=False(如果Content-Dispositioninline)它在版本Django 2.1或版本Django 2.2中都不可用>,现在(21.05.2019)它是 Django dev 版本,所以对于inline,我使用手动版本。
  • 有关@don_vanchos 评论的更多信息,请参阅Django issue #30196
【解决方案5】:

我可以说我已经成功地使用了新的 (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.8Python 3.4 上测试了我的代码。所以类似的solution in django-sendfile 可能更适合你。

Django 的跟踪器中有一个long standing ticket 承认这一点,但尚未提出任何补丁。所以不幸的是,这与使用我能找到的强大的测试库一样接近,如果有更好的解决方案,请告诉我。

【讨论】:

  • 太棒了!需要的!
【解决方案6】:

来自 Django 的 escape_uri_path 函数是适合我的解决方案。

阅读Django Docs here,了解当前指定了哪些 RFC 标准。

from django.utils.encoding import escape_uri_path

file = "response.zip"
response = HttpResponse(content_type='application/zip')
response['Content-Disposition'] = f"attachment; filename*=utf-8''{escape_uri_path(file)}"

【讨论】:

    【解决方案7】:

    一个黑客:

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-17
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 2012-10-07
    • 2017-05-26
    • 2014-06-23
    相关资源
    最近更新 更多