Skip to content

Commit 6af4e64

Browse files
committed
feat: update code from upstream 3.14.1
1 parent b2f1947 commit 6af4e64

File tree

7 files changed

+206
-51
lines changed

7 files changed

+206
-51
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project
66
adheres to [Semantic Versioning](https://semver.org/).
77

8+
## Unreleased
9+
10+
### :rocket: Added
11+
12+
- Update code with CPython 3.14.1 version
13+
814
## [1.1.0] - 2025-11-23
915

1016
[1.1.0]: https://github.com/rogdham/backports.zstd/releases/tag/v1.1.0

src/python/backports/zstd/tarfile.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize,
364364
fileobj = _StreamProxy(fileobj)
365365
comptype = fileobj.getcomptype()
366366

367-
self.name = name or ""
367+
self.name = os.fspath(name) if name is not None else ""
368368
self.mode = mode
369369
self.comptype = comptype
370370
self.fileobj = fileobj
@@ -2726,6 +2726,9 @@ def makelink_with_filter(self, tarinfo, targetpath,
27262726
return
27272727
else:
27282728
if os.path.exists(tarinfo._link_target):
2729+
if os.path.lexists(targetpath):
2730+
# Avoid FileExistsError on following os.link.
2731+
os.unlink(targetpath)
27292732
os.link(tarinfo._link_target, targetpath)
27302733
return
27312734
except symlink_exception:

src/python/backports/zstd/zipfile/__init__.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def is_zipfile(filename):
262262
else:
263263
with open(filename, "rb") as fp:
264264
result = _check_zipfile(fp)
265-
except OSError:
265+
except (OSError, BadZipFile):
266266
pass
267267
return result
268268

@@ -272,9 +272,6 @@ def _handle_prepended_data(endrec, debug=0):
272272

273273
# "concat" is zero, unless zip was concatenated to another file
274274
concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
275-
if endrec[_ECD_SIGNATURE] == stringEndArchive64:
276-
# If Zip64 extension structures are present, account for them
277-
concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator)
278275

279276
if debug > 2:
280277
inferred = concat + offset_cd
@@ -286,33 +283,49 @@ def _EndRecData64(fpin, offset, endrec):
286283
"""
287284
Read the ZIP64 end-of-archive records and use that to update endrec
288285
"""
289-
try:
290-
fpin.seek(offset - sizeEndCentDir64Locator, 2)
291-
except OSError:
292-
# If the seek fails, the file is not large enough to contain a ZIP64
286+
offset -= sizeEndCentDir64Locator
287+
if offset < 0:
288+
# The file is not large enough to contain a ZIP64
293289
# end-of-archive record, so just return the end record we were given.
294290
return endrec
295-
291+
fpin.seek(offset)
296292
data = fpin.read(sizeEndCentDir64Locator)
297293
if len(data) != sizeEndCentDir64Locator:
298-
return endrec
294+
raise OSError("Unknown I/O error")
299295
sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data)
300296
if sig != stringEndArchive64Locator:
301297
return endrec
302298

303299
if diskno != 0 or disks > 1:
304300
raise BadZipFile("zipfiles that span multiple disks are not supported")
305301

306-
# Assume no 'zip64 extensible data'
307-
fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
302+
offset -= sizeEndCentDir64
303+
if reloff > offset:
304+
raise BadZipFile("Corrupt zip64 end of central directory locator")
305+
# First, check the assumption that there is no prepended data.
306+
fpin.seek(reloff)
307+
extrasz = offset - reloff
308308
data = fpin.read(sizeEndCentDir64)
309309
if len(data) != sizeEndCentDir64:
310-
return endrec
310+
raise OSError("Unknown I/O error")
311+
if not data.startswith(stringEndArchive64) and reloff != offset:
312+
# Since we already have seen the Zip64 EOCD Locator, it's
313+
# possible we got here because there is prepended data.
314+
# Assume no 'zip64 extensible data'
315+
fpin.seek(offset)
316+
extrasz = 0
317+
data = fpin.read(sizeEndCentDir64)
318+
if len(data) != sizeEndCentDir64:
319+
raise OSError("Unknown I/O error")
320+
if not data.startswith(stringEndArchive64):
321+
raise BadZipFile("Zip64 end of central directory record not found")
322+
311323
sig, sz, create_version, read_version, disk_num, disk_dir, \
312324
dircount, dircount2, dirsize, diroffset = \
313325
struct.unpack(structEndArchive64, data)
314-
if sig != stringEndArchive64:
315-
return endrec
326+
if (diroffset + dirsize != reloff or
327+
sz + 12 != sizeEndCentDir64 + extrasz):
328+
raise BadZipFile("Corrupt zip64 end of central directory record")
316329

317330
# Update the original endrec using data from the ZIP64 record
318331
endrec[_ECD_SIGNATURE] = sig
@@ -322,6 +335,7 @@ def _EndRecData64(fpin, offset, endrec):
322335
endrec[_ECD_ENTRIES_TOTAL] = dircount2
323336
endrec[_ECD_SIZE] = dirsize
324337
endrec[_ECD_OFFSET] = diroffset
338+
endrec[_ECD_LOCATION] = offset - extrasz
325339
return endrec
326340

327341

@@ -355,7 +369,7 @@ def _EndRecData(fpin):
355369
endrec.append(filesize - sizeEndCentDir)
356370

357371
# Try to read the "Zip64 end of central directory" structure
358-
return _EndRecData64(fpin, -sizeEndCentDir, endrec)
372+
return _EndRecData64(fpin, filesize - sizeEndCentDir, endrec)
359373

360374
# Either this is not a ZIP file, or it is a ZIP file with an archive
361375
# comment. Search the end of the file for the "end of central directory"
@@ -379,8 +393,7 @@ def _EndRecData(fpin):
379393
endrec.append(maxCommentStart + start)
380394

381395
# Try to read the "Zip64 end of central directory" structure
382-
return _EndRecData64(fpin, maxCommentStart + start - filesize,
383-
endrec)
396+
return _EndRecData64(fpin, maxCommentStart + start, endrec)
384397

385398
# Unable to find a valid end of central directory structure
386399
return None
@@ -2142,7 +2155,7 @@ def _write_end_record(self):
21422155
" would require ZIP64 extensions")
21432156
zip64endrec = struct.pack(
21442157
structEndArchive64, stringEndArchive64,
2145-
44, 45, 45, 0, 0, centDirCount, centDirCount,
2158+
sizeEndCentDir64 - 12, 45, 45, 0, 0, centDirCount, centDirCount,
21462159
centDirSize, centDirOffset)
21472160
self.fp.write(zip64endrec)
21482161

tests/test/support/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"BrokenIter",
6969
"in_systemd_nspawn_sync_suppressed",
7070
"run_no_yield_async_fn", "run_yielding_async_fn", "async_yield",
71-
"reset_code",
71+
"reset_code", "on_github_actions"
7272
]
7373

7474

@@ -1366,6 +1366,7 @@ def reset_code(f: types.FunctionType) -> types.FunctionType:
13661366
f.__code__ = f.__code__.replace()
13671367
return f
13681368

1369+
on_github_actions = "GITHUB_ACTIONS" in os.environ
13691370

13701371
#=======================================================================
13711372
# Check for the presence of docstrings.

tests/test/test_tarfile.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,57 @@ def test_next_on_empty_tarfile(self):
854854
with tarfile.open(fileobj=fd, mode="r") as tf:
855855
self.assertEqual(tf.next(), None)
856856

857+
def _setup_symlink_to_target(self, temp_dirpath):
858+
target_filepath = os.path.join(temp_dirpath, "target")
859+
ustar_dirpath = os.path.join(temp_dirpath, "ustar")
860+
hardlink_filepath = os.path.join(ustar_dirpath, "lnktype")
861+
with open(target_filepath, "wb") as f:
862+
f.write(b"target")
863+
os.makedirs(ustar_dirpath)
864+
os.symlink(target_filepath, hardlink_filepath)
865+
return target_filepath, hardlink_filepath
866+
867+
def _assert_on_file_content(self, filepath, digest):
868+
with open(filepath, "rb") as f:
869+
data = f.read()
870+
self.assertEqual(sha256sum(data), digest)
871+
872+
@unittest.skipUnless(
873+
hasattr(os, "link"), "Missing hardlink implementation"
874+
)
875+
@os_helper.skip_unless_symlink
876+
def test_extract_hardlink_on_symlink(self):
877+
"""
878+
This test verifies that extracting a hardlink will not follow an
879+
existing symlink after a FileExistsError on os.link.
880+
"""
881+
with os_helper.temp_dir() as DIR:
882+
target_filepath, hardlink_filepath = self._setup_symlink_to_target(DIR)
883+
with tarfile.open(tarname, encoding="iso8859-1") as tar:
884+
tar.extract("ustar/regtype", DIR, filter="data")
885+
tar.extract("ustar/lnktype", DIR, filter="data")
886+
self._assert_on_file_content(target_filepath, sha256sum(b"target"))
887+
self._assert_on_file_content(hardlink_filepath, sha256_regtype)
888+
889+
@unittest.skipUnless(
890+
hasattr(os, "link"), "Missing hardlink implementation"
891+
)
892+
@os_helper.skip_unless_symlink
893+
def test_extractall_hardlink_on_symlink(self):
894+
"""
895+
This test verifies that extracting a hardlink will not follow an
896+
existing symlink after a FileExistsError on os.link.
897+
"""
898+
with os_helper.temp_dir() as DIR:
899+
target_filepath, hardlink_filepath = self._setup_symlink_to_target(DIR)
900+
with tarfile.open(tarname, encoding="iso8859-1") as tar:
901+
tar.extractall(
902+
DIR, members=["ustar/regtype", "ustar/lnktype"], filter="data",
903+
)
904+
self._assert_on_file_content(target_filepath, sha256sum(b"target"))
905+
self._assert_on_file_content(hardlink_filepath, sha256_regtype)
906+
907+
857908
class MiscReadTest(MiscReadTestBase, unittest.TestCase):
858909
test_fail_comp = None
859910

@@ -1750,6 +1801,16 @@ def test_file_mode(self):
17501801
finally:
17511802
os.umask(original_umask)
17521803

1804+
def test_pathlike_name(self):
1805+
expected_name = os.path.abspath(tmpname)
1806+
tarpath = os_helper.FakePath(tmpname)
1807+
1808+
for func in (tarfile.open, tarfile.TarFile.open):
1809+
with self.subTest():
1810+
with func(tarpath, self.mode) as tar:
1811+
self.assertEqual(tar.name, expected_name)
1812+
os_helper.unlink(tmpname)
1813+
17531814

17541815
class GzipStreamWriteTest(GzipTest, StreamWriteTest):
17551816
def test_source_directory_not_leaked(self):

tests/test/test_zipfile/_path/test_path.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def test_pathlike_construction(self, alpharep):
274274
zipfile_ondisk = self.zipfile_ondisk(alpharep)
275275
pathlike = FakePath(str(zipfile_ondisk))
276276
root = zipfile.Path(pathlike)
277-
root.root.close() # gh-137589
277+
root.root.close()
278278

279279
@pass_alpharep
280280
def test_traverse_pathlike(self, alpharep):
@@ -373,7 +373,7 @@ def test_root_on_disk(self, alpharep):
373373
root = zipfile.Path(self.zipfile_ondisk(alpharep))
374374
assert root.name == 'alpharep.zip' == root.filename.name
375375
assert root.stem == 'alpharep' == root.filename.stem
376-
root.root.close() # gh-137589
376+
root.root.close()
377377

378378
@pass_alpharep
379379
def test_suffix(self, alpharep):
@@ -577,11 +577,11 @@ def test_pickle(self, alpharep, path_type, subpath):
577577
zipfile_ondisk = path_type(str(self.zipfile_ondisk(alpharep)))
578578
root = zipfile.Path(zipfile_ondisk, at=subpath)
579579
saved_1 = pickle.dumps(root)
580-
root.root.close() # gh-137589
580+
root.root.close()
581581
restored_1 = pickle.loads(saved_1)
582582
first, *rest = restored_1.iterdir()
583583
assert first.read_text(encoding='utf-8').startswith('content of ')
584-
restored_1.root.close() # gh-137589
584+
restored_1.root.close()
585585

586586
@pass_alpharep
587587
def test_extract_orig_with_implied_dirs(self, alpharep):
@@ -593,7 +593,7 @@ def test_extract_orig_with_implied_dirs(self, alpharep):
593593
# wrap the zipfile for its side effect
594594
zipfile.Path(zf)
595595
zf.extractall(source_path.parent)
596-
zf.close() # gh-137589
596+
zf.close()
597597

598598
@pass_alpharep
599599
def test_getinfo_missing(self, alpharep):

0 commit comments

Comments
 (0)