diff --git a/CHANGES.rst b/CHANGES.rst index fe9870aa..64892ae9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,8 +12,12 @@ Unreleased - :meth:`flask.RequestContext.copy` includes the current session object in the request context copy. This prevents ``flask.session`` pointing to an out-of-date object. (`#2935`) +- Using built-in RequestContext, unprintable Unicode characters in Host + header will result in a HTTP 400 response and not HTTP 500 as previously. + (`#2994`) .. _#2935: https://github.com/pallets/flask/issues/2935 +.. _#2994: https://github.com/pallets/flask/pull/2994 Version 1.0.3 diff --git a/flask/ctx.py b/flask/ctx.py index e41adbd2..f8cdb60b 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -282,7 +282,11 @@ class RequestContext(object): if request is None: request = app.request_class(environ) self.request = request - self.url_adapter = app.create_url_adapter(self.request) + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e self.flashes = None self.session = session @@ -305,7 +309,8 @@ class RequestContext(object): # functions. self._after_request_functions = [] - self.match_request() + if self.url_adapter is not None: + self.match_request() def _get_g(self): return _app_ctx_stack.top.g diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index d8ef4970..6cb9c37d 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -229,3 +229,57 @@ def test_session_error_pops_context(): assert response.status_code == 500 assert not flask.request assert not flask.current_app + + +def test_bad_environ_raises_bad_request(): + app = flask.Flask(__name__) + + # We cannot use app.test_client() for the Unicode-rich Host header, + # because werkzeug enforces latin1 on Python 2. + # However it works when actually passed to the server. + + from flask.testing import make_test_environ_builder + builder = make_test_environ_builder(app) + environ = builder.get_environ() + + # use a non-printable character in the Host - this is key to this test + environ['HTTP_HOST'] = u'\x8a' + + with app.request_context(environ): + response = app.full_dispatch_request() + assert response.status_code == 400 + + +def test_environ_for_valid_idna_completes(): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return 'Hello World!' + + # We cannot use app.test_client() for the Unicode-rich Host header, + # because werkzeug enforces latin1 on Python 2. + # However it works when actually passed to the server. + + from flask.testing import make_test_environ_builder + builder = make_test_environ_builder(app) + environ = builder.get_environ() + + # these characters are all IDNA-compatible + environ['HTTP_HOST'] = u'ąśźäüжŠßя.com' + + with app.request_context(environ): + response = app.full_dispatch_request() + + assert response.status_code == 200 + + +def test_normal_environ_completes(): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return 'Hello World!' + + response = app.test_client().get('/', headers={'host': 'xn--on-0ia.com'}) + assert response.status_code == 200