blob: 4649312bb66e62299a87759615d8cc9330723329 [file] [log] [blame]
"""
A helper module that can work with paths
that can refer to data inside a zipfile
"""
import errno as _errno
import os as _os
import stat as _stat
import sys as _sys
import time as _time
import zipfile as _zipfile
_DFLT_DIR_MODE = (
_stat.S_IXOTH
| _stat.S_IXGRP
| _stat.S_IXUSR
| _stat.S_IROTH
| _stat.S_IRGRP
| _stat.S_IRUSR
)
_DFLT_FILE_MODE = _stat.S_IROTH | _stat.S_IRGRP | _stat.S_IRUSR
if _sys.version_info[0] == 2:
from StringIO import StringIO as _BaseBytesIO
from StringIO import StringIO as _BaseStringIO
class _StringIO(_BaseStringIO):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
return False
class _BytesIO(_BaseBytesIO):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
return False
else:
from io import BytesIO as _BytesIO
from io import StringIO as _StringIO
def _locate(path):
full_path = path
if _os.path.exists(path):
return path, None
else:
rest = []
root = _os.path.splitdrive(path)
while path and path != root:
path, bn = _os.path.split(path)
rest.append(bn)
if _os.path.exists(path):
break
if path == root:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
if not _os.path.isfile(path):
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
rest.reverse()
return path, "/".join(rest).strip("/")
_open = open
def open(path, mode="r"):
if "w" in mode or "a" in mode:
raise IOError(_errno.EINVAL, path, "Write access not supported")
elif "r+" in mode:
raise IOError(_errno.EINVAL, path, "Write access not supported")
full_path = path
path, rest = _locate(path)
if not rest:
return _open(path, mode)
else:
try:
zf = _zipfile.ZipFile(path, "r")
except _zipfile.error:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
try:
data = zf.read(rest)
except (_zipfile.error, KeyError):
zf.close()
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
zf.close()
if mode == "rb":
return _BytesIO(data)
else:
if _sys.version_info[0] == 3:
data = data.decode("ascii")
return _StringIO(data)
def listdir(path):
full_path = path
path, rest = _locate(path)
if not rest and not _os.path.isfile(path):
return _os.listdir(path)
else:
try:
zf = _zipfile.ZipFile(path, "r")
except _zipfile.error:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
result = set()
seen = False
try:
for nm in zf.namelist():
if rest is None:
seen = True
value = nm.split("/")[0]
if value:
result.add(value)
elif nm.startswith(rest):
if nm == rest:
seen = True
value = ""
pass
elif nm[len(rest)] == "/":
seen = True
value = nm[len(rest) + 1 :].split("/")[0] # noqa: E203
else:
value = None
if value:
result.add(value)
except _zipfile.error:
zf.close()
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
zf.close()
if not seen:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
return list(result)
def isfile(path):
full_path = path
path, rest = _locate(path)
if not rest:
ok = _os.path.isfile(path)
if ok:
try:
zf = _zipfile.ZipFile(path, "r")
return False
except (_zipfile.error, IOError):
return True
return False
zf = None
try:
zf = _zipfile.ZipFile(path, "r")
zf.getinfo(rest)
zf.close()
return True
except (KeyError, _zipfile.error):
if zf is not None:
zf.close()
# Check if this is a directory
try:
zf.getinfo(rest + "/")
except KeyError:
pass
else:
return False
rest = rest + "/"
for nm in zf.namelist():
if nm.startswith(rest):
# Directory
return False
# No trace in zipfile
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
def isdir(path):
full_path = path
path, rest = _locate(path)
if not rest:
ok = _os.path.isdir(path)
if not ok:
try:
zf = _zipfile.ZipFile(path, "r")
except (_zipfile.error, IOError):
return False
return True
return True
zf = None
try:
try:
zf = _zipfile.ZipFile(path)
except _zipfile.error:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
try:
zf.getinfo(rest)
except KeyError:
pass
else:
# File found
return False
rest = rest + "/"
try:
zf.getinfo(rest)
except KeyError:
pass
else:
# Directory entry found
return True
for nm in zf.namelist():
if nm.startswith(rest):
return True
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
finally:
if zf is not None:
zf.close()
def islink(path):
full_path = path
path, rest = _locate(path)
if not rest:
return _os.path.islink(path)
try:
zf = _zipfile.ZipFile(path)
except _zipfile.error:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
try:
try:
zf.getinfo(rest)
except KeyError:
pass
else:
# File
return False
rest += "/"
try:
zf.getinfo(rest)
except KeyError:
pass
else:
# Directory
return False
for nm in zf.namelist():
if nm.startswith(rest):
# Directory without listing
return False
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
finally:
zf.close()
def readlink(path):
full_path = path
path, rest = _locate(path)
if rest:
# No symlinks inside zipfiles
raise OSError(_errno.ENOENT, full_path, "No such file or directory")
return _os.readlink(path)
def getmode(path):
full_path = path
path, rest = _locate(path)
if not rest:
return _stat.S_IMODE(_os.stat(path).st_mode)
zf = None
try:
zf = _zipfile.ZipFile(path)
info = None
try:
info = zf.getinfo(rest)
except KeyError:
pass
if info is None:
try:
info = zf.getinfo(rest + "/")
except KeyError:
pass
if info is None:
rest = rest + "/"
for nm in zf.namelist():
if nm.startswith(rest):
break
else:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
# Directory exists, but has no entry of its own.
return _DFLT_DIR_MODE
# The mode is stored without file-type in external_attr.
if (info.external_attr >> 16) != 0:
return _stat.S_IMODE(info.external_attr >> 16)
else:
return _DFLT_FILE_MODE
finally:
if zf is not None:
zf.close()
def getmtime(path):
full_path = path
path, rest = _locate(path)
if not rest:
return _os.path.getmtime(path)
zf = None
try:
zf = _zipfile.ZipFile(path)
info = None
try:
info = zf.getinfo(rest)
except KeyError:
pass
if info is None:
try:
info = zf.getinfo(rest + "/")
except KeyError:
pass
if info is None:
rest = rest + "/"
for nm in zf.namelist():
if nm.startswith(rest):
break
else:
raise IOError(_errno.ENOENT, full_path, "No such file or directory")
# Directory exists, but has no entry of its
# own, fake mtime by using the timestamp of
# the zipfile itself.
return _os.path.getmtime(path)
return _time.mktime(info.date_time + (0, 0, -1))
finally:
if zf is not None:
zf.close()