[3.12] gh-123270: Replaced SanitizedNames with a more surgical fix. (GH-123354) by miss-islington · Pull Request #123411 · python/cpython
Expand Up
@@ -4,6 +4,7 @@
import pathlib
import pickle
import sys
import time
import unittest
import zipfile
Expand Down Expand Up @@ -592,7 +593,11 @@ def test_getinfo_missing(self, alpharep):
def test_malformed_paths(self): """ Path should handle malformed paths. Path should handle malformed paths gracefully.
Paths with leading slashes are not visible.
Paths with dots are treated like regular files. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") Expand All @@ -601,15 +606,71 @@ def test_malformed_paths(self): zf.writestr("../parent.txt", b"content") zf.filename = '' root = zipfile.Path(zf) assert list(map(str, root.iterdir())) == [ 'one-slash.txt', 'two-slash.txt', 'parent.txt', ] assert list(map(str, root.iterdir())) == ['../'] assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
def test_unsupported_names(self): """ Path segments with special characters are readable.
On some platforms or file systems, characters like ``:`` and ``?`` are not allowed, but they are valid in the zip file. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr("path?", b"content") zf.writestr("V: NMS.flac", b"fLaC...") zf.filename = '' root = zipfile.Path(zf) contents = root.iterdir() assert next(contents).name == 'path?' assert next(contents).name == 'V: NMS.flac' assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
def test_backslash_not_separator(self): """ In a zip file, backslashes are not separators. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") zf.filename = '' root = zipfile.Path(zf) (first,) = root.iterdir() assert not first.is_dir() assert first.name == 'foo\\bar'
@pass_alpharep def test_interface(self, alpharep): from importlib.resources.abc import Traversable
zf = zipfile.Path(alpharep) assert isinstance(zf, Traversable)
class DirtyZipInfo(zipfile.ZipInfo): """ Bypass name sanitization. """
def __init__(self, filename, *args, **kwargs): super().__init__(filename, *args, **kwargs) self.filename = filename
@classmethod def for_name(cls, name, archive): """ Construct the same way that ZipFile.writestr does.
TODO: extract this functionality and re-use """ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) self.compress_type = archive.compression self.compress_level = archive.compresslevel if self.filename.endswith('/'): # pragma: no cover self.external_attr = 0o40775 << 16 # drwxrwxr-x self.external_attr |= 0x10 # MS-DOS directory flag else: self.external_attr = 0o600 << 16 # ?rw------- return self
Expand Down Expand Up @@ -592,7 +593,11 @@ def test_getinfo_missing(self, alpharep):
def test_malformed_paths(self): """ Path should handle malformed paths. Path should handle malformed paths gracefully.
Paths with leading slashes are not visible.
Paths with dots are treated like regular files. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") Expand All @@ -601,15 +606,71 @@ def test_malformed_paths(self): zf.writestr("../parent.txt", b"content") zf.filename = '' root = zipfile.Path(zf) assert list(map(str, root.iterdir())) == [ 'one-slash.txt', 'two-slash.txt', 'parent.txt', ] assert list(map(str, root.iterdir())) == ['../'] assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
def test_unsupported_names(self): """ Path segments with special characters are readable.
On some platforms or file systems, characters like ``:`` and ``?`` are not allowed, but they are valid in the zip file. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr("path?", b"content") zf.writestr("V: NMS.flac", b"fLaC...") zf.filename = '' root = zipfile.Path(zf) contents = root.iterdir() assert next(contents).name == 'path?' assert next(contents).name == 'V: NMS.flac' assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
def test_backslash_not_separator(self): """ In a zip file, backslashes are not separators. """ data = io.BytesIO() zf = zipfile.ZipFile(data, "w") zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content") zf.filename = '' root = zipfile.Path(zf) (first,) = root.iterdir() assert not first.is_dir() assert first.name == 'foo\\bar'
@pass_alpharep def test_interface(self, alpharep): from importlib.resources.abc import Traversable
zf = zipfile.Path(alpharep) assert isinstance(zf, Traversable)
class DirtyZipInfo(zipfile.ZipInfo): """ Bypass name sanitization. """
def __init__(self, filename, *args, **kwargs): super().__init__(filename, *args, **kwargs) self.filename = filename
@classmethod def for_name(cls, name, archive): """ Construct the same way that ZipFile.writestr does.
TODO: extract this functionality and re-use """ self = cls(filename=name, date_time=time.localtime(time.time())[:6]) self.compress_type = archive.compression self.compress_level = archive.compresslevel if self.filename.endswith('/'): # pragma: no cover self.external_attr = 0o40775 << 16 # drwxrwxr-x self.external_attr |= 0x10 # MS-DOS directory flag else: self.external_attr = 0o600 << 16 # ?rw------- return self