Пример #1
0
def save_as(conn_data: SSHConnectionData, remote_path: str,
            cli_opts: RemoteFileCLIOptions) -> None:
    ddir = cache_dir()
    os.makedirs(ddir, exist_ok=True)
    last_used_store_path = os.path.join(ddir, 'remote-file-last-used.txt')
    try:
        with open(last_used_store_path) as f:
            last_used_path = f.read()
    except FileNotFoundError:
        last_used_path = tempfile.gettempdir()
    last_used_file = os.path.join(last_used_path,
                                  os.path.basename(remote_path))
    print(
        'Where do you wish to save the file? Leaving it blank will save it as:',
        styled(last_used_file, fg='yellow'))
    print('Relative paths will be resolved from:',
          styled(os.getcwd(), fg_intense=True, bold=True))
    print()
    from ..tui.path_completer import PathCompleter
    try:
        dest = PathCompleter().input()
    except (KeyboardInterrupt, EOFError):
        return
    if dest:
        dest = os.path.expandvars(os.path.expanduser(dest))
        if os.path.isdir(dest):
            dest = os.path.join(dest, os.path.basename(remote_path))
        with open(last_used_store_path, 'w') as f:
            f.write(os.path.dirname(os.path.abspath(dest)))
    else:
        dest = last_used_file
    if os.path.exists(dest):
        print(reset_terminal(), end='')
        print(
            f'The file {styled(dest, fg="yellow")} already exists. What would you like to do?'
        )
        print(
            f'{key("O")}verwrite  {key("A")}bort  Auto {key("R")}ename {key("N")}ew name'
        )
        response = get_key_press('anor', 'a')
        if response == 'a':
            return
        if response == 'n':
            print(reset_terminal(), end='')
            return save_as(conn_data, remote_path, cli_opts)

        if response == 'r':
            q = dest
            c = 0
            while os.path.exists(q):
                c += 1
                b, ext = os.path.splitext(dest)
                q = f'{b}-{c}{ext}'
            dest = q
    if os.path.dirname(dest):
        os.makedirs(os.path.dirname(dest), exist_ok=True)
    with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master:
        if master.check_hostname_matches():
            if not master.download():
                show_error('Failed to copy file from remote machine')
Пример #2
0
 def __init__(self, name: Optional[str] = None):
     self.matches: List[str] = []
     self.history_path = None
     if name:
         ddir = os.path.join(cache_dir(), 'ask')
         with suppress(FileExistsError):
             os.makedirs(ddir)
         self.history_path = os.path.join(ddir, name)
Пример #3
0
 def __init__(self, name=None):
     self.matches = []
     self.history_path = None
     if name:
         ddir = os.path.join(cache_dir(), 'ask')
         try:
             os.makedirs(ddir)
         except FileExistsError:
             pass
         self.history_path = os.path.join(ddir, name)
Пример #4
0
def run_ssh(ssh_args: List[str], server_args: List[str],
            found_extra_args: Tuple[str, ...]) -> NoReturn:
    cmd = [ssh_exe()] + ssh_args
    hostname, remote_args = server_args[0], server_args[1:]
    if not remote_args:
        cmd.append('-t')
    insertion_point = len(cmd)
    cmd.append('--')
    cmd.append(hostname)
    uname = getuser()
    if hostname.startswith('ssh://'):
        from urllib.parse import urlparse
        purl = urlparse(hostname)
        hostname_for_match = purl.hostname or hostname[6:].split('/', 1)[0]
        uname = purl.username or uname
    elif '@' in hostname and hostname[0] != '@':
        uname, hostname_for_match = hostname.split('@', 1)
    else:
        hostname_for_match = hostname
    hostname_for_match = hostname_for_match.split('@', 1)[-1].split(':', 1)[0]
    overrides: List[str] = []
    literal_env: Dict[str, str] = {}
    pat = re.compile(r'^([a-zA-Z0-9_]+)[ \t]*=')
    for i, a in enumerate(found_extra_args):
        if i % 2 == 1:
            aq = pat.sub(r'\1 ', a.lstrip())
            key = aq.split(maxsplit=1)[0]
            if key == 'clone_env':
                literal_env = add_cloned_env(aq.split(maxsplit=1)[1])
            elif key != 'hostname':
                overrides.append(aq)
    if overrides:
        overrides.insert(0, f'hostname {uname}@{hostname_for_match}')
    host_opts = init_config(hostname_for_match, uname, overrides)
    if host_opts.share_connections:
        cmd[insertion_point:insertion_point] = connection_sharing_args(
            int(os.environ['KITTY_PID']))
    use_kitty_askpass = host_opts.askpass == 'native' or (
        host_opts.askpass == 'unless-set' and 'SSH_ASKPASS' not in os.environ)
    need_to_request_data = True
    if use_kitty_askpass:
        sentinel = os.path.join(cache_dir(),
                                'openssh-is-new-enough-for-askpass')
        sentinel_exists = os.path.exists(sentinel)
        if sentinel_exists or ssh_version() >= (8, 4):
            if not sentinel_exists:
                open(sentinel, 'w').close()
            # SSH_ASKPASS_REQUIRE was introduced in 8.4 release on 2020-09-27
            need_to_request_data = False
            os.environ['SSH_ASKPASS_REQUIRE'] = 'force'
        os.environ['SSH_ASKPASS'] = os.path.join(shell_integration_dir, 'ssh',
                                                 'askpass.py')
    if need_to_request_data and host_opts.share_connections:
        cp = subprocess.run(cmd[:1] + ['-O', 'check'] + cmd[1:],
                            stdout=subprocess.DEVNULL,
                            stderr=subprocess.DEVNULL)
        if cp.returncode == 0:
            # we will use the master connection so SSH does not need to use the tty
            need_to_request_data = False
    with restore_terminal_state() as echo_on:
        rcmd, replacements, shm_name = get_remote_command(
            remote_args,
            host_opts,
            hostname_for_match,
            uname,
            echo_on,
            request_data=need_to_request_data,
            literal_env=literal_env)
        cmd += rcmd
        colors_changed = change_colors(host_opts.color_scheme)
        try:
            p = subprocess.Popen(cmd)
        except FileNotFoundError:
            raise SystemExit(
                'Could not find the ssh executable, is it in your PATH?')
        else:
            rq = '' if need_to_request_data else 'id={REQUEST_ID}:pwfile={PASSWORD_FILENAME}:pw={DATA_PASSWORD}'.format(
                **replacements)
            with drain_potential_tty_garbage(p, rq):
                try:
                    raise SystemExit(p.wait())
                except KeyboardInterrupt:
                    raise SystemExit(1)
        finally:
            if colors_changed:
                print(end=restore_colors(), flush=True)
Пример #5
0
 def tearDown(self):
     os.rmdir(cache_dir())
     cache_dir.clear_override()
Пример #6
0
def fetch_themes(
    name: str = 'kitty-themes',
    url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master',
    cache_age: float = 1,
) -> str:
    now = datetime.datetime.now(datetime.timezone.utc)
    cache_age_delta = datetime.timedelta(days=cache_age)

    class Metadata:
        def __init__(self) -> None:
            self.etag = ''
            self.timestamp = now

        def __str__(self) -> str:
            return json.dumps({
                'etag': self.etag,
                'timestamp': self.timestamp.isoformat()
            })

    dest_path = os.path.join(cache_dir(), f'{name}.zip')
    m = Metadata()
    with suppress(Exception), zipfile.ZipFile(dest_path, 'r') as zf:
        q = json.loads(zf.comment)
        m.etag = str(q.get('etag') or '')
        m.timestamp = datetime.datetime.fromisoformat(q['timestamp'])
        if cache_age < 0 or (now - m.timestamp) < cache_age_delta:
            return dest_path
    if cache_age < 0:
        raise NoCacheFound(
            'No local themes cache found and negative cache age specified, aborting'
        )

    rq = Request(url)
    m.timestamp = now
    if m.etag:
        rq.add_header('If-None-Match', m.etag)
    try:
        res = urlopen(rq, timeout=30)
    except HTTPError as e:
        if m.etag and e.code == http.HTTPStatus.NOT_MODIFIED:
            set_comment_in_zip_file(dest_path, str(m))
            return dest_path
        raise
    m.etag = res.headers.get('etag') or ''

    needs_delete = False
    try:
        with tempfile.NamedTemporaryFile(
                suffix=f'-{os.path.basename(dest_path)}',
                dir=os.path.dirname(dest_path),
                delete=False) as f:
            needs_delete = True
            shutil.copyfileobj(res, f)
            f.flush()
            set_comment_in_zip_file(f.name, str(m))
            os.replace(f.name, dest_path)
            needs_delete = False
    finally:
        if needs_delete:
            os.unlink(f.name)
    return dest_path
Пример #7
0
def history_path() -> str:
    return os.path.join(cache_dir(), 'transfer-ask.history')