8000 [3.10] gh-122905: Sanitize names in zipfile.Path. (GH-122906) (#123160) · python/cpython@e0264a6 · GitHub
[go: up one dir, main page]

Skip to content

Commit e0264a6

Browse files
authored
[3.10] gh-122905: Sanitize names in zipfile.Path. (GH-122906) (#123160)
[3.10] [3.11] gh-122905: Sanitize names in zipfile.Path. (GH-122906) (GH-122925) * gh-122905: Sanitize names in zipfile.Path. (GH-122906) Ported from zipp 3.19.1; ref jaraco/zippGH-119. (cherry picked from commit 9cd0326) * [3.11] gh-122905: Sanitize names in zipfile.Path. (GH-122906) Ported from zipp 3.19.1; ref jaraco/zippGH-119. (cherry picked from commit 9cd0326) (cherry picked from commit 795f259)
1 parent 31302f5 commit e0264a6

File tree

3 files changed

+78
-1
lines changed

3 files changed

+78
-1
lines changed

Lib/test/test_zipfile.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,6 +3280,23 @@ def test_extract_orig_with_implied_dirs(self, alpharep):
32803280
zipfile.Path(zf)
32813281
zf.extractall(source_path.parent)
32823282

3283+
def test_malformed_paths(self):
3284+
"""
3285+
Path should handle malformed paths.
3286+
"""
3287+
data = io.BytesIO()
3288+
zf = zipfile.ZipFile(data, "w")
3289+
zf.writestr("/one-slash.txt", b"content")
3290+
zf.writestr("//two-slash.txt", b"content")
3291+
zf.writestr("../parent.txt", b"content")
3292+
zf.filename = ''
3293+
root = zipfile.Path(zf)
3294+
assert list(map(str, root.iterdir())) == [
3295+
'one-slash.txt',
3296+
'two-slash.txt',
3297+
'parent.txt',
3298+
]
3299+
32833300

32843301
class StripExtraTests(unittest.TestCase):
32853302
# Note: all of the "z" characters are technically invalid, but up

Lib/zipfile.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import itertools
1010
import os
1111
import posixpath
12+
import re
1213
import shutil
1314
import stat
1415
import struct
@@ -2182,7 +2183,65 @@ def _difference(minuend, subtrahend):
21822183
return itertools.filterfalse(set(subtrahend).__contains__, minuend)
21832184

21842185

2185-
class CompleteDirs(ZipFile):
2186+
class SanitizedNames:
2187+
"""
2188+
ZipFile mix-in to ensure names are sanitized.
2189+
"""
2190+
2191+
def namelist(self):
2192+
return list(map(self._sanitize, super().namelist()))
2193+
2194+
@staticmethod
2195+
def _sanitize(name):
2196+
r"""
2197+
Ensure a relative path with posix separators and no dot names.
2198+
Modeled after
2199+
https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
2200+
but provides consistent cross-platform behavior.
2201+
>>> san = SanitizedNames._sanitize
2202+
>>> san('/foo/bar')
2203+
'foo/bar'
2204+
>>> san('//foo.txt')
2205+
'foo.txt'
2206+
>>> san('foo/.././bar.txt')
2207+
'foo/bar.txt'
2208+
>>> san('foo../.bar.txt')
2209+
'foo../.bar.txt'
2210+
>>> san('\\foo\\bar.txt')
2211+
'foo/bar.txt'
2212+
>>> san('D:\\foo.txt')
2213+
'D/foo.txt'
2214+
>>> san('\\\\server\\share\\file.txt')
2215+
'server/share/file.txt'
2216+
>>> san('\\\\?\\GLOBALROOT\\Volume3')
2217+
'?/GLOBALROOT/Volume3'
2218+
>>> san('\\\\.\\PhysicalDrive1\\root')
2219+
'PhysicalDrive1/root'
2220+
Retain any trailing slash.
2221+
>>> san('abc/')
2222+
'abc/'
2223+
Raises a ValueError if the result is empty.
2224+
>>> san('../..')
2225+
Traceback (most recent call last):
2226+
...
2227+
ValueError: Empty filename
2228+
"""
2229+
2230+
def allowed(part):
2231+
return part and part not in {'..', '.'}
2232+
2233+
# Remove the drive letter.
2234+
# Don't use ntpath.splitdrive, because that also strips UNC paths
2235+
bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
2236+
clean = bare.replace('\\', '/')
2237+
parts = clean.split('/')
2238+
joined = '/'.join(filter(allowed, parts))
2239+
if not joined:
2240+
raise ValueError("Empty filename")
2241+
return joined + '/' * name.endswith('/')
2242+
2243+
2244+
class CompleteDirs(SanitizedNames, ZipFile):
21862245
"""
21872246
A ZipFile subclass that ensures that implied directories
21882247
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)
0