From 028f4a9f835fecf5800d7215b29ee621c4934081 Mon Sep 17 00:00:00 2001 From: ptweezy Date: Tue, 27 Oct 2026 20:25:52 -0400 Subject: [PATCH] Fix the mtime of IGzipFile for a modification time of 0 The getter of _last_mtime returned None when the stored time was 0, so a gzip header with a modification time of 0 read as None. Keep a separate flag that is set when the first header is read. --- CHANGELOG.rst | 5 +++++ src/isal/isal_zlibmodule.c | 7 ++++++- tests/test_igzip.py | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa72ae40..6cce2656 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -119,6 +119,11 @@ version 1.8.1-dev + Fix ``readinto()`` of the private ``isal_zlib._GzipReader`` class, which wrote into read-only objects such as ``bytes``. It raises ``TypeError`` now. ``igzip.IGzipFile`` was not affected. ++ Fix the ``mtime`` attribute of ``igzip.IGzipFile`` objects in read mode, + which was ``None`` for gzip files with a modification time of 0. It is 0 now, + as in the ``gzip`` module. ``gzip -n``, ``python -m isal.igzip -n``, and + ``igzip_threaded`` writers make such files, and so does ``gzip.compress`` by + default on Python 3.14. + Fix a crash when there is not enough memory to create an ``igzip_lib.IgzipDecompressor``. It raises ``MemoryError`` now. + Fix a crash when there is not enough memory to raise ``BadGzipFile`` for diff --git a/src/isal/isal_zlibmodule.c b/src/isal/isal_zlibmodule.c index bc992ddd..3ee12d83 100644 --- a/src/isal/isal_zlibmodule.c +++ b/src/isal/isal_zlibmodule.c @@ -1611,6 +1611,9 @@ typedef struct _GzipReaderStruct { char stream_phase; char all_bytes_read; char closed; + /* Set when the first gzip header is read. A modification time of 0 is + valid, so _last_mtime cannot tell whether a header was read. */ + char has_last_mtime; uint32_t _last_mtime; PyThread_type_lock lock; struct inflate_state state; @@ -1709,6 +1712,7 @@ GzipReader__new__(PyTypeObject *type, PyObject *args, PyObject *kwargs) self->fp = fp; self->stream_phase = GzipReader_HEADER; self->closed = 0; + self->has_last_mtime = 0; self->_last_mtime = 0; self->lock = PyThread_allocate_lock(); if (self->lock == NULL) { @@ -1869,6 +1873,7 @@ GzipReader_read_into_buffer(GzipReader *self, uint8_t *out_buffer, size_t out_bu } uint8_t flags = current_pos[3]; self->_last_mtime = load_u32_le(current_pos + 4); + self->has_last_mtime = 1; // Skip XFL and header flag uint8_t *header_cursor = current_pos + 10; if (flags & FEXTRA) { @@ -2280,7 +2285,7 @@ GzipReader_flush(GzipReader *self, PyObject *Py_UNUSED(ignore)) { static PyObject * GzipReader_get_last_mtime(GzipReader *self, void *Py_UNUSED(closure)) { - if (self->_last_mtime) { + if (self->has_last_mtime) { return PyLong_FromUnsignedLong(self->_last_mtime); } Py_RETURN_NONE; diff --git a/tests/test_igzip.py b/tests/test_igzip.py index b31adb85..70826210 100644 --- a/tests/test_igzip.py +++ b/tests/test_igzip.py @@ -80,6 +80,15 @@ def test_igzip_reader_read_zero(): assert test.read(0) == b"" +@pytest.mark.parametrize("mtime", [0, 1, 2**32 - 1]) +def test_igzipfile_mtime(mtime): + data = gzip.compress(DATA, mtime=mtime) + with igzip.IGzipFile(fileobj=io.BytesIO(data)) as test: + assert test.mtime is None + assert test.read() == DATA + assert test.mtime == mtime + + @pytest.mark.parametrize(["method", "args"], [ ("read", ("x",)), ("read", (1, 2)),