在Flask响应头中设置Unicode文件名

时间:2017-04-12 09:30:27

标签: python python-3.x flask http-headers

我正在尝试设置Content-Disposition标头以将文件发送到客户端。文件名是Unicode。当我尝试设置标头时,它会失败并显示UnicodeEncodeError。我尝试了encodedecode的各种组合,但无法使其发挥作用。如何发送带有Unicode文件名的文件?

destination_file = 'python_report.html'
response.headers['Content-Disposition'] = 'attachment; filename=' + destination_file
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/http/server.py", line 495, in send_header
    ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 41-42: ordinal not in range(256)

1 个答案:

答案 0 :(得分:7)

RFC 2231 section 4描述了如何指定要使用的编码而不是Latin-1作为标头值。使用标头选项filename*=UTF-8''...,其中...是网址编码的名称。您还可以添加filename选项以提供Latin-1后备。

直到最近,浏览器才一直支持这一点。 This page有一些关于浏览器支持的指标。值得注意的是,IE8将忽略UTF-8选项,如果UTF-8选项位于Latin-1选项之前,它将完全失败。

Flask 1.0 supports calling send_file with Unicode filenames。如果您使用Flask 1.0,则可以将send_fileas_attachment=True和Unicode文件名一起使用。

from flask import send_file

@app.route('/send-python-report')
def send_python_report():
    return send_file('python_report.html', as_attachment=True)

在此之前,您可以使用Flask将使用的相同过程手动构建标题。

import unicodedata

from flask import send_file
from werkzeug.urls import url_quote

@app.route('/send-python-report')
def send_python_report():
    filename = 'python_report.html'
    rv = send_file(filename)

    try:
        filename = filename.encode('latin-1')
    except UnicodeEncodeError:
        filenames = {
            'filename': unicodedata.normalize('NFKD', filename).encode('latin-1', 'ignore'),
            'filename*': "UTF-8''{}".format(url_quote(filename)),
        }
    else:
        filenames = {'filename': filename}

    rv.headers.set('Content-Disposition', 'attachment', **filenames)
    return rv

为了安全起见,如果文件名由用户输入提供,则应使用send_from_directory。该过程与上述相同,取代了函数。

WSGI不确保标头选项的顺序,因此如果您要支持IE8,则必须使用dump_options_header OrderedDict完全手动构建标头值。否则,filename*可能会出现在filename之前,如上所述,这在IE8中不起作用。

from collections import OrderedDict
import unicodedata

from flask import send_file
from werkzeug.http import dump_options_header
from werkzeug.urls import url_quote

@app.route('/send-python-report')
def send_python_report():
    filename = 'python_report.html'
    rv = send_file(filename)
    filenames = OrderedDict()

    try:
        filename = filename.encode('latin-1')
    except UnicodeEncodeError:
        filenames['filename'] = unicodedata.normalize('NFKD', filename).encode('latin-1', 'ignore')
        filenames['filename*']: "UTF-8''{}".format(url_quote(filename))
    else:
        filenames['filename'] = filename

    rv.headers.set('Content-Disposition', dump_options_header('attachment', filenames))
    return rv