Skip to content

Commit 9cd0326

Browse files
authored
gh-122905: Sanitize names in zipfile.Path. (#122906)
Ported from zipp 3.19.1; ref jaraco/zipp#119.
1 parent 4534068 commit 9cd0326

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

Lib/test/test_zipfile/_path/test_path.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,20 @@ def test_getinfo_missing(self, alpharep):
577577
zipfile.Path(alpharep)
578578
with self.assertRaises(KeyError):
579579
alpharep.getinfo('does-not-exist')
580+
581+
def test_malformed_paths(self):
582+
"""
583+
Path should handle malformed paths.
584+
"""
585+
data = io.BytesIO()
586+
zf = zipfile.ZipFile(data, "w")
587+
zf.writestr("/one-slash.txt", b"content")
588+
zf.writestr("//two-slash.txt", b"content")
589+
zf.writestr("../parent.txt", b"content")
590+
zf.filename = ''
591+
root = zipfile.Path(zf)
592+
assert list(map(str, root.iterdir())) == [
593+
'one-slash.txt',
594+
'two-slash.txt',
595+
'parent.txt',
596+
]

Lib/zipfile/_path/__init__.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,69 @@ def __setstate__(self, state):
8585
super().__init__(*args, **kwargs)
8686

8787

88-
class CompleteDirs(InitializedState, zipfile.ZipFile):
88+
class SanitizedNames:
89+
"""
90+
ZipFile mix-in to ensure names are sanitized.
91+
"""
92+
93+
def namelist(self):
94+
return list(map(self._sanitize, super().namelist()))
95+
96+
@staticmethod
97+
def _sanitize(name):
98+
r"""
99+
Ensure a relative path with posix separators and no dot names.
100+
101+
Modeled after
102+
https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
103+
but provides consistent cross-platform behavior.
104+
105+
>>> san = SanitizedNames._sanitize
106+
>>> san('/foo/bar')
107+
'foo/bar'
108+
>>> san('//foo.txt')
109+
'foo.txt'
110+
>>> san('foo/.././bar.txt')
111+
'foo/bar.txt'
112+
>>> san('foo../.bar.txt')
113+
'foo../.bar.txt'
114+
>>> san('\\foo\\bar.txt')
115+
'foo/bar.txt'
116+
>>> san('D:\\foo.txt')
117+
'D/foo.txt'
118+
>>> san('\\\\server\\share\\file.txt')
119+
'server/share/file.txt'
120+
>>> san('\\\\?\\GLOBALROOT\\Volume3')
121+
'?/GLOBALROOT/Volume3'
122+
>>> san('\\\\.\\PhysicalDrive1\\root')
123+
'PhysicalDrive1/root'
124+
125+
Retain any trailing slash.
126+
>>> san('abc/')
127+
'abc/'
128+
129+
Raises a ValueError if the result is empty.
130+
>>> san('../..')
131+
Traceback (most recent call last):
132+
...
133+
ValueError: Empty filename
134+
"""
135+
136+
def allowed(part):
137+
return part and part not in {'..', '.'}
138+
139+
# Remove the drive letter.
140+
# Don't use ntpath.splitdrive, because that also strips UNC paths
141+
bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
142+
clean = bare.replace('\\', '/')
143+
parts = clean.split('/')
144+
joined = '/'.join(filter(allowed, parts))
145+
if not joined:
146+
raise ValueError("Empty filename")
147+
return joined + '/' * name.endswith('/')
148+
149+
150+
class CompleteDirs(InitializedState, SanitizedNames, zipfile.ZipFile):
89151
"""
90152
A ZipFile subclass that ensures that implied directories
91153
are always included in the namelist.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:class:`zipfile.Path` objects now sanitize names from the zipfile.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy