From 64681af6c69ec52a76ef64877ae56851ccab5f8e Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Mon, 19 May 2025 15:37:47 -0400 Subject: [PATCH 1/7] gh-133998: Fix gzip file creation when time is out of range --- Doc/library/gzip.rst | 9 ++++++--- Lib/gzip.py | 2 ++ Lib/test/test_gzip.py | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst index c9d96085ef739d..664ef86eab5963 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*. + Use *mtime* = ``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..85742dfbabd66c 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -297,6 +297,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..58eca7b1fa439b 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,27 @@ def test_mtime(self): self.assertEqual(dataRead, data1) self.assertEqual(fRead.mtime, mtime) + def test_mtime_out_of_range(self): + # exception should be raised when mtime<0 or mtime>=2**32 and is + # explicitly specified + with self.assertRaises(Exception): + with gzip.GzipFile(self.filename, 'w', mtime=-1) as fWrite: + pass + with self.assertRaises(Exception): + 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 From 3241cf27b0ffae5843687d088f174c215603ab0f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 20:29:36 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst 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..518c3f7021496f --- /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*. From 79d8a72dd72651ba110d3c0bfa59f902e55b18fa Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Mon, 19 May 2025 16:51:23 -0400 Subject: [PATCH 3/7] Remove extra newline --- Lib/test/test_gzip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index 58eca7b1fa439b..8b0afae049c530 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -372,7 +372,6 @@ def test_mtime_out_of_range(self): fRead.read() self.assertEqual(fRead.mtime, 0) - def test_metadata(self): mtime = 123456789 From e701310c970ec9e723cc173f0001f34fc657e90f Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Wed, 21 May 2025 10:59:05 -0400 Subject: [PATCH 4/7] Address review comments regarding documentation --- Doc/library/gzip.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/gzip.rst b/Doc/library/gzip.rst index 664ef86eab5963..4bf2a592cbd936 100644 --- a/Doc/library/gzip.rst +++ b/Doc/library/gzip.rst @@ -101,12 +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*. - Use *mtime* = ``0`` to generate a compressed stream that does not depend on + 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. + 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. From 90a881b888f7939fc8d9655285b44764ef6b861f Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Wed, 21 May 2025 11:12:18 -0400 Subject: [PATCH 5/7] Keep docstring in line with documentation --- Lib/gzip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/gzip.py b/Lib/gzip.py index 85742dfbabd66c..06d6d7277a6865 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. """ From 9638ca0fe5339ba2d7611d26e56e936f4ae4ba7e Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Wed, 21 May 2025 11:15:54 -0400 Subject: [PATCH 6/7] Improve error message when mtime is out of range --- Lib/gzip.py | 3 +++ Lib/test/test_gzip.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/gzip.py b/Lib/gzip.py index 06d6d7277a6865..5285354d8c6e7a 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -206,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') diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index 8b0afae049c530..0882c823d57410 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -353,12 +353,12 @@ def test_mtime(self): self.assertEqual(fRead.mtime, mtime) def test_mtime_out_of_range(self): - # exception should be raised when mtime<0 or mtime>=2**32 and is + # ValueError should be raised when mtime<0 or mtime>=2**32 and is # explicitly specified - with self.assertRaises(Exception): + with self.assertRaises(ValueError): with gzip.GzipFile(self.filename, 'w', mtime=-1) as fWrite: pass - with self.assertRaises(Exception): + with self.assertRaises(ValueError): with gzip.GzipFile(self.filename, 'w', mtime=2**32) as fWrite: pass From 20da98ae35facbbd12d7e567b413ed0217fda7ce Mon Sep 17 00:00:00 2001 From: Aohan Dang Date: Wed, 21 May 2025 12:03:22 -0400 Subject: [PATCH 7/7] Re-format changelog message --- .../next/Library/2025-05-19-20-29-35.gh-issue-133998.KmElUw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 518c3f7021496f..37177cdcb5e4b8 100644 --- 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 @@ -1 +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*. +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.