Philipp Hörist pushed to branch master at gajim / python-nbxmpp

Commits:

4 changed files:

Changes:

  • nbxmpp/dispatcher_nb.py
    ... ... @@ -25,6 +25,7 @@ import locale
    25 25
     import re
    
    26 26
     import uuid
    
    27 27
     import logging
    
    28
    +import inspect
    
    28 29
     from xml.parsers.expat import ExpatError
    
    29 30
     
    
    30 31
     from nbxmpp.simplexml import NodeBuilder
    
    ... ... @@ -32,6 +33,7 @@ from nbxmpp.plugin import PlugIn
    32 33
     from nbxmpp.protocol import NS_STREAMS
    
    33 34
     from nbxmpp.protocol import NS_HTTP_BIND
    
    34 35
     from nbxmpp.protocol import NodeProcessed
    
    36
    +from nbxmpp.protocol import InvalidFrom
    
    35 37
     from nbxmpp.protocol import Iq
    
    36 38
     from nbxmpp.protocol import Presence
    
    37 39
     from nbxmpp.protocol import Message
    
    ... ... @@ -39,6 +41,8 @@ from nbxmpp.protocol import Protocol
    39 41
     from nbxmpp.protocol import Node
    
    40 42
     from nbxmpp.protocol import Error
    
    41 43
     from nbxmpp.protocol import ERR_FEATURE_NOT_IMPLEMENTED
    
    44
    +from nbxmpp.misc import unwrap_carbon
    
    45
    +from nbxmpp.util import PropertyDict
    
    42 46
     
    
    43 47
     
    
    44 48
     log = logging.getLogger('nbxmpp.dispatcher_nb')
    
    ... ... @@ -430,8 +434,11 @@ class XMPPDispatcher(PlugIn):
    430 434
             if not session:
    
    431 435
                 session = self
    
    432 436
             session.Stream._mini_dom = None
    
    433
    -        name = stanza.getName()
    
    434 437
     
    
    438
    +        # Count stanza
    
    439
    +        self._owner.Smacks.count_incoming(stanza.getName())
    
    440
    +
    
    441
    +        name = stanza.getName()
    
    435 442
             if name == 'features':
    
    436 443
                 self._owner.got_features = True
    
    437 444
                 session.Stream.features = stanza
    
    ... ... @@ -449,43 +456,63 @@ class XMPPDispatcher(PlugIn):
    449 456
                 if name not in ('features', 'stream'):
    
    450 457
                     log.warning('Unknown stanza: %s', stanza)
    
    451 458
                 else:
    
    452
    -                log.debug('Got %s / %s stanza' % (xmlns, name))
    
    459
    +                log.debug('Got %s / %s stanza', xmlns, name)
    
    453 460
                 name = 'unknown'
    
    454 461
             else:
    
    455
    -            log.debug('Got %s / %s stanza' % (xmlns, name))
    
    462
    +            log.debug('Got %s / %s stanza', xmlns, name)
    
    456 463
     
    
    457 464
             # Convert simplexml to Protocol object
    
    458 465
             stanza = self.handlers[xmlns][name]['type'](node=stanza)
    
    459 466
     
    
    467
    +        properties = PropertyDict()
    
    468
    +        if stanza.getName() == 'message':
    
    469
    +            # https://tools.ietf.org/html/rfc6120#section-8.1.1.1
    
    470
    +            # If the stanza does not include a 'to' address then the client MUST
    
    471
    +            # treat it as if the 'to' address were included with a value of the
    
    472
    +            # client's full JID.
    
    473
    +
    
    474
    +            own_jid = self._owner.get_bound_jid()
    
    475
    +            to = stanza.getTo()
    
    476
    +            if to is None:
    
    477
    +                stanza.setTo(own_jid)
    
    478
    +            elif not to.bareMatch(own_jid):
    
    479
    +                log.warning('Message addressed to someone else: %s', stanza)
    
    480
    +                return
    
    481
    +
    
    482
    +            try:
    
    483
    +                stanza, properties.carbon_type = unwrap_carbon(stanza, own_jid)
    
    484
    +            except InvalidFrom as exc:
    
    485
    +                log.warning(exc)
    
    486
    +                return
    
    487
    +            except NodeProcessed as exc:
    
    488
    +                log.info(exc)
    
    489
    +                return
    
    490
    +
    
    460 491
             typ = stanza.getType()
    
    461 492
             if name == 'message' and not typ:
    
    462 493
                 typ = 'normal'
    
    463 494
             elif not typ:
    
    464 495
                 typ = ''
    
    465
    -        stanza.props = stanza.getProperties()
    
    466
    -        ID = stanza.getID()
    
    467 496
     
    
    497
    +        stanza.props = stanza.getProperties()
    
    468 498
             log.debug('type: %s, properties: %s', typ, stanza.props)
    
    469 499
     
    
    470
    -        # If server supports stream management
    
    471
    -        if hasattr(self._owner, 'Smacks'):
    
    472
    -            self._owner.Smacks.count_incoming(stanza.getName())
    
    473
    -
    
    500
    +        _id = stanza.getID()
    
    474 501
             processed = False
    
    475
    -        if ID in session._expected:
    
    476
    -            if isinstance(session._expected[ID], tuple):
    
    477
    -                cb, args = session._expected[ID]
    
    502
    +        if _id in session._expected:
    
    503
    +            if isinstance(session._expected[_id], tuple):
    
    504
    +                cb, args = session._expected[_id]
    
    478 505
                     log.debug('Expected stanza arrived. Callback %s(%s) found',
    
    479 506
                               cb, args)
    
    480 507
                     try:
    
    481
    -                    cb(session,stanza,**args)
    
    508
    +                    cb(session, stanza, **args)
    
    482 509
                     except NodeProcessed:
    
    483 510
                         pass
    
    484 511
                     except Exception:
    
    485 512
                         raise
    
    486 513
                 else:
    
    487 514
                     log.debug('Expected stanza arrived')
    
    488
    -                session._expected[ID] = stanza
    
    515
    +                session._expected[_id] = stanza
    
    489 516
                 processed = True
    
    490 517
     
    
    491 518
             # Gather specifics depending on stanza properties
    
    ... ... @@ -511,7 +538,13 @@ class XMPPDispatcher(PlugIn):
    511 538
                 if not processed or handler['system']:
    
    512 539
                     try:
    
    513 540
                         log.info('Call handler: %s', handler['func'].__qualname__)
    
    514
    -                    handler['func'](session, stanza)
    
    541
    +                    # Backwards compatibility until all handlers support
    
    542
    +                    # properties
    
    543
    +                    signature = inspect.signature(handler['func'])
    
    544
    +                    if 'properties' in signature.parameters:
    
    545
    +                        handler['func'](session, stanza, properties)
    
    546
    +                    else:
    
    547
    +                        handler['func'](session, stanza)
    
    515 548
                     except NodeProcessed:
    
    516 549
                         processed = True
    
    517 550
                     except Exception:
    

  • nbxmpp/misc.py
    1
    +# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
    
    2
    +#
    
    3
    +# This file is part of nbxmpp.
    
    4
    +#
    
    5
    +# This program is free software; you can redistribute it and/or
    
    6
    +# modify it under the terms of the GNU General Public License
    
    7
    +# as published by the Free Software Foundation; either version 2
    
    8
    +# of the License, or (at your option) any later version.
    
    9
    +#
    
    10
    +# This program is distributed in the hope that it will be useful,
    
    11
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    12
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    13
    +# GNU General Public License for more details.
    
    14
    +#
    
    15
    +# You should have received a copy of the GNU General Public License
    
    16
    +# along with this program; If not, see <http://www.gnu.org/licenses/>.
    
    17
    +
    
    18
    +import logging
    
    19
    +
    
    20
    +from nbxmpp.protocol import NS_CARBONS
    
    21
    +from nbxmpp.protocol import NS_FORWARD
    
    22
    +from nbxmpp.protocol import NS_MUC_USER
    
    23
    +from nbxmpp.protocol import NodeProcessed
    
    24
    +from nbxmpp.protocol import InvalidFrom
    
    25
    +from nbxmpp.protocol import Message
    
    26
    +
    
    27
    +log = logging.getLogger('nbxmpp.misc')
    
    28
    +
    
    29
    +
    
    30
    +def unwrap_carbon(stanza, own_jid):
    
    31
    +    carbon = stanza.getTag('received', namespace=NS_CARBONS)
    
    32
    +    if carbon is None:
    
    33
    +        carbon = stanza.getTag('sent', namespace=NS_CARBONS)
    
    34
    +        if carbon is None:
    
    35
    +            return stanza, None
    
    36
    +
    
    37
    +    # Carbon must be from our bare jid
    
    38
    +    if not stanza.getFrom() == own_jid.getBare():
    
    39
    +        raise InvalidFrom('Invalid from: %s' % stanza.getAttr('from'))
    
    40
    +
    
    41
    +    forwarded = carbon.getTag('forwarded', namespace=NS_FORWARD)
    
    42
    +    message = Message(node=forwarded.getTag('message'))
    
    43
    +
    
    44
    +    type_ = carbon.getName()
    
    45
    +    to = message.getTo()
    
    46
    +    if to is None:
    
    47
    +        message.setTo(own_jid.getBare())
    
    48
    +
    
    49
    +    frm = message.getFrom()
    
    50
    +    if frm is None:
    
    51
    +        message.setFrom(own_jid.getBare())
    
    52
    +
    
    53
    +    if type_ == 'received':
    
    54
    +        if message.getFrom().bareMatch(own_jid):
    
    55
    +            # Drop 'received' Carbons from ourself, we already
    
    56
    +            # got the message with the 'sent' Carbon or via the
    
    57
    +            # message itself
    
    58
    +            raise NodeProcessed('Drop "received"-Carbon from ourself')
    
    59
    +
    
    60
    +        if message.getTag('x', namespace=NS_MUC_USER) is not None:
    
    61
    +            # A MUC broadcasts messages sent to us to all resources
    
    62
    +            # there is no need to process the received carbon
    
    63
    +            raise NodeProcessed('Drop MUC-PM "received"-Carbon')
    
    64
    +
    
    65
    +    return message, type_

  • nbxmpp/protocol.py
    ... ... @@ -647,6 +647,9 @@ class UnsupportedVersion(StreamError):
    647 647
     class XMLNotWellFormed(StreamError):
    
    648 648
         pass
    
    649 649
     
    
    650
    +class InvalidStanza(Exception):
    
    651
    +    pass
    
    652
    +
    
    650 653
     stream_exceptions = {'bad-format': BadFormat,
    
    651 654
                         'bad-namespace-prefix': BadNamespacePrefix,
    
    652 655
                         'conflict': Conflict,
    
    ... ... @@ -747,6 +750,12 @@ class JID:
    747 750
             """
    
    748 751
             return self.__str__(0)
    
    749 752
     
    
    753
    +    def getBare(self):
    
    754
    +        """
    
    755
    +        Return the bare representation of JID. I.e. string value w/o resource
    
    756
    +        """
    
    757
    +        return self.__str__(0)
    
    758
    +
    
    750 759
         def __eq__(self, other):
    
    751 760
             """
    
    752 761
             Compare the JID to another instance or to string for equality
    

  • nbxmpp/util.py
    ... ... @@ -17,6 +17,7 @@
    17 17
     
    
    18 18
     import base64
    
    19 19
     
    
    20
    +
    
    20 21
     def b64decode(data, return_type=str):
    
    21 22
         if isinstance(data, str):
    
    22 23
             data = data.encode()
    
    ... ... @@ -25,6 +26,7 @@ def b64decode(data, return_type=str):
    25 26
             return result
    
    26 27
         return result.decode()
    
    27 28
     
    
    29
    +
    
    28 30
     def b64encode(data, return_type=str):
    
    29 31
         if isinstance(data, str):
    
    30 32
             data = data.encode()
    
    ... ... @@ -32,3 +34,17 @@ def b64encode(data, return_type=str):
    32 34
         if return_type == bytes:
    
    33 35
             return result
    
    34 36
         return result.decode()
    
    37
    +
    
    38
    +
    
    39
    +class PropertyDict:
    
    40
    +    def __init__(self):
    
    41
    +        self.__data = {}
    
    42
    +
    
    43
    +    def __getattr__(self, key):
    
    44
    +        return self.__data[key]
    
    45
    +
    
    46
    +    def __setattr__(self, key, value):
    
    47
    +        if '__' in key:
    
    48
    +            super().__setattr__(key, value)
    
    49
    +        else:
    
    50
    +            self.__data[key] = value