@@ -3054,6 +3054,83 @@ def test_implied_dirs_performance(self):
30543054 data = ['/' .join (string .ascii_lowercase + str (n )) for n in range (10000 )]
30553055 zipfile .CompleteDirs ._implied_dirs (data )
30563056
3057+ def test_malformed_paths (self ):
3058+ """
3059+ Path should handle malformed paths gracefully.
3060+
3061+ Paths with leading slashes are not visible.
3062+
3063+ Paths with dots are treated like regular files.
3064+ """
3065+ data = io .BytesIO ()
3066+ zf = zipfile .ZipFile (data , "w" )
3067+ zf .writestr ("/one-slash.txt" , b"content" )
3068+ zf .writestr ("//two-slash.txt" , b"content" )
3069+ zf .writestr ("../parent.txt" , b"content" )
3070+ zf .filename = ''
3071+ root = zipfile .Path (zf )
3072+ assert list (map (str , root .iterdir ())) == ['../' ]
3073+ assert root .joinpath ('..' ).joinpath ('parent.txt' ).read_bytes () == b'content'
3074+
3075+ def test_unsupported_names (self ):
3076+ """
3077+ Path segments with special characters are readable.
3078+
3079+ On some platforms or file systems, characters like
3080+ ``:`` and ``?`` are not allowed, but they are valid
3081+ in the zip file.
3082+ """
3083+ data = io .BytesIO ()
3084+ zf = zipfile .ZipFile (data , "w" )
3085+ zf .writestr ("path?" , b"content" )
3086+ zf .writestr ("V: NMS.flac" , b"fLaC..." )
3087+ zf .filename = ''
3088+ root = zipfile .Path (zf )
3089+ contents = root .iterdir ()
3090+ assert next (contents ).name == 'path?'
3091+ assert next (contents ).name == 'V: NMS.flac'
3092+ assert root .joinpath ('V: NMS.flac' ).read_bytes () == b"fLaC..."
3093+
3094+ def test_backslash_not_separator (self ):
3095+ """
3096+ In a zip file, backslashes are not separators.
3097+ """
3098+ data = io .BytesIO ()
3099+ zf = zipfile .ZipFile (data , "w" )
3100+ zf .writestr (DirtyZipInfo .for_name ("foo\\ bar" , zf ), b"content" )
3101+ zf .filename = ''
3102+ root = zipfile .Path (zf )
3103+ (first ,) = root .iterdir ()
3104+ assert not first .is_dir ()
3105+ assert first .name == 'foo\\ bar'
3106+
3107+
3108+ class DirtyZipInfo (zipfile .ZipInfo ):
3109+ """
3110+ Bypass name sanitization.
3111+ """
3112+
3113+ def __init__ (self , filename , * args , ** kwargs ):
3114+ super ().__init__ (filename , * args , ** kwargs )
3115+ self .filename = filename
3116+
3117+ @classmethod
3118+ def for_name (cls , name , archive ):
3119+ """
3120+ Construct the same way that ZipFile.writestr does.
3121+
3122+ TODO: extract this functionality and re-use
3123+ """
3124+ self = cls (filename = name , date_time = time .localtime (time .time ())[:6 ])
3125+ self .compress_type = archive .compression
3126+ self .compress_level = archive .compresslevel
3127+ if self .filename .endswith ('/' ): # pragma: no cover
3128+ self .external_attr = 0o40775 << 16 # drwxrwxr-x
3129+ self .external_attr |= 0x10 # MS-DOS directory flag
3130+ else :
3131+ self .external_attr = 0o600 << 16 # ?rw-------
3132+ return self
3133+
30573134
30583135if __name__ == "__main__" :
30593136 unittest .main ()
0 commit comments