@@ -86,7 +86,69 @@ def __setstate__(self, state):
8686 super ().__init__ (* args , ** kwargs )
8787
8888
89- class CompleteDirs (InitializedState , zipfile .ZipFile ):
89+ class SanitizedNames :
90+ """
91+ ZipFile mix-in to ensure names are sanitized.
92+ """
93+
94+ def namelist (self ):
95+ return list (map (self ._sanitize , super ().namelist ()))
96+
97+ @staticmethod
98+ def _sanitize (name ):
99+ r"""
100+ Ensure a relative path with posix separators and no dot names.
101+
102+ Modeled after
103+ https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
104+ but provides consistent cross-platform behavior.
105+
106+ >>> san = SanitizedNames._sanitize
107+ >>> san('/foo/bar')
108+ 'foo/bar'
109+ >>> san('//foo.txt')
110+ 'foo.txt'
111+ >>> san('foo/.././bar.txt')
112+ 'foo/bar.txt'
113+ >>> san('foo../.bar.txt')
114+ 'foo../.bar.txt'
115+ >>> san('\\foo\\bar.txt')
116+ 'foo/bar.txt'
117+ >>> san('D:\\foo.txt')
118+ 'D/foo.txt'
119+ >>> san('\\\\server\\share\\file.txt')
120+ 'server/share/file.txt'
121+ >>> san('\\\\?\\GLOBALROOT\\Volume3')
122+ '?/GLOBALROOT/Volume3'
123+ >>> san('\\\\.\\PhysicalDrive1\\root')
124+ 'PhysicalDrive1/root'
125+
126+ Retain any trailing slash.
127+ >>> san('abc/')
128+ 'abc/'
129+
130+ Raises a ValueError if the result is empty.
131+ >>> san('../..')
132+ Traceback (most recent call last):
133+ ...
134+ ValueError: Empty filename
135+ """
136+
137+ def allowed (part ):
138+ return part and part not in {'..' , '.' }
139+
140+ # Remove the drive letter.
141+ # Don't use ntpath.splitdrive, because that also strips UNC paths
142+ bare = re .sub ('^([A-Z]):' , r'\1' , name , flags = re .IGNORECASE )
143+ clean = bare .replace ('\\ ' , '/' )
144+ parts = clean .split ('/' )
145+ joined = '/' .join (filter (allowed , parts ))
146+ if not joined :
147+ raise ValueError ("Empty filename" )
148+ return joined + '/' * name .endswith ('/' )
149+
150+
151+ class CompleteDirs (InitializedState , SanitizedNames , zipfile .ZipFile ):
90152 """
91153 A ZipFile subclass that ensures that implied directories
92154 are always included in the namelist.
0 commit comments