diff options
| author | Andreas Pelme <andreas@pelme.se> | 2022-11-20 21:46:55 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-12-17 08:46:37 +0100 |
| commit | ab7a85ac297464df82d8363455609979ca3603db (patch) | |
| tree | d22374e4fee09779ce1fffedaac6678325462c33 /django | |
| parent | a1bcdc94da6d597c51b4eca0411a97a6460b482e (diff) | |
Fixed #34170 -- Implemented Heal The Breach (HTB) in GzipMiddleware.
Diffstat (limited to 'django')
| -rw-r--r-- | django/middleware/gzip.py | 12 | ||||
| -rw-r--r-- | django/utils/text.py | 28 |
2 files changed, 34 insertions, 6 deletions
diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py index db8847a2f3..d91246c007 100644 --- a/django/middleware/gzip.py +++ b/django/middleware/gzip.py @@ -13,6 +13,8 @@ class GZipMiddleware(MiddlewareMixin): on the Accept-Encoding header. """ + max_random_bytes = 100 + def process_response(self, request, response): # It's not worth attempting to compress really short responses. if not response.streaming and len(response.content) < 200: @@ -31,11 +33,17 @@ class GZipMiddleware(MiddlewareMixin): if response.streaming: # Delete the `Content-Length` header for streaming content, because # we won't know the compressed size until we stream it. - response.streaming_content = compress_sequence(response.streaming_content) + response.streaming_content = compress_sequence( + response.streaming_content, + max_random_bytes=self.max_random_bytes, + ) del response.headers["Content-Length"] else: # Return the compressed content only if it's actually shorter. - compressed_content = compress_string(response.content) + compressed_content = compress_string( + response.content, + max_random_bytes=self.max_random_bytes, + ) if len(compressed_content) >= len(response.content): return response response.content = compressed_content diff --git a/django/utils/text.py b/django/utils/text.py index dcfe3fba0e..86d3b52741 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -1,4 +1,6 @@ +import gzip import re +import secrets import unicodedata from gzip import GzipFile from gzip import compress as gzip_compress @@ -314,8 +316,23 @@ def phone2numeric(phone): return "".join(char2number.get(c, c) for c in phone.lower()) -def compress_string(s): - return gzip_compress(s, compresslevel=6, mtime=0) +def _get_random_filename(max_random_bytes): + return b"a" * secrets.randbelow(max_random_bytes) + + +def compress_string(s, *, max_random_bytes=None): + compressed_data = gzip_compress(s, compresslevel=6, mtime=0) + + if not max_random_bytes: + return compressed_data + + compressed_view = memoryview(compressed_data) + header = bytearray(compressed_view[:10]) + header[3] = gzip.FNAME + + filename = _get_random_filename(max_random_bytes) + b"\x00" + + return bytes(header) + filename + compressed_view[10:] class StreamingBuffer(BytesIO): @@ -327,9 +344,12 @@ class StreamingBuffer(BytesIO): # Like compress_string, but for iterators of strings. -def compress_sequence(sequence): +def compress_sequence(sequence, *, max_random_bytes=None): buf = StreamingBuffer() - with GzipFile(mode="wb", compresslevel=6, fileobj=buf, mtime=0) as zfile: + filename = _get_random_filename(max_random_bytes) if max_random_bytes else None + with GzipFile( + filename=filename, mode="wb", compresslevel=6, fileobj=buf, mtime=0 + ) as zfile: # Output headers... yield buf.read() for item in sequence: |
