| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2013-2023, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- """
- Viewer for PyInstaller-generated archives.
- """
- import argparse
- import os
- import sys
- import PyInstaller.log
- from PyInstaller.archive.readers import CArchiveReader, ZlibArchiveReader
- try:
- from argcomplete import autocomplete
- except ImportError:
- def autocomplete(parser):
- return None
- class ArchiveViewer:
- def __init__(self, filename, interactive_mode, recursive_mode, brief_mode):
- self.filename = filename
- self.interactive_mode = interactive_mode
- self.recursive_mode = recursive_mode
- self.brief_mode = brief_mode
- self.stack = []
- # Recursive mode implies non-interactive mode
- if self.recursive_mode:
- self.interactive_mode = False
- def main(self):
- # Open top-level (initial) archive
- archive = self._open_toplevel_archive(self.filename)
- archive_name = os.path.basename(self.filename)
- self.stack.append((archive_name, archive))
- # Not-interactive mode
- if not self.interactive_mode:
- return self._non_interactive_processing()
- # Interactive mode; show top-level archive
- self._show_archive_contents(archive_name, archive)
- # Interactive command processing
- while True:
- # Read command
- try:
- tokens = input('? ').split(None, 1)
- except EOFError:
- # Ctrl-D
- print(file=sys.stderr) # Clear line.
- break
- # Print usage?
- if not tokens:
- self._print_usage()
- continue
- # Process
- command = tokens[0].upper()
- if command == 'Q':
- break
- elif command == 'U':
- self._move_up_the_stack()
- elif command == 'O':
- self._open_embedded_archive(*tokens[1:])
- elif command == 'X':
- self._extract_file(*tokens[1:])
- elif command == 'S':
- archive_name, archive = self.stack[-1]
- self._show_archive_contents(archive_name, archive)
- else:
- self._print_usage()
- def _non_interactive_processing(self):
- archive_count = 0
- while self.stack:
- archive_name, archive = self.stack.pop()
- archive_count += 1
- if archive_count > 1:
- print("")
- self._show_archive_contents(archive_name, archive)
- if not self.recursive_mode:
- continue
- # Scan for embedded archives
- if isinstance(archive, CArchiveReader):
- for name, (*_, typecode) in archive.toc.items():
- if typecode == 'z':
- try:
- embedded_archive = archive.open_embedded_archive(name)
- except Exception as e:
- print(f"Could not open embedded archive {name!r}: {e}", file=sys.stderr)
- self.stack.append((name, embedded_archive))
- def _print_usage(self):
- print("U: go up one level", file=sys.stderr)
- print("O <name>: open embedded archive with given name", file=sys.stderr)
- print("X <name>: extract file with given name", file=sys.stderr)
- print("S: list the contents of current archive again", file=sys.stderr)
- print("Q: quit", file=sys.stderr)
- def _move_up_the_stack(self):
- if len(self.stack) > 1:
- self.stack.pop()
- archive_name, archive = self.stack[-1]
- self._show_archive_contents(archive_name, archive)
- else:
- print("Already in the top archive!", file=sys.stderr)
- def _open_toplevel_archive(self, filename):
- if not os.path.isfile(filename):
- print(f"Archive {filename} does not exist!", file=sys.stderr)
- sys.exit(1)
- if filename[-4:].lower() == '.pyz':
- return ZlibArchiveReader(filename)
- return CArchiveReader(filename)
- def _open_embedded_archive(self, archive_name=None):
- # Ask for name if not provided
- if not archive_name:
- archive_name = input('Open name? ')
- archive_name = archive_name.strip()
- # No name given; abort
- if not archive_name:
- return
- # Open the embedded archive
- _, parent_archive = self.stack[-1]
- if not hasattr(parent_archive, 'open_embedded_archive'):
- print("Archive does not support embedded archives!", file=sys.stderr)
- return
- try:
- archive = parent_archive.open_embedded_archive(archive_name)
- except Exception as e:
- print(f"Could not open embedded archive {archive_name!r}: {e}", file=sys.stderr)
- return
- # Add to stack and display contents
- self.stack.append((archive_name, archive))
- self._show_archive_contents(archive_name, archive)
- def _extract_file(self, name=None):
- # Ask for name if not provided
- if not name:
- name = input('Extract name? ')
- name = name.strip()
- # Archive
- archive_name, archive = self.stack[-1]
- # Retrieve data
- try:
- if isinstance(archive, CArchiveReader):
- data = archive.extract(name)
- elif isinstance(archive, ZlibArchiveReader):
- data = archive.extract(name, raw=True)
- if data is None:
- raise ValueError("Entry has no associated data!")
- else:
- raise NotImplementedError(f"Extraction from archive type {type(archive)} not implemented!")
- except Exception as e:
- print(f"Failed to extract data for entry {name!r} from {archive_name!r}: {e}", file=sys.stderr)
- return
- # Write to file
- filename = input('Output filename? ')
- if not filename:
- print(repr(data))
- else:
- with open(filename, 'wb') as fp:
- fp.write(data)
- def _show_archive_contents(self, archive_name, archive):
- if isinstance(archive, CArchiveReader):
- if archive.options:
- print(f"Options in {archive_name!r} (PKG/CArchive):")
- for option in archive.options:
- print(f" {option}")
- print(f"Contents of {archive_name!r} (PKG/CArchive):")
- if self.brief_mode:
- for name in archive.toc.keys():
- print(f" {name}")
- else:
- print(" position, length, uncompressed_length, is_compressed, typecode, name")
- for name, (position, length, uncompressed_length, is_compressed, typecode) in archive.toc.items():
- print(f" {position}, {length}, {uncompressed_length}, {is_compressed}, {typecode!r}, {name!r}")
- elif isinstance(archive, ZlibArchiveReader):
- print(f"Contents of {archive_name!r} (PYZ):")
- if self.brief_mode:
- for name in archive.toc.keys():
- print(f" {name}")
- else:
- print(" typecode, position, length, name")
- for name, (typecode, position, length) in archive.toc.items():
- print(f" {typecode}, {position}, {length}, {name!r}")
- else:
- print(f"Contents of {name} (unknown)")
- print(f"FIXME: implement content listing for archive type {type(archive)}!")
- def run():
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '-l',
- '--list',
- default=False,
- action='store_true',
- dest='listing_mode',
- help='List the archive contents and exit (default: %(default)s).',
- )
- parser.add_argument(
- '-r',
- '--recursive',
- default=False,
- action='store_true',
- dest='recursive',
- help='Recursively print an archive log (default: %(default)s). Implies --list.',
- )
- parser.add_argument(
- '-b',
- '--brief',
- default=False,
- action='store_true',
- dest='brief',
- help='When displaying archive contents, show only file names. (default: %(default)s).',
- )
- PyInstaller.log.__add_options(parser)
- parser.add_argument(
- 'filename',
- metavar='pyi_archive',
- help="PyInstaller archive to process.",
- )
- autocomplete(parser)
- args = parser.parse_args()
- PyInstaller.log.__process_options(parser, args)
- try:
- viewer = ArchiveViewer(
- filename=args.filename,
- interactive_mode=not args.listing_mode,
- recursive_mode=args.recursive,
- brief_mode=args.brief,
- )
- viewer.main()
- except KeyboardInterrupt:
- raise SystemExit("Aborted by user.")
- if __name__ == '__main__':
- run()
|