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:
Goffi 2019-12-20 12:28:04 +01:00
commit 2d49de9209
2 changed files with 119 additions and 7 deletions

View file

@ -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
=====

View file

@ -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__(