[3.12] gh-123270: Replaced SanitizedNames with a more surgical fix. (… · python/cpython@95b073b

@@ -4,6 +4,7 @@

44

import pathlib

55

import pickle

66

import sys

7+

import time

78

import unittest

89

import zipfile

910

@@ -592,7 +593,11 @@ def test_getinfo_missing(self, alpharep):

592593593594

def 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

"""

597602

data = io.BytesIO()

598603

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

@@ -601,15 +606,71 @@ def test_malformed_paths(self):

601606

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

602607

zf.filename = ''

603608

root = 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

611645

def test_interface(self, alpharep):

612646

from importlib.resources.abc import Traversable

613647614648

zf = zipfile.Path(alpharep)

615649

assert 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