Set Unicode filename in Flask response header Set Unicode filename in Flask response header flask flask

Set Unicode filename in Flask response header


RFC 2231 section 4 describes how to specify an encoding to use instead of Latin-1 for a header value. Use the header option filename*=UTF-8''..., where ... is the url-encoded name. You can also include the filename option to provide a Latin-1 fallback.

Until relatively recently, browsers did not consistently support this. This page has some metrics on browser support. Notably, IE8 will ignore the UTF-8 option, and will fail completely if the UTF-8 option comes before the Latin-1 option.

Flask 1.0 supports calling send_file with Unicode filenames. If you use Flask 1.0, you can use send_file with as_attachment=True and a Unicode filename.

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

Until then, you can construct the header manually using the same process Flask will use.

import unicodedatafrom flask import send_filefrom 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

For security you should use send_from_directory instead if the filename is provided by user input. The process is the same as above, substituting the function.


WSGI doesn't ensure the order of header options, so if you want to support IE8 you must construct the header value completely manually using dump_options_header with an OrderedDict. Otherwise, filename* may appear before filename, which as noted above does not work in IE8.

from collections import OrderedDictimport unicodedatafrom flask import send_filefrom werkzeug.http import dump_options_headerfrom 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