def add_to_menu(uris): for uri in uris: iface = config.iface_cache.get_interface(uri) icon_path = config.iface_cache.get_icon_path(iface) feed_category = '' for meta in iface.get_metadata(namespaces.XMLNS_IFACE, 'category'): c = meta.content if '\n' in c: raise Exception("Invalid category '%s'" % c) feed_category = c break xdgutils.add_to_menu(iface, icon_path, feed_category) if find_in_path('0launch'): return if find_in_path('sudo') and find_in_path('gnome-terminal') and find_in_path('apt-get'): check_call(['gnome-terminal', '--disable-factory', '-x', 'sh', '-c', 'echo "We need to install the zeroinstall-injector package to make the menu items work."; ' 'sudo apt-get install zeroinstall-injector || sleep 4']) if find_in_path('0launch'): return import gtk box = gtk.MessageDialog(None, 0, buttons = gtk.BUTTONS_OK) box.set_markup("The new menu item won't work until the '<b>zeroinstall-injector</b>' package is installed.\n" "Please install it using your distribution's package manager.") box.run() box.destroy() gtk.gdk.flush()
def action_run(self, uri): iface = self.iface_cache.get_interface(uri) reader.update_from_cache(iface) if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')): if gtk.pygtk_version >= (2, 16, 0) and gtk.gdk.WINDOWING == 'quartz': script = ['0launch', '--', uri] osascript = support.find_in_path('osascript') subprocess.Popen([ osascript, '-e', 'tell app "Terminal"', '-e', 'activate', '-e', 'do script "%s"' % ' '.join(script), '-e', 'end tell' ]) return for terminal in [ 'x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt', 'konsole' ]: exe = support.find_in_path(terminal) if exe: if terminal == 'gnome-terminal': flag = '-x' else: flag = '-e' subprocess.Popen([terminal, flag, '0launch', '--', uri]) break else: box = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator")) box.run() box.destroy() else: subprocess.Popen(['0launch', '--', uri])
def action_run(self, uri): iface = self.iface_cache.get_interface(uri) reader.update_from_cache(iface) if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')): if gtk.pygtk_version >= (2,16,0) and gtk.gdk.WINDOWING == 'quartz': script = ['0launch', '--', uri] osascript = support.find_in_path('osascript') subprocess.Popen([osascript, '-e', 'tell app "Terminal"', '-e', 'activate', '-e', 'do script "%s"' % ' '.join(script), '-e', 'end tell']) return for terminal in ['x-terminal-emulator', 'xterm', 'gnome-terminal', 'rxvt', 'konsole']: exe = support.find_in_path(terminal) if exe: if terminal == 'gnome-terminal': flag = '-x' else: flag = '-e' subprocess.Popen([terminal, flag, '0launch', '--', uri]) break else: box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator")) box.run() box.destroy() else: subprocess.Popen(['0launch', '--', uri])
def _run_gpg(args, **kwargs): global _gnupg_options if _gnupg_options is None: gpg_path = find_in_path('gpg') or find_in_path('gpg2') or 'gpg' _gnupg_options = [gpg_path, '--no-secmem-warning'] if hasattr(os, 'geteuid') and os.geteuid() == 0 and 'GNUPGHOME' not in os.environ: _gnupg_options += ['--homedir', os.path.join(basedir.home, '.gnupg')] info(_("Running as root, so setting GnuPG home to %s"), _gnupg_options[-1]) return subprocess.Popen(_gnupg_options + args, **kwargs)
def _run_gpg(args, **kwargs): global _gnupg_options if _gnupg_options is None: gpg_path = find_in_path('gpg') or find_in_path('gpg2') or 'gpg' _gnupg_options = [gpg_path, '--no-secmem-warning', '--preserve-permissions', '--no-permission-warning'] if hasattr(os, 'geteuid') and os.geteuid() == 0 and 'GNUPGHOME' not in os.environ: _gnupg_options += ['--homedir', os.path.join(basedir.home, '.gnupg')] info(_("Running as root, so setting GnuPG home to %s"), _gnupg_options[-1]) return subprocess.Popen(_gnupg_options + args, **kwargs)
def _run_gpg(args, **kwargs): """@type args: [str] @rtype: subprocess.Popen""" global _gnupg_options if _gnupg_options is None: gpg_path = os.environ.get('ZEROINSTALL_GPG') or find_in_path('gpg') or find_in_path('gpg2') or 'gpg' _gnupg_options = [gpg_path, '--no-secmem-warning'] if hasattr(os, 'geteuid') and os.geteuid() == 0 and 'GNUPGHOME' not in os.environ: _gnupg_options += ['--homedir', os.path.join(basedir.home, '.gnupg')] logger.info(_("Running as root, so setting GnuPG home to %s"), _gnupg_options[-1]) return subprocess.Popen(_gnupg_options + args, universal_newlines = True, **kwargs)
def canonical_iface_uri(uri): """If uri is a relative path, convert to an absolute one. A "file:///foo" URI is converted to "/foo". An "alias:prog" URI expands to the URI in the 0alias script Otherwise, return it unmodified. @rtype: str @raise SafeException: if uri isn't valid """ if uri.startswith('http://') or uri.startswith('https://'): if uri.count("/") < 3: raise SafeException(_("Missing / after hostname in URI '%s'") % uri) return uri elif uri.startswith('file:///'): path = uri[7:] elif uri.startswith('file:'): if uri[5] == '/': raise SafeException(_('Use file:///path for absolute paths, not {uri}').format(uri = uri)) path = os.path.abspath(uri[5:]) elif uri.startswith('alias:'): from zeroinstall import alias alias_prog = uri[6:] if not os.path.isabs(alias_prog): full_path = support.find_in_path(alias_prog) if not full_path: raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog) else: full_path = alias_prog return alias.parse_script(full_path).uri else: path = os.path.realpath(uri) if os.path.isfile(path): return path if '/' not in uri: alias_path = support.find_in_path(uri) if alias_path is not None: from zeroinstall import alias try: alias.parse_script(alias_path) except alias.NotAnAliasScript: pass else: raise SafeException(_("Bad interface name '{uri}'.\n" "(hint: try 'alias:{uri}' instead)".format(uri = uri))) raise SafeException(_("Bad interface name '%(uri)s'.\n" "(doesn't start with 'http:', and " "doesn't exist as a local file '%(interface_uri)s' either)") % {'uri': uri, 'interface_uri': path})
def _0install_man(config, command): from zeroinstall import apps, alias, helpers path = support.find_in_path(command) if not path: return None with open(path, 'rt') as stream: app_info = apps.parse_script_header(stream) if app_info: app = config.app_mgr.lookup_app(app_info.name) sels = app.get_selections() main = None else: alias_info = alias.parse_script_header(stream) if alias_info is None: return None sels = helpers.ensure_cached(alias_info.uri, alias_info.command, config=config) if not sels: # Cancelled by user sys.exit(1) main = alias_info.main helpers.exec_man(config.stores, sels, main, fallback_name=command) assert 0
def _add_with_helper(self, required_digest, path): """Use 0store-secure-add to copy 'path' to the system store. @param required_digest: the digest for path @type required_digest: str @param path: root of implementation directory structure @type path: str @return: True iff the directory was copied into the system cache successfully """ if required_digest.startswith('sha1='): return False # Old digest alg not supported helper = support.find_in_path('0store-secure-add-helper') if not helper: logger.info(_("'0store-secure-add-helper' command not found. Not adding to system cache.")) return False import subprocess env = os.environ.copy() env['ENV_NOT_CLEARED'] = 'Unclean' # (warn about insecure configurations) env['HOME'] = 'Unclean' # (warn about insecure configurations) dev_null = os.open(os.devnull, os.O_RDONLY) try: logger.info(_("Trying to add to system cache using %s"), helper) child = subprocess.Popen([helper, required_digest], stdin = dev_null, cwd = path, env = env) exit_code = child.wait() finally: os.close(dev_null) if exit_code: logger.warn(_("0store-secure-add-helper failed.")) return False logger.info(_("Added succcessfully.")) return True
def _0install_man(config, command): """@type command: str""" from zeroinstall import apps, alias, helpers path = support.find_in_path(command) if not path: return None try: with open(path, "rt") as stream: app_info = apps.parse_script_header(stream) if app_info: app = config.app_mgr.lookup_app(app_info.name) sels = app.get_selections() main = None else: alias_info = alias.parse_script_header(stream) if alias_info is None: return None sels = helpers.ensure_cached(alias_info.uri, alias_info.command, config=config) if not sels: # Cancelled by user sys.exit(1) main = alias_info.main except IOError as ex: logger.info("%s: falling back to `man %s`", ex, command) os.execlp("man", "man", command) sys.exit(1) helpers.exec_man(config.stores, sels, main, fallback_name=command) assert 0
def _0install_man(config, command): """@type command: str""" from zeroinstall import apps, alias, helpers path = support.find_in_path(command) if not path: return None try: with open(path, 'rt') as stream: app_info = apps.parse_script_header(stream) if app_info: app = config.app_mgr.lookup_app(app_info.name) sels = app.get_selections() main = None else: alias_info = alias.parse_script_header(stream) if alias_info is None: return None sels = helpers.ensure_cached(alias_info.uri, alias_info.command, config=config) if not sels: # Cancelled by user sys.exit(1) main = alias_info.main except IOError as ex: logger.info("%s: falling back to `man %s`", ex, command) os.execlp('man', 'man', command) sys.exit(1) helpers.exec_man(config.stores, sels, main, fallback_name=command) assert 0
def run(uri, args, prog_args): print "Running program..." if copied_0launch_in_cache: launch = os.path.join(copied_0launch_in_cache, '0launch') else: launch = find_in_path('0launch') os.execv(launch, [launch] + args + [uri] + prog_args)
def _add_with_helper(self, required_digest, path): """Use 0store-secure-add to copy 'path' to the system store. @param required_digest: the digest for path @type required_digest: str @param path: root of implementation directory structure @type path: str @return: True iff the directory was copied into the system cache successfully """ if required_digest.startswith('sha1='): return False # Old digest alg not supported helper = support.find_in_path('0store-secure-add-helper') if not helper: info(_("'0store-secure-add-helper' command not found. Not adding to system cache.")) return False import subprocess env = os.environ.copy() env['ENV_NOT_CLEARED'] = 'Unclean' # (warn about insecure configurations) env['HOME'] = 'Unclean' # (warn about insecure configurations) dev_null = os.open(os.devnull, os.O_RDONLY) try: info(_("Trying to add to system cache using %s"), helper) child = subprocess.Popen([helper, required_digest], stdin = dev_null, cwd = path, env = env) exit_code = child.wait() finally: os.close(dev_null) if exit_code: warn(_("0store-secure-add-helper failed.")) return False info(_("Added succcessfully.")) return True
def _0install_man(config, command): from zeroinstall import apps, alias, helpers path = support.find_in_path(command) if not path: return None with open(path, 'rt') as stream: app_info = apps.parse_script_header(stream) if app_info: app = config.app_mgr.lookup_app(app_info.name) sels = app.get_selections() main = None else: alias_info = alias.parse_script_header(stream) if alias_info is None: return None sels = helpers.ensure_cached(alias_info.uri, alias_info.command, config = config) if not sels: # Cancelled by user sys.exit(1) main = alias_info.main helpers.exec_man(config.stores, sels, main, fallback_name = command) assert 0
def canonical_iface_uri(uri): """If uri is a relative path, convert to an absolute one. A "file:///foo" URI is converted to "/foo". An "alias:prog" URI expands to the URI in the 0alias script Otherwise, return it unmodified. @rtype: str @raise SafeException: if uri isn't valid """ if uri.startswith('http://') or uri.startswith('https://'): if uri.count("/") < 3: raise SafeException(_("Missing / after hostname in URI '%s'") % uri) return uri elif uri.startswith('file:///'): return uri[7:] elif uri.startswith('alias:'): from zeroinstall import alias, support alias_prog = uri[6:] if not os.path.isabs(alias_prog): full_path = support.find_in_path(alias_prog) if not full_path: raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog) else: full_path = alias_prog interface_uri, main = alias.parse_script(full_path) return interface_uri else: iface_uri = os.path.realpath(uri) if os.path.isfile(iface_uri): return iface_uri raise SafeException(_("Bad interface name '%(uri)s'.\n" "(doesn't start with 'http:', and " "doesn't exist as a local file '%(interface_uri)s' either)") % {'uri': uri, 'interface_uri': iface_uri})
def _run_gpg(args, **kwargs): global _gnupg_options if _gnupg_options is None: gpg_path = os.environ.get('ZEROINSTALL_GPG') or find_in_path( 'gpg') or find_in_path('gpg2') or 'gpg' _gnupg_options = [gpg_path, '--no-secmem-warning'] if hasattr(os, 'geteuid') and os.geteuid( ) == 0 and 'GNUPGHOME' not in os.environ: _gnupg_options += [ '--homedir', os.path.join(basedir.home, '.gnupg') ] logger.info(_("Running as root, so setting GnuPG home to %s"), _gnupg_options[-1]) return subprocess.Popen(_gnupg_options + args, universal_newlines=True, **kwargs)
def _exec_maybe_sandboxed(writable, prog, *args): """execlp prog, with (only) the 'writable' directory writable if sandboxing is available. If no sandbox is available, run without a sandbox.""" prog_path = find_in_path(prog) if not prog_path: raise Exception(_("'%s' not found in $PATH") % prog) if _pola_run is None: os.execlp(prog_path, prog_path, *args) # We have pola-shell :-) pola_args = ['--prog', prog_path, '-f', '/'] for a in args: pola_args += ['-a', a] if writable: pola_args += ['-fw', writable] os.execl(_pola_run, _pola_run, *pola_args)
def check_type_ok(mime_type): """Check we have the needed software to extract from an archive of the given type. @type mime_type: str @raise SafeException: if the needed software is not available""" assert mime_type if mime_type == 'application/x-rpm': if not find_in_path('rpm2cpio'): raise SafeException( _("This package looks like an RPM, but you don't have the rpm2cpio command " "I need to extract it. Install the 'rpm' package first (this works even if " "you're on a non-RPM-based distribution such as Debian).")) elif mime_type == 'application/x-deb': if not find_in_path('ar'): raise SafeException( _("This package looks like a Debian package, but you don't have the 'ar' command " "I need to extract it. Install the package containing it (sometimes called 'binutils') " "first. This works even if you're on a non-Debian-based distribution such as Red Hat)." )) elif mime_type == 'application/x-bzip-compressed-tar': pass # We'll fall back to Python's built-in tar.bz2 support elif mime_type == 'application/zip': if not find_in_path('unzip'): raise SafeException( _("This package looks like a zip-compressed archive, but you don't have the 'unzip' command " "I need to extract it. Install the package containing it first." )) elif mime_type == 'application/vnd.ms-cab-compressed': if not find_in_path('cabextract'): raise SafeException( _("This package looks like a Microsoft Cabinet archive, but you don't have the 'cabextract' command " "I need to extract it. Install the package containing it first." )) elif mime_type == 'application/x-apple-diskimage': if not find_in_path('hdiutil'): raise SafeException( _("This package looks like a Apple Disk Image, but you don't have the 'hdiutil' command " "I need to extract it.")) elif mime_type == 'application/x-lzma-compressed-tar': pass # We can get it through Zero Install elif mime_type == 'application/x-xz-compressed-tar': if not find_in_path('unxz'): raise SafeException( _("This package looks like a xz-compressed package, but you don't have the 'unxz' command " "I need to extract it. Install the package containing it (it's probably called 'xz-utils') " "first.")) elif mime_type in ('application/x-compressed-tar', 'application/x-tar', 'application/x-ruby-gem'): pass else: from zeroinstall import version raise SafeException( _("Unsupported archive type '%(type)s' (for injector version %(version)s)" ) % { 'type': mime_type, 'version': version })
def _add_with_helper(self, required_digest, path, dry_run): """Use 0store-secure-add to copy 'path' to the system store. @param required_digest: the digest for path @type required_digest: str @param path: root of implementation directory structure @type path: str @return: True iff the directory was copied into the system cache successfully""" if required_digest.startswith('sha1='): return False # Old digest alg not supported if os.environ.get('ZEROINSTALL_PORTABLE_BASE'): return False # Can't use helper with portable mode helper = support.find_in_path('0store-secure-add-helper') if not helper: logger.info( _("'0store-secure-add-helper' command not found. Not adding to system cache." )) return False if dry_run: print( _("[dry-run] would use {helper} to store {required_digest} in system store" ).format(helper=helper, required_digest=required_digest)) self.dry_run_names.add(required_digest) return True import subprocess env = os.environ.copy() env['ENV_NOT_CLEARED'] = 'Unclean' # (warn about insecure configurations) env['HOME'] = 'Unclean' # (warn about insecure configurations) dev_null = os.open(os.devnull, os.O_RDONLY) try: logger.info(_("Trying to add to system cache using %s"), helper) child = subprocess.Popen([helper, required_digest], stdin=dev_null, cwd=path, env=env) exit_code = child.wait() finally: os.close(dev_null) if exit_code: logger.warning(_("0store-secure-add-helper failed.")) return False logger.info(_("Added succcessfully.")) return True
def action_run(self, uri): iface = self.iface_cache.get_interface(uri) reader.update_from_cache(iface) if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')): for terminal in ['xterm', 'gnome-terminal', 'rxvt', 'konsole']: exe = support.find_in_path(terminal) if exe: if terminal == 'gnome-terminal': flag = '-x' else: flag = '-e' subprocess.Popen([terminal, flag, '0launch', '--', uri]) break else: box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Can't find a suitable terminal emulator")) box.run() box.destroy() else: subprocess.Popen(['0launch', '--', uri])
def _add_with_helper(self, required_digest, path, dry_run): """Use 0store-secure-add to copy 'path' to the system store. @param required_digest: the digest for path @type required_digest: str @param path: root of implementation directory structure @type path: str @return: True iff the directory was copied into the system cache successfully """ if required_digest.startswith('sha1='): return False # Old digest alg not supported if os.environ.get('ZEROINSTALL_PORTABLE_BASE'): return False # Can't use helper with portable mode helper = support.find_in_path('0store-secure-add-helper') if not helper: logger.info(_("'0store-secure-add-helper' command not found. Not adding to system cache.")) return False if dry_run: print(_("[dry-run] would use {helper} to store {required_digest} in system store").format( helper = helper, required_digest = required_digest)) self.dry_run_names.add(required_digest) return True import subprocess env = os.environ.copy() env['ENV_NOT_CLEARED'] = 'Unclean' # (warn about insecure configurations) env['HOME'] = 'Unclean' # (warn about insecure configurations) dev_null = os.open(os.devnull, os.O_RDONLY) try: logger.info(_("Trying to add to system cache using %s"), helper) child = subprocess.Popen([helper, required_digest], stdin = dev_null, cwd = path, env = env) exit_code = child.wait() finally: os.close(dev_null) if exit_code: logger.warning(_("0store-secure-add-helper failed.")) return False logger.info(_("Added succcessfully.")) return True
def spawn_maybe_sandboxed(readable, writable, tmpdir, prog, args): """spawn prog, with (only) the 'writable' directories writable if sandboxing is available. The readable directories will be readable, as well as various standard locations. If no sandbox is available, run without a sandbox.""" USE_PLASH = 'USE_PLASH_0COMPILE' assert os.path.isabs(prog) _pola_run = find_in_path('pola-run') if _pola_run is None: #print "Not using sandbox (plash not installed)" use_plash = False else: use_plash = os.environ.get(USE_PLASH, '').lower() or 'not set' if use_plash in ('not set', 'false'): print "Not using plash: $%s is %s" % (USE_PLASH, use_plash) use_plash = False elif use_plash == 'true': use_plash = True else: raise Exception('$%s must be "true" or "false", not "%s"' % (USE_PLASH, use_plash)) if not use_plash: return subprocess.Popen([prog] + args) print "Using plash to sandbox the build..." # We have pola-shell :-) pola_args = ['--prog', prog, '-B'] for a in args: pola_args += ['-a', a] for r in readable: pola_args += ['-f', r] for w in writable: pola_args += ['-fw', w] pola_args += ['-tw', '/tmp', tmpdir] os.environ['TMPDIR'] = '/tmp' return subprocess.Popen([_pola_run] + pola_args)
def canonical_iface_uri(uri): """If uri is a relative path, convert to an absolute one. A "file:///foo" URI is converted to "/foo". An "alias:prog" URI expands to the URI in the 0alias script Otherwise, return it unmodified. @rtype: str @raise SafeException: if uri isn't valid """ if uri.startswith('http://') or uri.startswith('https://'): if uri.count("/") < 3: raise SafeException(_("Missing / after hostname in URI '%s'") % uri) return uri elif uri.startswith('file:///'): path = uri[7:] elif uri.startswith('file:'): if uri[5] == '/': raise SafeException(_('Use file:///path for absolute paths, not {uri}').format(uri = uri)) path = os.path.abspath(uri[5:]) elif uri.startswith('alias:'): from zeroinstall import alias, support alias_prog = uri[6:] if not os.path.isabs(alias_prog): full_path = support.find_in_path(alias_prog) if not full_path: raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog) else: full_path = alias_prog return alias.parse_script(full_path).uri else: path = os.path.realpath(uri) # Might be local sweet directory if os.path.exists(path): return path raise SafeException(_("Bad interface name '%(uri)s'.\n" "(doesn't start with 'http:', and " "doesn't exist as a local file '%(interface_uri)s' either)") % {'uri': uri, 'interface_uri': path})
def check_type_ok(mime_type): """Check we have the needed software to extract from an archive of the given type. @type mime_type: str @raise SafeException: if the needed software is not available""" assert mime_type if mime_type == 'application/x-rpm': if not find_in_path('rpm2cpio'): raise SafeException(_("This package looks like an RPM, but you don't have the rpm2cpio command " "I need to extract it. Install the 'rpm' package first (this works even if " "you're on a non-RPM-based distribution such as Debian).")) elif mime_type == 'application/x-deb': if not find_in_path('ar'): raise SafeException(_("This package looks like a Debian package, but you don't have the 'ar' command " "I need to extract it. Install the package containing it (sometimes called 'binutils') " "first. This works even if you're on a non-Debian-based distribution such as Red Hat).")) elif mime_type == 'application/x-bzip-compressed-tar': pass # We'll fall back to Python's built-in tar.bz2 support elif mime_type == 'application/zip': if not find_in_path('unzip'): raise SafeException(_("This package looks like a zip-compressed archive, but you don't have the 'unzip' command " "I need to extract it. Install the package containing it first.")) elif mime_type == 'application/vnd.ms-cab-compressed': if not find_in_path('cabextract'): raise SafeException(_("This package looks like a Microsoft Cabinet archive, but you don't have the 'cabextract' command " "I need to extract it. Install the package containing it first.")) elif mime_type == 'application/x-apple-diskimage': if not find_in_path('hdiutil'): raise SafeException(_("This package looks like a Apple Disk Image, but you don't have the 'hdiutil' command " "I need to extract it.")) elif mime_type == 'application/x-lzma-compressed-tar': pass # We can get it through Zero Install elif mime_type == 'application/x-xz-compressed-tar': if not find_in_path('unxz'): raise SafeException(_("This package looks like a xz-compressed package, but you don't have the 'unxz' command " "I need to extract it. Install the package containing it (it's probably called 'xz-utils') " "first.")) elif mime_type in ('application/x-compressed-tar', 'application/x-tar', 'application/x-ruby-gem'): pass else: from zeroinstall import version raise SafeException(_("Unsupported archive type '%(type)s' (for injector version %(version)s)") % {'type': mime_type, 'version': version})
def get_gpg(): return find_in_path('gpg') or find_in_path('gpg2')
def extract_tar(stream, destdir, extract, decompress, start_offset = 0): if extract: # Limit the characters we accept, to avoid sending dodgy # strings to tar if not re.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract): raise SafeException(_('Illegal character in extract attribute')) assert decompress in [None, 'bzip2', 'gzip', 'lzma', 'xz'] if _gnu_tar(): ext_cmd = ['tar'] if decompress: if decompress == 'bzip2': ext_cmd.append('--bzip2') elif decompress == 'gzip': ext_cmd.append('-z') elif decompress == 'lzma': unlzma = find_in_path('unlzma') if not unlzma: unlzma = os.path.abspath(os.path.join(os.path.dirname(__file__), '_unlzma')) ext_cmd.append('--use-compress-program=' + unlzma) elif decompress == 'xz': unxz = find_in_path('unxz') if not unxz: unxz = os.path.abspath(os.path.join(os.path.dirname(__file__), '_unxz')) ext_cmd.append('--use-compress-program=' + unxz) if recent_gnu_tar(): ext_cmd.extend(('-x', '--no-same-owner', '--no-same-permissions')) else: ext_cmd.extend(('xf', '-')) if extract: ext_cmd.append(extract) _extract(stream, destdir, ext_cmd, start_offset) else: import tempfile # Since we don't have GNU tar, use python's tarfile module. This will probably # be a lot slower and we do not support lzma and xz; however, it is portable. # (lzma and xz are handled by first uncompressing stream to a temporary file. # this is simple to do, but less efficient than piping through the program) if decompress is None: rmode = 'r|' elif decompress == 'bzip2': rmode = 'r|bz2' elif decompress == 'gzip': rmode = 'r|gz' elif decompress == 'lzma': unlzma = find_in_path('unlzma') if not unlzma: unlzma = os.path.abspath(os.path.join(os.path.dirname(__file__), '_unlzma')) temp = tempfile.NamedTemporaryFile(suffix='.tar', mode='w+b') subprocess.check_call((unlzma), stdin=stream, stdout=temp) rmode = 'r|' stream = temp elif decompress == 'xz': unxz = find_in_path('unxz') if not unxz: unxz = os.path.abspath(os.path.join(os.path.dirname(__file__), '_unxz')) temp = tempfile.NamedTemporaryFile(suffix='.tar', mode='w+b') subprocess.check_call((unxz), stdin=stream, stdout=temp) rmode = 'r|' stream = temp else: raise SafeException(_('GNU tar unavailable; unsupported compression format: %s') % decompress) import tarfile stream.seek(start_offset) # Python 2.5.1 crashes if name is None; see Python bug #1706850 tar = tarfile.open(name = '', mode = rmode, fileobj = stream) current_umask = os.umask(0) os.umask(current_umask) uid = gid = None try: uid = os.geteuid() gid = os.getegid() except: logger.debug(_("Can't get uid/gid")) def chmod_extract(tarinfo): # If any X bit is set, they all must be if tarinfo.mode & 0o111: tarinfo.mode |= 0o111 # Everyone gets read and write (subject to the umask) # No special bits are allowed. tarinfo.mode = ((tarinfo.mode | 0o666) & ~current_umask) & 0o777 # Don't change owner, even if run as root if uid: tarinfo.uid = uid if gid: tarinfo.gid = gid tar.extract(tarinfo, destdir) extracted_anything = False ext_dirs = [] for tarinfo in tar: if extract is None or \ tarinfo.name.startswith(extract + '/') or \ tarinfo.name == extract: if tarinfo.isdir(): ext_dirs.append(tarinfo) chmod_extract(tarinfo) extracted_anything = True # Due to a bug in tarfile (python versions < 2.5), we have to manually # set the mtime of each directory that we extract after extracting everything. for tarinfo in ext_dirs: dirname = os.path.join(destdir, tarinfo.name) os.utime(dirname, (tarinfo.mtime, tarinfo.mtime)) tar.close() if extract and not extracted_anything: raise SafeException(_('Unable to find specified file = %s in archive') % extract)
def extract_tar(stream, destdir, extract, decompress, start_offset=0): if extract: # Limit the characters we accept, to avoid sending dodgy # strings to tar if not re.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract): raise SafeException(_('Illegal character in extract attribute')) assert decompress in [None, 'bzip2', 'gzip', 'lzma', 'xz'] if _gnu_tar(): ext_cmd = ['tar'] if decompress: if decompress == 'bzip2': ext_cmd.append('--bzip2') elif decompress == 'gzip': ext_cmd.append('-z') elif decompress == 'lzma': unlzma = find_in_path('unlzma') if not unlzma: unlzma = os.path.abspath( os.path.join(os.path.dirname(__file__), '_unlzma')) ext_cmd.append('--use-compress-program=' + unlzma) elif decompress == 'xz': ext_cmd.append('--use-compress-program=unxz') if recent_gnu_tar(): ext_cmd.extend(('-x', '--no-same-owner', '--no-same-permissions')) else: ext_cmd.extend(('xf', '-')) if extract: ext_cmd.append(extract) _extract(stream, destdir, ext_cmd, start_offset) else: # Since we don't have GNU tar, use python's tarfile module. This will probably # be a lot slower and we do not support lzma and xz; however, it is portable. if decompress is None: rmode = 'r|' elif decompress == 'bzip2': rmode = 'r|bz2' elif decompress == 'gzip': rmode = 'r|gz' else: raise SafeException( _('GNU tar unavailable; unsupported compression format: %s') % decompress) import tarfile stream.seek(start_offset) # Python 2.5.1 crashes if name is None; see Python bug #1706850 tar = tarfile.open(name='', mode=rmode, fileobj=stream) current_umask = os.umask(0) os.umask(current_umask) uid = gid = None try: uid = os.geteuid() gid = os.getegid() except: debug(_("Can't get uid/gid")) def chmod_extract(tarinfo): # If any X bit is set, they all must be if tarinfo.mode & 0111: tarinfo.mode |= 0111 # Everyone gets read and write (subject to the umask) # No special bits are allowed. tarinfo.mode = ((tarinfo.mode | 0666) & ~current_umask) & 0777 # Don't change owner, even if run as root if uid: tarinfo.uid = uid if gid: tarinfo.gid = gid tar.extract(tarinfo, destdir) extracted_anything = False ext_dirs = [] for tarinfo in tar: if extract is None or \ tarinfo.name.startswith(extract + '/') or \ tarinfo.name == extract: if tarinfo.isdir(): ext_dirs.append(tarinfo) chmod_extract(tarinfo) extracted_anything = True # Due to a bug in tarfile (python versions < 2.5), we have to manually # set the mtime of each directory that we extract after extracting everything. for tarinfo in ext_dirs: dirname = os.path.join(destdir, tarinfo.name) os.utime(dirname, (tarinfo.mtime, tarinfo.mtime)) tar.close() if extract and not extracted_anything: raise SafeException( _('Unable to find specified file = %s in archive') % extract)
class AbstractTestUnpack(): def setUp(self): BaseTest.setUp(self) self.tmpdir = tempfile.mkdtemp('-testunpack') os.umask(0o022) def tearDown(self): BaseTest.tearDown(self) support.ro_rmtree(self.tmpdir) assert os.umask(0o022) == 0o022 def testBadExt(self): try: with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.foo', stream, self.tmpdir) assert False except SafeException as ex: assert 'Unknown extension' in str(ex) def testTgz(self): with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir) self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') @skipIf(sys.getfilesystemencoding().lower() != "utf-8", "tar only unpacks to utf-8") def testNonAsciiTgz(self): with open('unicode.tar.gz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir) self.assert_manifest( 'sha1new=e42ffed02179169ef2fa14a46b0d9aea96a60c10') @skipIf(not find_in_path('hdiutil'), "not running on MacOS X; no hdiutil") def testDmg(self): with open('HelloWorld.dmg', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.dmg', stream, self.tmpdir) self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') def testZip(self): with open('HelloWorld.zip', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.zip', stream, self.tmpdir) self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') def testExtract(self): with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir, extract='HelloWorld') self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') @skipIf(sys.getfilesystemencoding().lower() != "utf-8", "tar only unpacks to utf-8") def testExtractNonAscii(self): with open('unicode.tar.gz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir, extract=b'unicode'.decode('ascii')) self.assert_manifest('sha1=af2d132f5f15532bbf041b59414d08c8bc1a616e') def testExtractOver(self): with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive_over('ftp://foo/file.tgz', stream, self.tmpdir, extract='HelloWorld') self.assert_manifest('sha1=491678c37f77fadafbaae66b13d48d237773a68f') def testExtractZip(self): with open('HelloWorld.zip', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.zip', stream, self.tmpdir, extract='HelloWorld') self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') def testExtractIllegal(self): try: with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir, extract='Hello`World`') assert False except SafeException as ex: assert 'Illegal' in str(ex) def testExtractFails(self): stderr = os.dup(2) try: null = os.open(os.devnull, os.O_WRONLY) os.close(2) os.dup2(null, 2) try: with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tgz', stream, self.tmpdir, extract='HelloWorld2') assert False except SafeException as ex: if ('Failed to extract' not in str(ex) and # GNU tar 'Unable to find' not in str(ex)): # Python tar raise ex finally: os.dup2(stderr, 2) def testTargz(self): with open('HelloWorld.tgz', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tar.GZ', stream, self.tmpdir) self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') def testTbz(self): with open('HelloWorld.tar.bz2', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tar.bz2', stream, self.tmpdir) self.assert_manifest('sha1=3ce644dc725f1d21cfcf02562c76f375944b266a') def testTar(self): with open('HelloWorld.tar', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.tar', stream, self.tmpdir) self.assert_manifest( 'sha1new=290eb133e146635fe37713fd58174324a16d595f') @skipIf(not find_in_path('rpm2cpio'), "not running; no rpm2cpio") def testRPM(self): with open('dummy-1-1.noarch.rpm', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.rpm', stream, self.tmpdir) self.assert_manifest('sha1=7be9228c8fe2a1434d4d448c4cf130e3c8a4f53d') def testDeb(self): with open('dummy_1-1_all.deb', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.deb', stream, self.tmpdir) self.assert_manifest( 'sha1new=2c725156ec3832b7980a3de2270b3d8d85d4e3ea') def testGem(self): with open('hello-0.1.gem', 'rb') as stream: unpack.unpack_archive('ftp://foo/file.gem', stream, self.tmpdir) self.assert_manifest( 'sha1new=fbd4827be7a18f9821790bdfd83132ee60d54647') def testSpecial(self): os.chmod(self.tmpdir, 0o2755) store = Store(self.tmpdir) with open('HelloWorld.tgz', 'rb') as stream: store.add_archive_to_cache( 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266a', stream, 'http://foo/foo.tgz') def testBad(self): logging.getLogger('').setLevel(logging.ERROR) store = Store(self.tmpdir) try: with open('HelloWorld.tgz', 'rb') as stream: store.add_archive_to_cache( 'sha1=3ce644dc725f1d21cfcf02562c76f375944b266b', stream, 'http://foo/foo.tgz') assert 0 except BadDigest: pass logging.getLogger('').setLevel(logging.INFO) def assert_manifest(self, required): alg_name = required.split('=', 1)[0] manifest.fixup_permissions(self.tmpdir) sha1 = alg_name + '=' + manifest.add_manifest_file( self.tmpdir, manifest.get_algorithm(alg_name)).hexdigest() self.assertEqual(sha1, required) # Check permissions are sensible for root, dirs, files in os.walk(self.tmpdir): for f in files + dirs: full = os.path.join(root, f) if os.path.islink(full): continue full_mode = os.stat(full).st_mode self.assertEqual(0o444, full_mode & 0o666) # Must be r-?r-?r-?
support.ro_rmtree(self.tmpdir) assert os.umask(0022) == 0022 def testBadExt(self): try: unpack.unpack_archive("ftp://foo/file.foo", file("HelloWorld.tgz"), self.tmpdir) assert False except SafeException, ex: assert "Unknown extension" in str(ex) def testTgz(self): unpack.unpack_archive("ftp://foo/file.tgz", file("HelloWorld.tgz"), self.tmpdir) self.assert_manifest("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a") @skipIf(not find_in_path("hdiutil"), "not running on MacOS X; no hdiutil") def testDmg(self): unpack.unpack_archive("ftp://foo/file.dmg", file("HelloWorld.dmg"), self.tmpdir) self.assert_manifest("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a") def testZip(self): unpack.unpack_archive("ftp://foo/file.zip", file("HelloWorld.zip"), self.tmpdir) self.assert_manifest("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a") def testExtract(self): unpack.unpack_archive("ftp://foo/file.tgz", file("HelloWorld.tgz"), self.tmpdir, extract="HelloWorld") self.assert_manifest("sha1=3ce644dc725f1d21cfcf02562c76f375944b266a") def testExtractOver(self): unpack.unpack_archive_over("ftp://foo/file.tgz", file("HelloWorld.tgz"), self.tmpdir, extract="HelloWorld") self.assert_manifest("sha1=491678c37f77fadafbaae66b13d48d237773a68f")