]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add a configurable header size limit to HTTPServer and simple_httpclient.
authorBen Darnell <ben@bendarnell.com>
Sun, 6 Apr 2014 12:10:16 +0000 (13:10 +0100)
committerBen Darnell <ben@bendarnell.com>
Sun, 6 Apr 2014 12:10:16 +0000 (13:10 +0100)
tornado/http1connection.py
tornado/httpserver.py
tornado/simple_httpclient.py
tornado/test/httpserver_test.py
tornado/test/simple_httpclient_test.py
tornado/websocket.py

index 7e1ab3d727d521d4c98386d66e8a693f1fed706a..eb1f309069b62e8c7c8257885bcb40403ff6ea6d 100644 (file)
@@ -35,7 +35,8 @@ class HTTP1Connection(object):
     until the HTTP conection is closed.
     """
     def __init__(self, stream, address, is_client,
-                 no_keep_alive=False, protocol=None, chunk_size=None):
+                 no_keep_alive=False, protocol=None, chunk_size=None,
+                 max_header_size=None):
         self.is_client = is_client
         self.stream = stream
         self.address = address
@@ -58,6 +59,7 @@ class HTTP1Connection(object):
         else:
             self.protocol = "http"
         self._chunk_size = chunk_size or 65536
+        self._max_header_size = max_header_size or 65536
         self._disconnect_on_finish = False
         self._clear_request_state()
         self.stream.set_close_callback(self._on_connection_close)
@@ -112,7 +114,9 @@ class HTTP1Connection(object):
         assert isinstance(delegate, httputil.HTTPMessageDelegate)
         self.message_delegate = delegate
         try:
-            header_data = yield self.stream.read_until_regex(b"\r?\n\r?\n")
+            header_data = yield self.stream.read_until_regex(
+                b"\r?\n\r?\n",
+                max_bytes=self._max_header_size)
             self._reading = True
             self._finish_future = Future()
             start_line, headers = self._parse_headers(header_data)
index 1a0aff297bbb61b05e4360ce98c3ac130d6a19de..6f9b9db2391c408af65a663a8d0374f7ccc9304c 100644 (file)
@@ -136,13 +136,14 @@ class HTTPServer(TCPServer, httputil.HTTPServerConnectionDelegate):
     """
     def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
                  xheaders=False, ssl_options=None, protocol=None, gzip=False,
-                 chunk_size=None, **kwargs):
+                 chunk_size=None, max_header_size=None, **kwargs):
         self.request_callback = request_callback
         self.no_keep_alive = no_keep_alive
         self.xheaders = xheaders
         self.protocol = protocol
         self.gzip = gzip
         self.chunk_size = chunk_size
+        self.max_header_size = max_header_size
         TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
                            **kwargs)
 
@@ -150,7 +151,8 @@ class HTTPServer(TCPServer, httputil.HTTPServerConnectionDelegate):
         conn = HTTP1Connection(stream, address=address, is_client=False,
                                no_keep_alive=self.no_keep_alive,
                                protocol=self.protocol,
-                               chunk_size=self.chunk_size)
+                               chunk_size=self.chunk_size,
+                               max_header_size=self.max_header_size)
         conn.start_serving(self, gzip=self.gzip)
 
     def start_request(self, connection):
index 4f562226b45251eebbbb7680104f0d0565b34d06..bb59b30f717d501af96f89e430e95f03b8507a6b 100644 (file)
@@ -48,7 +48,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
     """
     def initialize(self, io_loop, max_clients=10,
                    hostname_mapping=None, max_buffer_size=104857600,
-                   resolver=None, defaults=None):
+                   resolver=None, defaults=None, max_header_size=None):
         """Creates a AsyncHTTPClient.
 
         Only a single AsyncHTTPClient instance exists per IOLoop
@@ -75,6 +75,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
         self.active = {}
         self.waiting = {}
         self.max_buffer_size = max_buffer_size
+        self.max_header_size = max_header_size
         if resolver:
             self.resolver = resolver
             self.own_resolver = False
@@ -120,7 +121,8 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
 
     def _handle_request(self, request, release_callback, final_callback):
         _HTTPConnection(self.io_loop, self, request, release_callback,
-                        final_callback, self.max_buffer_size, self.resolver)
+                        final_callback, self.max_buffer_size, self.resolver,
+                        self.max_header_size)
 
     def _release_fetch(self, key):
         del self.active[key]
@@ -147,7 +149,8 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
     _SUPPORTED_METHODS = set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
 
     def __init__(self, io_loop, client, request, release_callback,
-                 final_callback, max_buffer_size, resolver):
+                 final_callback, max_buffer_size, resolver,
+                 max_header_size):
         self.start_time = io_loop.time()
         self.io_loop = io_loop
         self.client = client
@@ -156,6 +159,7 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
         self.final_callback = final_callback
         self.max_buffer_size = max_buffer_size
         self.resolver = resolver
+        self.max_header_size = max_header_size
         self.code = None
         self.headers = None
         self.chunks = []
@@ -330,7 +334,8 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
         self.stream.set_nodelay(True)
         self.connection = HTTP1Connection(
             self.stream, self._sockaddr, is_client=True,
-            no_keep_alive=True, protocol=self.parsed.scheme)
+            no_keep_alive=True, protocol=self.parsed.scheme,
+            max_header_size=self.max_header_size)
         start_line = httputil.RequestStartLine(self.request.method,
                                                req_path, 'HTTP/1.1')
         self.connection.write_headers(
index 9cadb042bba0b8feccc49bf0630e51a2d1ef63a9..7a2d651da33cdc793a960d31a09f40feae8fe58b 100644 (file)
@@ -826,3 +826,21 @@ class StreamingChunkSizeTest(AsyncHTTPTestCase):
             write(compressed[20:])
         self.fetch_chunk_sizes(body_producer=body_producer,
                                headers={'Content-Encoding': 'gzip'})
+
+
+class MaxHeaderSizeTest(AsyncHTTPTestCase):
+    def get_app(self):
+        return Application([('/', HelloWorldRequestHandler)])
+
+    def get_httpserver_options(self):
+        return dict(max_header_size=1024)
+
+    def test_small_headers(self):
+        response = self.fetch("/", headers={'X-Filler': 'a' * 100})
+        response.rethrow()
+        self.assertEqual(response.body, b"Hello world")
+
+    def test_large_headers(self):
+        with ExpectLog(gen_log, "Unsatisfiable read"):
+            response = self.fetch("/", headers={'X-Filler': 'a' * 1000})
+        self.assertEqual(response.code, 599)
index ad905552ea65b02feeb58d620b5867ff142bdea2..5ca7955998fc0ee70f1de5b447c8f396f8285a01 100644 (file)
@@ -496,3 +496,32 @@ class ResolveTimeoutTestCase(AsyncHTTPTestCase):
     def test_resolve_timeout(self):
         response = self.fetch('/hello', connect_timeout=0.1)
         self.assertEqual(response.code, 599)
+
+
+class MaxHeaderSizeTest(AsyncHTTPTestCase):
+    def get_app(self):
+        class SmallHeaders(RequestHandler):
+            def get(self):
+                self.set_header("X-Filler", "a" * 100)
+                self.write("ok")
+
+        class LargeHeaders(RequestHandler):
+            def get(self):
+                self.set_header("X-Filler", "a" * 1000)
+                self.write("ok")
+
+        return Application([('/small', SmallHeaders),
+                            ('/large', LargeHeaders)])
+
+    def get_http_client(self):
+        return SimpleAsyncHTTPClient(io_loop=self.io_loop, max_header_size=1024)
+
+    def test_small_headers(self):
+        response = self.fetch('/small')
+        response.rethrow()
+        self.assertEqual(response.body, b'ok')
+
+    def test_large_headers(self):
+        with ExpectLog(gen_log, "Unsatisfiable read"):
+            response = self.fetch('/large')
+        self.assertEqual(response.code, 599)
index 40d04e65a635ba7627e607eafddb694dcd3501ec..66a4fedf9df6c67c67dd7d3edb933b2604ec5a91 100644 (file)
@@ -823,7 +823,7 @@ class WebSocketClientConnection(simple_httpclient._HTTPConnection):
         self.resolver = Resolver(io_loop=io_loop)
         super(WebSocketClientConnection, self).__init__(
             io_loop, None, request, lambda: None, self._on_http_response,
-            104857600, self.resolver)
+            104857600, self.resolver, 65536)
 
     def close(self, code=None, reason=None):
         """Closes the websocket connection.