diff --git a/CHANGES.rst b/CHANGES.rst index 3d198aec..7539c972 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -36,13 +36,22 @@ Unreleased (ISO-8859-1). This fixes compatibility with Gunicorn, which is stricter about header encodings than PEP 3333. (`#2766`_) - Allow custom CLIs using ``FlaskGroup`` to set the debug flag without - it always being overwritten based on environment variables. (`#2765`_) + it always being overwritten based on environment variables. + (`#2765`_) - ``flask --version`` outputs Werkzeug's version and simplifies the Python version. (`#2825`_) +- :func:`send_file` handles an ``attachment_filename`` that is a + native Python 2 string (bytes) with UTF-8 coded bytes. (`#2933`_) +- A catch-all error handler registered for ``HTTPException`` will not + handle ``RoutingExcpetion``, which is used internally during + routing. This fixes the unexpected behavior that had been introduced + in 1.0. (`#2986`_) .. _#2766: https://github.com/pallets/flask/issues/2766 .. _#2765: https://github.com/pallets/flask/pull/2765 .. _#2825: https://github.com/pallets/flask/pull/2825 +.. _#2933: https://github.com/pallets/flask/issues/2933 +.. _#2986: https://github.com/pallets/flask/pull/2986 Version 1.0.2 diff --git a/flask/app.py b/flask/app.py index 28901b89..92731cca 100644 --- a/flask/app.py +++ b/flask/app.py @@ -20,7 +20,8 @@ from threading import Lock from werkzeug.datastructures import Headers, ImmutableDict from werkzeug.exceptions import BadRequest, BadRequestKeyError, HTTPException, \ InternalServerError, MethodNotAllowed, default_exceptions -from werkzeug.routing import BuildError, Map, RequestRedirect, Rule +from werkzeug.routing import BuildError, Map, RequestRedirect, \ + RoutingException, Rule from . import cli, json from ._compat import integer_types, reraise, string_types, text_type @@ -1631,6 +1632,16 @@ class Flask(_PackageBoundObject): registered error handlers and fall back to returning the exception as response. + .. versionchanged:: 1.0.3 + ``RoutingException``, used internally for actions such as + slash redirects during routing, is not passed to error + handlers. + + .. versionchanged:: 1.0 + Exceptions are looked up by code *and* by MRO, so + ``HTTPExcpetion`` subclasses can be handled with a catch-all + handler for the base ``HTTPException``. + .. versionadded:: 0.3 """ # Proxy exceptions don't have error codes. We want to always return @@ -1638,6 +1649,12 @@ class Flask(_PackageBoundObject): if e.code is None: return e + # RoutingExceptions are used internally to trigger routing + # actions, such as slash redirects raising RequestRedirect. They + # are not raised or handled in user code. + if isinstance(e, RoutingException): + return e + handler = self._find_error_handler(e) if handler is None: return e diff --git a/flask/helpers.py b/flask/helpers.py index 5afba8d5..03129df9 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -577,6 +577,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') + if not isinstance(attachment_filename, text_type): + attachment_filename = attachment_filename.decode('utf-8') + try: attachment_filename = attachment_filename.encode('ascii') except UnicodeEncodeError: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 921a9942..8f2fc1c9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -680,6 +680,8 @@ class TestSendfile(object): (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'), + # Native string not marked as Unicode on Python 2 + ('tést.txt', 'test.txt', 't%C3%A9st.txt'), )) def test_attachment_filename_encoding(self, filename, ascii, utf8): rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=filename) diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index 18d5f277..f4a08f58 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -184,6 +184,10 @@ def test_default_error_handler(): def forbidden(): raise Forbidden() + @app.route("/slash/") + def slash(): + return "slash" + app.register_blueprint(bp, url_prefix='/bp') c = app.test_client() @@ -191,5 +195,5 @@ def test_default_error_handler(): assert c.get('/bp/forbidden').data == b'bp-forbidden' assert c.get('/undefined').data == b'default' assert c.get('/forbidden').data == b'forbidden' - - + # Don't handle RequestRedirect raised when adding slash. + assert c.get("/slash", follow_redirects=True).data == b"slash"