Skip to content

Commit 1daad8a

Browse files
gh-133998: Fix gzip file creation when time is out of range (GH-134278)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent f159419 commit 1daad8a

4 files changed

Lines changed: 41 additions & 5 deletions

File tree

Doc/library/gzip.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,13 @@ The module defines the following items:
108108
is no compression. The default is ``9``.
109109

110110
The optional *mtime* argument is the timestamp requested by gzip. The time
111-
is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970.
112-
If *mtime* is omitted or ``None``, the current time is used. Use *mtime* = 0
113-
to generate a compressed stream that does not depend on creation time.
111+
is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. Set
112+
*mtime* to ``0`` to generate a compressed stream that does not depend on
113+
creation time. If *mtime* is omitted or ``None``, the current time is used;
114+
however, if the current time is outside the range 00:00:00 UTC, January 1,
115+
1970 through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
116+
argument is outside the range ``0`` to ``2**32-1``, then the value ``0``
117+
is used instead.
114118

115119
See below for the :attr:`mtime` attribute that is set when decompressing.
116120

Lib/gzip.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,10 @@ def __init__(self, filename=None, mode=None,
188188
189189
The optional mtime argument is the timestamp requested by gzip. The time
190190
is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970.
191-
If mtime is omitted or None, the current time is used. Use mtime = 0
192-
to generate a compressed stream that does not depend on creation time.
191+
Set mtime to 0 to generate a compressed stream that does not depend on
192+
creation time. If mtime is omitted or None, the current time is used.
193+
If the resulting mtime is outside the range 0 to 2**32-1, then the
194+
value 0 is used instead.
193195
194196
"""
195197

@@ -295,6 +297,8 @@ def _write_gzip_header(self, compresslevel):
295297
mtime = self._write_mtime
296298
if mtime is None:
297299
mtime = time.time()
300+
if not 0 <= mtime < 2**32:
301+
mtime = 0
298302
write32u(self.fileobj, int(mtime))
299303
if compresslevel == _COMPRESS_LEVEL_BEST:
300304
xfl = b'\002'
@@ -663,6 +667,8 @@ def compress(data, compresslevel=_COMPRESS_LEVEL_TRADEOFF, *, mtime=0):
663667
gzip_data = zlib.compress(data, level=compresslevel, wbits=31)
664668
if mtime is None:
665669
mtime = time.time()
670+
if not 0 <= mtime < 2**32:
671+
mtime = 0
666672
# Reuse gzip header created by zlib, replace mtime and OS byte for
667673
# consistency.
668674
header = struct.pack("<4sLBB", gzip_data, int(mtime), gzip_data[8], 255)

Lib/test/test_gzip.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
import unittest
1212
from subprocess import PIPE, Popen
13+
from unittest import mock
1314
from test.support import catch_unraisable_exception
1415
from test.support import force_not_colorized_test_class, import_helper
1516
from test.support import os_helper
@@ -350,6 +351,26 @@ def test_mtime(self):
350351
self.assertEqual(dataRead, data1)
351352
self.assertEqual(fRead.mtime, mtime)
352353

354+
def test_mtime_out_of_range(self):
355+
for mtime in (-1, 2**32):
356+
with gzip.GzipFile(self.filename, 'w', mtime=mtime) as fWrite:
357+
fWrite.write(data1)
358+
with gzip.GzipFile(self.filename) as fRead:
359+
fRead.read(1)
360+
self.assertEqual(fRead.mtime, 0)
361+
datac = gzip.compress(data1, mtime=mtime)
362+
with gzip.GzipFile(fileobj=io.BytesIO(datac)) as fRead:
363+
fRead.read(1)
364+
self.assertEqual(fRead.mtime, 0)
365+
366+
for mtime in (-1, 2**32):
367+
with mock.patch('time.time', return_value=float(mtime)):
368+
with gzip.GzipFile(self.filename, 'w') as fWrite:
369+
fWrite.write(data1)
370+
with gzip.GzipFile(self.filename) as fRead:
371+
fRead.read(1)
372+
self.assertEqual(fRead.mtime, 0)
373+
353374
def test_metadata(self):
354375
mtime = 123456789
355376

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :exc:`struct.error` exception when creating a file with
2+
:class:`gzip.GzipFile` or compressing data with :func:`gzip.compress`
3+
if the system time is outside the range 00:00:00 UTC, January 1, 1970
4+
through 06:28:15 UTC, February 7, 2106, or explicitly passed *mtime*
5+
argument is outside the range ``0`` to ``2**32-1``.

0 commit comments

Comments
 (0)