Philipp Hörist pushed to branch master at gajim / gajim

Commits:

5 changed files:

Changes:

  • gajim/common/connection_handlers.py
    ... ... @@ -725,8 +725,6 @@ class ConnectionHandlersBase:
    725 725
             # IDs of sent messages (https://trac.gajim.org/ticket/8222)
    
    726 726
             self.sent_message_ids = []
    
    727 727
     
    
    728
    -        self.received_message_hashes = []
    
    729
    -
    
    730 728
             # We decrypt GPG messages one after the other. Keep queue in mem
    
    731 729
             self.gpg_messages_to_decrypt = []
    
    732 730
     
    
    ... ... @@ -946,7 +944,8 @@ class ConnectionHandlersBase:
    946 944
         def _on_message_received(self, obj):
    
    947 945
             if isinstance(obj, MessageReceivedEvent):
    
    948 946
                 app.nec.push_incoming_event(
    
    949
    -                DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
    
    947
    +                DecryptedMessageReceivedEvent(
    
    948
    +                    None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
    
    950 949
             else:
    
    951 950
                 app.nec.push_incoming_event(
    
    952 951
                     MamDecryptedMessageReceivedEvent(None, **vars(obj)))
    
    ... ... @@ -1012,14 +1011,14 @@ class ConnectionHandlersBase:
    1012 1011
                 return True
    
    1013 1012
             elif obj.mtype == 'groupchat':
    
    1014 1013
                 app.nec.push_incoming_event(GcMessageReceivedEvent(None,
    
    1015
    -                conn=self, msg_obj=obj))
    
    1014
    +                conn=self, msg_obj=obj, stanza_id=obj.unique_id))
    
    1016 1015
                 return True
    
    1017 1016
     
    
    1018 1017
         def _nec_gc_message_received(self, obj):
    
    1019 1018
             if obj.conn.name != self.name:
    
    1020 1019
                 return
    
    1021 1020
             if (app.config.should_log(obj.conn.name, obj.jid) and
    
    1022
    -            obj.msgtxt and obj.nick):
    
    1021
    +                obj.msgtxt and obj.nick):
    
    1023 1022
                 # if not obj.nick, it means message comes from room itself
    
    1024 1023
                 # usually it hold description and can be send at each connection
    
    1025 1024
                 # so don't store it in logs
    
    ... ... @@ -1028,7 +1027,8 @@ class ConnectionHandlersBase:
    1028 1027
                                             KindConstant.GC_MSG,
    
    1029 1028
                                             message=obj.msgtxt,
    
    1030 1029
                                             contact_name=obj.nick,
    
    1031
    -                                        additional_data=obj.additional_data)
    
    1030
    +                                        additional_data=obj.additional_data,
    
    1031
    +                                        stanza_id=obj.unique_id)
    
    1032 1032
                 app.logger.set_room_last_message_time(obj.room_jid, obj.timestamp)
    
    1033 1033
     
    
    1034 1034
         # process and dispatch an error message
    
    ... ... @@ -2048,8 +2048,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
    2048 2048
             app.nec.push_incoming_event(SignedInEvent(None, conn=self))
    
    2049 2049
             self.send_awaiting_pep()
    
    2050 2050
             self.continue_connect_info = None
    
    2051
    -        # hashes of already received messages
    
    2052
    -        self.received_message_hashes = []
    
    2053 2051
     
    
    2054 2052
         def _SearchCB(self, con, iq_obj):
    
    2055 2053
             log.debug('SearchCB')
    

  • gajim/common/connection_handlers_events.py
    ... ... @@ -139,6 +139,28 @@ class HelperEvent:
    139 139
                 if oob_desc is not None:
    
    140 140
                     self.additional_data['gajim']['oob_desc'] = oob_desc
    
    141 141
     
    
    142
    +    def get_stanza_id(self, stanza, query=False):
    
    143
    +        if query:
    
    144
    +            # On a MAM query the stanza-id is maybe not set, so
    
    145
    +            # get the id of the stanza
    
    146
    +            return stanza.getAttr('id')
    
    147
    +        stanza_id, by = stanza.getStanzaIDAttrs()
    
    148
    +        if by is None:
    
    149
    +            # We can not verify who set this stanza-id, ignore it.
    
    150
    +            return
    
    151
    +        elif not self.conn.get_own_jid().bareMatch(by):
    
    152
    +            # by attribute does not match the server, ignore it.
    
    153
    +            return
    
    154
    +        return stanza_id
    
    155
    +
    
    156
    +    @staticmethod
    
    157
    +    def get_forwarded_message(stanza):
    
    158
    +        forwarded = stanza.getTag('forwarded',
    
    159
    +                                  namespace=nbxmpp.NS_FORWARD,
    
    160
    +                                  protocol=True)
    
    161
    +        if forwarded is not None:
    
    162
    +            return forwarded.getTag('message', protocol=True)
    
    163
    +
    
    142 164
     class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
    
    143 165
         name = 'http-auth-received'
    
    144 166
         base_network_events = []
    
    ... ... @@ -1026,6 +1048,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1026 1048
             :stanza:        Complete stanza Node
    
    1027 1049
             :forwarded:     Forwarded Node
    
    1028 1050
             :result:        Result Node
    
    1051
    +        :unique_id:     The unique stable id
    
    1029 1052
             '''
    
    1030 1053
             self._set_base_event_vars_as_attributes(base_event)
    
    1031 1054
             self.additional_data = {}
    
    ... ... @@ -1046,6 +1069,13 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1046 1069
                 log.info('Received groupchat message from user archive')
    
    1047 1070
                 return False
    
    1048 1071
     
    
    1072
    +        # use stanza-id as unique-id
    
    1073
    +        self.unique_id, origin_id = self.get_unique_id()
    
    1074
    +
    
    1075
    +        # Check for duplicates
    
    1076
    +        if app.logger.find_stanza_id(self.unique_id, origin_id):
    
    1077
    +            return
    
    1078
    +
    
    1049 1079
             self.msgtxt = self.msg_.getTagData('body')
    
    1050 1080
             self.query_id = self.result.getAttr('queryid')
    
    1051 1081
     
    
    ... ... @@ -1057,24 +1087,12 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1057 1087
                 to = own_jid
    
    1058 1088
     
    
    1059 1089
             if frm.bareMatch(own_jid):
    
    1060
    -            self.stanza_id = self.msg_.getOriginID()
    
    1061
    -            if self.stanza_id is None:
    
    1062
    -                self.stanza_id = self.msg_.getID()
    
    1063
    -
    
    1064 1090
                 self.with_ = to
    
    1065 1091
                 self.kind = KindConstant.CHAT_MSG_SENT
    
    1066 1092
             else:
    
    1067
    -            if self.result.getNamespace() == nbxmpp.NS_MAM_2:
    
    1068
    -                self.stanza_id = self.result.getID()
    
    1069
    -            else:
    
    1070
    -                self.stanza_id = self.msg_.getID()
    
    1071
    -
    
    1072 1093
                 self.with_ = frm
    
    1073 1094
                 self.kind = KindConstant.CHAT_MSG_RECV
    
    1074 1095
     
    
    1075
    -        if self.stanza_id is None:
    
    1076
    -            log.debug('Could not retrieve stanza-id')
    
    1077
    -
    
    1078 1096
             delay = self.forwarded.getTagAttr(
    
    1079 1097
                 'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    1080 1098
             if delay is None:
    
    ... ... @@ -1097,9 +1115,31 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1097 1115
                     log.warning('Received MAM message with '
    
    1098 1116
                                 'invalid user timestamp: %s', user_delay)
    
    1099 1117
     
    
    1100
    -        log.debug('Received mam-message: stanza id: %s', self.stanza_id)
    
    1118
    +        log.debug('Received mam-message: unique id: %s', self.unique_id)
    
    1101 1119
             return True
    
    1102 1120
     
    
    1121
    +    def get_unique_id(self):
    
    1122
    +        if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
    
    1123
    +            # On our own Messages we have to check for both
    
    1124
    +            # stanza-id and origin-id, because other resources
    
    1125
    +            # maybe not support origin-id
    
    1126
    +            stanza_id = None
    
    1127
    +            if self.result.getNamespace() == nbxmpp.NS_MAM_2:
    
    1128
    +                # Only mam:2 ensures valid stanza-id
    
    1129
    +                stanza_id = self.get_stanza_id(self.result, query=True)
    
    1130
    +
    
    1131
    +            # try always to get origin-id because its a message
    
    1132
    +            # we sent.
    
    1133
    +            origin_id = self.msg_.getOriginID()
    
    1134
    +
    
    1135
    +            return stanza_id, origin_id
    
    1136
    +
    
    1137
    +        # A message we received
    
    1138
    +        elif self.result.getNamespace() == nbxmpp.NS_MAM_2:
    
    1139
    +            # Only mam:2 ensures valid stanza-id
    
    1140
    +            return self.get_stanza_id(self.result, query=True), None
    
    1141
    +        return None, None
    
    1142
    +
    
    1103 1143
     class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    
    1104 1144
         name = 'mam-decrypted-message-received'
    
    1105 1145
         base_network_events = []
    
    ... ... @@ -1177,6 +1217,14 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1177 1217
                             self.stanza.getFrom())
    
    1178 1218
                 return
    
    1179 1219
     
    
    1220
    +        # Check for duplicates
    
    1221
    +        self.unique_id = self.get_unique_id()
    
    1222
    +        # Check groupchat messages for duplicates,
    
    1223
    +        # We do this because of MUC History messages
    
    1224
    +        if self.stanza.getType() == 'groupchat':
    
    1225
    +            if app.logger.find_stanza_id(self.unique_id):
    
    1226
    +                return
    
    1227
    +
    
    1180 1228
             address_tag = self.stanza.getTag('addresses',
    
    1181 1229
                 namespace=nbxmpp.NS_ADDRESS)
    
    1182 1230
             # Be sure it comes from one of our resource, else ignore address element
    
    ... ... @@ -1248,7 +1296,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1248 1296
                                  conn=self.conn,
    
    1249 1297
                                  stanza=self.stanza,
    
    1250 1298
                                  forwarded=forwarded,
    
    1251
    -                             result=result))
    
    1299
    +                             result=result,
    
    1300
    +                             stanza_id=self.unique_id))
    
    1252 1301
                 return
    
    1253 1302
     
    
    1254 1303
             # Mediated invitation?
    
    ... ... @@ -1320,6 +1369,38 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1320 1369
     
    
    1321 1370
             return True
    
    1322 1371
     
    
    1372
    +    def get_unique_id(self):
    
    1373
    +        if self.stanza.getType() == 'groupchat':
    
    1374
    +            # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
    
    1375
    +            return self.get_stanza_id(self.stanza)
    
    1376
    +
    
    1377
    +        elif self.stanza.getType() != 'chat':
    
    1378
    +            return
    
    1379
    +
    
    1380
    +        # Messages we receive live
    
    1381
    +        if self.conn.archiving_namespace != nbxmpp.NS_MAM_2:
    
    1382
    +            # Only mam:2 ensures valid stanza-id
    
    1383
    +            return
    
    1384
    +
    
    1385
    +        # Sent Carbon
    
    1386
    +        sent_carbon = self.stanza.getTag('sent',
    
    1387
    +                                         namespace=nbxmpp.NS_CARBONS,
    
    1388
    +                                         protocol=True)
    
    1389
    +        if sent_carbon is not None:
    
    1390
    +            message = self.get_forwarded_message(sent_carbon)
    
    1391
    +            return self.get_stanza_id(message)
    
    1392
    +
    
    1393
    +        # Received Carbon
    
    1394
    +        received_carbon = self.stanza.getTag('received',
    
    1395
    +                                             namespace=nbxmpp.NS_CARBONS,
    
    1396
    +                                             protocol=True)
    
    1397
    +        if received_carbon is not None:
    
    1398
    +            message = self.get_forwarded_message(received_carbon)
    
    1399
    +            return self.get_stanza_id(message)
    
    1400
    +
    
    1401
    +        # Normal Message
    
    1402
    +        return self.get_stanza_id(self.stanza)
    
    1403
    +
    
    1323 1404
     class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
    
    1324 1405
         name = 'message-received'
    
    1325 1406
         base_network_events = []
    
    ... ... @@ -1416,6 +1497,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1416 1497
             self.stanza = self.msg_obj.stanza
    
    1417 1498
             self.additional_data = self.msg_obj.additional_data
    
    1418 1499
             self.id_ = self.msg_obj.id_
    
    1500
    +        self.unique_id = self.msg_obj.unique_id
    
    1419 1501
             self.jid = self.msg_obj.jid
    
    1420 1502
             self.fjid = self.msg_obj.fjid
    
    1421 1503
             self.resource = self.msg_obj.resource
    
    ... ... @@ -1472,22 +1554,6 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1472 1554
             if replace:
    
    1473 1555
                 self.correct_id = replace.getAttr('id')
    
    1474 1556
     
    
    1475
    -        # ignore message duplicates
    
    1476
    -        if self.msgtxt and self.id_ and self.jid:
    
    1477
    -            self.msghash = hashlib.sha256(("%s|%s|%s" % (
    
    1478
    -                hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(),
    
    1479
    -                hashlib.sha256(self.id_.encode('utf-8')).hexdigest(),
    
    1480
    -                hashlib.sha256(self.jid.encode('utf-8')).hexdigest())).encode(
    
    1481
    -                'utf-8')).digest()
    
    1482
    -            if self.msghash in self.conn.received_message_hashes:
    
    1483
    -                log.info("Ignoring duplicated message from '%s' with id '%s'" % (self.jid, self.id_))
    
    1484
    -                return False
    
    1485
    -            else:
    
    1486
    -                log.debug("subhashes: msgtxt, id_, jid = ('%s', '%s', '%s')" % (hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(), hashlib.sha256(self.id_.encode('utf-8')).hexdigest(), hashlib.sha256(self.jid.encode('utf-8')).hexdigest()))
    
    1487
    -                self.conn.received_message_hashes.append(self.msghash)
    
    1488
    -                # only record the last 20000 hashes (should be about 1MB [32 bytes per hash]
    
    1489
    -                # and about 24 hours if you receive a message every 5 seconds)
    
    1490
    -                self.conn.received_message_hashes = self.conn.received_message_hashes[-20000:]
    
    1491 1557
             return True
    
    1492 1558
     
    
    1493 1559
     class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
    
    ... ... @@ -1513,6 +1579,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
    1513 1579
             else:
    
    1514 1580
                 self.additional_data = self.msg_obj.additional_data
    
    1515 1581
             self.id_ = self.msg_obj.stanza.getID()
    
    1582
    +        self.unique_id = self.msg_obj.unique_id
    
    1516 1583
             self.fjid = self.msg_obj.fjid
    
    1517 1584
             self.msgtxt = self.msg_obj.msgtxt
    
    1518 1585
             self.jid = self.msg_obj.jid
    

  • gajim/common/logger.py
    ... ... @@ -1064,9 +1064,9 @@ class Logger:
    1064 1064
             :param msg:         The message text
    
    1065 1065
             """
    
    1066 1066
     
    
    1067
    -        # Add 5 minutes around the timestamp
    
    1068
    -        start_time = timestamp - 300
    
    1069
    -        end_time = timestamp + 300
    
    1067
    +        # Add 10 seconds around the timestamp
    
    1068
    +        start_time = timestamp - 10
    
    1069
    +        end_time = timestamp + 10
    
    1070 1070
     
    
    1071 1071
             log.debug('start: %s, end: %s, jid: %s, message: %s',
    
    1072 1072
                       start_time, end_time, jid, msg)
    
    ... ... @@ -1084,6 +1084,33 @@ class Logger:
    1084 1084
                 return True
    
    1085 1085
             return False
    
    1086 1086
     
    
    1087
    +    def find_stanza_id(self, stanza_id, origin_id=None):
    
    1088
    +        """
    
    1089
    +        Checks if a stanza-id is already in the `logs` table
    
    1090
    +
    
    1091
    +        :param stanza_id:   The stanza-id
    
    1092
    +
    
    1093
    +        return True if the stanza-id was found
    
    1094
    +        """
    
    1095
    +        ids = []
    
    1096
    +        if stanza_id is not None:
    
    1097
    +            ids.append(stanza_id)
    
    1098
    +        if origin_id is not None:
    
    1099
    +            ids.append(origin_id)
    
    1100
    +
    
    1101
    +        sql = '''
    
    1102
    +              SELECT stanza_id FROM logs
    
    1103
    +              WHERE stanza_id IN ({values}) LIMIT 1
    
    1104
    +              '''.format(values=', '.join('?' * len(ids)))
    
    1105
    +
    
    1106
    +        result = self.con.execute(sql, tuple(ids)).fetchone()
    
    1107
    +
    
    1108
    +        if result is not None:
    
    1109
    +            log.info('Found duplicated message, stanza-id: %s, origin-id: %s',
    
    1110
    +                     stanza_id, origin_id)
    
    1111
    +            return True
    
    1112
    +        return False
    
    1113
    +
    
    1087 1114
         def insert_jid(self, jid, kind=None, type_=JIDConstant.NORMAL_TYPE):
    
    1088 1115
             """
    
    1089 1116
             Insert a new jid into the `jids` table.
    
    ... ... @@ -1129,6 +1156,9 @@ class Logger:
    1129 1156
     
    
    1130 1157
             lastrowid = self.con.execute(sql, (jid_id, time_, kind) + tuple(kwargs.values())).lastrowid
    
    1131 1158
     
    
    1159
    +        log.info('Insert into DB: jid: %s, time: %s, kind: %s, stanza_id: %s',
    
    1160
    +                 jid, time_, kind, kwargs.get('stanza_id', None))
    
    1161
    +
    
    1132 1162
             if unread and kind == KindConstant.CHAT_MSG_RECV:
    
    1133 1163
                 sql = '''INSERT INTO unread_messages (message_id, jid_id)
    
    1134 1164
                          VALUES (?, (SELECT jid_id FROM jids WHERE jid = ?))'''
    

  • gajim/common/message_archiving.py
    ... ... @@ -127,14 +127,18 @@ class ConnectionArchive313:
    127 127
         def _nec_mam_decrypted_message_received(self, obj):
    
    128 128
             if obj.conn.name != self.name:
    
    129 129
                 return
    
    130
    +        # if self.archiving_namespace != nbxmpp.NS_MAM_2:
    
    131
    +        # Fallback duplicate search without stanza-id
    
    130 132
             duplicate = app.logger.search_for_duplicate(
    
    131 133
                 obj.with_, obj.timestamp, obj.msgtxt)
    
    132
    -        if not duplicate:
    
    133
    -            app.logger.insert_into_logs(
    
    134
    -                obj.with_, obj.timestamp, obj.kind,
    
    135
    -                unread=False,
    
    136
    -                message=obj.msgtxt,
    
    137
    -                additional_data=obj.additional_data)
    
    134
    +        if duplicate:
    
    135
    +            return
    
    136
    +        app.logger.insert_into_logs(
    
    137
    +            obj.with_, obj.timestamp, obj.kind,
    
    138
    +            unread=False,
    
    139
    +            message=obj.msgtxt,
    
    140
    +            additional_data=obj.additional_data,
    
    141
    +            stanza_id=obj.unique_id)
    
    138 142
     
    
    139 143
         def get_query_id(self):
    
    140 144
             self.mam_query_id = self.connection.getAnID()
    

  • gajim/session.py
    ... ... @@ -120,7 +120,8 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
    120 120
                     jid, obj.timestamp, log_type,
    
    121 121
                     message=msg_to_log,
    
    122 122
                     subject=obj.subject,
    
    123
    -                additional_data=obj.additional_data)
    
    123
    +                additional_data=obj.additional_data,
    
    124
    +                stanza_id=obj.unique_id)
    
    124 125
     
    
    125 126
             # Handle chat states
    
    126 127
             if contact and (not obj.forwarded or not obj.sent):