Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 66d3383

Browse filesBrowse files
committed
[3.9] [3.11] gh-123270: Replaced SanitizedNames with a more surgical fix. (GH-123354)
Applies changes from zipp 3.20.1 and jaraco/zippGH-124 (cherry picked from commit 2231286) (cherry picked from commit 17b77bb) Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
1 parent 3f5d9d1 commit 66d3383
Copy full SHA for 66d3383

File tree

3 files changed

+85
-2
lines changed
Filter options

3 files changed

+85
-2
lines changed

‎Lib/test/test_zipfile.py

Copy file name to clipboardExpand all lines: Lib/test/test_zipfile.py
+75Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3054,6 +3054,81 @@ def test_implied_dirs_performance(self):
30543054
data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)]
30553055
zipfile.CompleteDirs._implied_dirs(data)
30563056

3057+
def test_malformed_paths(self):
3058+
"""
3059+
Path should handle malformed paths gracefully.
3060+
3061+
Paths with leading slashes are not visible.
3062+
3063+
Paths with dots are treated like regular files.
3064+
"""
3065+
data = io.BytesIO()
3066+
zf = zipfile.ZipFile(data, "w")
3067+
zf.writestr("../parent.txt", b"content")
3068+
zf.filename = ''
3069+
root = zipfile.Path(zf)
3070+
assert list(map(str, root.iterdir())) == ['../']
3071+
assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
3072+
3073+
def test_unsupported_names(self):
3074+
"""
3075+
Path segments with special characters are readable.
3076+
3077+
On some platforms or file systems, characters like
3078+
``:`` and ``?`` are not allowed, but they are valid
3079+
in the zip file.
3080+
"""
3081+
data = io.BytesIO()
3082+
zf = zipfile.ZipFile(data, "w")
3083+
zf.writestr("path?", b"content")
3084+
zf.writestr("V: NMS.flac", b"fLaC...")
3085+
zf.filename = ''
3086+
root = zipfile.Path(zf)
3087+
contents = root.iterdir()
3088+
assert next(contents).name == 'path?'
3089+
assert next(contents).name == 'V: NMS.flac'
3090+
assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
3091+
3092+
def test_backslash_not_separator(self):
3093+
"""
3094+
In a zip file, backslashes are not separators.
3095+
"""
3096+
data = io.BytesIO()
3097+
zf = zipfile.ZipFile(data, "w")
3098+
zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
3099+
zf.filename = ''
3100+
root = zipfile.Path(zf)
3101+
(first,) = root.iterdir()
3102+
assert not first.is_dir()
3103+
assert first.name == 'foo\\bar'
3104+
3105+
3106+
class DirtyZipInfo(zipfile.ZipInfo):
3107+
"""
3108+
Bypass name sanitization.
3109+
"""
3110+
3111+
def __init__(self, filename, *args, **kwargs):
3112+
super().__init__(filename, *args, **kwargs)
3113+
self.filename = filename
3114+
3115+
@classmethod
3116+
def for_name(cls, name, archive):
3117+
"""
3118+
Construct the same way that ZipFile.writestr does.
3119+
3120+
TODO: extract this functionality and re-use
3121+
"""
3122+
self = cls(filename=name, date_time=time.localtime(time.time())[:6])
3123+
self.compress_type = archive.compression
3124+
self.compress_level = archive.compresslevel
3125+
if self.filename.endswith('/'): # pragma: no cover
3126+
self.external_attr = 0o40775 << 16 # drwxrwxr-x
3127+
self.external_attr |= 0x10 # MS-DOS directory flag
3128+
else:
3129+
self.external_attr = 0o600 << 16 # ?rw-------
3130+
return self
3131+
30573132

30583133
if __name__ == "__main__":
30593134
unittest.main()

‎Lib/zipfile.py

Copy file name to clipboardExpand all lines: Lib/zipfile.py
+7-2Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,7 +2146,7 @@ def _parents(path):
21462146
def _ancestry(path):
21472147
"""
21482148
Given a path with elements separated by
2149-
posixpath.sep, generate all elements of that path
2149+
posixpath.sep, generate all elements of that path.
21502150
21512151
>>> list(_ancestry('b/d'))
21522152
['b/d', 'b']
@@ -2158,9 +2158,14 @@ def _ancestry(path):
21582158
['b']
21592159
>>> list(_ancestry(''))
21602160
[]
2161+
2162+
Multiple separators are treated like a single.
2163+
2164+
>>> list(_ancestry('//b//d///f//'))
2165+
['//b//d///f', '//b//d', '//b']
21612166
"""
21622167
path = path.rstrip(posixpath.sep)
2163-
while path and path != posixpath.sep:
2168+
while path.rstrip(posixpath.sep):
21642169
yield path
21652170
path, tail = posixpath.split(path)
21662171

+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Applied a more surgical fix for malformed payloads in :class:`zipfile.Path`
2+
causing infinite loops (gh-122905) without breaking contents using
3+
legitimate characters.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.