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