1""" 2A helper module that can work with paths 3that can refer to data inside a zipfile 4 5XXX: Need to determine if isdir("zipfile.zip") 6should return True or False. Currently returns 7True, but that might do the wrong thing with 8data-files that are zipfiles. 9""" 10import os as _os 11import zipfile as _zipfile 12import errno as _errno 13import time as _time 14import sys as _sys 15import stat as _stat 16 17_DFLT_DIR_MODE = ( 18 _stat.S_IXOTH 19 | _stat.S_IXGRP 20 | _stat.S_IXUSR 21 | _stat.S_IROTH 22 | _stat.S_IRGRP 23 | _stat.S_IRUSR) 24 25_DFLT_FILE_MODE = ( 26 _stat.S_IROTH 27 | _stat.S_IRGRP 28 | _stat.S_IRUSR) 29 30 31if _sys.version_info[0] == 2: 32 from StringIO import StringIO as _BaseStringIO 33 from StringIO import StringIO as _BaseBytesIO 34 35 class _StringIO (_BaseStringIO): 36 def __enter__(self): 37 return self 38 39 def __exit__(self, exc_type, exc_value, traceback): 40 self.close() 41 return False 42 43 class _BytesIO (_BaseBytesIO): 44 def __enter__(self): 45 return self 46 47 def __exit__(self, exc_type, exc_value, traceback): 48 self.close() 49 return False 50 51else: 52 from io import StringIO as _StringIO 53 from io import BytesIO as _BytesIO 54 55 56def _locate(path): 57 full_path = path 58 if _os.path.exists(path): 59 return path, None 60 61 else: 62 rest = [] 63 root = _os.path.splitdrive(path) 64 while path and path != root: 65 path, bn = _os.path.split(path) 66 rest.append(bn) 67 if _os.path.exists(path): 68 break 69 70 if path == root: 71 raise IOError( 72 _errno.ENOENT, full_path, 73 "No such file or directory") 74 75 if not _os.path.isfile(path): 76 raise IOError( 77 _errno.ENOENT, full_path, 78 "No such file or directory") 79 80 rest.reverse() 81 return path, '/'.join(rest).strip('/') 82 83 84_open = open 85 86 87def open(path, mode='r'): 88 if 'w' in mode or 'a' in mode: 89 raise IOError( 90 _errno.EINVAL, path, "Write access not supported") 91 elif 'r+' in mode: 92 raise IOError( 93 _errno.EINVAL, path, "Write access not supported") 94 95 full_path = path 96 path, rest = _locate(path) 97 if not rest: 98 return _open(path, mode) 99 100 else: 101 try: 102 zf = _zipfile.ZipFile(path, 'r') 103 104 except _zipfile.error: 105 raise IOError( 106 _errno.ENOENT, full_path, 107 "No such file or directory") 108 109 try: 110 data = zf.read(rest) 111 except (_zipfile.error, KeyError): 112 zf.close() 113 raise IOError( 114 _errno.ENOENT, full_path, 115 "No such file or directory") 116 zf.close() 117 118 if mode == 'rb': 119 return _BytesIO(data) 120 121 else: 122 if _sys.version_info[0] == 3: 123 data = data.decode('ascii') 124 125 return _StringIO(data) 126 127 128def listdir(path): 129 full_path = path 130 path, rest = _locate(path) 131 if not rest and not _os.path.isfile(path): 132 return _os.listdir(path) 133 134 else: 135 try: 136 zf = _zipfile.ZipFile(path, 'r') 137 138 except _zipfile.error: 139 raise IOError( 140 _errno.ENOENT, full_path, 141 "No such file or directory") 142 143 result = set() 144 seen = False 145 try: 146 for nm in zf.namelist(): 147 if rest is None: 148 seen = True 149 value = nm.split('/')[0] 150 if value: 151 result.add(value) 152 153 elif nm.startswith(rest): 154 if nm == rest: 155 seen = True 156 value = '' 157 pass 158 elif nm[len(rest)] == '/': 159 seen = True 160 value = nm[len(rest)+1:].split('/')[0] 161 else: 162 value = None 163 164 if value: 165 result.add(value) 166 except _zipfile.error: 167 zf.close() 168 raise IOError( 169 _errno.ENOENT, full_path, 170 "No such file or directory") 171 172 zf.close() 173 174 if not seen: 175 raise IOError( 176 _errno.ENOENT, full_path, 177 "No such file or directory") 178 179 return list(result) 180 181 182def isfile(path): 183 full_path = path 184 path, rest = _locate(path) 185 if not rest: 186 ok = _os.path.isfile(path) 187 if ok: 188 try: 189 zf = _zipfile.ZipFile(path, 'r') 190 return False 191 except (_zipfile.error, IOError): 192 return True 193 return False 194 195 zf = None 196 try: 197 zf = _zipfile.ZipFile(path, 'r') 198 zf.getinfo(rest) 199 zf.close() 200 return True 201 except (KeyError, _zipfile.error): 202 if zf is not None: 203 zf.close() 204 205 # Check if this is a directory 206 try: 207 zf.getinfo(rest + '/') 208 except KeyError: 209 pass 210 else: 211 return False 212 213 rest = rest + '/' 214 for nm in zf.namelist(): 215 if nm.startswith(rest): 216 # Directory 217 return False 218 219 # No trace in zipfile 220 raise IOError( 221 _errno.ENOENT, full_path, 222 "No such file or directory") 223 224 225def isdir(path): 226 full_path = path 227 path, rest = _locate(path) 228 if not rest: 229 ok = _os.path.isdir(path) 230 if not ok: 231 try: 232 zf = _zipfile.ZipFile(path, 'r') 233 except (_zipfile.error, IOError): 234 return False 235 return True 236 return True 237 238 zf = None 239 try: 240 try: 241 zf = _zipfile.ZipFile(path) 242 except _zipfile.error: 243 raise IOError( 244 _errno.ENOENT, full_path, 245 "No such file or directory") 246 247 try: 248 zf.getinfo(rest) 249 except KeyError: 250 pass 251 else: 252 # File found 253 return False 254 255 rest = rest + '/' 256 try: 257 zf.getinfo(rest) 258 except KeyError: 259 pass 260 else: 261 # Directory entry found 262 return True 263 264 for nm in zf.namelist(): 265 if nm.startswith(rest): 266 return True 267 268 raise IOError( 269 _errno.ENOENT, full_path, 270 "No such file or directory") 271 finally: 272 if zf is not None: 273 zf.close() 274 275 276def islink(path): 277 full_path = path 278 path, rest = _locate(path) 279 if not rest: 280 return _os.path.islink(path) 281 282 try: 283 zf = _zipfile.ZipFile(path) 284 except _zipfile.error: 285 raise IOError( 286 _errno.ENOENT, full_path, 287 "No such file or directory") 288 try: 289 try: 290 zf.getinfo(rest) 291 except KeyError: 292 pass 293 else: 294 # File 295 return False 296 297 rest += '/' 298 try: 299 zf.getinfo(rest) 300 except KeyError: 301 pass 302 else: 303 # Directory 304 return False 305 306 for nm in zf.namelist(): 307 if nm.startswith(rest): 308 # Directory without listing 309 return False 310 311 raise IOError( 312 _errno.ENOENT, full_path, 313 "No such file or directory") 314 315 finally: 316 zf.close() 317 318 319def readlink(path): 320 full_path = path 321 path, rest = _locate(path) 322 if rest: 323 # No symlinks inside zipfiles 324 raise OSError( 325 _errno.ENOENT, full_path, 326 "No such file or directory") 327 328 return _os.readlink(path) 329 330 331def getmode(path): 332 full_path = path 333 path, rest = _locate(path) 334 if not rest: 335 return _stat.S_IMODE(_os.stat(path).st_mode) 336 337 zf = None 338 try: 339 zf = _zipfile.ZipFile(path) 340 info = None 341 342 try: 343 info = zf.getinfo(rest) 344 except KeyError: 345 pass 346 347 if info is None: 348 try: 349 info = zf.getinfo(rest + '/') 350 except KeyError: 351 pass 352 353 if info is None: 354 rest = rest + '/' 355 for nm in zf.namelist(): 356 if nm.startswith(rest): 357 break 358 else: 359 raise IOError( 360 _errno.ENOENT, full_path, 361 "No such file or directory") 362 363 # Directory exists, but has no entry of its own. 364 return _DFLT_DIR_MODE 365 366 # The mode is stored without file-type in external_attr. 367 if (info.external_attr >> 16) != 0: 368 return _stat.S_IMODE(info.external_attr >> 16) 369 else: 370 return _DFLT_FILE_MODE 371 372 finally: 373 if zf is not None: 374 zf.close() 375 376 377def getmtime(path): 378 full_path = path 379 path, rest = _locate(path) 380 if not rest: 381 return _os.path.getmtime(path) 382 383 zf = None 384 try: 385 zf = _zipfile.ZipFile(path) 386 info = None 387 388 try: 389 info = zf.getinfo(rest) 390 except KeyError: 391 pass 392 393 if info is None: 394 try: 395 info = zf.getinfo(rest + '/') 396 except KeyError: 397 pass 398 399 if info is None: 400 rest = rest + '/' 401 for nm in zf.namelist(): 402 if nm.startswith(rest): 403 break 404 else: 405 raise IOError( 406 _errno.ENOENT, full_path, 407 "No such file or directory") 408 409 # Directory exists, but has no entry of its 410 # own, fake mtime by using the timestamp of 411 # the zipfile itself. 412 return _os.path.getmtime(path) 413 414 return _time.mktime(info.date_time + (0, 0, -1)) 415 416 finally: 417 if zf is not None: 418 zf.close() 419