Browse Source

Merge pull request #2223 from antlarr/master

Fix send_file's attachment_filename to work with non-ascii filenames
pull/2237/head
David Lord 8 years ago committed by GitHub
parent
commit
8b45009dbc
  1. 24
      flask/helpers.py
  2. 16
      tests/test_helpers.py

24
flask/helpers.py

@ -17,6 +17,7 @@ import mimetypes
from time import time from time import time
from zlib import adler32 from zlib import adler32
from threading import RLock from threading import RLock
import unicodedata
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from functools import update_wrapper from functools import update_wrapper
@ -478,6 +479,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
The `attachment_filename` is preferred over `filename` for MIME-type The `attachment_filename` is preferred over `filename` for MIME-type
detection. detection.
.. versionchanged:: 0.13
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
:param filename_or_fp: the filename of the file to send in `latin-1`. :param filename_or_fp: the filename of the file to send in `latin-1`.
This is relative to the :attr:`~Flask.root_path` This is relative to the :attr:`~Flask.root_path`
if a relative path is specified. if a relative path is specified.
@ -534,8 +540,22 @@ 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')
headers.add('Content-Disposition', 'attachment',
filename=attachment_filename) normalized = unicodedata.normalize(
'NFKD', text_type(attachment_filename)
)
try:
normalized.encode('ascii')
except UnicodeEncodeError:
filenames = {
'filename': normalized.encode('ascii', 'ignore'),
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
}
else:
filenames = {'filename': attachment_filename}
headers.add('Content-Disposition', 'attachment', **filenames)
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:

16
tests/test_helpers.py

@ -540,10 +540,11 @@ class TestSendfile(object):
value, options = \ value, options = \
parse_options_header(rv.headers['Content-Disposition']) parse_options_header(rv.headers['Content-Disposition'])
assert value == 'attachment' assert value == 'attachment'
assert options['filename'] == 'index.html'
assert 'filename*' not in rv.headers['Content-Disposition']
rv.close() rv.close()
with app.test_request_context(): with app.test_request_context():
assert options['filename'] == 'index.html'
rv = flask.send_file('static/index.html', as_attachment=True) rv = flask.send_file('static/index.html', as_attachment=True)
value, options = parse_options_header(rv.headers['Content-Disposition']) value, options = parse_options_header(rv.headers['Content-Disposition'])
assert value == 'attachment' assert value == 'attachment'
@ -560,6 +561,19 @@ 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():
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt')
content_disposition = set(rv.headers['Content-Disposition'].split('; '))
assert content_disposition == set((
'attachment',
'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

Loading…
Cancel
Save