From 6423e6c95aca8905bdff9f9e540091744b7ce065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 25 Feb 2017 22:43:59 +0100 Subject: [PATCH 01/13] [httpupload] Remove Image Button --- httpupload/httpupload.py | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index 0c3446f..d3b96db 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -178,14 +178,10 @@ class HttpuploadPlugin(GajimPlugin): log.info("Account %s: httpupload is_supported: %s" % (str(chat_control.account), str(is_supported))) if not is_supported: text = _('Your server does not support http uploads') - image_text = text else: text = _('Send file via http upload') - image_text = _('Send image via http upload') base.button.set_sensitive(is_supported) base.button.set_tooltip_text(text) - base.image_button.set_sensitive(is_supported) - base.image_button.set_tooltip_text(image_text) class Base(object): @@ -204,34 +200,20 @@ class Base(object): self.button.set_image(img) self.button.set_tooltip_text(_('Your server does not support http uploads')) self.button.set_relief(Gtk.ReliefStyle.NONE) - self.image_button = Gtk.Button(label=None, stock=None, use_underline=True) - self.image_button.set_property('can-focus', False) - self.image_button.set_relief(Gtk.ReliefStyle.NONE) - self.image_button.set_sensitive(False) - img = Gtk.Image() - img.set_from_file(self.plugin.local_file_path('image.png')) - self.image_button.set_image(img) - self.image_button.set_tooltip_text(_('Your server does not support http uploads')) + send_button = chat_control.xml.get_object('send_button') actions_hbox.add(self.button) - actions_hbox.add(self.image_button) - send_button_pos = actions_hbox.child_get_property(send_button, 'position') - actions_hbox.child_set_property(self.image_button, 'position', send_button_pos - 1) actions_hbox.child_set_property(self.button, 'position', send_button_pos - 1) file_id = self.button.connect('clicked', self.on_file_button_clicked) - image_id = self.image_button.connect('clicked', self.on_image_button_clicked) chat_control.handlers[file_id] = self.button - chat_control.handlers[image_id] = self.image_button self.button.show() - self.image_button.show() def disconnect_from_chat_control(self): actions_hbox = self.chat_control.xml.get_object('actions_hbox') actions_hbox.remove(self.button) - actions_hbox.remove(self.image_button) def encryption_activated(self): if not encryption_available: @@ -345,17 +327,6 @@ class Base(object): log.info("Upload completed successfully") xhtml = None is_image = mime_type.split('/', 1)[0] == 'image' - if ((not isinstance(self.chat_control, chat_control.ChatControl) - or not self.chat_control.gpg_is_active) - and self.dialog_type == 'image' - and is_image - and not self.encrypted_upload - ): - progress_messages.put(_('Calculating (possible) image thumbnail...')) - thumb = thumbnail(path_to_file) - if thumb: - xhtml = '
%s' % \ - (get.getData(), get.getData(), thumb) progress_window.close_dialog() id_ = gajim.get_an_id() def add_oob_tag(): @@ -457,11 +428,6 @@ class Base(object): default_response = Gtk.ResponseType.OK,) self.dlg.set_transient_for(self.chat_control.parent_win.window) - def on_image_button_clicked(self, widget): - self.dialog_type = 'image' - self.dlg = ImageChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None) - self.dlg.set_transient_for(self.chat_control.parent_win.window) - class StreamFileWithProgress: def __init__(self, path, mode, callback=None, From b0831d35463afc99b743a160fa63f3767e7a5005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 26 Feb 2017 01:54:46 +0100 Subject: [PATCH 02/13] [httpupload] Refactor HttpUploadPlugin Class - There is no need for discovery code, Gajim does this already - Generate only one Base instance per account - Use less global vars and simplify code --- httpupload/httpupload.py | 201 +++++++++++++++------------------------ 1 file changed, 76 insertions(+), 125 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index d3b96db..a703959 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -28,14 +28,11 @@ import binascii from common import gajim from common import ged -import chat_control from plugins import GajimPlugin from plugins.helpers import log_calls -from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog +from dialogs import FileChooserDialog, ErrorDialog import nbxmpp -from .thumbnail import thumbnail - log = logging.getLogger('gajim.plugin_system.httpupload') try: @@ -55,165 +52,119 @@ except Exception as e: encryption_available = False # XEP-0363 (http://xmpp.org/extensions/xep-0363.html) +IQ_CALLBACK = {} NS_HTTPUPLOAD = 'urn:xmpp:http:upload' TAGSIZE = 16 -jid_to_servers = {} -iq_ids_to_callbacks = {} -last_info_query = {} - class HttpuploadPlugin(GajimPlugin): - - @log_calls('HttpuploadPlugin') def init(self): if not encryption_available: self.available_text = DEP_MSG self.config_dialog = None # HttpuploadPluginConfigDialog(self) - self.controls = [] self.events_handlers = {} - self.events_handlers['agent-info-received'] = (ged.PRECORE, - self.handle_agent_info_received) - self.events_handlers['raw-iq-received'] = (ged.PRECORE, - self.handle_iq_received) + self.events_handlers['agent-info-received'] = ( + ged.PRECORE, self.handle_agent_info_received) + self.events_handlers['raw-iq-received'] = ( + ged.PRECORE, self.handle_iq_received) self.gui_extension_points = { 'chat_control_base': (self.connect_with_chat_control, - self.disconnect_from_chat_control), - 'chat_control_base_update_toolbar': (self.update_button_state, - None)} - self.first_run = True + self.disconnect_from_chat_control), + 'chat_control_base_update_toolbar': (self.update_chat_control, + None)} + self.gui_interfaces = {} - def handle_iq_received(self, event): - global iq_ids_to_callbacks + @staticmethod + def handle_iq_received(event): id_ = event.stanza.getAttr("id") - if str(id_) in iq_ids_to_callbacks: + if id_ in IQ_CALLBACK: try: - iq_ids_to_callbacks[str(id_)](event.stanza) + IQ_CALLBACK[id_](event.stanza) except: raise finally: - del iq_ids_to_callbacks[str(id_)] + del IQ_CALLBACK[id_] def handle_agent_info_received(self, event): - global jid_to_servers - if NS_HTTPUPLOAD in event.features and gajim.jid_is_transport(event.jid): - own_jid = gajim.get_jid_without_resource(str(event.stanza.getTo())) - jid_to_servers[own_jid] = event.jid # map own jid to upload component's jid - log.info(own_jid + " can do http uploads via component " + event.jid) - # update all buttons - for base in self.controls: - self.update_button_state(base.chat_control) + if (NS_HTTPUPLOAD in event.features and + gajim.jid_is_transport(event.jid)): + account = event.conn.name + interface = self.get_interface(account) + interface.enabled = True + interface.component = event.jid + interface.update_button_states(True) - @log_calls('HttpuploadPlugin') - def connect_with_chat_control(self, control): - self.chat_control = control - base = Base(self, self.chat_control) - self.controls.append(base) - if self.first_run: - # TODO: Potentially add back keyboard shortcut - self.first_run = False - self.update_button_state(self.chat_control) + def connect_with_chat_control(self, chat_control): + account = chat_control.contact.account.name + self.get_interface(account).add_button(chat_control) - @log_calls('HttpuploadPlugin') def disconnect_from_chat_control(self, chat_control): - for control in self.controls: - control.disconnect_from_chat_control() - self.controls = [] + jid = chat_control.contact.jid + account = chat_control.account + interface = self.get_interface(account) + if jid not in interface.controls: + return + actions_hbox = chat_control.xml.get_object('actions_hbox') + actions_hbox.remove(interface.controls[jid]) - @log_calls('HttpuploadPlugin') - def update_button_state(self, chat_control): - global jid_to_servers - global iq_ids_to_callbacks - global last_info_query + def update_chat_control(self, chat_control): + account = chat_control.account + if gajim.connections[account].connection is None: + self.get_interface(account).update_button_states(False) - if gajim.connections[chat_control.account].connection == None and \ - gajim.get_jid_from_account(chat_control.account) in jid_to_servers: - # maybe don't delete this and detect vanished upload components when actually trying to upload something - log.info("Deleting %s from jid_to_servers (disconnected)" % gajim.get_jid_from_account(chat_control.account)) - del jid_to_servers[gajim.get_jid_from_account(chat_control.account)] - - # query info at most every 60 seconds in case something goes wrong - if ((not chat_control.account in last_info_query or - last_info_query[chat_control.account] + 60 < time.time()) - and not gajim.get_jid_from_account(chat_control.account) in jid_to_servers - and gajim.account_is_connected(chat_control.account) - ): - log.info("Account %s: Using dicovery to find jid of httpupload component" % chat_control.account) - id_ = gajim.get_an_id() - iq = nbxmpp.Iq( - typ='get', - to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)), - queryNS="http://jabber.org/protocol/disco#items" - ) - iq.setID(id_) - def query_info(stanza): - global last_info_query - for item in stanza.getTag("query").getTags("item"): - id_ = gajim.get_an_id() - iq = nbxmpp.Iq( - typ='get', - to=item.getAttr("jid"), - queryNS="http://jabber.org/protocol/disco#info" - ) - iq.setID(id_) - last_info_query[chat_control.account] = time.time() - gajim.connections[chat_control.account].connection.send(iq) - iq_ids_to_callbacks[str(id_)] = query_info - gajim.connections[chat_control.account].connection.send(iq) - #send disco query to main server jid - id_ = gajim.get_an_id() - iq = nbxmpp.Iq( - typ='get', - to=gajim.get_server_from_jid(gajim.get_jid_from_account(chat_control.account)), - queryNS="http://jabber.org/protocol/disco#info" - ) - iq.setID(id_) - last_info_query[chat_control.account] = time.time() - gajim.connections[chat_control.account].connection.send(iq) - - for base in self.controls: - if base.chat_control == chat_control: - is_supported = gajim.get_jid_from_account(chat_control.account) in jid_to_servers and \ - gajim.connections[chat_control.account].connection != None - log.info("Account %s: httpupload is_supported: %s" % (str(chat_control.account), str(is_supported))) - if not is_supported: - text = _('Your server does not support http uploads') - else: - text = _('Send file via http upload') - base.button.set_sensitive(is_supported) - base.button.set_tooltip_text(text) + def get_interface(self, account): + try: + return self.gui_interfaces[account] + except KeyError: + self.gui_interfaces[account] = Base(self) + return self.gui_interfaces[account] class Base(object): - def __init__(self, plugin, chat_control): + def __init__(self, plugin): self.dlg = None self.dialog_type = 'file' self.plugin = plugin self.encrypted_upload = False - self.chat_control = chat_control - actions_hbox = chat_control.xml.get_object('actions_hbox') - self.button = Gtk.Button(label=None, stock=None, use_underline=True) - self.button.set_property('can-focus', False) - self.button.set_sensitive(False) + self.enabled = False + self.component = None + self.controls = {} + + def add_button(self, chat_control): + jid = chat_control.contact.jid + img = Gtk.Image() img.set_from_file(self.plugin.local_file_path('httpupload.png')) - self.button.set_image(img) - self.button.set_tooltip_text(_('Your server does not support http uploads')) - self.button.set_relief(Gtk.ReliefStyle.NONE) + actions_hbox = chat_control.xml.get_object('actions_hbox') + button = Gtk.Button(label=None, stock=None, use_underline=True) + button.set_property('can-focus', False) + button.set_image(img) + button.set_relief(Gtk.ReliefStyle.NONE) + actions_hbox.add(button) send_button = chat_control.xml.get_object('send_button') - actions_hbox.add(self.button) - send_button_pos = actions_hbox.child_get_property(send_button, 'position') - actions_hbox.child_set_property(self.button, 'position', send_button_pos - 1) + button_pos = actions_hbox.child_get_property(send_button, 'position') + actions_hbox.child_set_property(button, 'position', button_pos - 1) - file_id = self.button.connect('clicked', self.on_file_button_clicked) - chat_control.handlers[file_id] = self.button - self.button.show() + self.controls[jid] = button + id_ = button.connect('clicked', self.on_file_button_clicked, jid, chat_control) + chat_control.handlers[id_] = button + self.set_button_state(self.enabled, button) + button.show() + @staticmethod + def set_button_state(state, button): + if state: + button.set_sensitive(state) + button.set_tooltip_text(_('Send file via http upload')) + else: + button.set_sensitive(state) + button.set_tooltip_text( + _('Your server does not support http uploads')) - def disconnect_from_chat_control(self): - actions_hbox = self.chat_control.xml.get_object('actions_hbox') - actions_hbox.remove(self.button) + def update_button_states(self, state): + for jid in self.controls: + self.set_button_state(state, self.controls[jid]) def encryption_activated(self): if not encryption_available: @@ -420,7 +371,7 @@ class Base(object): self.chat_control.msg_textview.grab_focus() - def on_file_button_clicked(self, widget): + def on_file_button_clicked(self, widget, jid, chat_control): self.dialog_type = 'file' self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None, title_text = _('Choose file to send'), action = Gtk.FileChooserAction.OPEN, From d6ed9c4fd34fa786bf17a563d975b9cd113205f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 26 Feb 2017 23:28:53 +0100 Subject: [PATCH 03/13] [httpupload] Refactor FileChooserDialog --- httpupload/httpupload.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index a703959..aa63cec 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -122,8 +122,6 @@ class HttpuploadPlugin(GajimPlugin): class Base(object): def __init__(self, plugin): - self.dlg = None - self.dialog_type = 'file' self.plugin = plugin self.encrypted_upload = False self.enabled = False @@ -183,8 +181,9 @@ class Base(object): log.info('Encryption is: False / OMEMO not found') return False - def on_file_dialog_ok(self, widget, path_to_file=None): - global jid_to_servers + def on_file_dialog_ok(self, widget, jid, chat_control): + path_to_file = widget.get_filename() + widget.destroy() try: self.encrypted_upload = self.encryption_activated() @@ -192,20 +191,15 @@ class Base(object): log.debug(e) self.encrypted_upload = False - if not path_to_file: - path_to_file = self.dlg.get_filename() - if not path_to_file: - self.dlg.destroy() - return - self.dlg.destroy() - if not os.path.exists(path_to_file): + if not path_to_file or not os.path.exists(path_to_file): return + if self.encrypted_upload: filesize = os.path.getsize(path_to_file) + TAGSIZE # in bytes else: filesize = os.path.getsize(path_to_file) + invalid_file = False - msg = '' if os.path.isfile(path_to_file): stat = os.stat(path_to_file) if stat[6] == 0: @@ -215,7 +209,8 @@ class Base(object): invalid_file = True msg = _('File does not exist') if invalid_file: - ErrorDialog(_('Could not open file'), msg, transient_for=self.chat_control.parent_win.window) + ErrorDialog(_('Could not open file'), msg, + transient_for=chat_control.parent_win.window) return mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0] @@ -225,6 +220,7 @@ class Base(object): progress_messages = Queue(8) progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), progress_messages, self.plugin, parent=self.chat_control.parent_win.window) + def upload_file(stanza): slot = stanza.getTag("slot") if not slot: @@ -372,13 +368,15 @@ class Base(object): self.chat_control.msg_textview.grab_focus() def on_file_button_clicked(self, widget, jid, chat_control): - self.dialog_type = 'file' - self.dlg = FileChooserDialog(on_response_ok=self.on_file_dialog_ok, on_response_cancel=None, - title_text = _('Choose file to send'), action = Gtk.FileChooserAction.OPEN, - buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK), - default_response = Gtk.ResponseType.OK,) - self.dlg.set_transient_for(self.chat_control.parent_win.window) - + FileChooserDialog( + on_response_ok=lambda widget: self.on_file_dialog_ok(widget, jid, + chat_control), + title_text=_('Choose file to send'), + action=Gtk.FileChooserAction.OPEN, + buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, Gtk.ResponseType.OK), + default_response=Gtk.ResponseType.OK, + transient_for=chat_control.parent_win.window) class StreamFileWithProgress: def __init__(self, path, mode, callback=None, From 07edf439f4973eefd854422bf73ee186433f3fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sun, 26 Feb 2017 23:38:57 +0100 Subject: [PATCH 04/13] [httpupload] Refactor encryption_activated() --- httpupload/httpupload.py | 48 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index aa63cec..343ad06 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -43,13 +43,13 @@ try: from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers.modes import GCM - encryption_available = True -except Exception as e: + ENCRYPTION_AVAILABLE = True +except Exception as exc: DEP_MSG = 'For encryption of files, ' \ 'please install python-cryptography!' - log.debug('Cryptography Import Error: ' + str(e)) + log.debug('Cryptography Import Error: %s', exc) log.info('Decryption/Encryption disabled due to errors') - encryption_available = False + ENCRYPTION_AVAILABLE = False # XEP-0363 (http://xmpp.org/extensions/xep-0363.html) IQ_CALLBACK = {} @@ -59,7 +59,7 @@ TAGSIZE = 16 class HttpuploadPlugin(GajimPlugin): def init(self): - if not encryption_available: + if not ENCRYPTION_AVAILABLE: self.available_text = DEP_MSG self.config_dialog = None # HttpuploadPluginConfigDialog(self) self.events_handlers = {} @@ -116,13 +116,14 @@ class HttpuploadPlugin(GajimPlugin): try: return self.gui_interfaces[account] except KeyError: - self.gui_interfaces[account] = Base(self) + self.gui_interfaces[account] = Base(self, account) return self.gui_interfaces[account] class Base(object): - def __init__(self, plugin): + def __init__(self, plugin, account): self.plugin = plugin + self.account = account self.encrypted_upload = False self.enabled = False self.component = None @@ -164,40 +165,29 @@ class Base(object): for jid in self.controls: self.set_button_state(state, self.controls[jid]) - def encryption_activated(self): - if not encryption_available: + def encryption_activated(self, jid): + if not ENCRYPTION_AVAILABLE: return False - jid = self.chat_control.contact.jid - account = self.chat_control.account for plugin in gajim.plugin_manager.active_plugins: if type(plugin).__name__ == 'OmemoPlugin': - omemo = plugin - break - if omemo: - state = omemo.get_omemo_state(account) - log.info('Encryption is: ' + - str(state.encryption.is_active(jid))) - return state.encryption.is_active(jid) - log.info('Encryption is: False / OMEMO not found') + state = plugin.get_omemo_state(self.account) + encryption = state.encryption.is_active(jid) + log.info('Encryption is: %s', encryption) + return encryption + log.info('OMEMO not found, encryption disabled') return False def on_file_dialog_ok(self, widget, jid, chat_control): path_to_file = widget.get_filename() widget.destroy() - try: - self.encrypted_upload = self.encryption_activated() - except Exception as e: - log.debug(e) - self.encrypted_upload = False - if not path_to_file or not os.path.exists(path_to_file): return - if self.encrypted_upload: - filesize = os.path.getsize(path_to_file) + TAGSIZE # in bytes - else: - filesize = os.path.getsize(path_to_file) + encrypted = self.encryption_activated(jid) + filesize = os.path.getsize(path_to_file) + if encrypted: + filesize += TAGSIZE invalid_file = False if os.path.isfile(path_to_file): From 2cc6f200dbe43e397c1a5319b90da849fe824522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 18:18:23 +0100 Subject: [PATCH 05/13] [httpupload] Add request_slot() methode --- httpupload/httpupload.py | 63 +++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index 343ad06..d2594ac 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -128,6 +128,7 @@ class Base(object): self.enabled = False self.component = None self.controls = {} + self.conn = gajim.connections[account].connection def add_button(self, chat_control): jid = chat_control.contact.jid @@ -184,11 +185,6 @@ class Base(object): if not path_to_file or not os.path.exists(path_to_file): return - encrypted = self.encryption_activated(jid) - filesize = os.path.getsize(path_to_file) - if encrypted: - filesize += TAGSIZE - invalid_file = False if os.path.isfile(path_to_file): stat = os.stat(path_to_file) @@ -203,13 +199,21 @@ class Base(object): transient_for=chat_control.parent_win.window) return + encrypted = self.encryption_activated(jid) + filesize = os.path.getsize(path_to_file) + if encrypted: + filesize += TAGSIZE + mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0] if not mime_type: mime_type = 'application/octet-stream' # fallback mime type - log.info("Detected MIME Type of file: " + str(mime_type)) + log.info("Detected MIME type of file: ", mime_type) + progress_messages = Queue(8) progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), - progress_messages, self.plugin, parent=self.chat_control.parent_win.window) + progress_messages, self.plugin, parent=chat_control.parent_win.window) + + self.request_slot(path_to_file, filesize, mime_type, encrypted) def upload_file(stanza): slot = stanza.getTag("slot") @@ -325,36 +329,6 @@ class Base(object): transient_for=self.chat_control.parent_win.window) return - # create iq for slot request - id_ = gajim.get_an_id() - iq = nbxmpp.Iq( - typ='get', - to=jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)], - queryNS=None - ) - iq.setID(id_) - request = iq.addChild( - name="request", - namespace=NS_HTTPUPLOAD - ) - filename = request.addChild( - name="filename", - ) - filename.addData(os.path.basename(path_to_file)) - size = request.addChild( - name="size", - ) - size.addData(filesize) - content_type = request.addChild( - name="content-type", - ) - content_type.addData(mime_type) - - # send slot request and register callback - log.debug("sending httpupload slot request iq...") - iq_ids_to_callbacks[str(id_)] = upload_file - gajim.connections[self.chat_control.account].connection.send(iq) - self.chat_control.msg_textview.grab_focus() def on_file_button_clicked(self, widget, jid, chat_control): @@ -368,6 +342,21 @@ class Base(object): default_response=Gtk.ResponseType.OK, transient_for=chat_control.parent_win.window) + def request_slot(self, path_to_file, filesize, mime_type, encrypted): + iq = nbxmpp.Iq(typ='get', to=self.component) + id_ = gajim.get_an_id() + iq.setID(id_) + request = iq.setTag(name="request", namespace=NS_HTTPUPLOAD) + request.addChild('filename', payload=os.path.basename(path_to_file)) + request.addChild('size', payload=filesize) + request.addChild('content-type', payload=mime_type) + + log.info("Sending request for slot") + IQ_CALLBACK[id_] = \ + lambda stanza: self.upload_file( + stanza, path_to_file, filesize, mime_type, encrypted) + self.conn.send(iq) + class StreamFileWithProgress: def __init__(self, path, mode, callback=None, encrypted_upload=False, key=None, iv=None, *args): From 0faed4c3581c00a0d06787379805eb7d070e715c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 18:50:21 +0100 Subject: [PATCH 06/13] [httpupload] Add File object --- httpupload/httpupload.py | 50 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index d2594ac..76a19e7 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -179,15 +179,15 @@ class Base(object): return False def on_file_dialog_ok(self, widget, jid, chat_control): - path_to_file = widget.get_filename() + path = widget.get_filename() widget.destroy() - if not path_to_file or not os.path.exists(path_to_file): + if not path or not os.path.exists(path): return invalid_file = False - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) + if os.path.isfile(path): + stat = os.stat(path) if stat[6] == 0: invalid_file = True msg = _('File is empty') @@ -200,20 +200,26 @@ class Base(object): return encrypted = self.encryption_activated(jid) - filesize = os.path.getsize(path_to_file) + size = os.path.getsize(path) + key, iv = None, None if encrypted: - filesize += TAGSIZE + key = os.urandom(32) + iv = os.urandom(16) + size += TAGSIZE - mime_type = mimetypes.MimeTypes().guess_type(path_to_file)[0] - if not mime_type: - mime_type = 'application/octet-stream' # fallback mime type - log.info("Detected MIME type of file: ", mime_type) + mime = mimetypes.MimeTypes().guess_type(path)[0] + if not mime: + mime = 'application/octet-stream' # fallback mime type + log.info("Detected MIME type of file: ", mime) progress_messages = Queue(8) progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), progress_messages, self.plugin, parent=chat_control.parent_win.window) - self.request_slot(path_to_file, filesize, mime_type, encrypted) + file = File(path=path, size=size, mime=mime, encrypted=encrypted, + key=key, iv=iv, control=chat_control, + progress=progress_window) + self.request_slot(file) def upload_file(stanza): slot = stanza.getTag("slot") @@ -342,21 +348,29 @@ class Base(object): default_response=Gtk.ResponseType.OK, transient_for=chat_control.parent_win.window) - def request_slot(self, path_to_file, filesize, mime_type, encrypted): + def request_slot(self, file): iq = nbxmpp.Iq(typ='get', to=self.component) id_ = gajim.get_an_id() iq.setID(id_) request = iq.setTag(name="request", namespace=NS_HTTPUPLOAD) - request.addChild('filename', payload=os.path.basename(path_to_file)) - request.addChild('size', payload=filesize) - request.addChild('content-type', payload=mime_type) + request.addChild('filename', payload=os.path.basename(file.path)) + request.addChild('size', payload=file.size) + request.addChild('content-type', payload=file.mime) log.info("Sending request for slot") - IQ_CALLBACK[id_] = \ - lambda stanza: self.upload_file( - stanza, path_to_file, filesize, mime_type, encrypted) + IQ_CALLBACK[id_] = lambda stanza: self.received_slot(stanza, file) self.conn.send(iq) + +class File: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + self.stream = None + self.put = None + self.get = None + + class StreamFileWithProgress: def __init__(self, path, mode, callback=None, encrypted_upload=False, key=None, iv=None, *args): From d7fa0de8aab9918042114356a38b6d78a554e685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 19:43:07 +0100 Subject: [PATCH 07/13] [httpupload] Add request_received() methode --- httpupload/httpupload.py | 101 ++++++++++++++------------------------- 1 file changed, 37 insertions(+), 64 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index 76a19e7..f4ae65e 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -222,50 +222,6 @@ class Base(object): self.request_slot(file) def upload_file(stanza): - slot = stanza.getTag("slot") - if not slot: - log.error("got unexpected stanza: "+str(stanza)) - progress_window.close_dialog() - error = stanza.getTag("error") - if error and error.getTag("text"): - ErrorDialog(_('Could not request upload slot'), - _('Got unexpected response from server: %s') % str(error.getTagData("text")), - transient_for=self.chat_control.parent_win.window) - else: - ErrorDialog(_('Could not request upload slot'), - _('Got unexpected response from server (protocol mismatch??)'), - transient_for=self.chat_control.parent_win.window) - return - - try: - if self.encrypted_upload: - key = os.urandom(32) - iv = os.urandom(16) - data = StreamFileWithProgress(path_to_file, - "rb", - progress_window.update_progress, - self.encrypted_upload, key, iv) - else: - data = StreamFileWithProgress(path_to_file, - "rb", - progress_window.update_progress) - except: - log.error("Could not open file") - progress_window.close_dialog() - ErrorDialog(_('Could not open file'), - _('Exception raised while opening file (see error log for more information)'), - transient_for=self.chat_control.parent_win.window) - raise # fill error log with useful information - - put = slot.getTag("put") - get = slot.getTag("get") - if not put or not get: - log.error("got unexpected stanza: " + str(stanza)) - progress_window.close_dialog() - ErrorDialog(_('Could not request upload slot'), - _('Got unexpected response from server (protocol mismatch??)'), - transient_for=self.chat_control.parent_win.window) - return def upload_complete(response_code): if response_code == 0: @@ -322,19 +278,6 @@ class Base(object): gajim.thread_interface(uploader, [], upload_complete) - is_supported = gajim.get_jid_from_account(self.chat_control.account) in jid_to_servers and \ - gajim.connections[self.chat_control.account].connection != None - log.info("jid_to_servers of %s: %s ; connection: %s", - gajim.get_jid_from_account(self.chat_control.account), - str(jid_to_servers[gajim.get_jid_from_account(self.chat_control.account)]), - str(gajim.connections[self.chat_control.account].connection)) - if not is_supported: - progress_window.close_dialog() - log.error("upload component vanished, account got disconnected??") - ErrorDialog(_('Your server does not support http uploads or you just got disconnected.\nPlease try to reconnect or reopen the chat window to fix this.'), - transient_for=self.chat_control.parent_win.window) - return - self.chat_control.msg_textview.grab_focus() def on_file_button_clicked(self, widget, jid, chat_control): @@ -361,6 +304,37 @@ class Base(object): IQ_CALLBACK[id_] = lambda stanza: self.received_slot(stanza, file) self.conn.send(iq) + def received_slot(self, stanza, file): + if stanza.getType() == 'error': + file.progress.close_dialog() + ErrorDialog(_('Could not request upload slot'), + stanza.getErrorMsg(), + transient_for=file.control.parent_win.window) + log.error(stanza) + return + + try: + file.put = stanza.getTag("slot").getTag("put") + file.get = stanza.getTag("slot").getTag("get") + except Exception as exc: + file.progress.close_dialog() + log.error("Got unexpected stanza: ", stanza) + log.exception('Error') + ErrorDialog(_('Could not request upload slot'), + _('Got unexpected response from server (see log)'), + transient_for=file.control.parent_win.window) + return + + try: + file.stream = StreamFileWithProgress(file, "rb") + except Exception as exc: + file.progress.close_dialog() + log.exception("Could not open file") + ErrorDialog(_('Could not open file'), + _('Exception raised while opening file (see log)'), + transient_for=file.control.parent_win.window) + return + class File: def __init__(self, **kwargs): @@ -372,10 +346,9 @@ class File: class StreamFileWithProgress: - def __init__(self, path, mode, callback=None, - encrypted_upload=False, key=None, iv=None, *args): - self.backing = open(path, mode) - self.encrypted_upload = encrypted_upload + def __init__(self, file, mode, *args): + self.backing = open(file.path, mode) + self.encrypted_upload = file.encrypted self.backing.seek(0, os.SEEK_END) if self.encrypted_upload: if os.name == 'nt': @@ -383,14 +356,14 @@ class StreamFileWithProgress: else: self.backend = default_backend() self.encryptor = Cipher( - algorithms.AES(key), - GCM(iv), + algorithms.AES(file.key), + GCM(file.iv), backend=self.backend).encryptor() self._total = self.backing.tell() + TAGSIZE else: self._total = self.backing.tell() self.backing.seek(0) - self._callback = callback + self._callback = file.progress.update_progress self._args = args self._seen = 0 From 54eeb183e6f47811715643575c64a724bf8c49dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 21:08:40 +0100 Subject: [PATCH 08/13] [httpupload] Refactor upload methods --- httpupload/httpupload.py | 132 +++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index f4ae65e..e792e91 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -19,12 +19,14 @@ from gi.repository import GObject, Gtk, GLib import os import sys import time +import threading from urllib.request import Request, urlopen import mimetypes # better use the magic packet, but that's not a standard lib import gtkgui_helpers import logging from queue import Queue import binascii +import certifi from common import gajim from common import ged @@ -210,7 +212,7 @@ class Base(object): mime = mimetypes.MimeTypes().guess_type(path)[0] if not mime: mime = 'application/octet-stream' # fallback mime type - log.info("Detected MIME type of file: ", mime) + log.info("Detected MIME type of file: %s", mime) progress_messages = Queue(8) progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), @@ -221,65 +223,6 @@ class Base(object): progress=progress_window) self.request_slot(file) - def upload_file(stanza): - - def upload_complete(response_code): - if response_code == 0: - return # Upload was aborted - if 200 <= response_code < 300: - log.info("Upload completed successfully") - xhtml = None - is_image = mime_type.split('/', 1)[0] == 'image' - progress_window.close_dialog() - id_ = gajim.get_an_id() - def add_oob_tag(): - pass - if self.encrypted_upload: - keyAndIv = '#' + binascii.hexlify(iv) + binascii.hexlify(key) - self.chat_control.send_message(message=get.getData() + keyAndIv, xhtml=None) - else: - self.chat_control.send_message(message=get.getData(), xhtml=xhtml) - self.chat_control.msg_textview.grab_focus() - else: - progress_window.close_dialog() - log.error("got unexpected http upload response code: " + str(response_code)) - ErrorDialog(_('Could not upload file'), - _('Got unexpected http response code from server: ') + str(response_code), - transient_for=self.chat_control.parent_win.window) - - def on_upload_error(): - progress_window.close_dialog() - ErrorDialog(_('Could not upload file'), - _('Got unexpected exception while uploading file' - ' (see error log for more information)'), - transient_for=self.chat_control.parent_win.window) - return 0 - - def uploader(): - progress_messages.put(_('Uploading file via HTTP...')) - try: - headers = {'User-Agent': 'Gajim %s' % gajim.version, - 'Content-Type': mime_type} - request = Request(put.getData(), data=data, headers=headers, method='PUT') - log.debug("opening urllib upload request...") - transfer = urlopen(request, timeout=30) - data.close() - log.debug("urllib upload request done, response code: " + str(transfer.getcode())) - return transfer.getcode() - except UploadAbortedException: - log.info("Upload aborted") - except: - log.error("Exception during upload", exc_info=sys.exc_info()) - GLib.idle_add(on_upload_error) - return 0 - - log.info("Uploading file to '%s'..." % str(put.getData())) - log.info("Please download from '%s' later..." % str(get.getData())) - - gajim.thread_interface(uploader, [], upload_complete) - - self.chat_control.msg_textview.grab_focus() - def on_file_button_clicked(self, widget, jid, chat_control): FileChooserDialog( on_response_ok=lambda widget: self.on_file_dialog_ok(widget, jid, @@ -314,8 +257,8 @@ class Base(object): return try: - file.put = stanza.getTag("slot").getTag("put") - file.get = stanza.getTag("slot").getTag("get") + file.put = stanza.getTag("slot").getTag("put").getData() + file.get = stanza.getTag("slot").getTag("get").getData() except Exception as exc: file.progress.close_dialog() log.error("Got unexpected stanza: ", stanza) @@ -335,6 +278,60 @@ class Base(object): transient_for=file.control.parent_win.window) return + log.info('Uploading file to %s', file.put) + log.info('Please download from %s', file.get) + + thread = threading.Thread(target=self.upload_file, args=(file,)) + thread.daemon = True + thread.start() + + def upload_file(self, file): + + # progress_messages.put(_('Uploading file via HTTP...')) + try: + headers = {'User-Agent': 'Gajim %s' % gajim.version, + 'Content-Type': file.mime} + request = Request(file.put, data=file.stream, headers=headers, method='PUT') + log.debug("opening urllib upload request...") + if os.name == 'nt': + transfer = urlopen(request, cafile=certifi.where(), timeout=30) + else: + transfer = urlopen(request, timeout=30) + file.stream.close() + log.debug('urllib upload request done, response code: ', transfer.getcode()) + GLib.idle_add(self.upload_complete, transfer.getcode(), file) + except: + log.error("Exception during upload", exc_info=sys.exc_info()) + GLib.idle_add(file.progress.close_dialog) + GLib.idle_add(self.on_upload_error, file) + + def upload_complete(self, response_code, file): + if response_code == 0: + return # Upload was aborted + if 200 <= response_code < 300: + log.info("Upload completed successfully") + file.progress.close_dialog() + message = file.get + if file.encrypted: + message += '#' + binascii.hexlify(file.iv + file.key) + file.control.send_message(message=message) + file.control.msg_textview.grab_focus() + else: + file.progress.close_dialog() + log.error('Got unexpected http upload response code: ', + response_code) + ErrorDialog( + _('Could not upload file'), + _('HTTP response code from server: %s') % response_code, + transient_for=file.control.parent_win.window) + + def on_upload_error(self, file): + file.progress.close_dialog() + ErrorDialog(_('Could not upload file'), + _('Got unexpected exception while uploading file' + ' (see log)'), + transient_for=file.control.parent_win.window) + class File: def __init__(self, **kwargs): @@ -381,13 +378,15 @@ class StreamFileWithProgress: data += self.encryptor.tag self._seen += TAGSIZE if self._callback: - self._callback(self._seen, self._total, *self._args) + GLib.idle_add( + self._callback, self._seen, self._total, *self._args) return data else: data = self.backing.read(size) self._seen += len(data) if self._callback: - self._callback(self._seen, self._total, *self._args) + GLib.idle_add( + self._callback, self._seen, self._total, *self._args) return data def close(self): @@ -445,7 +444,7 @@ class ProgressWindow: def update_progress(self, seen, total): if self.stopped == True: - raise UploadAbortedException + raise if self.pulse_progressbar_timeout_id: GLib.source_remove(self.pulse_progressbar_timeout_id) self.pulse_progressbar_timeout_id = None @@ -457,6 +456,7 @@ class ProgressWindow: def close_dialog(self): self.on_cancel(None) + class UploadAbortedException(Exception): def __str__(self): - return "Upload Aborted" + return "Upload Aborted" \ No newline at end of file From 361ff561aacbba215bac941cff76210622d6cebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 21:28:53 +0100 Subject: [PATCH 09/13] [httpupload] Refactor ProgressWindow --- httpupload/httpupload.py | 76 +++++++++++----------------- httpupload/upload_progress_dialog.ui | 15 +++--- 2 files changed, 38 insertions(+), 53 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index e792e91..e5583a6 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -214,13 +214,13 @@ class Base(object): mime = 'application/octet-stream' # fallback mime type log.info("Detected MIME type of file: %s", mime) - progress_messages = Queue(8) - progress_window = ProgressWindow(_('HTTP Upload'), _('Requesting HTTP Upload Slot...'), - progress_messages, self.plugin, parent=chat_control.parent_win.window) + event = threading.Event() + progress = ProgressWindow( + self.plugin, chat_control.parent_win.window, event) file = File(path=path, size=size, mime=mime, encrypted=encrypted, key=key, iv=iv, control=chat_control, - progress=progress_window) + progress=progress, event=event) self.request_slot(file) def on_file_button_clicked(self, widget, jid, chat_control): @@ -287,7 +287,8 @@ class Base(object): def upload_file(self, file): - # progress_messages.put(_('Uploading file via HTTP...')) + GLib.idle_add(file.progress.label.set_text, + _('Uploading file via HTTP...')) try: headers = {'User-Agent': 'Gajim %s' % gajim.version, 'Content-Type': file.mime} @@ -344,6 +345,7 @@ class File: class StreamFileWithProgress: def __init__(self, file, mode, *args): + self.event = file.event self.backing = open(file.path, mode) self.encrypted_upload = file.encrypted self.backing.seek(0, os.SEEK_END) @@ -368,6 +370,8 @@ class StreamFileWithProgress: return self._total def read(self, size): + if self.event.isSet(): + raise UploadAbortedException if self.encrypted_upload: data = self.backing.read(size) if len(data) > 0: @@ -394,69 +398,49 @@ class StreamFileWithProgress: class ProgressWindow: - def __init__(self, title_text, during_text, messages_queue, plugin, parent): + def __init__(self, plugin, parent, event): self.plugin = plugin + self.event = event self.xml = gtkgui_helpers.get_gtk_builder(self.plugin.local_file_path('upload_progress_dialog.ui')) - self.messages_queue = messages_queue self.dialog = self.xml.get_object('progress_dialog') self.dialog.set_transient_for(parent) + self.dialog.set_title('HTTP Upload') self.label = self.xml.get_object('label') - self.cancel_button = self.xml.get_object('close_button') - self.label.set_markup('' + during_text + '') + self.label.set_text(_('Requesting HTTP Upload Slot...')) + # self.label.set_markup('' + during_text + '') self.progressbar = self.xml.get_object('progressbar') - self.progressbar.set_text("") - self.dialog.set_title(title_text) - #self.dialog.set_geometry_hints(min_width=400, min_height=96) - #self.dialog.set_position(Gtk.WIN_POS_CENTER_ON_PARENT) self.dialog.show_all() self.xml.connect_signals(self) - self.stopped = False - self.pulse_progressbar_timeout_id = GLib.timeout_add(100, self.pulse_progressbar) - self.process_messages_queue_timeout_id = GLib.timeout_add(100, self.process_messages_queue) - + self.pulse = GLib.timeout_add(100, self.pulse_progressbar) def pulse_progressbar(self): if self.dialog: self.progressbar.pulse() - return True # loop forever + return True return False - def process_messages_queue(self): - if not self.messages_queue.empty(): - self.label.set_markup('' + self.messages_queue.get() + '') - if self.dialog: - return True # loop forever - return False - - def on_progress_dialog_delete_event(self, widget, event): - self.stopped = True - if self.pulse_progressbar_timeout_id: - GLib.source_remove(self.pulse_progressbar_timeout_id) - GLib.source_remove(self.process_messages_queue_timeout_id) - - def on_cancel(self, widget): - self.stopped = True - if self.pulse_progressbar_timeout_id: - GLib.source_remove(self.pulse_progressbar_timeout_id) - GLib.source_remove(self.process_messages_queue_timeout_id) - self.dialog.destroy() + def on_destroy(self, *args): + self.event.set() + if self.pulse: + GLib.source_remove(self.pulse) def update_progress(self, seen, total): - if self.stopped == True: - raise - if self.pulse_progressbar_timeout_id: - GLib.source_remove(self.pulse_progressbar_timeout_id) - self.pulse_progressbar_timeout_id = None + if self.event.isSet(): + print('abort update progress') + return + if self.pulse: + GLib.source_remove(self.pulse) + self.pulse = None + print('remove') pct = (float(seen) / total) * 100.0 self.progressbar.set_fraction(float(seen) / total) self.progressbar.set_text(str(int(pct)) + "%") - log.debug('upload progress: %.2f%% (%d of %d bytes)' % (pct, seen, total)) - def close_dialog(self): - self.on_cancel(None) + def close_dialog(self, *args): + self.dialog.destroy() class UploadAbortedException(Exception): def __str__(self): - return "Upload Aborted" \ No newline at end of file + return "Upload Aborted" diff --git a/httpupload/upload_progress_dialog.ui b/httpupload/upload_progress_dialog.ui index b50c3c2..7c4f78c 100644 --- a/httpupload/upload_progress_dialog.ui +++ b/httpupload/upload_progress_dialog.ui @@ -1,13 +1,13 @@ - + - + True False upload-media dialog - + True @@ -25,7 +25,6 @@ False 2 4 - 0 3 @@ -35,7 +34,7 @@ True False True - + @@ -65,8 +64,10 @@ True False - 0 - True + + + + From 5b42455918bad69e2152f9f71cf3747d491bb9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 22:40:01 +0100 Subject: [PATCH 10/13] [httpupload] Better exception handling --- httpupload/httpupload.py | 48 ++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index e5583a6..9e96802 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -20,6 +20,8 @@ import os import sys import time import threading +import urllib +import ssl from urllib.request import Request, urlopen import mimetypes # better use the magic packet, but that's not a standard lib import gtkgui_helpers @@ -248,6 +250,7 @@ class Base(object): self.conn.send(iq) def received_slot(self, stanza, file): + log.info("Received slot") if stanza.getType() == 'error': file.progress.close_dialog() ErrorDialog(_('Could not request upload slot'), @@ -259,9 +262,9 @@ class Base(object): try: file.put = stanza.getTag("slot").getTag("put").getData() file.get = stanza.getTag("slot").getTag("get").getData() - except Exception as exc: + except Exception: file.progress.close_dialog() - log.error("Got unexpected stanza: ", stanza) + log.error("Got unexpected stanza: %s", stanza) log.exception('Error') ErrorDialog(_('Could not request upload slot'), _('Got unexpected response from server (see log)'), @@ -286,51 +289,58 @@ class Base(object): thread.start() def upload_file(self, file): - GLib.idle_add(file.progress.label.set_text, _('Uploading file via HTTP...')) try: headers = {'User-Agent': 'Gajim %s' % gajim.version, 'Content-Type': file.mime} request = Request(file.put, data=file.stream, headers=headers, method='PUT') - log.debug("opening urllib upload request...") + log.debug("Opening Urllib upload request...") if os.name == 'nt': transfer = urlopen(request, cafile=certifi.where(), timeout=30) else: transfer = urlopen(request, timeout=30) file.stream.close() - log.debug('urllib upload request done, response code: ', transfer.getcode()) + log.debug('Urllib upload request done, response code: %s', + transfer.getcode()) GLib.idle_add(self.upload_complete, transfer.getcode(), file) - except: - log.error("Exception during upload", exc_info=sys.exc_info()) - GLib.idle_add(file.progress.close_dialog) - GLib.idle_add(self.on_upload_error, file) + return + except UploadAbortedException as exc: + log.info(exc) + error_msg = exc + except urllib.error.URLError as exc: + if isinstance(exc.reason, ssl.SSLError): + error_msg = exc.reason.reason + if error_msg == 'CERTIFICATE_VERIFY_FAILED': + log.exception('Certificate verify failed') + except Exception as exc: + log.exception("Exception during upload") + error_msg = exc + GLib.idle_add(file.progress.close_dialog) + GLib.idle_add(self.on_upload_error, file, error_msg) - def upload_complete(self, response_code, file): - if response_code == 0: - return # Upload was aborted + @staticmethod + def upload_complete(response_code, file): + file.progress.close_dialog() if 200 <= response_code < 300: log.info("Upload completed successfully") - file.progress.close_dialog() message = file.get if file.encrypted: message += '#' + binascii.hexlify(file.iv + file.key) file.control.send_message(message=message) file.control.msg_textview.grab_focus() else: - file.progress.close_dialog() - log.error('Got unexpected http upload response code: ', + log.error('Got unexpected http upload response code: %s', response_code) ErrorDialog( _('Could not upload file'), _('HTTP response code from server: %s') % response_code, transient_for=file.control.parent_win.window) - def on_upload_error(self, file): + @staticmethod + def on_upload_error(file, reason): file.progress.close_dialog() - ErrorDialog(_('Could not upload file'), - _('Got unexpected exception while uploading file' - ' (see log)'), + ErrorDialog(_('Error'), str(reason), transient_for=file.control.parent_win.window) From ff135beec1409151e07f54d54c61bc647aefe995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 22:49:08 +0100 Subject: [PATCH 11/13] [httpupload] Clean up and satisfy pylint --- httpupload/httpupload.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index 9e96802..e9db753 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -14,28 +14,23 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . - -from gi.repository import GObject, Gtk, GLib import os -import sys -import time import threading -import urllib import ssl +import urllib from urllib.request import Request, urlopen -import mimetypes # better use the magic packet, but that's not a standard lib -import gtkgui_helpers +import mimetypes import logging -from queue import Queue import binascii import certifi +import nbxmpp +from gi.repository import Gtk, GLib + from common import gajim from common import ged from plugins import GajimPlugin -from plugins.helpers import log_calls from dialogs import FileChooserDialog, ErrorDialog -import nbxmpp log = logging.getLogger('gajim.plugin_system.httpupload') @@ -51,11 +46,10 @@ try: except Exception as exc: DEP_MSG = 'For encryption of files, ' \ 'please install python-cryptography!' - log.debug('Cryptography Import Error: %s', exc) + log.error('Cryptography Import Error: %s', exc) log.info('Decryption/Encryption disabled due to errors') ENCRYPTION_AVAILABLE = False -# XEP-0363 (http://xmpp.org/extensions/xep-0363.html) IQ_CALLBACK = {} NS_HTTPUPLOAD = 'urn:xmpp:http:upload' TAGSIZE = 16 @@ -65,7 +59,7 @@ class HttpuploadPlugin(GajimPlugin): def init(self): if not ENCRYPTION_AVAILABLE: self.available_text = DEP_MSG - self.config_dialog = None # HttpuploadPluginConfigDialog(self) + self.config_dialog = None self.events_handlers = {} self.events_handlers['agent-info-received'] = ( ged.PRECORE, self.handle_agent_info_received) @@ -151,7 +145,8 @@ class Base(object): actions_hbox.child_set_property(button, 'position', button_pos - 1) self.controls[jid] = button - id_ = button.connect('clicked', self.on_file_button_clicked, jid, chat_control) + id_ = button.connect( + 'clicked', self.on_file_button_clicked, jid, chat_control) chat_control.handlers[id_] = button self.set_button_state(self.enabled, button) button.show() @@ -294,14 +289,15 @@ class Base(object): try: headers = {'User-Agent': 'Gajim %s' % gajim.version, 'Content-Type': file.mime} - request = Request(file.put, data=file.stream, headers=headers, method='PUT') - log.debug("Opening Urllib upload request...") + request = Request( + file.put, data=file.stream, headers=headers, method='PUT') + log.info("Opening Urllib upload request...") if os.name == 'nt': transfer = urlopen(request, cafile=certifi.where(), timeout=30) else: transfer = urlopen(request, timeout=30) file.stream.close() - log.debug('Urllib upload request done, response code: %s', + log.info('Urllib upload request done, response code: %s', transfer.getcode()) GLib.idle_add(self.upload_complete, transfer.getcode(), file) return @@ -411,7 +407,9 @@ class ProgressWindow: def __init__(self, plugin, parent, event): self.plugin = plugin self.event = event - self.xml = gtkgui_helpers.get_gtk_builder(self.plugin.local_file_path('upload_progress_dialog.ui')) + glade_file = self.plugin.local_file_path('upload_progress_dialog.ui') + self.xml = Gtk.Builder() + self.xml.add_from_file(glade_file) self.dialog = self.xml.get_object('progress_dialog') self.dialog.set_transient_for(parent) self.dialog.set_title('HTTP Upload') @@ -437,12 +435,10 @@ class ProgressWindow: def update_progress(self, seen, total): if self.event.isSet(): - print('abort update progress') return if self.pulse: GLib.source_remove(self.pulse) self.pulse = None - print('remove') pct = (float(seen) / total) * 100.0 self.progressbar.set_fraction(float(seen) / total) self.progressbar.set_text(str(int(pct)) + "%") From fe8d23e4e0b4a82186114e1c291dc6a31dddb567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 23:03:45 +0100 Subject: [PATCH 12/13] [httpupload] Fix and simplify encryption --- httpupload/httpupload.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/httpupload/httpupload.py b/httpupload/httpupload.py index e9db753..9133c60 100644 --- a/httpupload/httpupload.py +++ b/httpupload/httpupload.py @@ -21,7 +21,7 @@ import urllib from urllib.request import Request, urlopen import mimetypes import logging -import binascii +from binascii import hexlify import certifi import nbxmpp @@ -35,10 +35,7 @@ from dialogs import FileChooserDialog, ErrorDialog log = logging.getLogger('gajim.plugin_system.httpupload') try: - if os.name == 'nt': - from cryptography.hazmat.backends.openssl import backend - else: - from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers.modes import GCM @@ -172,8 +169,8 @@ class Base(object): if type(plugin).__name__ == 'OmemoPlugin': state = plugin.get_omemo_state(self.account) encryption = state.encryption.is_active(jid) - log.info('Encryption is: %s', encryption) - return encryption + log.info('Encryption is: %s', bool(encryption)) + return bool(encryption) log.info('OMEMO not found, encryption disabled') return False @@ -298,7 +295,7 @@ class Base(object): transfer = urlopen(request, timeout=30) file.stream.close() log.info('Urllib upload request done, response code: %s', - transfer.getcode()) + transfer.getcode()) GLib.idle_add(self.upload_complete, transfer.getcode(), file) return except UploadAbortedException as exc: @@ -322,7 +319,7 @@ class Base(object): log.info("Upload completed successfully") message = file.get if file.encrypted: - message += '#' + binascii.hexlify(file.iv + file.key) + message += '#' + hexlify(file.iv + file.key).decode('utf-8') file.control.send_message(message=message) file.control.msg_textview.grab_focus() else: @@ -353,17 +350,13 @@ class StreamFileWithProgress: def __init__(self, file, mode, *args): self.event = file.event self.backing = open(file.path, mode) - self.encrypted_upload = file.encrypted + self.encrypted = file.encrypted self.backing.seek(0, os.SEEK_END) - if self.encrypted_upload: - if os.name == 'nt': - self.backend = backend - else: - self.backend = default_backend() + if self.encrypted: self.encryptor = Cipher( algorithms.AES(file.key), GCM(file.iv), - backend=self.backend).encryptor() + backend=default_backend()).encryptor() self._total = self.backing.tell() + TAGSIZE else: self._total = self.backing.tell() @@ -378,7 +371,7 @@ class StreamFileWithProgress: def read(self, size): if self.event.isSet(): raise UploadAbortedException - if self.encrypted_upload: + if self.encrypted: data = self.backing.read(size) if len(data) > 0: data = self.encryptor.update(data) @@ -415,7 +408,6 @@ class ProgressWindow: self.dialog.set_title('HTTP Upload') self.label = self.xml.get_object('label') self.label.set_text(_('Requesting HTTP Upload Slot...')) - # self.label.set_markup('' + during_text + '') self.progressbar = self.xml.get_object('progressbar') self.dialog.show_all() self.xml.connect_signals(self) From 6b1ad59ef99b88a525ab308c21e035da6933725d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Mon, 27 Feb 2017 20:03:14 +0100 Subject: [PATCH 13/13] [httpupload] Update manifest.ini --- httpupload/manifest.ini | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/httpupload/manifest.ini b/httpupload/manifest.ini index 0d2e319..140363d 100644 --- a/httpupload/manifest.ini +++ b/httpupload/manifest.ini @@ -1,16 +1,11 @@ [info] name: HttpUpload short_name: httpupload -version: 0.5.2 +version: 0.6.0 description: This plugin is designed to send a file to a contact or muc by using httpupload.
Your server must support XEP-0363: HTTP Upload.
- Conversations supported this.
- If the receiving side supports XEP-0071: XHTML-IM - and maintains the scheme data: URI, a thumbnail image is send along the link to the full size image. - If the receiving side doesn't support this, only a text message containing the link to the image is send. authors: Thilo Molitor Philipp Hörist Linus Heckemann -homepage: https://trac-plugins.gajim.org/wiki/HttpUploadPlugin +homepage: https://dev.gajim.org/gajim/gajim-plugins/wikis/HttpUploadPlugin min_gajim_version: 0.16.10 -max_gajim_version: 0.16.10.1