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

Skip to content

Commit 795f259

Browse files
authored
[3.11] gh-122905: Sanitize names in zipfile.Path. (GH-122906) (#122925)
* gh-122905: Sanitize names in zipfile.Path. (#122906) Ported from zipp 3.19.1; ref jaraco/zipp#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) Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
1 parent 8a978a7 commit 795f259

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
@@ -3651,6 +3651,23 @@ def test_extract_orig_with_implied_dirs(self, alpharep):
36513651
zipfile.Path(zf)
36523652
zf.extractall(source_path.parent)
36533653

3654+
def test_malformed_paths(self):
3655+
"""
3656+
Path should handle malformed paths.
3657+
"""
3658+
data = io.BytesIO()
3659+
zf = zipfile.ZipFile(data, "w")
3660+
zf.writestr("/one-slash.txt", b"content")
3661+
zf.writestr("//two-slash.txt", b"content")
3662+
zf.writestr("../parent.txt", b"content")
3663+
zf.filename = ''
3664+
root = zipfile.Path(zf)
3665+
assert list(map(str, root.iterdir())) == [
3666+
'one-slash.txt',
3667+
'two-slash.txt',
3668+
'parent.txt',
3669+
]
3670+
36543671

36553672
class EncodedMetadataTests(unittest.TestCase):
36563673
file_names = ['\u4e00', '\u4e8c', '\u4e09'] # Han 'one', 'two', 'three'

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
@@ -2243,7 +2244,65 @@ def _difference(minuend, subtrahend):
22432244
return itertools.filterfalse(set(subtrahend).__contains__, minuend)
22442245

22452246

2246-
class CompleteDirs(ZipFile):
2247+
class SanitizedNames:
2248+
"""
2249+
ZipFile mix-in to ensure names are sanitized.
2250+
"""
2251+
2252+
def namelist(self):
2253+
return list(map(self._sanitize, super().namelist()))
2254+
2255+
@staticmethod
2256+
def _sanitize(name):
2257+
r"""
2258+
Ensure a relative path with posix separators and no dot names.
2259+
Modeled after
2260+
https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
2261+
but provides consistent cross-platform behavior.
2262+
>>> san = SanitizedNames._sanitize
2263+
>>> san('/foo/bar')
2264+
'foo/bar'
2265+
>>> san('//foo.txt')
2266+
'foo.txt'
2267+
>>> san('foo/.././bar.txt')
2268+
'foo/bar.txt'
2269+
>>> san('foo../.bar.txt')
2270+
'foo../.bar.txt'
2271+
>>> san('\\foo\\bar.txt')
2272+
'foo/bar.txt'
2273+
>>> san('D:\\foo.txt')
2274+
'D/foo.txt'
2275+
>>> san('\\\\server\\share\\file.txt')
2276+
'server/share/file.txt'
2277+
>>> san('\\\\?\\GLOBALROOT\\Volume3')
2278+
'?/GLOBALROOT/Volume3'
2279+
>>> san('\\\\.\\PhysicalDrive1\\root')
2280+
'PhysicalDrive1/root'
2281+
Retain any trailing slash.
2282+
>>> san('abc/')
2283+
'abc/'
2284+
Raises a ValueError if the result is empty.
2285+
>>> san('../..')
2286+
Traceback (most recent call last):
2287+
...
2288+
ValueError: Empty filename
2289+
"""
2290+
2291+
def allowed(part):
2292+
return part and part not in {'..', '.'}
2293+
2294+
# Remove the drive letter.
2295+
# Don't use ntpath.splitdrive, because that also strips UNC paths
2296+
bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
2297+
clean = bare.replace('\\', '/')
2298+
parts = clean.split('/')
2299+
joined = '/'.join(filter(allowed, parts))
2300+
if not joined:
2301+
raise ValueError("Empty filename")
2302+
return joined + '/' * name.endswith('/')
2303+
2304+
2305+
class CompleteDirs(SanitizedNames, ZipFile):
22472306
"""
22482307
A ZipFile subclass that ensures that implied directories
22492308
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