Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/library/zipfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,11 @@ Instances have the following methods and attributes:

Size of the uncompressed file.

.. attribute:: ZipInfo.extended_mtime

Extended modified timestamp of the uncompressed file.

.. versionadded:: 3.14

.. _zipfile-commandline:
.. program:: zipfile
Expand Down
51 changes: 51 additions & 0 deletions Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import array
import contextlib
import datetime
import importlib.util
import io
import itertools
Expand Down Expand Up @@ -1877,6 +1878,54 @@ def test_read_after_write_unicode_filenames(self):
zipfp.writestr('приклад', b'sample')
self.assertEqual(zipfp.read('приклад'), b'sample')

def create_zipfile_with_extended_timestamp_extra_data(self, filename, extra_timestamp_data):
with zipfile.ZipFile(TESTFN, mode='w') as zf:
filename_encoded = filename.encode("utf-8")
zip_info = zipfile.ZipInfo(filename)

tag_for_extra_block = b'\x55\x54'

# Set only modification time
flags = b'\x01'

# modification time
if extra_timestamp_data is not None:
mtime = struct.pack('<l', extra_timestamp_data)
else:
mtime = b''
extra_data = flags + mtime

tsize = len(extra_data).to_bytes(2, 'little')

zip_info.extra = tag_for_extra_block + tsize + extra_data

# add the file to the ZIP archive
zf.writestr(zip_info, b'Hello World!')

def test_read_zipfile_containing_extended_timestamp_extra_field(self):
timestamp_data = int(datetime.datetime(2024, 6, 26, 9, 27, 30).timestamp())
self.create_zipfile_with_extended_timestamp_extra_data("aayush.txt", timestamp_data)
with zipfile.ZipFile(TESTFN, "r") as zf:
self.assertEqual(zf.filelist[0].extended_mtime, timestamp_data)

def test_read_zipfile_containing_timestamp_beyond_2038(self):
# Test for 2038 problem
timestamp_data = -1
self.create_zipfile_with_extended_timestamp_extra_data("aayush.txt", timestamp_data)
with zipfile.ZipFile(TESTFN, "r") as zf:
self.assertEqual(zf.filelist[0].extended_mtime, timestamp_data)

def test_read_zipfile_zero_timestamp_extra_field(self):
timestamp_data = 0
self.create_zipfile_with_extended_timestamp_extra_data("aayush.txt", timestamp_data)
with zipfile.ZipFile(TESTFN, "r") as zf:
self.assertEqual(zf.filelist[0].extended_mtime, timestamp_data)

def test_read_zipfile_none_timestamp_extra_field(self):
self.create_zipfile_with_extended_timestamp_extra_data("aayush.txt", None)
with zipfile.ZipFile(TESTFN, "r") as zf:
self.assertIsNone(zf.filelist[0].extended_mtime)

def test_exclusive_create_zip_file(self):
"""Test exclusive creating a new zipfile."""
unlink(TESTFN2)
Expand Down Expand Up @@ -2227,6 +2276,8 @@ def test_create_empty_zipinfo_default_attributes(self):
self.assertEqual(zi.file_size, 0)
self.assertEqual(zi.compress_size, 0)

self.assertIsNone(zi.extended_mtime)

def test_zipfile_with_short_extra_field(self):
"""If an extra field in the header is less than 4 bytes, skip it."""
zipdata = (
Expand Down
11 changes: 11 additions & 0 deletions Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ class ZipInfo:
'file_size',
'_raw_time',
'_end_offset',
'extended_mtime',
)

def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
Expand Down Expand Up @@ -431,6 +432,7 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
self.compress_size = 0 # Size of the compressed file
self.file_size = 0 # Size of the uncompressed file
self._end_offset = None # Start of the next local header or central directory
self.extended_mtime = None # Extended modified timestamp
# Other attributes are set by class ZipFile:
# header_offset Byte offset to the file header
# CRC CRC-32 of the uncompressed file
Expand Down Expand Up @@ -563,6 +565,15 @@ def _decodeExtra(self, filename_crc):
except UnicodeDecodeError as e:
raise BadZipFile('Corrupt unicode path extra field (0x7075): invalid utf-8 bytes') from e

elif tp == 0x5455:
if ln == 0:
raise BadZipFile('Empty extended timestamp extra field (0x5455)')
flags = extra[4]
if flags & 0x1 and ln >= 5:
try:
self.extended_mtime, = unpack("<l", extra[5:9])
except struct.error as e:
raise BadZipFile("Corrupt extended stamp extra field (0x5455)") from e
extra = extra[ln+4:]

@classmethod
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add attribute :attr:`zipfile.ZipInfo.extended_mtime`.