jp (file): new get command + encryption with upload:
- new file/get command let download a file from URL. It handles `aesgcm:` scheme by thanks to backend decryption on the fly. - new `--encrypt` option for upload. When used, the file will be encrypted using AES-GCM algorithm, and the `aesgcm:` URL will be returned - for both commands, the XMLUI note is displayed in case of error
This commit is contained in:
parent
5c1dd0da49
commit
2d49de9209
2 changed files with 119 additions and 7 deletions
|
|
@ -118,6 +118,23 @@ Several files are expected from Louise, accept them and store them do
|
|||
|
||||
$ jp file receive --multiple --path ~/Downloads/Louise louise@example.org
|
||||
|
||||
get
|
||||
===
|
||||
|
||||
Download a file from an URI. This commands handle URI scheme common with XMPP, so in
|
||||
addition to ``http`` and ``https``, you can use it with ``aesgcm`` scheme (encrypted files
|
||||
with key in URL, this is notably used with OMEMO encryption).
|
||||
|
||||
As usual, you can use ``-P, --progress`` to see a progress bar.
|
||||
|
||||
example
|
||||
-------
|
||||
|
||||
Download an encrypted file with a progress bar, and save it to current working directory
|
||||
with the same name as in the URL (``some_image.jpg``). The URL fragment part (after ``#``)
|
||||
is used for decryption, so be sure to not leak the URL when you manipulate one::
|
||||
|
||||
$ jp file get -P "aesgcm://upload.example.org/wvgSUlURU_UPspAv/some_image.jpg#7d8509c43479591f8d8492f84369875ca983db58f43225c40229eb06d05b2037c841b2346c9642a88ba4a91aa96a0e8f"
|
||||
|
||||
upload
|
||||
======
|
||||
|
|
@ -132,6 +149,11 @@ feature).
|
|||
|
||||
As usual, you can use ``-P, --progress`` to see a progress bar.
|
||||
|
||||
You can encrypt the file using ``AES GCM`` with the ``-e, --encrypt`` argument. You will
|
||||
then get an ``aesgcm://`` link instead of the usual ``https``, this link contains the
|
||||
decryption key (in the fragment part) so be sure to not leak it and to transmit it only
|
||||
over encrypted communication channels.
|
||||
|
||||
.. _XEP-0363 (HTTP File Upload): XEP-0363: HTTP File Upload
|
||||
|
||||
example
|
||||
|
|
@ -141,6 +163,10 @@ Upload a document to a server::
|
|||
|
||||
$ jp file upload -P ~/Documents/something_interesting.odt
|
||||
|
||||
Encrypt and upload a document to server::
|
||||
|
||||
$ jp file upload -P -e ~/Documents/something_secret.odt
|
||||
|
||||
share
|
||||
=====
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
|
||||
from . import base
|
||||
from . import xmlui_manager
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
|
|
@ -29,6 +30,8 @@ from sat_frontends.jp.constants import Const as C
|
|||
from sat_frontends.jp import common
|
||||
from sat_frontends.tools import jid
|
||||
from sat.tools.common.ansi import ANSI as A
|
||||
from urllib.parse import urlparse
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
|
||||
import json
|
||||
|
|
@ -254,7 +257,8 @@ class Request(base.CommandBase):
|
|||
path = os.path.abspath(self.filename)
|
||||
|
||||
if os.path.exists(path) and not self.args.force:
|
||||
message = _(f"File {path} already exists! Do you want to overwrite?")
|
||||
message = _("File {path} already exists! Do you want to overwrite?").format(
|
||||
path = path)
|
||||
await self.host.confirmOrQuit(message, _("file request cancelled"))
|
||||
|
||||
self.full_dest_jid = await self.host.get_full_jid(self.args.jid)
|
||||
|
|
@ -413,6 +417,77 @@ class Receive(base.CommandAnswering):
|
|||
await self.start_answering()
|
||||
|
||||
|
||||
class Get(base.CommandBase):
|
||||
|
||||
def __init__(self, host):
|
||||
super(Get, self).__init__(
|
||||
host, "get", use_progress=True, use_verbose=True,
|
||||
help=_("download a file from URI")
|
||||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument(
|
||||
'-o', '--dest_file', type=str, default='',
|
||||
help=_("destination file (DEFAULT: filename from URL)")
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
help=_("overwrite existing file without confirmation"),
|
||||
)
|
||||
self.parser.add_argument("uri", type=str, help=_("URI of the file to retrieve"))
|
||||
|
||||
async def onProgressStarted(self, metadata):
|
||||
self.disp(_("File download started"), 2)
|
||||
|
||||
async def onProgressFinished(self, metadata):
|
||||
self.disp(_("File downloaded successfully"), 2)
|
||||
|
||||
async def onProgressError(self, error_msg):
|
||||
self.disp(_("Error while downloading file: {}").format(error_msg), error=True)
|
||||
|
||||
async def gotId(self, data):
|
||||
"""Called when a progress id has been received"""
|
||||
try:
|
||||
await self.set_progress_id(data["progress"])
|
||||
except KeyError:
|
||||
if 'xmlui' in data:
|
||||
ui = xmlui_manager.create(self.host, data['xmlui'])
|
||||
await ui.show()
|
||||
else:
|
||||
self.disp(_("Can't download file"), error=True)
|
||||
self.host.quit(C.EXIT_ERROR)
|
||||
|
||||
async def start(self):
|
||||
uri = self.args.uri
|
||||
dest_file = self.args.dest_file
|
||||
if not dest_file:
|
||||
parsed_uri = urlparse(uri)
|
||||
dest_file = Path(parsed_uri.path).name.strip() or "downloaded_file"
|
||||
|
||||
dest_file = Path(dest_file).expanduser().resolve()
|
||||
if dest_file.exists() and not self.args.force:
|
||||
message = _("File {path} already exists! Do you want to overwrite?").format(
|
||||
path = dest_file)
|
||||
await self.host.confirmOrQuit(message, _("file download cancelled"))
|
||||
|
||||
options = {}
|
||||
|
||||
try:
|
||||
download_data = await self.host.bridge.fileDownload(
|
||||
uri,
|
||||
str(dest_file),
|
||||
data_format.serialise(options),
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"error while trying to download a file: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
await self.gotId(download_data)
|
||||
|
||||
|
||||
class Upload(base.CommandBase):
|
||||
def __init__(self, host):
|
||||
super(Upload, self).__init__(
|
||||
|
|
@ -420,6 +495,12 @@ class Upload(base.CommandBase):
|
|||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument(
|
||||
"-e",
|
||||
"--encrypt",
|
||||
action="store_true",
|
||||
help=_("encrypt file using AES-GCM"),
|
||||
)
|
||||
self.parser.add_argument("file", type=str, help=_("file to upload"))
|
||||
self.parser.add_argument(
|
||||
"jid",
|
||||
|
|
@ -429,7 +510,7 @@ class Upload(base.CommandBase):
|
|||
self.parser.add_argument(
|
||||
"--ignore-tls-errors",
|
||||
action="store_true",
|
||||
help=_("ignore invalide TLS certificate"),
|
||||
help=_(r"ignore invalide TLS certificate (/!\ Dangerous /!\)"),
|
||||
)
|
||||
|
||||
async def onProgressStarted(self, metadata):
|
||||
|
|
@ -443,7 +524,7 @@ class Upload(base.CommandBase):
|
|||
self.disp("download URL not found in metadata")
|
||||
else:
|
||||
self.disp(_("URL to retrieve the file:"), 1)
|
||||
# XXX: url is display alone on a line to make parsing easier
|
||||
# XXX: url is displayed alone on a line to make parsing easier
|
||||
self.disp(url)
|
||||
|
||||
async def onProgressError(self, error_msg):
|
||||
|
|
@ -458,8 +539,11 @@ class Upload(base.CommandBase):
|
|||
try:
|
||||
await self.set_progress_id(data["progress"])
|
||||
except KeyError:
|
||||
# TODO: if 'xmlui' key is present, manage xmlui message display
|
||||
self.disp(_("Can't upload file"), error=True)
|
||||
if 'xmlui' in data:
|
||||
ui = xmlui_manager.create(self.host, data['xmlui'])
|
||||
await ui.show()
|
||||
else:
|
||||
self.disp(_("Can't upload file"), error=True)
|
||||
self.host.quit(C.EXIT_ERROR)
|
||||
|
||||
async def start(self):
|
||||
|
|
@ -479,6 +563,8 @@ class Upload(base.CommandBase):
|
|||
options = {}
|
||||
if self.args.ignore_tls_errors:
|
||||
options["ignore_tls_errors"] = True
|
||||
if self.args.encrypt:
|
||||
options["encryption"] = C.ENC_AES_GCM
|
||||
|
||||
path = os.path.abspath(file_)
|
||||
try:
|
||||
|
|
@ -490,7 +576,7 @@ class Upload(base.CommandBase):
|
|||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"can't while trying to upload a file: {e}", error=True)
|
||||
self.disp(f"error while trying to upload a file: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
await self.gotId(upload_data, file_)
|
||||
|
|
@ -727,7 +813,7 @@ class Share(base.CommandBase):
|
|||
|
||||
|
||||
class File(base.CommandBase):
|
||||
subcommands = (Send, Request, Receive, Upload, Share)
|
||||
subcommands = (Send, Request, Receive, Get, Upload, Share)
|
||||
|
||||
def __init__(self, host):
|
||||
super(File, self).__init__(
|
||||
|
|
|
|||
Loading…
Reference in a new issue