From: Ben Darnell Date: Tue, 26 May 2026 17:39:53 +0000 (-0400) Subject: http1connection: Enforce max_body_size in _GzipMessageDelegate X-Git-Tag: v6.5.6^2~4 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=ff808b33adc52d89a549376a5e3628e92abbc8ff;p=thirdparty%2Ftornado.git http1connection: Enforce max_body_size in _GzipMessageDelegate This ensures we limit the post-decompression size of the body, and not only the compressed size (which is enforced via the Content-Length header at header-processing time). --- diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 5b0dd1b8..3eb1dfc2 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -182,7 +182,9 @@ class HTTP1Connection(httputil.HTTPConnection): been read. The result is true if the stream is still open. """ if self.params.decompress: - delegate = _GzipMessageDelegate(delegate, self.params.chunk_size) + delegate = _GzipMessageDelegate( + delegate, self.params.chunk_size, self._max_body_size + ) return self._read_message(delegate) async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool: @@ -705,9 +707,16 @@ class HTTP1Connection(httputil.HTTPConnection): class _GzipMessageDelegate(httputil.HTTPMessageDelegate): """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.""" - def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None: + def __init__( + self, + delegate: httputil.HTTPMessageDelegate, + chunk_size: int, + max_body_size: int, + ) -> None: self._delegate = delegate self._chunk_size = chunk_size + self._max_body_size = max_body_size + self._decompressed_body_size = 0 self._decompressor = None # type: Optional[GzipDecompressor] def headers_received( @@ -732,6 +741,9 @@ class _GzipMessageDelegate(httputil.HTTPMessageDelegate): compressed_data, self._chunk_size ) if decompressed: + self._decompressed_body_size += len(decompressed) + if self._decompressed_body_size > self._max_body_size: + raise httputil.HTTPInputError("decompressed body too large") ret = self._delegate.data_received(decompressed) if ret is not None: await ret diff --git a/tornado/test/httpserver_test.py b/tornado/test/httpserver_test.py index 008078ed..b3f6ed18 100644 --- a/tornado/test/httpserver_test.py +++ b/tornado/test/httpserver_test.py @@ -1133,7 +1133,7 @@ class GzipBaseTest(AsyncHTTPTestCase): class GzipTest(GzipBaseTest, AsyncHTTPTestCase): def get_httpserver_options(self): - return dict(decompress_request=True) + return dict(decompress_request=True, max_body_size=100) def test_gzip(self): response = self.post_gzip("foo=bar") @@ -1154,6 +1154,10 @@ class GzipTest(GzipBaseTest, AsyncHTTPTestCase): ) self.assertEqual(json_decode(response.body), {"foo": ["bar"]}) + def test_size_limit(self): + with ExpectLog(gen_log, ".*decompressed body too large", level=logging.INFO): + self.post_gzip("x" * 101) + class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase): def test_gzip_unsupported(self):