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