diff --git a/CHANGES.rst b/CHANGES.rst index fd4e0533..a0a9cd28 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,12 @@ Version 1.0.3 Unreleased +- :func:`send_file` encodes filenames as ASCII instead of Latin-1 + (ISO-8859-1). This fixes compatibility with Gunicorn, which is + stricter about header encodings than PEP 3333. (`#2766`_) + +.. _#2766: https://github.com/pallets/flask/issues/2766 + Version 1.0.2 ------------- diff --git a/flask/helpers.py b/flask/helpers.py index df0b91fc..7679a496 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -506,6 +506,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + :param filename_or_fp: the filename of the file to send. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. @@ -564,11 +568,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, 'sending as attachment') try: - attachment_filename = attachment_filename.encode('latin-1') + attachment_filename = attachment_filename.encode('ascii') except UnicodeEncodeError: filenames = { 'filename': unicodedata.normalize( - 'NFKD', attachment_filename).encode('latin-1', 'ignore'), + 'NFKD', attachment_filename).encode('ascii', 'ignore'), 'filename*': "UTF-8''%s" % url_quote(attachment_filename), } else: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b3535b28..ae1c0805 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -638,15 +638,22 @@ class TestSendfile(object): assert options['filename'] == 'index.txt' rv.close() - def test_attachment_with_utf8_filename(self, app, req_ctx): - 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" - )) + @pytest.mark.usefixtures('req_ctx') + @pytest.mark.parametrize(('filename', 'ascii', 'utf8'), ( + ('index.html', 'index.html', False), + (u'Ñandú/pingüino.txt', '"Nandu/pinguino.txt"', + '%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt'), + (u'Vögel.txt', 'Vogel.txt', 'V%C3%B6gel.txt'), + )) + def test_attachment_filename_encoding(self, filename, ascii, utf8): + rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=filename) rv.close() + content_disposition = rv.headers['Content-Disposition'] + assert 'filename=%s' % ascii in content_disposition + if utf8: + assert "filename*=UTF-8''" + utf8 in content_disposition + else: + assert "filename*=UTF-8''" not in content_disposition def test_static_file(self, app, req_ctx): # default cache timeout is 12 hours