From ed9775fb77bc2291473c176937326aadd435f73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarek=20Pi=C3=B3rkowski?= Date: Mon, 12 Nov 2018 16:59:09 -0500 Subject: [PATCH 1/3] Handle errors during create_url_adapter If create_url_adapter raises (which it can if werkzeug cannot bind environment, for example on non-ASCII Host header), we handle it as other routing exceptions rather than raising through. ref https://github.com/pallets/werkzeug/issues/640 --- flask/ctx.py | 9 +++++++-- tests/test_reqctx.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) 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..dfdd95ae 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -229,3 +229,26 @@ 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__) + + @app.route('/') + def index(): + # shouldn't get here anyway + assert False + + response = app.test_client().get('/', headers={'host': 'ąśź.com'}) + assert response.status_code == 400 + + +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 From 9b0bd91195828e16887a31fec1ce2ad16999bb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarek=20Pi=C3=B3rkowski?= Date: Tue, 13 Nov 2018 13:09:16 -0500 Subject: [PATCH 2/3] Correct tests We've discovered that passing Unicode in Host actually works, except for test client limitations on Python 2 - and the only things that don't work are non-printable characters. --- tests/test_reqctx.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index dfdd95ae..6cb9c37d 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -234,13 +234,44 @@ def test_session_error_pops_context(): 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(): - # shouldn't get here anyway - assert False + return 'Hello World!' - response = app.test_client().get('/', headers={'host': 'ąśź.com'}) - assert response.status_code == 400 + # 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(): From a5709e785061b657b9541fac0f407d496e7107ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarek=20Pi=C3=B3rkowski?= Date: Tue, 13 Nov 2018 13:40:50 -0500 Subject: [PATCH 3/3] Add changelog entry --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) 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