Browse Source

Fix send_file to work with non-ascii filenames

This commit implements https://tools.ietf.org/html/rfc2231#section-4 in
order to support sending unicode characters. Tested on both Firefox and
Chromium under Linux.

This adds unidecode as a dependency, which might be relaxed by using
.encode('latin-1', 'ignore') but wouldn't be as useful.

Also, added a test for the correct headers to be added.

Previously, using a filename parameter to send_file with unicode characters, it
failed with the next error since HTTP headers don't allow non latin-1 characters.
Error on request:
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 193, in run_wsgi
    execute(self.server.app)
  File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 186, in execute
    write(b'')
  File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 152, in write
    self.send_header(key, value)
  File "/usr/lib64/python3.6/http/server.py", line 509, in send_header
    ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\uff0f' in position 58: ordinal not in range(256)

Fixes #1286
pull/2223/head
Antonio Larrosa 8 years ago
parent
commit
0049922f2e
  1. 6
      flask/helpers.py
  2. 1
      setup.py
  3. 11
      tests/test_helpers.py
  4. 1
      tox.ini

6
flask/helpers.py

@ -41,6 +41,7 @@ from .signals import message_flashed
from .globals import session, _request_ctx_stack, _app_ctx_stack, \ from .globals import session, _request_ctx_stack, _app_ctx_stack, \
current_app, request current_app, request
from ._compat import string_types, text_type from ._compat import string_types, text_type
from unidecode import unidecode
# sentinel # sentinel
@ -534,8 +535,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
if attachment_filename is None: if attachment_filename is None:
raise TypeError('filename unavailable, required for ' raise TypeError('filename unavailable, required for '
'sending as attachment') 'sending as attachment')
filename_dict = {
'filename': unidecode(attachment_filename),
'filename*': "UTF-8''%s" % url_quote(attachment_filename)}
headers.add('Content-Disposition', 'attachment', headers.add('Content-Disposition', 'attachment',
filename=attachment_filename) **filename_dict)
if current_app.use_x_sendfile and filename: if current_app.use_x_sendfile and filename:
if file is not None: if file is not None:

1
setup.py

@ -75,6 +75,7 @@ setup(
'Jinja2>=2.4', 'Jinja2>=2.4',
'itsdangerous>=0.21', 'itsdangerous>=0.21',
'click>=2.0', 'click>=2.0',
'unidecode',
], ],
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',

11
tests/test_helpers.py

@ -560,6 +560,17 @@ class TestSendfile(object):
assert options['filename'] == 'index.txt' assert options['filename'] == 'index.txt'
rv.close() rv.close()
def test_attachment_with_utf8_filename(self):
app = flask.Flask(__name__)
with app.test_request_context():
with open(os.path.join(app.root_path, 'static/index.html')) as f:
rv = flask.send_file(f, as_attachment=True,
attachment_filename='Ñandú/pingüino.txt')
value, options = \
parse_options_header(rv.headers['Content-Disposition'])
assert options == {'filename': 'Nandu/pinguino.txt', 'filename*': "UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"}
rv.close()
def test_static_file(self): def test_static_file(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
# default cache timeout is 12 hours # default cache timeout is 12 hours

1
tox.ini

@ -27,6 +27,7 @@ deps=
devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/pallets/itsdangerous.git
devel: git+https://github.com/jek/blinker.git devel: git+https://github.com/jek/blinker.git
simplejson: simplejson simplejson: simplejson
unidecode
[testenv:docs] [testenv:docs]
deps = sphinx deps = sphinx

Loading…
Cancel
Save