]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Correct handling of HTTP version numbers.
authorBen Darnell <ben@bendarnell.com>
Sun, 11 Jan 2015 18:50:58 +0000 (13:50 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 11 Jan 2015 18:50:58 +0000 (13:50 -0500)
Per RFC 7230 section 2.6, we always output HTTP/1.1 even if the
request was HTTP/1.0 (and we accept any 1.x version).

tornado/http1connection.py
tornado/httputil.py
tornado/simple_httpclient.py
tornado/test/httpserver_test.py
tornado/test/iostream_test.py
tornado/web.py

index 1ff9b83358e9165bf13661b924e36e82c280cbcd..32bf83f2d100d84592872e91c46e0042339ebb44 100644 (file)
@@ -326,8 +326,10 @@ class HTTP1Connection(httputil.HTTPConnection):
 
     def write_headers(self, start_line, headers, chunk=None, callback=None):
         """Implements `.HTTPConnection.write_headers`."""
+        lines = []
         if self.is_client:
             self._request_start_line = start_line
+            lines.append(utf8('%s %s HTTP/1.1' % (start_line[0], start_line[1])))
             # Client requests with a non-empty body must have either a
             # Content-Length or a Transfer-Encoding.
             self._chunking_output = (
@@ -336,6 +338,7 @@ class HTTP1Connection(httputil.HTTPConnection):
                 'Transfer-Encoding' not in headers)
         else:
             self._response_start_line = start_line
+            lines.append(utf8('HTTP/1.1 %s %s' % (start_line[1], start_line[2])))
             self._chunking_output = (
                 # TODO: should this use
                 # self._request_start_line.version or
@@ -365,7 +368,6 @@ class HTTP1Connection(httputil.HTTPConnection):
             self._expected_content_remaining = int(headers['Content-Length'])
         else:
             self._expected_content_remaining = None
-        lines = [utf8("%s %s %s" % start_line)]
         lines.extend([utf8(n) + b": " + utf8(v) for n, v in headers.get_all()])
         for line in lines:
             if b'\n' in line:
index dfee4f6508dea625606eba1217752a2cee6f7db1..0f798ca95758718c1992e30b5ba38d5cf06675f9 100644 (file)
@@ -548,6 +548,8 @@ class HTTPConnection(object):
             headers.
         :arg callback: a callback to be run when the write is complete.
 
+        The ``version`` field of ``start_line`` is ignored.
+
         Returns a `.Future` if no callback is given.
         """
         raise NotImplementedError()
@@ -787,7 +789,7 @@ def parse_request_start_line(line):
         method, path, version = line.split(" ")
     except ValueError:
         raise HTTPInputError("Malformed HTTP request line")
-    if not version.startswith("HTTP/"):
+    if not re.match(r"^HTTP/1\.[0-9]$", version):
         raise HTTPInputError(
             "Malformed HTTP version in HTTP Request-Line: %r" % version)
     return RequestStartLine(method, path, version)
@@ -806,7 +808,7 @@ def parse_response_start_line(line):
     ResponseStartLine(version='HTTP/1.1', code=200, reason='OK')
     """
     line = native_str(line)
-    match = re.match("(HTTP/1.[01]) ([0-9]+) ([^\r]*)", line)
+    match = re.match("(HTTP/1.[0-9]) ([0-9]+) ([^\r]*)", line)
     if not match:
         raise HTTPInputError("Error parsing response start line")
     return ResponseStartLine(match.group(1), int(match.group(2)),
index 7c915e90a6abf863a095902464841ae9037df489..31d076e2d114d0840dcf586410aee11eebcaf43f 100644 (file)
@@ -345,7 +345,7 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
                 decompress=self.request.decompress_response),
             self._sockaddr)
         start_line = httputil.RequestStartLine(self.request.method,
-                                               req_path, 'HTTP/1.1')
+                                               req_path, '')
         self.connection.write_headers(start_line, self.request.headers)
         if self.request.expect_100_continue:
             self._read_response()
index 49a0996599589423a4727953570fba987c38085c..36365e0d70437efecc0b48050c5b55bf1e3b70a3 100644 (file)
@@ -562,7 +562,7 @@ class UnixSocketTest(AsyncTestCase):
         self.stream.write(b"GET /hello HTTP/1.0\r\n\r\n")
         self.stream.read_until(b"\r\n", self.stop)
         response = self.wait()
-        self.assertEqual(response, b"HTTP/1.0 200 OK\r\n")
+        self.assertEqual(response, b"HTTP/1.1 200 OK\r\n")
         self.stream.read_until(b"\r\n\r\n", self.stop)
         headers = HTTPHeaders.parse(self.wait().decode('latin1'))
         self.stream.read_bytes(int(headers["Content-Length"]), self.stop)
@@ -636,7 +636,7 @@ class KeepAliveTest(AsyncHTTPTestCase):
     def read_headers(self):
         self.stream.read_until(b'\r\n', self.stop)
         first_line = self.wait()
-        self.assertTrue(first_line.startswith(self.http_version + b' 200'), first_line)
+        self.assertTrue(first_line.startswith(b'HTTP/1.1 200'), first_line)
         self.stream.read_until(b'\r\n\r\n', self.stop)
         header_bytes = self.wait()
         headers = HTTPHeaders.parse(header_bytes.decode('latin1'))
index b0e94fb20f8e53a67ffff2a38753822476076e94..c622bb40fb536cfd60c170c4a2b59cb59683ab64 100644 (file)
@@ -57,7 +57,7 @@ class TestIOStreamWebMixin(object):
 
         stream.read_until_close(self.stop)
         data = self.wait()
-        self.assertTrue(data.startswith(b"HTTP/1.0 200"))
+        self.assertTrue(data.startswith(b"HTTP/1.1 200"))
         self.assertTrue(data.endswith(b"Hello"))
 
     def test_read_zero_bytes(self):
@@ -70,7 +70,7 @@ class TestIOStreamWebMixin(object):
         # normal read
         self.stream.read_bytes(9, self.stop)
         data = self.wait()
-        self.assertEqual(data, b"HTTP/1.0 ")
+        self.assertEqual(data, b"HTTP/1.1 ")
 
         # zero bytes
         self.stream.read_bytes(0, self.stop)
@@ -125,7 +125,7 @@ class TestIOStreamWebMixin(object):
         self.assertIs(connect_result, stream)
         yield stream.write(b"GET / HTTP/1.0\r\n\r\n")
         first_line = yield stream.read_until(b"\r\n")
-        self.assertEqual(first_line, b"HTTP/1.0 200 OK\r\n")
+        self.assertEqual(first_line, b"HTTP/1.1 200 OK\r\n")
         # callback=None is equivalent to no callback.
         header_data = yield stream.read_until(b"\r\n\r\n", callback=None)
         headers = HTTPHeaders.parse(header_data.decode('latin1'))
index 2d1dac0fd23f7fdd11707d886596584ef350560e..038d424e8942cf51aae8f8bbef44f411455d6282 100644 (file)
@@ -840,7 +840,7 @@ class RequestHandler(object):
                 for cookie in self._new_cookie.values():
                     self.add_header("Set-Cookie", cookie.OutputString(None))
 
-            start_line = httputil.ResponseStartLine(self.request.version,
+            start_line = httputil.ResponseStartLine('',
                                                     self._status_code,
                                                     self._reason)
             return self.request.connection.write_headers(