From 14afe29ee1880c12e731b6a7a0e54661ef5b0265 Mon Sep 17 00:00:00 2001 From: Adam Englander Date: Mon, 6 May 2019 16:03:14 -0400 Subject: [PATCH] Update the request header compatibility code for Python 2.7 to properly fold headers persuant to RFC 2616. Resolves #1080 --- CHANGES.rst | 7 +++++++ src/werkzeug/serving.py | 25 ++++++++++++++++++++++--- tests/test_serving.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 00b8f3091..2d3696de3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,12 @@ .. currentmodule:: werkzeug +Version 0.15.3 +-------------- + +- Properly handle multi-line header folding in development server in + Python 2.7. (:issue:`1080`) + + Version 0.15.2 -------------- diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py index 4a179b3e5..632aa7b95 100644 --- a/src/werkzeug/serving.py +++ b/src/werkzeug/serving.py @@ -218,6 +218,7 @@ def shutdown_server(): for key, value in self.get_header_items(): key = key.upper().replace("-", "_") + value = value.replace("\r\n", "") if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): key = "HTTP_" + key if key in environ: @@ -434,7 +435,8 @@ def get_header_items(self): This function provides Python 2/3 compatibility as related to the parsing of request headers. Python 2.7 is not compliant with RFC 3875 Section 4.1.18 which requires multiple values for headers - to be provided. This function will return a matching list regardless + to be provided or RFC 2616 which allows for folding of multi-line + headers. This function will return a matching list regardless of Python version. It can be removed once Python 2.7 support is dropped. @@ -445,9 +447,26 @@ def get_header_items(self): # W3C RFC 2616 Section 4.2. items = [] for header in self.headers.headers: - # Remove "\n\r" from the header and split on ":" to get + # Remove "\r\n" from the header and split on ":" to get # the field name and value. - key, value = header[0:-2].split(":", 1) + try: + key, value = header[0:-2].split(":", 1) + except ValueError as e: + # If header could not be slit with : but starts with white + # space and it follows an existing header, it's a folded + # header. + if header[0] in ("\t", " ") and len(items) > 0: + # Pop off the last header + key, value = items.pop() + # Append the current header to the value of the last + # header which will be placed back on the end of the + # list + value = value + header + # Otherwise it's just a bad header and should error + else: + # Re-raise the value error + raise e + # Add the key and the value once stripped of leading # white space. The specification allows for stripping # trailing white space but the Python 3 code does not diff --git a/tests/test_serving.py b/tests/test_serving.py index 94a46b7fd..9e3f4b49b 100644 --- a/tests/test_serving.py +++ b/tests/test_serving.py @@ -510,6 +510,36 @@ def app(environ, start_response): conn.close() +def test_multiline_header_folding_for_http_1_1(dev_server): + """ + This is testing the provision of multi-line header folding per: + * RFC 2616 Section 2.2 + * RFC 3875 Section 4.1.18 + """ + server = dev_server( + r""" + from werkzeug.wrappers import Response + def app(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [environ['HTTP_XYZ'].encode()] + """ + ) + + conn = httplib.HTTPConnection("127.0.0.1", server.port) + conn.connect() + conn.putrequest("GET", "/") + conn.putheader("Accept", "text/plain") + conn.putheader("XYZ", "first-line", "second-line", "third-line") + conn.endheaders() + conn.send(b"") + res = conn.getresponse() + + assert res.status == 200 + assert res.read() == b"first-line\tsecond-line\tthird-line" + + conn.close() + + def can_test_unix_socket(): if not hasattr(socket, "AF_UNIX"): return False