@@ -3054,6 +3054,83 @@ 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 ("/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
+
3057
3134
3058
3135
if __name__ == "__main__" :
3059
3136
unittest .main ()
0 commit comments