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