[3.12] gh-123270: Replaced SanitizedNames with a more surgical fix. (… · python/cpython@95b073b
@@ -4,6 +4,7 @@
44import pathlib
55import pickle
66import sys
7+import time
78import unittest
89import zipfile
910@@ -592,7 +593,11 @@ def test_getinfo_missing(self, alpharep):
592593593594def test_malformed_paths(self):
594595"""
595- Path should handle malformed paths.
596+ Path should handle malformed paths gracefully.
597+598+ Paths with leading slashes are not visible.
599+600+ Paths with dots are treated like regular files.
596601 """
597602data = io.BytesIO()
598603zf = zipfile.ZipFile(data, "w")
@@ -601,15 +606,71 @@ def test_malformed_paths(self):
601606zf.writestr("../parent.txt", b"content")
602607zf.filename = ''
603608root = zipfile.Path(zf)
604-assert list(map(str, root.iterdir())) == [
605-'one-slash.txt',
606-'two-slash.txt',
607-'parent.txt',
608- ]
609+assert list(map(str, root.iterdir())) == ['../']
610+assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
611+612+def test_unsupported_names(self):
613+"""
614+ Path segments with special characters are readable.
615+616+ On some platforms or file systems, characters like
617+ ``:`` and ``?`` are not allowed, but they are valid
618+ in the zip file.
619+ """
620+data = io.BytesIO()
621+zf = zipfile.ZipFile(data, "w")
622+zf.writestr("path?", b"content")
623+zf.writestr("V: NMS.flac", b"fLaC...")
624+zf.filename = ''
625+root = zipfile.Path(zf)
626+contents = root.iterdir()
627+assert next(contents).name == 'path?'
628+assert next(contents).name == 'V: NMS.flac'
629+assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
630+631+def test_backslash_not_separator(self):
632+"""
633+ In a zip file, backslashes are not separators.
634+ """
635+data = io.BytesIO()
636+zf = zipfile.ZipFile(data, "w")
637+zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
638+zf.filename = ''
639+root = zipfile.Path(zf)
640+ (first,) = root.iterdir()
641+assert not first.is_dir()
642+assert first.name == 'foo\\bar'
609643610644@pass_alpharep
611645def test_interface(self, alpharep):
612646from importlib.resources.abc import Traversable
613647614648zf = zipfile.Path(alpharep)
615649assert isinstance(zf, Traversable)
650+651+652+class DirtyZipInfo(zipfile.ZipInfo):
653+"""
654+ Bypass name sanitization.
655+ """
656+657+def __init__(self, filename, *args, **kwargs):
658+super().__init__(filename, *args, **kwargs)
659+self.filename = filename
660+661+@classmethod
662+def for_name(cls, name, archive):
663+"""
664+ Construct the same way that ZipFile.writestr does.
665+666+ TODO: extract this functionality and re-use
667+ """
668+self = cls(filename=name, date_time=time.localtime(time.time())[:6])
669+self.compress_type = archive.compression
670+self.compress_level = archive.compresslevel
671+if self.filename.endswith('/'): # pragma: no cover
672+self.external_attr = 0o40775 << 16 # drwxrwxr-x
673+self.external_attr |= 0x10 # MS-DOS directory flag
674+else:
675+self.external_attr = 0o600 << 16 # ?rw-------
676+return self