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 9cd0326

Browse filesBrowse 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
Copy full SHA for 9cd0326

File tree

3 files changed

+81
-1
lines changed
Filter options

3 files changed

+81
-1
lines changed

‎Lib/test/test_zipfile/_path/test_path.py

Copy file name to clipboardExpand all lines: Lib/test/test_zipfile/_path/test_path.py
+17Lines 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

Copy file name to clipboardExpand all lines: Lib/zipfile/_path/__init__.py
+63-1Lines 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.
+1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.