Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
-
6b2a9da4
by Philipp Hörist at 2018-12-20T22:16:03Z
4 changed files:
Changes:
| ... | ... | @@ -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:
|
| 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_
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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
|