Skip to content

Commit 0d39086

Browse files
committed
feat: update code from upstream 3.14.5
1 parent 01db30f commit 0d39086

8 files changed

Lines changed: 113 additions & 49 deletions

File tree

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.5 version
13+
814
## [1.4.0] - 2026-05-03
915

1016
[1.4.0]: https://github.com/rogdham/backports.zstd/releases/tag/v1.4.0

src/c/compression_zstd/compressor.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ zstd_contentsize_converter(PyObject *size, unsigned long long *p)
7676
if (PyErr_ExceptionMatches(PyExc_OverflowError)) {
7777
PyErr_Format(PyExc_ValueError,
7878
"size argument should be a positive int less "
79-
"than %ull", ZSTD_CONTENTSIZE_ERROR);
79+
"than %llu", ZSTD_CONTENTSIZE_ERROR);
8080
return 0;
8181
}
8282
return 0;
@@ -85,7 +85,7 @@ zstd_contentsize_converter(PyObject *size, unsigned long long *p)
8585
*p = ZSTD_CONTENTSIZE_ERROR;
8686
PyErr_Format(PyExc_ValueError,
8787
"size argument should be a positive int less "
88-
"than %ull", ZSTD_CONTENTSIZE_ERROR);
88+
"than %llu", ZSTD_CONTENTSIZE_ERROR);
8989
return 0;
9090
}
9191
*p = pledged_size;

src/c/compression_zstd/decompressor.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ _zstd_set_d_parameters(ZstdDecompressor *self, PyObject *options)
113113
int key_v = PyLong_AsInt(key);
114114
Py_DECREF(key);
115115
if (key_v == -1 && PyErr_Occurred()) {
116+
Py_DECREF(value);
116117
return -1;
117118
}
118119

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os
2+
import sys
3+
4+
if sys.version_info >= (3, 12):
5+
os_path_splitroot = os.path.splitroot
6+
7+
elif os.name == 'posix':
8+
# from Lib/posixpath.py
9+
def os_path_splitroot(p):
10+
"""Split a pathname into drive, root and tail.
11+
12+
The tail contains anything after the root."""
13+
p = os.fspath(p)
14+
if isinstance(p, bytes):
15+
sep = b'/'
16+
empty = b''
17+
else:
18+
sep = '/'
19+
empty = ''
20+
if p[:1] != sep:
21+
# Relative path, e.g.: 'foo'
22+
return empty, empty, p
23+
elif p[1:2] != sep or p[2:3] == sep:
24+
# Absolute path, e.g.: '/foo', '///foo', '////foo', etc.
25+
return empty, sep, p[1:]
26+
else:
27+
# Precisely two leading slashes, e.g.: '//foo'. Implementation defined per POSIX, see
28+
# https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
29+
return empty, p[:2], p[2:]
30+
31+
elif os.name == 'nt':
32+
# from Lib/ntpath.py
33+
def os_path_splitroot(p):
34+
"""Split a pathname into drive, root and tail.
35+
36+
The tail contains anything after the root."""
37+
p = os.fspath(p)
38+
if isinstance(p, bytes):
39+
sep = b'\\'
40+
altsep = b'/'
41+
colon = b':'
42+
unc_prefix = b'\\\\?\\UNC\\'
43+
empty = b''
44+
else:
45+
sep = '\\'
46+
altsep = '/'
47+
colon = ':'
48+
unc_prefix = '\\\\?\\UNC\\'
49+
empty = ''
50+
normp = p.replace(altsep, sep)
51+
if normp[:1] == sep:
52+
if normp[1:2] == sep:
53+
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
54+
# Device drives, e.g. \\.\device or \\?\device
55+
start = 8 if normp[:8].upper() == unc_prefix else 2
56+
index = normp.find(sep, start)
57+
if index == -1:
58+
return p, empty, empty
59+
index2 = normp.find(sep, index + 1)
60+
if index2 == -1:
61+
return p, empty, empty
62+
return p[:index2], p[index2:index2 + 1], p[index2 + 1:]
63+
else:
64+
# Relative path with root, e.g. \Windows
65+
return empty, p[:1], p[1:]
66+
elif normp[1:2] == colon:
67+
if normp[2:3] == sep:
68+
# Absolute drive-letter path, e.g. X:\Windows
69+
return p[:2], p[2:3], p[3:]
70+
else:
71+
# Relative path with drive, e.g. X:Windows
72+
return p[:2], empty, p[2:]
73+
else:
74+
# Relative path, e.g. Windows
75+
return empty, empty, p
76+
77+
else:
78+
raise ImportError('no os specific module found')

src/python/backports/zstd/_shutil.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,9 @@ def _unpack_zipfile(filename, extract_dir):
119119
if not zipfile.is_zipfile(filename):
120120
raise ReadError("%s is not a zip file" % filename)
121121

122-
zip = zipfile.ZipFile(filename)
123-
try:
124-
for info in zip.infolist():
125-
name = info.filename
126-
127-
# don't extract absolute paths or ones with .. in them
128-
if name.startswith('/') or '..' in name:
129-
continue
130-
131-
targetpath = os.path.join(extract_dir, *name.split('/'))
132-
if not targetpath:
133-
continue
134-
135-
_ensure_directory(targetpath)
136-
if not name.endswith('/'):
137-
# file
138-
with zip.open(name, 'r') as source, \
139-
open(targetpath, 'wb') as target:
140-
copyfileobj(source, target)
141-
finally:
142-
zip.close()
122+
with zipfile.ZipFile(filename) as zip:
123+
zip._ignore_invalid_names = True
124+
zip.extractall(extract_dir)
143125

144126
def _unpack_tarfile(filename, extract_dir, *, filter=None):
145127
"""Unpack tar/tar.gz/tar.bz2/tar.xz/tar.zst `filename` to `extract_dir`

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
lzma = None
3333

3434
from backports import zstd
35+
from backports.zstd._compat import os_path_splitroot
3536

3637
__all__ = ["BadZipFile", "BadZipfile", "error",
3738
"ZIP_STORED", "ZIP_DEFLATED", "ZIP_BZIP2", "ZIP_LZMA",
@@ -1405,6 +1406,7 @@ class ZipFile:
14051406

14061407
fp = None # Set here since __del__ checks it
14071408
_windows_illegal_name_trans_table = None
1409+
_ignore_invalid_names = False
14081410

14091411
def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
14101412
compresslevel=None, *, strict_timestamps=True, metadata_encoding=None):
@@ -1890,21 +1892,31 @@ def _extract_member(self, member, targetpath, pwd):
18901892

18911893
# build the destination pathname, replacing
18921894
# forward slashes to platform specific separators.
1893-
arcname = member.filename.replace('/', os.path.sep)
1894-
1895-
if os.path.altsep:
1895+
arcname = member.filename
1896+
if os.path.sep != '/':
1897+
arcname = arcname.replace('/', os.path.sep)
1898+
if os.path.altsep and os.path.altsep != '/':
18961899
arcname = arcname.replace(os.path.altsep, os.path.sep)
18971900
# interpret absolute pathname as relative, remove drive letter or
18981901
# UNC path, redundant separators, "." and ".." components.
1899-
arcname = os.path.splitdrive(arcname)[1]
1902+
drive, root, arcname = os_path_splitroot(arcname)
1903+
if self._ignore_invalid_names and (drive or root):
1904+
return None
1905+
if self._ignore_invalid_names and os.path.pardir in arcname.split(os.path.sep):
1906+
return None
19001907
invalid_path_parts = ('', os.path.curdir, os.path.pardir)
19011908
arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
19021909
if x not in invalid_path_parts)
19031910
if os.path.sep == '\\':
19041911
# filter illegal characters on Windows
1905-
arcname = self._sanitize_windows_name(arcname, os.path.sep)
1912+
arcname2 = self._sanitize_windows_name(arcname, os.path.sep)
1913+
if self._ignore_invalid_names and arcname2 != arcname:
1914+
return None
1915+
arcname = arcname2
19061916

19071917
if not arcname and not member.is_dir():
1918+
if self._ignore_invalid_names:
1919+
return None
19081920
raise ValueError("Empty filename.")
19091921

19101922
targetpath = os.path.join(targetpath, arcname)

sync/cpython.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"src/c/compression_zstd/clinic/decompressor.c.h": [
2020
"Modules/_zstd/clinic/decompressor.c.h"
2121
],
22+
"src/python/backports/zstd/_compat.py": ["Lib/posixpath.py", "Lib/ntpath.py"],
2223
"src/python/backports/zstd/_streams.py": ["Lib/compression/_common/_streams.py"],
2324
"src/python/backports/zstd/__init__.py": ["Lib/compression/zstd/__init__.py"],
2425
"src/python/backports/zstd/_zstdfile.py": ["Lib/compression/zstd/_zstdfile.py"],

tests/test/test_zipfile/test_core.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@
3333
from test.support.import_helper import ensure_lazy_imports
3434

3535

36-
# backportszstd: specific test conditions
37-
gh_96290_support = bool(ntpath.splitdrive("\\\\conky\\\\mountpoint\\foo\\bar")[0])
38-
39-
4036
TESTFN2 = TESTFN + "2"
4137
TESTFNDIR = TESTFN + "d"
4238
FIXEDTEST_SIZE = 1000
@@ -1756,22 +1752,10 @@ def test_extract_hackers_arcnames_windows_only(self):
17561752
(r'C:\foo\bar', 'foo/bar'),
17571753
(r'//conky/mountpoint/foo/bar', 'foo/bar'),
17581754
(r'\\conky\mountpoint\foo\bar', 'foo/bar'),
1759-
]
1760-
if gh_96290_support:
1761-
windows_hacknames += [
1762-
(r'///conky/mountpoint/foo/bar', 'mountpoint/foo/bar'),
1763-
(r'\\\conky\mountpoint\foo\bar', 'mountpoint/foo/bar'),
1764-
(r'//conky//mountpoint/foo/bar', 'mountpoint/foo/bar'),
1765-
(r'\\conky\\mountpoint\foo\bar', 'mountpoint/foo/bar'),
1766-
]
1767-
else:
1768-
windows_hacknames += [
1769-
(r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
1770-
(r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
1771-
(r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
1772-
(r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
1773-
]
1774-
windows_hacknames += [
1755+
(r'///conky/mountpoint/foo/bar', 'mountpoint/foo/bar'),
1756+
(r'\\\conky\mountpoint\foo\bar', 'mountpoint/foo/bar'),
1757+
(r'//conky//mountpoint/foo/bar', 'mountpoint/foo/bar'),
1758+
(r'\\conky\\mountpoint\foo\bar', 'mountpoint/foo/bar'),
17751759
(r'//?/C:/foo/bar', 'foo/bar'),
17761760
(r'\\?\C:\foo\bar', 'foo/bar'),
17771761
(r'C:/../C:/foo/bar', 'C_/foo/bar'),
@@ -1915,7 +1899,7 @@ def test_write_without_source_date_epoch(self):
19151899
zip_info = zf.getinfo("test_no_source_date_epoch.txt")
19161900
current_time = time.localtime()[:6]
19171901
for z_time, c_time in zip(zip_info.date_time, current_time):
1918-
self.assertAlmostEqual(z_time, c_time, delta=1)
1902+
self.assertAlmostEqual(z_time, c_time, delta=2)
19191903

19201904
def test_close(self):
19211905
"""Check that the zipfile is closed after the 'with' block."""

0 commit comments

Comments
 (0)