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