diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst index c9d96085ef739d..4bf2a592cbd936 100644 --- a/Doc/library/gzip.rst +++ b/Doc/library/gzip.rst @@ -101,9 +101,12 @@ The module defines the following items: is no compression. The default is ``9``. The optional *mtime* argument is the timestamp requested by gzip. The time - is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. - If *mtime* is omitted or ``None``, the current time is used. Use *mtime* = 0 - to generate a compressed stream that does not depend on creation time. + is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. Set + *mtime* to ``0`` to generate a compressed stream that does not depend on + creation time. If *mtime* is omitted or ``None``, the current time is used; + however, if the current time is outside the range 00:00:00 UTC, January 1, + 1970 through 06:28:15 UTC, February 7, 2106, then the value ``0`` is used + instead. See below for the :attr:`mtime` attribute that is set when decompressing. diff --git a/Lib/gzip.py b/Lib/gzip.py index c00f51858de0f0..5285354d8c6e7a 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -188,8 +188,11 @@ def __init__(self, filename=None, mode=None, The optional mtime argument is the timestamp requested by gzip. The time is in Unix format, i.e., seconds since 00:00:00 UTC, January 1, 1970. - If mtime is omitted or None, the current time is used. Use mtime = 0 - to generate a compressed stream that does not depend on creation time. + Set mtime to 0 to generate a compressed stream that does not depend on + creation time. If mtime is omitted or None, the current time is used; + however, if the current time is outside the range 00:00:00 UTC, January + 1, 1970 through 06:28:15 UTC, February 7, 2106, then the value 0 is used + instead. """ @@ -203,6 +206,9 @@ def __init__(self, filename=None, mode=None, if mode and 'b' not in mode: mode += 'b' + if mtime is not None and (mtime < 0 or mtime >= 2**32): + raise ValueError(f'mtime must be in the range 0 through {2**32-1}') + try: if fileobj is None: fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') @@ -297,6 +303,8 @@ def _write_gzip_header(self, compresslevel): mtime = self._write_mtime if mtime is None: mtime = time.time() + if mtime < 0 or mtime >= 2**32: + mtime = 0 write32u(self.fileobj, int(mtime)) if compresslevel == _COMPRESS_LEVEL_BEST: xfl = b'\002' diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index fa5de7c190e6a3..0882c823d57410 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -11,6 +11,7 @@ import unittest import warnings from subprocess import PIPE, Popen +from unittest import mock from test.support import catch_unraisable_exception from test.support import import_helper from test.support import os_helper @@ -351,6 +352,26 @@ def test_mtime(self): self.assertEqual(dataRead, data1) self.assertEqual(fRead.mtime, mtime) + def test_mtime_out_of_range(self): + # ValueError should be raised when mtime<0 or mtime>=2**32 and is + # explicitly specified + with self.assertRaises(ValueError): + with gzip.GzipFile(self.filename, 'w', mtime=-1) as fWrite: + pass + with self.assertRaises(ValueError): + with gzip.GzipFile(self.filename, 'w', mtime=2**32) as fWrite: + pass + + # mtime should be set to 0 when time.time() is out of range and mtime is + # not explicitly given + for mtime in (-1, 2**32): + with mock.patch('time.time', return_value=float(mtime)): + with gzip.GzipFile(self.filename, 'w') as fWrite: + fWrite.write(data1) + with gzip.GzipFile(self.filename) as fRead: + fRead.read() + self.assertEqual(fRead.mtime, 0) + def test_metadata(self): mtime = 123456789 diff --git a/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst new file mode 100644 index 00000000000000..37177cdcb5e4b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst @@ -0,0 +1 @@ +Fix ``struct.error`` exception when creating a file with ``gzip.GzipFile()`` if the system time is outside the range 00:00:00 UTC, January 1, 1970 through 06:28:15 UTC, February 7, 2106.