1
0
mirror of https://github.com/django/django.git synced 2025-10-31 09:41:08 +00:00

Fixed #34170 -- Implemented Heal The Breach (HTB) in GzipMiddleware.

This commit is contained in:
Andreas Pelme
2022-11-20 21:46:55 +01:00
committed by Mariusz Felisiak
parent a1bcdc94da
commit ab7a85ac29
5 changed files with 106 additions and 17 deletions

View File

@@ -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

View File

@@ -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: