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:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							a1bcdc94da
						
					
				
				
					commit
					ab7a85ac29
				
			| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user