def get_http_size(url, ttl=1): assert url.lower().startswith('http://') address = urlparse.urlparse(url) http = httplib.HTTPConnection(host(address), port(address) or 80) parts = url.split('/', 3) if len(parts) == 4: path = parts[3] else: path = '' http.request('HEAD', '/' + path, headers={'Host': host(address)}) response = http.getresponse() try: if response.status == 200: return response.getheader('Content-Length') elif response.status in (301, 302): new_url_rel = response.getheader('Location') or response.getheader( 'URI') new_url = urlparse.urljoin(url, new_url_rel) else: raise SafeException("HTTP error: got status code %s" % response.status) finally: response.close() if ttl: info("Resource moved! Checking new URL %s" % new_url) assert new_url return get_http_size(new_url, ttl - 1) else: raise SafeException('Too many redirections.')
def generate_public_xml(config, source_xml_path): """Load source_xml_path and expand any relative URLs.""" try: with open(source_xml_path, 'rb') as stream: doc = minidom.parse(stream) except: print("Failed to process %s" % (source_xml_path)) raise root = doc.documentElement declared_iface = root.getAttribute('uri') if not declared_iface: raise SafeException("Feed '{path}' missing 'uri' attribute on root".format(path = source_xml_path)) if not declared_iface.startswith(config.REPOSITORY_BASE_URL): raise SafeException("Feed '{path}' declares uri='{uri}', which is not under REPOSITORY_BASE_URL ({base})".format( path = source_xml_path, uri = declared_iface, base = config.REPOSITORY_BASE_URL)) rel_uri = declared_iface[len(config.REPOSITORY_BASE_URL):] expected_path = join('feeds', config.get_feeds_rel_path(rel_uri)) if expected_path != source_xml_path: raise SafeException("Feed '{path}' with uri='{uri}' should be located at '{expected_path}'".format( path = source_xml_path, uri = declared_iface, expected_path = expected_path)) expand_relative_urls(config, root) return doc
def handle(config, options, args): if len(args) == 0: if options.gui is None and os.environ.get('DISPLAY', None): options.gui = True if options.gui: from zeroinstall import helpers return helpers.get_selections_gui(None, []) else: for key, setting_type in settings.iteritems(): value = getattr(config, key) print(key, "=", setting_type.format(value)) return elif len(args) > 2: raise UsageError() option = args[0] if option not in settings: raise SafeException(_('Unknown option "%s"') % option) if len(args) == 1: value = getattr(config, option) print(settings[option].format(value)) else: value = settings[option].parse(args[1]) if option == 'network_use' and value not in model.network_levels: raise SafeException( _("Must be one of %s") % list(model.network_levels)) setattr(config, option, value) config.save_globals()
def spawn_and_check_maybe_sandboxed(readable, writable, tmpdir, prog, args): child = spawn_maybe_sandboxed(readable, writable, tmpdir, prog, args) status = child.wait() if status > 0: raise SafeException('Command failed with exit status %d' % status) elif status < 0: raise SafeException('Command failed with signal %d' % -status)
def parse_version(version_string): """Convert a version string to an internal representation. The parsed format can be compared quickly using the standard Python functions. - Version := DottedList ("-" Mod DottedList?)* - DottedList := (Integer ("." Integer)*) @rtype: tuple (opaque) @raise SafeException: if the string isn't a valid version @since: 0.24 (moved from L{reader}, from where it is still available):""" if version_string is None: return None parts = _version_re.split(version_string) if parts[-1] == '': del parts[-1] # Ends with a modifier else: parts.append('') if not parts: raise SafeException(_("Empty version string!")) l = len(parts) try: for x in range(0, l, 2): part = parts[x] if part: parts[x] = map(int, parts[x].split('.')) else: parts[x] = [] # (because ''.split('.') == [''], not []) for x in range(1, l, 2): parts[x] = _version_mod_to_value[parts[x]] return parts except ValueError, ex: raise SafeException( _("Invalid version format in '%(version_string)s': %(exception)s") % { 'version_string': version_string, 'exception': ex })
def pick_digest(impl): from zeroinstall.zerostore import manifest, parse_algorithm_digest_pair best = None for digest in impl.digests: alg_name, digest_value = parse_algorithm_digest_pair(digest) alg = manifest.algorithms.get(alg_name, None) if alg and (best is None or best.rating < alg.rating): best = alg required_digest = digest if best is None: if not impl.digests: raise SafeException( "No <manifest-digest> given for '%(implementation)s' version %(version)s" % { 'implementation': impl.feed.get_name(), 'version': impl.get_version() }) raise SafeException( "Unknown digest algorithms '%(algorithms)s' for '%(implementation)s' version %(version)s" % { 'algorithms': impl.digests, 'implementation': impl.feed.get_name(), 'version': impl.get_version() }) return required_digest
def handle(config, options, args): if len(args) == 0: from zeroinstall import helpers if helpers.get_selections_gui(None, [], use_gui = options.gui) == helpers.DontUseGUI: for key, setting_type in settings.items(): value = getattr(config, key) print(key, "=", setting_type.format(value)) # (else we displayed the preferences dialog in the GUI) return elif len(args) > 2: raise UsageError() option = args[0] if option not in settings: raise SafeException(_('Unknown option "%s"') % option) if len(args) == 1: value = getattr(config, option) print(settings[option].format(value)) else: value = settings[option].parse(args[1]) if option == 'network_use' and value not in model.network_levels: raise SafeException(_("Must be one of %s") % list(model.network_levels)) setattr(config, option, value) config.save_globals()
def do_diff(args): """diff""" if args: raise __main__.UsageError() buildenv = BuildEnv() if not os.path.isdir('src'): raise SafeException('No local src directory to diff against!') new_src = os.path.realpath('src') src_impl = buildenv.chosen_impl(buildenv.interface) assert src_impl prog = find_in_path('diff') args = ['-ur', lookup(src_impl), new_src] status = os.spawnv(os.P_WAIT, prog, [prog] + args) if status == 0: return False elif status == 1: return True elif status > 1: raise SafeException("Program '%s' failed with exit code %d" % (prog, status)) elif status < 0: raise SafeException("Program '%s' failed with signal %d" % (prog, -status))
def lookup(uri, missing_ok=False): """Search repositories.json for the repository which hosts 'uri'.""" path = basedir.load_first_config('0install.net', '0repo', 'repositories.json') if path: with open(path, 'rb') as stream: db = json.load(stream) else: db = {} from_registry = None for key, value in db.items(): if uri.startswith(key): if from_registry: raise SafeException( "Multiple matching repositories! {a} and {b}".format( a=from_registry, b=value)) from_registry = value if not from_registry: if missing_ok: return None else: raise SafeException( "No registered repository for {uri} (hint: use '0repo register')" .format(uri=uri)) return from_registry
def _handle_restrictions(self, options): """Gets the list of restrictions specified by the user. Handles --before, --not-before, --version and --version-for options. If a mapping isn't present, then the user didn't specify a restriction. If an entry maps to None, the user wishes to remove the restriction.""" version = options.version interface_uri = self.interface_uri # Convert old --before and --not_before to new --version-for if options.before is not None or options.not_before is not None: if version is not None: raise SafeException( "Can't use --before or --not-before with --version") if options.before or options.not_before: version = (options.not_before or '') + '..' if options.before: version += '!' + options.before else: version = '' # Reset restrictions = dict((uri, (expr or None)) for (uri, expr) in (options.version_for or [])) # Convert the --version short-cut to --version-for if version is not None: if interface_uri in restrictions: raise SafeException( "Can't use --version and --version-for to set {uri}". format(uri=interface_uri)) restrictions[interface_uri] = version or None return restrictions
def unpack_archive(url, data, destdir, extract = None, type = None, start_offset = 0): """Unpack stream 'data' into directory 'destdir'. If extract is given, extract just that sub-directory from the archive (i.e. destdir/extract will exist afterwards). Works out the format from the name.""" if type is None: type = type_from_url(url) if type is None: raise SafeException(_("Unknown extension (and no MIME type given) in '%s'") % url) if type == 'application/x-bzip-compressed-tar': extract_tar(data, destdir, extract, 'bzip2', start_offset) elif type == 'application/x-deb': extract_deb(data, destdir, extract, start_offset) elif type == 'application/x-rpm': extract_rpm(data, destdir, extract, start_offset) elif type == 'application/zip': extract_zip(data, destdir, extract, start_offset) elif type == 'application/x-tar': extract_tar(data, destdir, extract, None, start_offset) elif type == 'application/x-lzma-compressed-tar': extract_tar(data, destdir, extract, 'lzma', start_offset) elif type == 'application/x-xz-compressed-tar': extract_tar(data, destdir, extract, 'xz', start_offset) elif type == 'application/x-compressed-tar': extract_tar(data, destdir, extract, 'gzip', start_offset) elif type == 'application/vnd.ms-cab-compressed': extract_cab(data, destdir, extract, start_offset) elif type == 'application/x-apple-diskimage': extract_dmg(data, destdir, extract, start_offset) elif type == 'application/x-ruby-gem': extract_gem(data, destdir, extract, start_offset) else: raise SafeException(_('Unknown MIME type "%(type)s" for "%(url)s"') % {'type': type, 'url': url})
def canonical_iface_uri(uri): """If uri is a relative path, convert to an absolute one. A "file:///foo" URI is converted to "/foo". 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:] 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 validate_name(name): """@type name: str""" if name == '0install': raise SafeException( "Creating an app called '0install' would cause trouble; try e.g. '00install' instead" ) if valid_name.match(name): return raise SafeException("Invalid application name '{name}'".format(name=name))
def handle(args): if args.key == '-': key = None else: # Get the fingerprint from the key ID (and check we have the secret key) try: keys = subprocess.check_output([ 'gpg', '-q', '--fixed-list-mode', '--fingerprint', '--with-colons', '--list-secret-keys', args.key ], encoding='utf-8') except subprocess.CalledProcessError as ex: raise SafeException("GPG key '{key}' not found ({ex})".format( key=args.key, ex=ex)) in_ssb = False fingerprint = None for line in keys.split('\n'): bits = line.split(':') if bits[0] == 'ssb': in_ssb = True elif bits[0] == 'sec': in_ssb = False elif bits[0] == 'fpr': if in_ssb and fingerprint is not None: pass # Ignore sub-keys (unless we don't have a primary - can that happen?) elif fingerprint is None: fingerprint = bits[9] else: raise SafeException( "Multiple GPG keys match '{key}':\n{output}".format( key=args.key, output=keys)) if fingerprint is None: raise SafeException( "GPG key not found '{key}'".format(key=args.key)) key = '0x' + fingerprint # Create the directory structure os.mkdir(args.path) os.chdir(args.path) os.mkdir('incoming') os.mkdir('feeds') os.mkdir('public') # Write the configuration file, with the GPG key filled in with open(join(topdir, 'resources', '0repo-config.py.template'), 'rt') as stream: data = stream.read() data = data.replace('"{{GPGKEY}}"', '"' + key + '"' if key else "None") with open('0repo-config.py', 'wt') as stream: stream.write(data) # Initialise the Git repository subprocess.check_call(['git', 'init', '-q', 'feeds']) scm.commit('feeds', [], 'Created new repository', key, extra_options=['--allow-empty'])
def unpack_archive_over(url, data, destdir, extract=None, type=None, start_offset=0): """Like unpack_archive, except that we unpack to a temporary directory first and then move things over, checking that we're not following symlinks at each stage. Use this when you want to unpack an unarchive into a directory which already has stuff in it. @since: 0.28""" import stat tmpdir = mkdtemp(dir=destdir) try: mtimes = [] unpack_archive(url, data, tmpdir, extract, type, start_offset) stem_len = len(tmpdir) for root, dirs, files in os.walk(tmpdir): relative_root = root[stem_len + 1:] or '.' target_root = os.path.join(destdir, relative_root) try: info = os.lstat(target_root) except OSError, ex: if ex.errno != 2: raise # Some odd error. # Doesn't exist. OK. os.mkdir(target_root) else: if stat.S_ISLNK(info.st_mode): raise SafeException( _('Attempt to unpack dir over symlink "%s"!') % relative_root) elif not stat.S_ISDIR(info.st_mode): raise SafeException( _('Attempt to unpack dir over non-directory "%s"!') % relative_root) mtimes.append( (relative_root, os.lstat(os.path.join(tmpdir, root)).st_mtime)) for s in dirs: # Symlinks are counted as directories src = os.path.join(tmpdir, relative_root, s) if os.path.islink(src): files.append(s) for f in files: src = os.path.join(tmpdir, relative_root, f) dest = os.path.join(destdir, relative_root, f) if os.path.islink(dest): raise SafeException( _('Attempt to unpack file over symlink "%s"!') % os.path.join(relative_root, f)) os.rename(src, dest) for path, mtime in mtimes[1:]: os.utime(os.path.join(destdir, path), (mtime, mtime))
def get_selections(self, prompt = False): if self._selections: assert not prompt return self._selections selections_file = self.config.get('compile', 'selections') if selections_file: if prompt: raise SafeException("Selections are fixed by %s" % selections_file) stream = file(selections_file) try: self._selections = selections.Selections(qdom.parse(stream)) finally: stream.close() from zeroinstall.injector import handler from zeroinstall.injector.config import load_config if os.isatty(1): h = handler.ConsoleHandler() else: h = handler.Handler() config = load_config(h) blocker = self._selections.download_missing(config) if blocker: print "Waiting for selected implementations to be downloaded..." h.wait_for_blocker(blocker) else: command = install_prog + ['download', '--source', '--xml'] if prompt and '--console' not in install_prog: if os.name == 'nt': command[0] += '-win' command.append('--gui') command.append(self.interface) child = subprocess.Popen(command, stdout = subprocess.PIPE) try: self._selections = selections.Selections(qdom.parse(child.stdout)) finally: if child.wait(): raise SafeException(' '.join(repr(x) for x in command) + " failed (exit code %d)" % child.returncode) self.root_impl = self._selections.selections[self.interface] self.orig_srcdir = os.path.realpath(lookup(self.root_impl)) self.user_srcdir = None if os.path.isdir('src'): self.user_srcdir = os.path.realpath('src') if self.user_srcdir == self.orig_srcdir or \ self.user_srcdir.startswith(os.path.join(self.orig_srcdir, '')) or \ self.orig_srcdir.startswith(os.path.join(self.user_srcdir, '')): info("Ignoring 'src' directory because it coincides with %s", self.orig_srcdir) self.user_srcdir = None return self._selections
def ensure_safe(rel_path): """Ensure path is relative and doesn't contain '.', '..' or other hidden components. Also, leading '-' is disallowed, as it may be confused with an option.""" if isabs(rel_path): raise SafeException("Path {path} not relative".format(path=rel_path)) if rel_path.startswith(".") or "/." in rel_path: raise SafeException( "Path {path} contains a dot-file component".format(path=rel_path)) if rel_path.startswith("-") or "/-" in rel_path: raise SafeException( "A component in {path} starts with '-'".format(path=rel_path)) return rel_path
def get_feed_dir(feed): if '#' in feed: raise SafeException("Invalid URL '%s'" % feed) scheme, rest = feed.split('://', 1) domain, rest = rest.split('/', 1) assert scheme in ( 'http', 'https', 'ftp' ) # Just to check for mal-formed lines; add more as needed for x in [scheme, domain, rest]: if not x or x.startswith('.'): raise SafeException("Invalid URL '%s'" % feed) return os.path.join('feeds', scheme, domain, escape_slashes(rest))
def get_http_size(url, ttl = 3, method = None): address = urllib.parse.urlparse(url) if url.lower().startswith('http://'): http = httplib.HTTPConnection(address.hostname, address.port or 80) elif url.lower().startswith('https://'): http = httplib.HTTPSConnection(address.hostname, address.port or 443) else: assert False, url parts = url.split('/', 3) if len(parts) == 4: path = parts[3] else: path = '' if method is None: if address.hostname.endswith('.s3.amazonaws.com'): method = 'GET' # HEAD doesn't work on S3 due to signature mismatch else: method = 'HEAD' http.request(method, '/' + path, headers = {'Host': address.hostname, 'User-agent': '0repo (http://0install.net/0repo.html)'}) response = http.getresponse() try: if response.status == 200: l = response.getheader('Content-Length') if l is None: if method == "HEAD": print("No Content-Length header returned; requesting whole archive...") return get_http_size(url, ttl, method = "GET") else: return len(response.read()) else: return int(l) elif response.status in (301, 302, 303): new_url_rel = response.getheader('Location') or response.getheader('URI') new_url = urllib.parse.urljoin(url, new_url_rel) else: raise SafeException("HTTP error: got status code %s for %s" % (response.status, url)) finally: response.close() if ttl: print("Moved") print("Checking new URL {}...".format(new_url), end = '') assert new_url return get_http_size(new_url, ttl - 1) else: raise SafeException('Too many redirections.')
def domain_from_url(url): """Extract the trust domain for a URL. @param url: the feed's URL @type url: str @return: the trust domain @rtype: str @since: 0.27 @raise SafeException: the URL can't be parsed""" import urlparse if os.path.isabs(url): raise SafeException(_("Can't get domain from a local path: '%s'") % url) domain = urlparse.urlparse(url)[1] if domain and domain != '*': return domain raise SafeException(_("Can't extract domain from URL '%s'") % url)
def integrate_shell(self, name): # TODO: remember which commands we create if not valid_name.match(name): raise SafeException( "Invalid shell command name '{name}'".format(name=name)) bin_dir = find_bin_dir() launcher = os.path.join(bin_dir, name) if os.path.exists(launcher): raise SafeException( "Command already exists: {path}".format(path=launcher)) with open(launcher, 'w') as stream: stream.write(_command_template.format(app=self.get_name())) # Make new script executable os.chmod(launcher, 0o111 | os.fstat(stream.fileno()).st_mode)
def release_without_0repo(archive_file, new_impls_feed): assert options.master_feed_file if not options.archive_dir_public_url: raise SafeException("Archive directory public URL is not set! Edit configuration and try again.") if status.updated_master_feed: print "Already added to master feed. Not changing." else: publish_opts = {} if os.path.exists(options.master_feed_file): # Check we haven't already released this version master = support.load_feed(os.path.realpath(options.master_feed_file)) existing_releases = [impl for impl in master.implementations.values() if impl.get_version() == status.release_version] if len(existing_releases): raise SafeException("Master feed %s already contains an implementation with version number %s!" % (options.master_feed_file, status.release_version)) previous_release = get_previous_release(status.release_version) previous_testing_releases = [impl for impl in master.implementations.values() if impl.get_version() == previous_release and impl.upstream_stability == model.stability_levels["testing"]] if previous_testing_releases: print "The previous release, version %s, is still marked as 'testing'. Set to stable?" % previous_release if support.get_choice(['Yes', 'No']) == 'Yes': publish_opts['select_version'] = previous_release publish_opts['set_stability'] = "stable" support.publish(options.master_feed_file, local = new_impls_feed, xmlsign = True, key = options.key, **publish_opts) status.updated_master_feed = 'true' status.save() # Copy files... uploads = [os.path.basename(archive_file)] for b in compiler.get_binary_feeds(): binary_feed = support.load_feed(b) impl, = binary_feed.implementations.values() uploads.append(os.path.basename(impl.download_sources[0].url)) upload_archives(options, status, uploads) feed_base = os.path.dirname(list(local_feed.feed_for)[0]) feed_files = [options.master_feed_file] print "Upload %s into %s" % (', '.join(feed_files), feed_base) cmd = options.master_feed_upload_command.strip() if cmd: support.show_and_run(cmd, feed_files) else: print "NOTE: No feed upload command set => you'll have to upload them yourself!"
def get_feeds_rel_path(config, url): """Return the relative path under "feeds" where we should store the feed with the given URL. @raise SafeException: url is not within this repository, or is otherwise malformed.""" if not url.startswith(config.REPOSITORY_BASE_URL): raise SafeException( "Feed URL '{url}' does not start with {base}".format( url=url, base=config.REPOSITORY_BASE_URL)) uri_rel_path = url[len(config.REPOSITORY_BASE_URL):] feeds_rel_path = config.get_feeds_rel_path(uri_rel_path) if not feeds_rel_path.endswith('.xml'): raise SafeException( "Feed relative path {path} must end in '.xml'".format( path=feeds_rel_path)) return ensure_safe(feeds_rel_path)
def add_manifest_file(dir, digest_or_alg): """Writes a .manifest file into 'dir', and returns the digest. You should call fixup_permissions before this to ensure that the permissions are correct. On exit, dir itself has mode 555. Subdirectories are not changed. @param dir: root of the implementation @param digest_or_alg: should be an instance of Algorithm. Passing a digest here is deprecated.""" mfile = os.path.join(dir, '.manifest') if os.path.islink(mfile) or os.path.exists(mfile): raise SafeException( _("Directory '%s' already contains a .manifest file!") % dir) manifest = '' if isinstance(digest_or_alg, Algorithm): alg = digest_or_alg digest = alg.new_digest() else: digest = digest_or_alg alg = get_algorithm('sha1') for line in alg.generate_manifest(dir): manifest += line + '\n' digest.update(manifest) os.chmod(dir, 0o755) stream = open(mfile, 'wb') os.chmod(dir, 0o555) stream.write(manifest) stream.close() os.chmod(mfile, 0o444) return digest
def add_to_menu(iface, icon_path, category, zlaunch=None): """Write a .desktop file for this application. @param iface: the program being added @param icon_path: the path of the icon, or None @param category: the freedesktop.org menu category""" tmpdir = tempfile.mkdtemp(prefix='zero2desktop-') try: desktop_name = os.path.join( tmpdir, 'zeroinstall-%s.desktop' % iface.get_name().lower().replace(os.sep, '-').replace(' ', '')) desktop = open(desktop_name, 'w') desktop.write( _template % { 'name': iface.get_name(), 'comment': iface.summary, '0launch': zlaunch or '0launch', 'iface': iface.uri, 'category': category }) if icon_path: desktop.write(_icon_template % icon_path) if len(iface.get_metadata(namespaces.XMLNS_IFACE, 'needs-terminal')): desktop.write('Terminal=true\n') desktop.close() status = os.spawnlp(os.P_WAIT, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name) finally: shutil.rmtree(tmpdir) if status: raise SafeException( _('Failed to run xdg-desktop-menu (error code %d)') % status)
def get_first_system_store(self): """The first system store is the one we try writing to first. @since: 0.30""" try: return self.stores[1] except IndexError: raise SafeException(_("No system stores have been configured"))
def do_copy_src(args): """copy-src""" if args: raise __main__.UsageError() buildenv = BuildEnv() src_impl = buildenv.chosen_impl(buildenv.interface) assert src_impl path = lookup(src_impl) assert path new_src = os.path.realpath('src') # Just for better messages if os.path.exists(new_src): raise SafeException("Directory '%s' already exists!" % new_src) shutil.copytree(path, 'src', symlinks=True) # Make all files writable by the owner for root, dirs, files in os.walk('src'): os.chmod(root, os.stat(root).st_mode | 0200) for f in files: path = os.path.join(root, f) if not os.path.islink(path): os.chmod(path, os.stat(path).st_mode | 0200) print "Copied as '%s'" % new_src
def _extract(stream, destdir, command, start_offset=0): """Run execvp('command') inside destdir in a child process, with stream seeked to 'start_offset' as stdin.""" # Some zip archives are missing timezone information; force consistent results child_env = os.environ.copy() child_env['TZ'] = 'GMT' stream.seek(start_offset) # TODO: use pola-run if available, once it supports fchmod child = subprocess.Popen(command, cwd=destdir, stdin=stream, stderr=subprocess.PIPE, env=child_env) unused, cerr = child.communicate() status = child.wait() if status != 0: raise SafeException( _('Failed to extract archive (using %(command)s); exit code %(status)d:\n%(err)s' ) % { 'command': command, 'status': status, 'err': cerr.strip() })
def parse(value): if value.lower() == 'true': return True elif value.lower() == 'false': return False else: raise SafeException(_('Must be True or False, not "%s"') % value)
def do_add(args): """add DIGEST (DIRECTORY | (ARCHIVE [EXTRACT]))""" from zeroinstall.zerostore import unpack if len(args) < 2: raise UsageError(_("Missing arguments")) digest = args[0] if os.path.isdir(args[1]): if len(args) > 2: raise UsageError(_("Too many arguments")) stores.add_dir_to_cache(digest, args[1]) elif os.path.isfile(args[1]): if len(args) > 3: raise UsageError(_("Too many arguments")) if len(args) > 2: extract = args[2] else: extract = None type = unpack.type_from_url(args[1]) if not type: raise SafeException( _("Unknown extension in '%s' - can't guess MIME type") % args[1]) unpack.check_type_ok(type) with open(args[1], 'rb') as stream: stores.add_archive_to_cache(digest, stream, args[1], extract, type=type) else: try: os.stat(args[1]) except OSError as ex: if ex.errno != errno.ENOENT: # No such file or directory raise UsageError(str(ex)) # E.g. permission denied raise UsageError(_("No such file or directory '%s'") % args[1])