zipio.py 9.7 KB

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