Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits: 6b2a9da4 by Philipp Hörist at 2018-12-20T22:16:03Z Add more message stanza processing logic
- Unwrap carbons before calling handlers - Introduce PropertyDict which can be passed through the handler chain - Add JID.getBare() as alias for getStripped() to make the purpose more evident
- - - - -
4 changed files:
- nbxmpp/dispatcher_nb.py - + nbxmpp/misc.py - nbxmpp/protocol.py - nbxmpp/util.py
Changes:
===================================== nbxmpp/dispatcher_nb.py ===================================== @@ -25,6 +25,7 @@ import locale import re import uuid import logging +import inspect from xml.parsers.expat import ExpatError
from nbxmpp.simplexml import NodeBuilder @@ -32,6 +33,7 @@ from nbxmpp.plugin import PlugIn from nbxmpp.protocol import NS_STREAMS from nbxmpp.protocol import NS_HTTP_BIND from nbxmpp.protocol import NodeProcessed +from nbxmpp.protocol import InvalidFrom from nbxmpp.protocol import Iq from nbxmpp.protocol import Presence from nbxmpp.protocol import Message @@ -39,6 +41,8 @@ from nbxmpp.protocol import Protocol from nbxmpp.protocol import Node from nbxmpp.protocol import Error from nbxmpp.protocol import ERR_FEATURE_NOT_IMPLEMENTED +from nbxmpp.misc import unwrap_carbon +from nbxmpp.util import PropertyDict
log = logging.getLogger('nbxmpp.dispatcher_nb') @@ -430,8 +434,11 @@ class XMPPDispatcher(PlugIn): if not session: session = self session.Stream._mini_dom = None - name = stanza.getName()
+ # Count stanza + self._owner.Smacks.count_incoming(stanza.getName()) + + name = stanza.getName() if name == 'features': self._owner.got_features = True session.Stream.features = stanza @@ -449,43 +456,63 @@ class XMPPDispatcher(PlugIn): if name not in ('features', 'stream'): log.warning('Unknown stanza: %s', stanza) else: - log.debug('Got %s / %s stanza' % (xmlns, name)) + log.debug('Got %s / %s stanza', xmlns, name) name = 'unknown' else: - log.debug('Got %s / %s stanza' % (xmlns, name)) + log.debug('Got %s / %s stanza', xmlns, name)
# Convert simplexml to Protocol object stanza = self.handlers[xmlns][name]['type'](node=stanza)
+ properties = PropertyDict() + if stanza.getName() == 'message': + # https://tools.ietf.org/html/rfc6120#section-8.1.1.1 + # If the stanza does not include a 'to' address then the client MUST + # treat it as if the 'to' address were included with a value of the + # client's full JID. + + own_jid = self._owner.get_bound_jid() + to = stanza.getTo() + if to is None: + stanza.setTo(own_jid) + elif not to.bareMatch(own_jid): + log.warning('Message addressed to someone else: %s', stanza) + return + + try: + stanza, properties.carbon_type = unwrap_carbon(stanza, own_jid) + except InvalidFrom as exc: + log.warning(exc) + return + except NodeProcessed as exc: + log.info(exc) + return + typ = stanza.getType() if name == 'message' and not typ: typ = 'normal' elif not typ: typ = '' - stanza.props = stanza.getProperties() - ID = stanza.getID()
+ stanza.props = stanza.getProperties() log.debug('type: %s, properties: %s', typ, stanza.props)
- # If server supports stream management - if hasattr(self._owner, 'Smacks'): - self._owner.Smacks.count_incoming(stanza.getName()) - + _id = stanza.getID() processed = False - if ID in session._expected: - if isinstance(session._expected[ID], tuple): - cb, args = session._expected[ID] + if _id in session._expected: + if isinstance(session._expected[_id], tuple): + cb, args = session._expected[_id] log.debug('Expected stanza arrived. Callback %s(%s) found', cb, args) try: - cb(session,stanza,**args) + cb(session, stanza, **args) except NodeProcessed: pass except Exception: raise else: log.debug('Expected stanza arrived') - session._expected[ID] = stanza + session._expected[_id] = stanza processed = True
# Gather specifics depending on stanza properties @@ -511,7 +538,13 @@ class XMPPDispatcher(PlugIn): if not processed or handler['system']: try: log.info('Call handler: %s', handler['func'].__qualname__) - handler['func'](session, stanza) + # Backwards compatibility until all handlers support + # properties + signature = inspect.signature(handler['func']) + if 'properties' in signature.parameters: + handler['func'](session, stanza, properties) + else: + handler['func'](session, stanza) except NodeProcessed: processed = True except Exception:
===================================== nbxmpp/misc.py ===================================== @@ -0,0 +1,65 @@ +# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com> +# +# This file is part of nbxmpp. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; If not, see http://www.gnu.org/licenses/. + +import logging + +from nbxmpp.protocol import NS_CARBONS +from nbxmpp.protocol import NS_FORWARD +from nbxmpp.protocol import NS_MUC_USER +from nbxmpp.protocol import NodeProcessed +from nbxmpp.protocol import InvalidFrom +from nbxmpp.protocol import Message + +log = logging.getLogger('nbxmpp.misc') + + +def unwrap_carbon(stanza, own_jid): + carbon = stanza.getTag('received', namespace=NS_CARBONS) + if carbon is None: + carbon = stanza.getTag('sent', namespace=NS_CARBONS) + if carbon is None: + return stanza, None + + # Carbon must be from our bare jid + if not stanza.getFrom() == own_jid.getBare(): + raise InvalidFrom('Invalid from: %s' % stanza.getAttr('from')) + + forwarded = carbon.getTag('forwarded', namespace=NS_FORWARD) + message = Message(node=forwarded.getTag('message')) + + type_ = carbon.getName() + to = message.getTo() + if to is None: + message.setTo(own_jid.getBare()) + + frm = message.getFrom() + if frm is None: + message.setFrom(own_jid.getBare()) + + if type_ == 'received': + if message.getFrom().bareMatch(own_jid): + # Drop 'received' Carbons from ourself, we already + # got the message with the 'sent' Carbon or via the + # message itself + raise NodeProcessed('Drop "received"-Carbon from ourself') + + if message.getTag('x', namespace=NS_MUC_USER) is not None: + # A MUC broadcasts messages sent to us to all resources + # there is no need to process the received carbon + raise NodeProcessed('Drop MUC-PM "received"-Carbon') + + return message, type_
===================================== nbxmpp/protocol.py ===================================== @@ -647,6 +647,9 @@ class UnsupportedVersion(StreamError): class XMLNotWellFormed(StreamError): pass
+class InvalidStanza(Exception): + pass + stream_exceptions = {'bad-format': BadFormat, 'bad-namespace-prefix': BadNamespacePrefix, 'conflict': Conflict, @@ -747,6 +750,12 @@ class JID: """ return self.__str__(0)
+ def getBare(self): + """ + Return the bare representation of JID. I.e. string value w/o resource + """ + return self.__str__(0) + def __eq__(self, other): """ Compare the JID to another instance or to string for equality
===================================== nbxmpp/util.py ===================================== @@ -17,6 +17,7 @@
import base64
+ def b64decode(data, return_type=str): if isinstance(data, str): data = data.encode() @@ -25,6 +26,7 @@ def b64decode(data, return_type=str): return result return result.decode()
+ def b64encode(data, return_type=str): if isinstance(data, str): data = data.encode() @@ -32,3 +34,17 @@ def b64encode(data, return_type=str): if return_type == bytes: return result return result.decode() + + +class PropertyDict: + def __init__(self): + self.__data = {} + + def __getattr__(self, key): + return self.__data[key] + + def __setattr__(self, key, value): + if '__' in key: + super().__setattr__(key, value) + else: + self.__data[key] = value
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/commit/6b2a9da405c02434831465b9b76...