From 9108696a7553123f57c5d42f9c4a90cad44532f4 Mon Sep 17 00:00:00 2001 From: Kevin Christopher Henry Date: Fri, 14 Oct 2016 07:41:42 -0400 Subject: [PATCH] Refs #19705 -- Changed gzip modification times to 0. This makes gzip output deterministic, which allows ConditionalGetMiddleware to reliably compare ETags on gzipped content (views using the gzip_page() decorator in particular). --- django/utils/text.py | 4 ++-- tests/middleware/tests.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index a77f27eed7..ec7af5389c 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -293,7 +293,7 @@ def phone2numeric(phone): # Used with permission. def compress_string(s): zbuf = BytesIO() - with GzipFile(mode='wb', compresslevel=6, fileobj=zbuf) as zfile: + with GzipFile(mode='wb', compresslevel=6, fileobj=zbuf, mtime=0) as zfile: zfile.write(s) return zbuf.getvalue() @@ -322,7 +322,7 @@ class StreamingBuffer(object): # Like compress_string, but for iterators of strings. def compress_sequence(sequence): buf = StreamingBuffer() - with GzipFile(mode='wb', compresslevel=6, fileobj=buf) as zfile: + with GzipFile(mode='wb', compresslevel=6, fileobj=buf, mtime=0) as zfile: # Output headers... yield buf.read() for item in sequence: diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index db8ed6ea90..12109b3137 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -770,6 +770,12 @@ class GZipMiddlewareTest(SimpleTestCase): with gzip.GzipFile(mode='rb', fileobj=BytesIO(gzipped_string)) as f: return f.read() + @staticmethod + def get_mtime(gzipped_string): + with gzip.GzipFile(mode='rb', fileobj=BytesIO(gzipped_string)) as f: + f.read() # must read the data before accessing the header + return f.mtime + def test_compress_response(self): """ Compression is performed on responses with compressible content. @@ -850,6 +856,20 @@ class GZipMiddlewareTest(SimpleTestCase): self.assertEqual(r.content, self.incompressible_string) self.assertIsNone(r.get('Content-Encoding')) + def test_compress_deterministic(self): + """ + Compression results are the same for the same content and don't + include a modification time (since that would make the results + of compression non-deterministic and prevent + ConditionalGetMiddleware from recognizing conditional matches + on gzipped content). + """ + r1 = GZipMiddleware().process_response(self.req, self.resp) + r2 = GZipMiddleware().process_response(self.req, self.resp) + self.assertEqual(r1.content, r2.content) + self.assertEqual(self.get_mtime(r1.content), 0) + self.assertEqual(self.get_mtime(r2.content), 0) + @ignore_warnings(category=RemovedInDjango21Warning) @override_settings(USE_ETAGS=True)