From 60a5041df55337ba74a7240a2bfa693280ec8979 Mon Sep 17 00:00:00 2001 From: lovetox Date: Thu, 1 Sep 2022 22:44:58 +0200 Subject: fix: Better validate message corrections - Refactor message processing code - Introduce 5 minute message correction limit --- gajim/common/const.py | 2 ++ gajim/common/modules/mam.py | 24 ++++---------- gajim/common/modules/message.py | 46 +++++++++++--------------- gajim/common/modules/util.py | 60 +++++++++++++++++++++++++++++++++ gajim/common/storage/archive.py | 73 +++++++++++++++++++++++++++++------------ 5 files changed, 140 insertions(+), 65 deletions(-) diff --git a/gajim/common/const.py b/gajim/common/const.py index 734d7bcdb..e1bfee598 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -36,6 +36,8 @@ from gajim.common.i18n import Q_ STOP_EVENT = True PROPAGATE_EVENT = False +MAX_MESSAGE_CORRECTION_DELAY = 300 + class EncryptionData(NamedTuple): additional_data: Any = None diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py index b0eaae21a..f7c24c84f 100644 --- a/gajim/common/modules/mam.py +++ b/gajim/common/modules/mam.py @@ -43,7 +43,6 @@ from gajim.common import types from gajim.common.events import ArchivingIntervalFinished from gajim.common.events import FeatureDiscovered from gajim.common.events import MamMessageReceived -from gajim.common.events import MessageUpdated from gajim.common.events import RawMamMessageReceived from gajim.common.const import ArchiveState, ClientState from gajim.common.const import KindConstant @@ -51,7 +50,7 @@ from gajim.common.const import SyncThreshold from gajim.common.helpers import AdditionalDataDict from gajim.common.helpers import get_retraction_text from gajim.common.modules.misc import parse_oob -from gajim.common.modules.misc import parse_correction +from gajim.common.modules.util import check_if_message_correction from gajim.common.modules.util import get_eme_message from gajim.common.modules.util import as_task from gajim.common.modules.base import BaseModule @@ -311,21 +310,12 @@ class MAM(BaseModule): 'kind': kind, } - correct_id = parse_correction(properties) - if correct_id is not None: - nickname = properties.muc_nickname or properties.nickname - app.ged.raise_event(MessageUpdated(account=self._account, - jid=jid, - msgtxt=properties.body, - nickname=nickname, - properties=properties, - correct_id=correct_id)) - app.storage.archive.store_message_correction( - self._account, - jid, - correct_id, - properties.body, - properties.type.is_groupchat) + if check_if_message_correction(properties, + self._account, + properties.jid, + properties.body, + kind, + self._log): return app.storage.archive.insert_into_logs( diff --git a/gajim/common/modules/message.py b/gajim/common/modules/message.py index e0cc5f027..b58fd23b4 100644 --- a/gajim/common/modules/message.py +++ b/gajim/common/modules/message.py @@ -32,13 +32,12 @@ from gajim.common import types from gajim.common.events import GcMessageReceived from gajim.common.events import MessageError from gajim.common.events import MessageReceived -from gajim.common.events import MessageUpdated from gajim.common.events import RawMessageReceived from gajim.common.helpers import AdditionalDataDict from gajim.common.const import KindConstant from gajim.common.modules.base import BaseModule +from gajim.common.modules.util import check_if_message_correction from gajim.common.modules.util import get_eme_message -from gajim.common.modules.misc import parse_correction from gajim.common.modules.misc import parse_oob from gajim.common.modules.misc import parse_xhtml from gajim.common.structs import OutgoingMessage @@ -190,23 +189,19 @@ class Message(BaseModule): 'properties': properties, } - correct_id = parse_correction(properties) - if correct_id is not None: - nickname = properties.muc_nickname or properties.nickname - event = MessageUpdated(account=self._account, - jid=event_attr['jid'], - msgtxt=msgtxt, - nickname=nickname, - properties=properties, - correct_id=correct_id) - - app.storage.archive.store_message_correction( - self._account, - jid, - correct_id, - msgtxt, - properties.type.is_groupchat) - app.ged.raise_event(event) + if type_.is_groupchat: + kind = KindConstant.GC_MSG + elif properties.is_sent_carbon: + kind = KindConstant.CHAT_MSG_SENT + else: + kind = KindConstant.CHAT_MSG_RECV + + if check_if_message_correction(properties, + self._account, + from_, + msgtxt, + kind, + self._log): return if type_.is_groupchat: @@ -224,10 +219,6 @@ class Message(BaseModule): app.ged.raise_event(MessageReceived(**event_attr)) - log_type = KindConstant.CHAT_MSG_RECV - if properties.is_sent_carbon: - log_type = KindConstant.CHAT_MSG_SENT - if not msgtxt: return @@ -235,7 +226,7 @@ class Message(BaseModule): self._account, fjid if properties.is_muc_pm else jid, properties.timestamp, - log_type, + kind, message=msgtxt, subject=properties.subject, additional_data=additional_data, @@ -399,12 +390,13 @@ class Message(BaseModule): return if message.correct_id is not None: - app.storage.archive.store_message_correction( + app.storage.archive.try_message_correction( self._account, message.jid, - message.correct_id, + None, message.message, - message.is_groupchat) + message.correct_id, + KindConstant.CHAT_MSG_SENT) return app.storage.archive.insert_into_logs( diff --git a/gajim/common/modules/util.py b/gajim/common/modules/util.py index 1faafdedd..fcddc94b2 100644 --- a/gajim/common/modules/util.py +++ b/gajim/common/modules/util.py @@ -19,19 +19,25 @@ from __future__ import annotations from typing import Any from typing import Union +import logging from logging import LoggerAdapter from functools import wraps from functools import partial import nbxmpp +from nbxmpp.protocol import JID from nbxmpp.protocol import Message from nbxmpp.structs import EMEData from nbxmpp.structs import MessageProperties +from nbxmpp.const import MessageType from nbxmpp.task import Task from gajim.common import app from gajim.common import types from gajim.common.const import EME_MESSAGES +from gajim.common.const import KindConstant +from gajim.common.events import MessageUpdated +from gajim.common.modules.misc import parse_correction def from_xs_boolean(value: Union[str, bool]) -> bool: @@ -115,3 +121,57 @@ def as_task(func): task_.start() return task_ return func_wrapper + + +def check_if_message_correction(properties: MessageProperties, + account: str, + jid: JID, + msgtxt: str, + kind: KindConstant, + logger: LoggerAdapter[logging.Logger]) -> bool: + + correct_id = parse_correction(properties) + if correct_id is None: + return False + + if properties.type not in (MessageType.GROUPCHAT, MessageType.CHAT): + logger.warning('Ignore correction with message type: %s', + properties.type) + return False + + nickname = None + if properties.type.is_groupchat: + if jid.is_bare: + logger.warning( + 'Ignore correction from bare groupchat jid: %s', jid) + return False + + nickname = jid.resource + jid = jid.new_as_bare() + + elif not properties.is_muc_pm: + jid = jid.new_as_bare() + + successful = app.storage.archive.try_message_correction( + account, + jid, + nickname, + msgtxt, + correct_id, + kind) + + if not successful: + logger.info('Message correction not successful') + return False + + nickname = properties.muc_nickname or properties.nickname + + event = MessageUpdated(account=account, + jid=jid, + msgtxt=msgtxt, + nickname=nickname, + properties=properties, + correct_id=correct_id) + + app.ged.raise_event(event) + return True diff --git a/gajim/common/storage/archive.py b/gajim/common/storage/archive.py index 3520d38f3..14b213544 100644 --- a/gajim/common/storage/archive.py +++ b/gajim/common/storage/archive.py @@ -42,6 +42,7 @@ from nbxmpp.structs import MessageProperties from gajim.common import app from gajim.common import configpaths from gajim.common.helpers import AdditionalDataDict +from gajim.common.const import MAX_MESSAGE_CORRECTION_DELAY from gajim.common.const import ShowConstant from gajim.common.const import KindConstant from gajim.common.const import JIDConstant @@ -1008,29 +1009,57 @@ class MessageArchiveStorage(SqliteStorage): tuple(jids) + (message_id, min_time)).fetchone() @timeit - def store_message_correction(self, - account: str, - jid: JID, - correct_id: str, - corrected_text: str, - is_groupchat: bool) -> None: - type_ = JIDConstant.NORMAL_TYPE - if is_groupchat: - type_ = JIDConstant.ROOM_TYPE + def try_message_correction(self, + account: str, + jid: JID, + nickname: Optional[str], + corrected_text: str, + correct_id: str, + kind: KindConstant) -> bool: + + '''Try to correct a message + + :param jid: This can be a full jid or bare jid. A full jid only if the + message is a MUC PM, otherwise a bare jid needs to be + passed. `nickname` should only be passed if the message + is a group chat message. + ''' - jid_id = self.get_jid_id(str(jid), type_=type_) account_id = self.get_account_id(account) - sql = ''' - SELECT log_line_id, message, additional_data - FROM logs - WHERE +jid_id = ? - AND account_id = ? - AND message_id = ? - ''' - row = self._con.execute( - sql, (jid_id, account_id, correct_id)).fetchone() - if row is None: - return + max_timestamp = time.time() - MAX_MESSAGE_CORRECTION_DELAY + + self._log.debug( + 'Check if message is correctable, parameters: %s %s %s %s %s', + jid, account_id, nickname, correct_id, max_timestamp) + + sql = '''SELECT log_line_id, message, additional_data + FROM logs + NATURAL JOIN jids jid_id + WHERE +jid = ? + AND account_id = ? + AND contact_name IS ? + AND message_id = ? + AND kind = ? + AND time > ? + ''' + + rows = self._con.execute(sql, (jid, + account_id, + nickname, + correct_id, + kind, + max_timestamp + )).fetchall() + + if not rows: + self._log.debug('No correctable messages found') + return False + + if len(rows) != 1: + self._log.warning('More than one correctable message found') + return False + + row = rows[0] if row.additional_data is None: additional_data = AdditionalDataDict() @@ -1052,6 +1081,8 @@ class MessageArchiveStorage(SqliteStorage): self._con.execute( sql, (corrected_text, serialized_dict, row.log_line_id)) + return True + @timeit def update_additional_data(self, account: str, -- cgit v1.2.3