Add a permission password to the transfer kitten

This commit is contained in:
Kovid Goyal 2021-09-10 18:09:59 +05:30
parent 0d89eb2c40
commit 24255be0bd
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 59 additions and 8 deletions

View File

@ -81,6 +81,15 @@ receiving computer. In :code:`normal` mode the last argument is assumed to be a
destination path on the receiving computer.
--permissions-password -p
The password to use to skip the transfer confirmation popup in kitty. Must match the
password set for the :opt:`file_transfer_password` option in kitty.conf. Note that
leading and trailing whitespace is removed from the password. A password starting with
., / or ~ characters is assumed to be a file name to read the password from. A value
of - means read the password from STDIN. A password that is purely a number less than 256
is assumed to be the number of a file descriptor from which to read the actual password.
--confirm-paths
type=bool-set
Before actually transferring files, show a mapping of local file names to remote file names
@ -288,8 +297,9 @@ class SendState(NameReprEnum):
class SendManager:
def __init__(self, request_id: str, files: Tuple[File, ...]):
def __init__(self, request_id: str, files: Tuple[File, ...], pw: Optional[str] = None):
self.files = files
self.password = pw or ''
self.fid_map = {f.file_id: f for f in self.files}
self.request_id = request_id
self.state = SendState.waiting_for_permission
@ -323,7 +333,7 @@ class SendManager:
self.all_started = not found_not_started
def start_transfer(self) -> str:
return FileTransmissionCommand(action=Action.send).serialize()
return FileTransmissionCommand(action=Action.send, password=self.password).serialize()
def next_chunk(self) -> str:
if self.active_file is None:
@ -389,10 +399,12 @@ class Send(Handler):
if before == SendState.waiting_for_permission:
if self.manager.state == SendState.permission_denied:
self.cmd.styled('Permission denied for this transfer', fg='red')
self.print()
self.quit_loop(1)
return
if self.manager.state == SendState.permission_granted:
self.cmd.styled('Permission granted for this transfer', fg='green')
self.print()
self.loop_tick()
def check_for_transmit_ok(self) -> None:
@ -435,6 +447,7 @@ class Send(Handler):
def on_interrupt(self) -> None:
self.cmd.styled('Interrupt requested, cancelling transfer, transferred files are in undefined state', fg='red')
self.print()
self.abort_transfer()
def abort_transfer(self) -> None:
@ -447,7 +460,7 @@ def send_main(cli_opts: TransferCLIOptions, args: List[str]) -> None:
files = files_for_send(cli_opts, args)
print(f'Found {len(files)} files and directories, requesting transfer permission…')
loop = Loop()
handler = Send(cli_opts, SendManager(random_id(), files))
handler = Send(cli_opts, SendManager(random_id(), files, cli_opts.permissions_password))
loop.loop(handler)
raise SystemExit(loop.return_code)
@ -459,8 +472,27 @@ def parse_transfer_args(args: List[str]) -> Tuple[TransferCLIOptions, List[str]]
)
def read_password(loc: str) -> str:
if not loc:
return ''
if loc.isdigit() and int(loc) >= 0 and int(loc) < 256:
with open(int(loc), 'rb') as f:
return f.read().decode('utf-8')
if loc[0] in ('.', '~', '/'):
if loc[0] == '~':
loc = os.path.expanduser(loc)
with open(loc, 'rb') as f:
return f.read().decode('utf-8')
if loc == '-':
return sys.stdin.read()
return loc
def main(args: List[str]) -> None:
cli_opts, items = parse_transfer_args(args)
if cli_opts.permissions_password:
cli_opts.permissions_password = read_password(cli_opts.permissions_password).strip()
if not items:
raise SystemExit('Usage: kitty +kitten transfer file_or_directory ...')
if cli_opts.direction == 'send':

View File

@ -16,7 +16,9 @@ from gettext import gettext as _
from time import monotonic
from typing import IO, Any, Callable, Deque, Dict, List, Optional, Tuple, Union
from kitty.fast_data_types import FILE_TRANSFER_CODE, OSC, add_timer, get_boss
from kitty.fast_data_types import (
FILE_TRANSFER_CODE, OSC, add_timer, get_boss, get_options
)
from .utils import log_error, sanitize_control_codes
@ -97,7 +99,7 @@ class FileTransmissionCommand:
ttype: TransmissionType = TransmissionType.simple
id: str = ''
file_id: str = ''
secret: str = ''
password: str = field(default='', metadata={'base64': True})
quiet: int = 0
mtime: int = -1
permissions: int = -1
@ -313,8 +315,9 @@ class ActiveReceive:
files: Dict[str, DestFile]
accepted: bool = False
def __init__(self, id: str, quiet: int) -> None:
def __init__(self, id: str, quiet: int, password: str) -> None:
self.id = id
self.password = password
self.files = {}
self.last_activity_at = monotonic()
self.send_acknowledgements = quiet < 1
@ -432,7 +435,7 @@ class FileTransmission:
if cmd.action is not Action.send:
log_error(f'File transmission command received for unknown or rejected id: {cmd.id}, ignoring')
return
ar = self.active_receives[cmd.id] = ActiveReceive(cmd.id, cmd.quiet)
ar = self.active_receives[cmd.id] = ActiveReceive(cmd.id, cmd.quiet, cmd.password)
self.start_receive(ar.id)
return
@ -518,6 +521,10 @@ class FileTransmission:
return False
def start_receive(self, ar_id: str) -> None:
ar = self.active_receives[ar_id]
if ar.password:
self.handle_send_confirmation(ar_id, {'response': 'y' if ar.password == get_options().file_transfer_password else 'n'})
return
boss = get_boss()
window = boss.window_id_map.get(self.window_id)
if window is not None:

View File

@ -2458,7 +2458,7 @@ opt('editor', '.',
The terminal editor (such as ``vim`` or ``nano``) to use when editing the kitty
config file or similar tasks.
The default value of . means to use the environment variables :envvar:`VISUAL`
The default value of :code:`.` means to use the environment variables :envvar:`VISUAL`
and :envvar:`EDITOR` in that order. If these variables aren't set, kitty will
run your :opt:`shell` (``$SHELL -l -i -c env``) to see if your shell config
files set :envvar:`VISUAL` or :envvar:`EDITOR`. If that doesn't work, kitty
@ -2563,6 +2563,13 @@ stored for writing to the system clipboard. See also :opt:`clipboard_control`.
A value of zero means no size limit is applied.
''')
opt('file_transfer_password', '', long_text='''
A password, that can be supplied to the file transfer kitten to skip
the transfer confirmation dialog. This should only be used
when initiating transfers from trusted computers, over trusted networks
or encrypted transports.
''')
opt('allow_hyperlinks', 'yes',
option_type='allow_hyperlinks', ctype='bool',
long_text='''

View File

@ -942,6 +942,9 @@ class Parser:
for k, v in env(val, ans["env"]):
ans["env"][k] = v
def file_transfer_password(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['file_transfer_password'] = str(val)
def focus_follows_mouse(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['focus_follows_mouse'] = to_bool(val)

View File

@ -345,6 +345,7 @@ option_names = ( # {{{
'enable_audio_bell',
'enabled_layouts',
'env',
'file_transfer_password',
'focus_follows_mouse',
'font_family',
'font_features',
@ -484,6 +485,7 @@ class Options:
editor: str = '.'
enable_audio_bell: bool = True
enabled_layouts: typing.List[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical']
file_transfer_password: str = ''
focus_follows_mouse: bool = False
font_family: str = 'monospace'
font_size: float = 11.0