From b600328639125320cf9738928c9f03fa9ee6a108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 4 Jan 2019 00:03:35 +0100 Subject: [PATCH] Refactor message handlers - Adapt to nbxmpp now unwraping MAM messages - Use nbxmpp properties - Save message-id to database --- gajim/common/logger.py | 8 ++ gajim/common/modules/mam.py | 175 +++++++++++--------------------- gajim/common/modules/message.py | 129 ++++++++++------------- gajim/common/modules/util.py | 29 ------ gajim/session.py | 3 +- 5 files changed, 125 insertions(+), 219 deletions(-) diff --git a/gajim/common/logger.py b/gajim/common/logger.py index c4d238f8e..735805f91 100644 --- a/gajim/common/logger.py +++ b/gajim/common/logger.py @@ -72,6 +72,7 @@ LOGS_SQL_STATEMENT = ''' subject TEXT, additional_data TEXT, stanza_id TEXT, + message_id TEXT, encryption TEXT, encryption_state TEXT, marker INTEGER @@ -228,6 +229,13 @@ class Logger: ] self._execute_multiple(con, statements) + if self._get_user_version(con) < 3: + statements = [ + 'ALTER TABLE logs ADD COLUMN "message_id" TEXT', + 'PRAGMA user_version=3' + ] + self._execute_multiple(con, statements) + def _migrate_cache(self, con): if self._get_user_version(con) == 0: # All migrations from 0.16.9 until 1.0.0 diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py index c6413484e..34eb04029 100644 --- a/gajim/common/modules/mam.py +++ b/gajim/common/modules/mam.py @@ -19,6 +19,7 @@ import time from datetime import datetime, timedelta import nbxmpp +from nbxmpp.structs import StanzaHandler from gajim.common import app from gajim.common.nec import NetworkEvent @@ -33,8 +34,6 @@ from gajim.common.modules.misc import parse_delay from gajim.common.modules.misc import parse_oob from gajim.common.modules.misc import parse_correction from gajim.common.modules.misc import parse_eme -from gajim.common.modules.util import is_self_message -from gajim.common.modules.util import is_muc_pm log = logging.getLogger('gajim.c.m.archiving') @@ -45,8 +44,9 @@ class MAM: self._account = con.name self.handlers = [ - ('message', self._mam_message_received, '', nbxmpp.NS_MAM_1), - ('message', self._mam_message_received, '', nbxmpp.NS_MAM_2), + StanzaHandler(name='message', + callback=self._mam_message_received, + priority=51), ] self.available = False @@ -72,132 +72,91 @@ class MAM: account=self._account, feature=self.archiving_namespace)) - def _from_valid_archive(self, stanza, message, groupchat): - if groupchat: - expected_archive = message.getFrom() + def _from_valid_archive(self, stanza, properties): + if properties.type.is_groupchat: + expected_archive = properties.jid else: expected_archive = self._con.get_own_jid() - archive_jid = stanza.getFrom() - if archive_jid is None: - if groupchat: - return - # Message from our own archive - return self._con.get_own_jid() + return properties.mam.archive.bareMatch(expected_archive) - if archive_jid.bareMatch(expected_archive): - return archive_jid + def _get_unique_id(self, properties): + if properties.type.is_groupchat: + return properties.mam.id, None - def _get_unique_id(self, result, message, groupchat, self_message, muc_pm): - stanza_id = result.getAttr('id') - if groupchat: - return stanza_id, None + if properties.is_self_message: + return None, properties.id - origin_id = message.getOriginID() - if self_message: - return None, origin_id + if properties.is_muc_pm: + return properties.mam.id, properties.id - if muc_pm: - return stanza_id, origin_id - - if self._con.get_own_jid().bareMatch(message.getFrom()): + if self._con.get_own_jid().bareMatch(properties.jid): # message we sent - return stanza_id, origin_id + return properties.mam.id, properties.id # A message we received - return stanza_id, None + return properties.mam.id, None + + def _mam_message_received(self, _con, stanza, properties): + if not properties.is_mam_message: + return - def _mam_message_received(self, _con, stanza): app.nec.push_incoming_event( NetworkIncomingEvent('raw-mam-message-received', conn=self._con, stanza=stanza)) - result = stanza.getTag('result', protocol=True) - queryid = result.getAttr('queryid') - forwarded = result.getTag('forwarded', - namespace=nbxmpp.NS_FORWARD, - protocol=True) - message = forwarded.getTag('message', protocol=True) - - groupchat = message.getType() == 'groupchat' - - archive_jid = self._from_valid_archive(stanza, message, groupchat) - if archive_jid is None: - log.warning('Message from invalid archive %s', stanza) + if not self._from_valid_archive(stanza, properties): + log.warning('Message from invalid archive %s', + properties.mam.archive) raise nbxmpp.NodeProcessed - log.info('Received message from archive: %s', archive_jid) - if not self._is_valid_request(archive_jid, queryid): - log.warning('Invalid MAM Message: unknown query id') + log.info('Received message from archive: %s', properties.mam.archive) + if not self._is_valid_request(properties): + log.warning('Invalid MAM Message: unknown query id %s', + properties.mam.query_id) log.debug(stanza) raise nbxmpp.NodeProcessed - # Timestamp parsing - # Most servers dont set the 'from' attr, so we cant check for it - delay_timestamp = parse_delay(forwarded) - if delay_timestamp is None: - log.warning('No timestamp on MAM message') - log.warning(stanza) - raise nbxmpp.NodeProcessed - - # Fix for self messaging - if not groupchat: - to = message.getTo() - if to is None: - # Some servers dont set the 'to' attribute when - # we send a message to ourself - message.setTo(self._con.get_own_jid()) - event_attrs = {} + groupchat = properties.type.is_groupchat + if groupchat: - event_attrs.update(self._parse_gc_attrs(message)) + event_attrs.update(self._parse_gc_attrs(properties)) else: - event_attrs.update(self._parse_chat_attrs(message)) + event_attrs.update(self._parse_chat_attrs(stanza, properties)) - self_message = is_self_message(message, groupchat) - muc_pm = is_muc_pm(message, event_attrs['with_'], groupchat) + stanza_id, message_id = self._get_unique_id(properties) - stanza_id, origin_id = self._get_unique_id( - result, message, groupchat, self_message, muc_pm) - message_id = message.getID() - - # Check for duplicates - namespace = self.archiving_namespace - if groupchat: - namespace = muc_caps_cache.get_mam_namespace( - archive_jid.getStripped()) - - if namespace == nbxmpp.NS_MAM_2: + if properties.mam.is_ver_2: # Search only with stanza-id for duplicates on mam:2 if app.logger.find_stanza_id(self._account, - archive_jid.getStripped(), + str(properties.mam.archive), stanza_id, - origin_id, + message_id, groupchat=groupchat): - log.info('Found duplicate with stanza-id') + log.info('Found duplicate with stanza-id: %s, message-id: %s', + stanza_id, message_id) raise nbxmpp.NodeProcessed - msgtxt = message.getTagData('body') - event_attrs.update( {'conn': self._con, 'additional_data': AdditionalDataDict(), 'encrypted': False, - 'timestamp': delay_timestamp, - 'self_message': self_message, + 'timestamp': properties.mam.timestamp, + 'self_message': properties.is_self_message, 'groupchat': groupchat, - 'muc_pm': muc_pm, + 'muc_pm': properties.is_muc_pm, 'stanza_id': stanza_id, - 'origin_id': origin_id, - 'message_id': message_id, + 'origin_id': message_id, + 'message_id': properties.id, 'correct_id': None, - 'archive_jid': archive_jid, - 'msgtxt': msgtxt, - 'message': message, - 'stanza': message, - 'namespace': namespace, + 'archive_jid': properties.mam.archive, + 'msgtxt': properties.body, + 'message': stanza, + 'stanza': stanza, + 'namespace': properties.mam.namespace, }) if groupchat: @@ -217,26 +176,18 @@ class MAM: raise nbxmpp.NodeProcessed @staticmethod - def _parse_gc_attrs(message): - with_ = message.getFrom() - nick = message.getFrom().getResource() - - # Get the real jid if we have it + def _parse_gc_attrs(properties): real_jid = None - muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if muc_user is not None: - real_jid = muc_user.getTagAttr('item', 'jid') - if real_jid is not None: - real_jid = nbxmpp.JID(real_jid) - - return {'with_': with_, - 'nick': nick, + if properties.muc_user is not None: + real_jid = properties.muc_user.jid + return {'with_': properties.jid, + 'nick': properties.muc_nickname, 'real_jid': real_jid, 'kind': KindConstant.GC_MSG} - def _parse_chat_attrs(self, message): - frm = message.getFrom() - to = message.getTo() + def _parse_chat_attrs(self, stanza, properties): + frm = properties.jid + to = stanza.getTo() if frm.bareMatch(self._con.get_own_jid()): with_ = to kind = KindConstant.CHAT_MSG_SENT @@ -290,17 +241,15 @@ class MAM: message=event.msgtxt, contact_name=event.nick, additional_data=event.additional_data, - stanza_id=stanza_id) + stanza_id=stanza_id, + message_id=event.message_id) app.nec.push_incoming_event( MamDecryptedMessageReceived(None, **vars(event))) - def _is_valid_request(self, jid, query_id): - if query_id is None: - return False - - valid_id = self._mam_query_ids.get(jid.getStripped(), None) - return valid_id == query_id + def _is_valid_request(self, properties): + valid_id = self._mam_query_ids.get(str(properties.mam.archive), None) + return valid_id == properties.mam.query_id def _get_query_id(self, jid): query_id = self._con.connection.getAnID() diff --git a/gajim/common/modules/message.py b/gajim/common/modules/message.py index df5f1ccf2..016d69e1d 100644 --- a/gajim/common/modules/message.py +++ b/gajim/common/modules/message.py @@ -18,9 +18,10 @@ import time import logging import nbxmpp +from nbxmpp.structs import StanzaHandler +from nbxmpp.const import MessageType from gajim.common import app -from gajim.common import helpers from gajim.common import caps_cache from gajim.common.i18n import _ from gajim.common.nec import NetworkIncomingEvent @@ -36,8 +37,6 @@ from gajim.common.modules.misc import parse_attention from gajim.common.modules.misc import parse_form from gajim.common.modules.misc import parse_oob from gajim.common.modules.misc import parse_xhtml -from gajim.common.modules.util import is_self_message -from gajim.common.modules.util import is_muc_pm from gajim.common.connection_handlers_events import MessageErrorEvent @@ -49,30 +48,26 @@ class Message: self._con = con self._account = con.name - self.handlers = [('message', self._message_received)] + self.handlers = [ + StanzaHandler(name='message', + callback=self._message_received, + priority=50), + ] # XEPs for which this message module should not be executed self._message_namespaces = set([nbxmpp.NS_PUBSUB_EVENT, nbxmpp.NS_ROSTERX, - nbxmpp.NS_MAM_1, - nbxmpp.NS_MAM_2, - nbxmpp.NS_CONFERENCE, - nbxmpp.NS_IBB, - nbxmpp.NS_CAPTCHA,]) + nbxmpp.NS_IBB]) def _message_received(self, _con, stanza, properties): + if properties.is_mam_message: + return # Check if a child of the message contains any # namespaces that we handle in other modules. # nbxmpp executes less common handlers last if self._message_namespaces & set(stanza.getProperties()): return - muc_user = stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if muc_user is not None and (stanza.getType() != 'error'): - if muc_user.getChildren(): - # Not a PM, handled by MUC module - return - log.info('Received from %s', stanza.getFrom()) app.nec.push_incoming_event(NetworkEvent( @@ -87,49 +82,36 @@ class Message: # Ugly, we treat the from attr as the remote jid, # to make that work with sent carbons we have to do this. # TODO: Check where in Gajim and plugins we depend on that behavior - stanza.setFrom(stanza.getTo().getBare()) + stanza.setFrom(stanza.getTo()) from_ = stanza.getFrom() - type_ = stanza.getType() - if type_ is None: - type_ = 'normal' + fjid = str(from_) + jid = from_.getBare() + resource = from_.getResource() - self_message = is_self_message(stanza, type_ == 'groupchat') - muc_pm = is_muc_pm(stanza, from_, type_ == 'groupchat') - - id_ = stanza.getID() - - fjid = None - if from_ is not None: - try: - fjid = helpers.parse_jid(str(from_)) - except helpers.InvalidFormat: - log.warning('Invalid JID: %s, ignoring it', - stanza.getFrom()) - return - - jid, resource = app.get_room_and_nick_from_fjid(fjid) + type_ = properties.type # Check for duplicates - stanza_id, origin_id = self._get_unique_id( - stanza, forwarded, sent, self_message, muc_pm) + stanza_id, message_id = self._get_unique_id(properties) # Check groupchat messages for duplicates, # We do this because of MUC History messages - if type_ == 'groupchat' or self_message or muc_pm: - if type_ == 'groupchat': + if (properties.type.is_groupchat or + properties.is_self_message or + properties.is_muc_pm): + if properties.type.is_groupchat: archive_jid = stanza.getFrom().getStripped() else: archive_jid = self._con.get_own_jid().getStripped() if app.logger.find_stanza_id(self._account, archive_jid, stanza_id, - origin_id, - type_ == 'groupchat'): + message_id, + properties.type.is_groupchat): return - thread_id = stanza.getThread() - msgtxt = stanza.getBody() + thread_id = properties.thread + msgtxt = properties.body # TODO: remove all control UI stuff gc_control = app.interface.msg_win_mgr.get_gc_control( @@ -139,7 +121,7 @@ class Message: gc_control = minimized.get(jid) if gc_control and jid == fjid: - if type_ == 'error': + if properties.type.is_error: if msgtxt: msgtxt = _('error while sending %(message)s ( %(error)s )') % { 'message': msgtxt, 'error': stanza.getErrorMsg()} @@ -148,11 +130,11 @@ class Message: # TODO: why is this here? if stanza.getTag('html'): stanza.delChild('html') - type_ = 'groupchat' + type_ = MessageType.GROUPCHAT session = None - if type_ != 'groupchat': - if muc_pm and type_ == 'error': + if not properties.type.is_groupchat: + if properties.is_muc_pm and properties.type.is_error: session = self._con.find_session(fjid, thread_id) if not session: session = self._con.get_latest_session(fjid) @@ -171,7 +153,7 @@ class Message: 'conn': self._con, 'stanza': stanza, 'account': self._account, - 'id_': id_, + 'id_': properties.id, 'encrypted': False, 'additional_data': AdditionalDataDict(), 'forwarded': forwarded, @@ -180,13 +162,14 @@ class Message: 'jid': jid, 'resource': resource, 'stanza_id': stanza_id, - 'unique_id': stanza_id or origin_id, - 'mtype': type_, + 'unique_id': stanza_id or message_id, + 'message_id': properties.id, + 'mtype': type_.value, 'msgtxt': msgtxt, 'thread_id': thread_id, 'session': session, - 'self_message': self_message, - 'muc_pm': muc_pm, + 'self_message': properties.is_self_message, + 'muc_pm': properties.is_muc_pm, 'gc_control': gc_control } @@ -313,7 +296,8 @@ class Message: message=event.msgtxt, contact_name=event.nick, additional_data=event.additional_data, - stanza_id=event.stanza_id) + stanza_id=event.stanza_id, + message_id=event.message_id) app.logger.set_room_last_message_time(event.room_jid, event.timestamp) self._con.get_module('MAM').save_archive_id( event.room_jid, event.stanza_id, event.timestamp) @@ -324,36 +308,29 @@ class Message: if stanza_id is None and namespace == nbxmpp.NS_MAM_2: log.warning('%s announces mam:2 without stanza-id', room_jid) - def _get_unique_id(self, stanza, _forwarded, _sent, self_message, _muc_pm): - if stanza.getType() == 'groupchat': - # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced - return self._get_stanza_id(stanza), None + def _get_unique_id(self, properties): + if properties.is_self_message: + # Deduplicate self message with message-id + return None, properties.id - if stanza.getType() != 'chat': + if properties.stanza_id.by is None: + # We can not verify who sent this stanza-id, ignore it. return None, None - # Messages we receive live - if self._con.get_module('MAM').archiving_namespace != nbxmpp.NS_MAM_2: + if properties.type.is_groupchat: + namespace = caps_cache.muc_caps_cache.get_mam_namespace( + properties.jid.getBare()) + archive = properties.jid + else: + namespace = self._con.get_module('MAM').archiving_namespace + archive = self._con.get_own_jid() + + if namespace != nbxmpp.NS_MAM_2: # Only mam:2 ensures valid stanza-id return None, None - if self_message: - return self._get_stanza_id(stanza), stanza.getOriginID() - return self._get_stanza_id(stanza), None - - def _get_stanza_id(self, stanza): - stanza_id, by = stanza.getStanzaIDAttrs() - if by is None: - # We can not verify who set this stanza-id, ignore it. - return - if stanza.getType() == 'groupchat': - if stanza.getFrom().bareMatch(by): - # by attribute must match the server - return stanza_id - elif self._con.get_own_jid().bareMatch(by): - # by attribute must match the server - return stanza_id - return + if archive.bareMatch(properties.stanza_id.by): + return properties.stanza_id.id, None class MessageReceivedEvent(NetworkIncomingEvent): diff --git a/gajim/common/modules/util.py b/gajim/common/modules/util.py index 810e6d38d..1a927c7ed 100644 --- a/gajim/common/modules/util.py +++ b/gajim/common/modules/util.py @@ -16,35 +16,6 @@ from typing import Union -import nbxmpp - -from gajim.common import app - - -def is_self_message(message: nbxmpp.Node, - groupchat: bool = False) -> bool: - if groupchat: - return False - frm = message.getFrom() - to = message.getTo() - return frm.bareMatch(to) - - -def is_muc_pm(message: nbxmpp.Node, - jid: nbxmpp.JID, - groupchat: bool = False) -> bool: - if groupchat: - return False - muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if muc_user is not None: - return muc_user.getChildren() == [] - - # muc#user namespace was added in MUC 1.28 so we need a fallback - # Check if we know the jid - if app.logger.jid_is_room_jid(jid.getStripped()): - return True - return False - def from_xs_boolean(value: Union[str, bool]) -> bool: if isinstance(value, bool): diff --git a/gajim/session.py b/gajim/session.py index 5e279ad06..120f49b59 100644 --- a/gajim/session.py +++ b/gajim/session.py @@ -134,7 +134,8 @@ class ChatControlSession: message=msg_to_log, subject=obj.subject, additional_data=obj.additional_data, - stanza_id=obj.unique_id) + stanza_id=obj.unique_id, + message_id=obj.message_id) self.conn.get_module('MAM').save_archive_id( None, obj.stanza_id, obj.timestamp)