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).
This commit is contained in:
Kevin Christopher Henry 2016-10-14 07:41:42 -04:00 committed by Tim Graham
parent 9eb49af821
commit 9108696a75
2 changed files with 22 additions and 2 deletions

View File

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

View File

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