[3.8] gh-123270: Replaced SanitizedNames with a more surgical fix. (G… · python/cpython@7bc367e

@@ -3007,6 +3007,83 @@ def test_implied_dirs_performance(self):

30073007

data = ['/'.join(string.ascii_lowercase + str(n)) for n in range(10000)]

30083008

zipfile.CompleteDirs._implied_dirs(data)

300930093010+

def test_malformed_paths(self):

3011+

"""

3012+

Path should handle malformed paths gracefully.

3013+3014+

Paths with leading slashes are not visible.

3015+3016+

Paths with dots are treated like regular files.

3017+

"""

3018+

data = io.BytesIO()

3019+

zf = zipfile.ZipFile(data, "w")

3020+

zf.writestr("/one-slash.txt", b"content")

3021+

zf.writestr("//two-slash.txt", b"content")

3022+

zf.writestr("../parent.txt", b"content")

3023+

zf.filename = ''

3024+

root = zipfile.Path(zf)

3025+

assert list(map(str, root.iterdir())) == ['../']

3026+

assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'

3027+3028+

def test_unsupported_names(self):

3029+

"""

3030+

Path segments with special characters are readable.

3031+3032+

On some platforms or file systems, characters like

3033+

``:`` and ``?`` are not allowed, but they are valid

3034+

in the zip file.

3035+

"""

3036+

data = io.BytesIO()

3037+

zf = zipfile.ZipFile(data, "w")

3038+

zf.writestr("path?", b"content")

3039+

zf.writestr("V: NMS.flac", b"fLaC...")

3040+

zf.filename = ''

3041+

root = zipfile.Path(zf)

3042+

contents = root.iterdir()

3043+

assert next(contents).name == 'path?'

3044+

assert next(contents).name == 'V: NMS.flac'

3045+

assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."

3046+3047+

def test_backslash_not_separator(self):

3048+

"""

3049+

In a zip file, backslashes are not separators.

3050+

"""

3051+

data = io.BytesIO()

3052+

zf = zipfile.ZipFile(data, "w")

3053+

zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")

3054+

zf.filename = ''

3055+

root = zipfile.Path(zf)

3056+

(first,) = root.iterdir()

3057+

assert not first.is_dir()

3058+

assert first.name == 'foo\\bar'

3059+3060+3061+

class DirtyZipInfo(zipfile.ZipInfo):

3062+

"""

3063+

Bypass name sanitization.

3064+

"""

3065+3066+

def __init__(self, filename, *args, **kwargs):

3067+

super().__init__(filename, *args, **kwargs)

3068+

self.filename = filename

3069+3070+

@classmethod

3071+

def for_name(cls, name, archive):

3072+

"""

3073+

Construct the same way that ZipFile.writestr does.

3074+3075+

TODO: extract this functionality and re-use

3076+

"""

3077+

self = cls(filename=name, date_time=time.localtime(time.time())[:6])

3078+

self.compress_type = archive.compression

3079+

self.compress_level = archive.compresslevel

3080+

if self.filename.endswith('/'): # pragma: no cover

3081+

self.external_attr = 0o40775 << 16 # drwxrwxr-x

3082+

self.external_attr |= 0x10 # MS-DOS directory flag

3083+

else:

3084+

self.external_attr = 0o600 << 16 # ?rw-------

3085+

return self

3086+3010308730113088

if __name__ == "__main__":

30123089

unittest.main()