archive_viewer.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2013-2021, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. #-----------------------------------------------------------------------------
  11. """
  12. Viewer for archives packaged by archive.py.
  13. """
  14. import argparse
  15. import os
  16. import pprint
  17. import sys
  18. import tempfile
  19. import zlib
  20. import PyInstaller.log
  21. from PyInstaller.archive.readers import CArchiveReader, NotAnArchiveError
  22. from PyInstaller.compat import stdin_input
  23. from PyInstaller.loader import pyimod02_archive
  24. stack = []
  25. cleanup = []
  26. def main(name, brief, debug, rec_debug, **unused_options):
  27. global stack
  28. if not os.path.isfile(name):
  29. print(name, "is an invalid file name!", file=sys.stderr)
  30. return 1
  31. arch = get_archive(name)
  32. stack.append((name, arch))
  33. if debug or brief:
  34. show_log(arch, rec_debug, brief)
  35. raise SystemExit(0)
  36. else:
  37. show(name, arch)
  38. while 1:
  39. try:
  40. toks = stdin_input('? ').split(None, 1)
  41. except EOFError:
  42. # Ctrl-D
  43. print(file=sys.stderr) # Clear line.
  44. break
  45. if not toks:
  46. usage()
  47. continue
  48. if len(toks) == 1:
  49. cmd = toks[0]
  50. arg = ''
  51. else:
  52. cmd, arg = toks
  53. cmd = cmd.upper()
  54. if cmd == 'U':
  55. if len(stack) > 1:
  56. arch = stack[-1][1]
  57. del stack[-1]
  58. name, arch = stack[-1]
  59. show(name, arch)
  60. elif cmd == 'O':
  61. if not arg:
  62. arg = stdin_input('open name? ')
  63. arg = arg.strip()
  64. try:
  65. arch = get_archive(arg)
  66. except NotAnArchiveError as e:
  67. print(e, file=sys.stderr)
  68. continue
  69. if arch is None:
  70. print(arg, "not found", file=sys.stderr)
  71. continue
  72. stack.append((arg, arch))
  73. show(arg, arch)
  74. elif cmd == 'X':
  75. if not arg:
  76. arg = stdin_input('extract name? ')
  77. arg = arg.strip()
  78. data = get_data(arg, arch)
  79. if data is None:
  80. print("Not found", file=sys.stderr)
  81. continue
  82. filename = stdin_input('to filename? ')
  83. if not filename:
  84. print(repr(data))
  85. else:
  86. with open(filename, 'wb') as fp:
  87. fp.write(data)
  88. elif cmd == 'Q':
  89. break
  90. else:
  91. usage()
  92. do_cleanup()
  93. def do_cleanup():
  94. global stack, cleanup
  95. stack = []
  96. for filename in cleanup:
  97. try:
  98. os.remove(filename)
  99. except Exception as e:
  100. print("could not delete", filename, e.args, file=sys.stderr)
  101. cleanup = []
  102. def usage():
  103. print("U: go up one level", file=sys.stderr)
  104. print("O <name>: open embedded archive name", file=sys.stderr)
  105. print("X <name>: extract name", file=sys.stderr)
  106. print("Q: quit", file=sys.stderr)
  107. def get_archive(name):
  108. if not stack:
  109. if name[-4:].lower() == '.pyz':
  110. return ZlibArchive(name)
  111. return CArchiveReader(name)
  112. parent = stack[-1][1]
  113. try:
  114. return parent.openEmbedded(name)
  115. except KeyError:
  116. return None
  117. except (ValueError, RuntimeError):
  118. ndx = parent.toc.find(name)
  119. dpos, dlen, ulen, flag, typcd, name = parent.toc[ndx]
  120. x, data = parent.extract(ndx)
  121. tempfilename = tempfile.mktemp()
  122. cleanup.append(tempfilename)
  123. with open(tempfilename, 'wb') as fp:
  124. fp.write(data)
  125. if typcd == 'z':
  126. return ZlibArchive(tempfilename)
  127. else:
  128. return CArchiveReader(tempfilename)
  129. def get_data(name, arch):
  130. if isinstance(arch.toc, dict):
  131. (ispkg, pos, length) = arch.toc.get(name, (0, None, 0))
  132. if pos is None:
  133. return None
  134. with arch.lib:
  135. arch.lib.seek(arch.start + pos)
  136. return zlib.decompress(arch.lib.read(length))
  137. ndx = arch.toc.find(name)
  138. dpos, dlen, ulen, flag, typcd, name = arch.toc[ndx]
  139. x, data = arch.extract(ndx)
  140. return data
  141. def show(name, arch):
  142. if isinstance(arch.toc, dict):
  143. print(" Name: (ispkg, pos, len)")
  144. toc = arch.toc
  145. else:
  146. print(" pos, length, uncompressed, iscompressed, type, name")
  147. toc = arch.toc.data
  148. pprint.pprint(toc)
  149. def get_content(arch, recursive, brief, output):
  150. if isinstance(arch.toc, dict):
  151. toc = arch.toc
  152. if brief:
  153. for name, _ in toc.items():
  154. output.append(name)
  155. else:
  156. output.append(toc)
  157. else:
  158. toc = arch.toc.data
  159. for el in toc:
  160. if brief:
  161. output.append(el[5])
  162. else:
  163. output.append(el)
  164. if recursive:
  165. if el[4] in ('z', 'a'):
  166. get_content(get_archive(el[5]), recursive, brief, output)
  167. stack.pop()
  168. def show_log(arch, recursive, brief):
  169. output = []
  170. get_content(arch, recursive, brief, output)
  171. # first print all TOCs
  172. for out in output:
  173. if isinstance(out, dict):
  174. pprint.pprint(out)
  175. # then print the other entries
  176. pprint.pprint([out for out in output if not isinstance(out, dict)])
  177. def get_archive_content(filename):
  178. """
  179. Get a list of the (recursive) content of archive `filename`.
  180. This function is primary meant to be used by runtests.
  181. """
  182. archive = get_archive(filename)
  183. stack.append((filename, archive))
  184. output = []
  185. get_content(archive, recursive=True, brief=True, output=output)
  186. do_cleanup()
  187. return output
  188. class ZlibArchive(pyimod02_archive.ZlibArchiveReader):
  189. def checkmagic(self):
  190. """
  191. Overridable.
  192. Check to see if the file object self.lib actually has a file we understand.
  193. """
  194. self.lib.seek(self.start) # default - magic is at start of file.
  195. if self.lib.read(len(self.MAGIC)) != self.MAGIC:
  196. raise RuntimeError("%s is not a valid %s archive file" % (self.path, self.__class__.__name__))
  197. if self.lib.read(len(self.pymagic)) != self.pymagic:
  198. print("Warning: pyz is from a different Python version", file=sys.stderr)
  199. self.lib.read(4)
  200. def run():
  201. parser = argparse.ArgumentParser()
  202. parser.add_argument(
  203. '-l',
  204. '--log',
  205. default=False,
  206. action='store_true',
  207. dest='debug',
  208. help='Print an archive log (default: %(default)s)',
  209. )
  210. parser.add_argument(
  211. '-r',
  212. '--recursive',
  213. default=False,
  214. action='store_true',
  215. dest='rec_debug',
  216. help='Recursively print an archive log (default: %(default)s). Can be combined with -r',
  217. )
  218. parser.add_argument(
  219. '-b',
  220. '--brief',
  221. default=False,
  222. action='store_true',
  223. dest='brief',
  224. help='Print only file name. (default: %(default)s). Can be combined with -r',
  225. )
  226. PyInstaller.log.__add_options(parser)
  227. parser.add_argument(
  228. 'name',
  229. metavar='pyi_archive',
  230. help="PyInstaller archive to show content of",
  231. )
  232. args = parser.parse_args()
  233. PyInstaller.log.__process_options(parser, args)
  234. try:
  235. raise SystemExit(main(**vars(args)))
  236. except KeyboardInterrupt:
  237. raise SystemExit("Aborted by user request.")
  238. if __name__ == '__main__':
  239. run()