Skip to content

Commit

Permalink
Fix send_file to work with non-ascii filenames
Browse files Browse the repository at this point in the history
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 pallets#1286
  • Loading branch information
antlarr committed Mar 23, 2017
1 parent 6efea34 commit 0049922
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 1 deletion.
6 changes: 5 additions & 1 deletion flask/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
current_app, request
from ._compat import string_types, text_type
from unidecode import unidecode


# sentinel
Expand Down Expand Up @@ -534,8 +535,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
if attachment_filename is None:
raise TypeError('filename unavailable, required for '
'sending as attachment')
filename_dict = {
'filename': unidecode(attachment_filename),
'filename*': "UTF-8''%s" % url_quote(attachment_filename)}
headers.add('Content-Disposition', 'attachment',
filename=attachment_filename)
**filename_dict)

if current_app.use_x_sendfile and filename:
if file is not None:
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def hello():
'Jinja2>=2.4',
'itsdangerous>=0.21',
'click>=2.0',
'unidecode',
],
classifiers=[
'Development Status :: 4 - Beta',
Expand Down
11 changes: 11 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,17 @@ def test_attachment(self):
assert options['filename'] == 'index.txt'
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):
app = flask.Flask(__name__)
# default cache timeout is 12 hours
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ deps=
devel: git+https://github.com/pallets/itsdangerous.git
devel: git+https://github.com/jek/blinker.git
simplejson: simplejson
unidecode

[testenv:docs]
deps = sphinx
Expand Down

0 comments on commit 0049922

Please sign in to comment.