class XMPPDispatcher(PlugIn): """ Handles XMPP stream and is the first who takes control over a fresh stanza Is plugged into NonBlockingClient but can be replugged to restart handled stream headers (used by SASL f.e.). """ def __init__(self): PlugIn.__init__(self) self.handlers = {} self._expected = {} self._defaultHandler = None self._pendingExceptions = [] self._eventHandler = None self._cycleHandlers = [] self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, self.RegisterEventHandler, self.UnregisterCycleHandler, self.RegisterCycleHandler, self.RegisterHandlerOnce, self.UnregisterHandler, self.RegisterProtocol, self.SendAndWaitForResponse, self.SendAndCallForResponse, self.getAnID, self.Event, self.send] # Let the dispatcher know if there is support for stream management self.sm = None # \ufddo -> \ufdef range c = u'\ufdd0' r = c.encode('utf8') while (c < u'\ufdef'): c = unichr(ord(c) + 1) r += '|' + c.encode('utf8') # \ufffe-\uffff, \u1fffe-\u1ffff, ..., \u10fffe-\u10ffff c = u'\ufffe' r += '|' + c.encode('utf8') r += '|' + unichr(ord(c) + 1).encode('utf8') while (c < u'\U0010fffe'): c = unichr(ord(c) + 0x10000) r += '|' + c.encode('utf8') r += '|' + unichr(ord(c) + 1).encode('utf8') self.invalid_chars_re = re.compile(r) def getAnID(self): global outgoingID outgoingID += 1 return repr(outgoingID) def dumpHandlers(self): """ Return set of user-registered callbacks in it's internal format. Used within the library to carry user handlers set over Dispatcher replugins """ return self.handlers def restoreHandlers(self, handlers): """ Restore user-registered callbacks structure from dump previously obtained via dumpHandlers. Used within the library to carry user handlers set over Dispatcher replugins. """ self.handlers = handlers def _init(self): """ Register default namespaces/protocols/handlers. Used internally """ # FIXME: inject dependencies, do not rely that they are defined by our # owner self.RegisterNamespace('unknown') self.RegisterNamespace(NS_STREAMS) self.RegisterNamespace(self._owner.defaultNamespace) self.RegisterProtocol('iq', Iq) self.RegisterProtocol('presence', Presence) self.RegisterProtocol('message', Message) self.RegisterDefaultHandler(self.returnStanzaHandler) self.RegisterEventHandler(self._owner._caller._event_dispatcher) self.on_responses = {} def plugin(self, owner): """ Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally """ self._init() self._owner.lastErrNode = None self._owner.lastErr = None self._owner.lastErrCode = None if hasattr(self._owner, 'StreamInit'): self._owner.StreamInit() else: self.StreamInit() def plugout(self): """ Prepare instance to be destructed """ self.Stream.dispatch = None self.Stream.features = None self.Stream.destroy() self._owner = None self.Stream = None def StreamInit(self): """ Send an initial stream header """ self._owner.Connection.sendqueue = [] self.Stream = simplexml.NodeBuilder() self.Stream.dispatch = self.dispatch self.Stream._dispatch_depth = 2 self.Stream.stream_header_received = self._check_stream_start self.Stream.features = None self._metastream = Node('stream:stream') self._metastream.setNamespace(self._owner.Namespace) self._metastream.setAttr('version', '1.0') self._metastream.setAttr('xmlns:stream', NS_STREAMS) self._metastream.setAttr('to', self._owner.Server) if locale.getdefaultlocale()[0]: self._metastream.setAttr('xml:lang', locale.getdefaultlocale()[0].split('_')[0]) self._owner.send("%s%s>" % (XML_DECLARATION, str(self._metastream)[:-2])) def _check_stream_start(self, ns, tag, attrs): if ns != NS_STREAMS or tag!='stream': raise ValueError('Incorrect stream start: (%s,%s). Terminating.' % (tag, ns)) def replace_non_character(self, data): return re.sub(self.invalid_chars_re, u'\ufffd'.encode('utf-8'), data) def ProcessNonBlocking(self, data): """ Check incoming stream for data waiting :param data: data received from transports/IO sockets :return: 1) length of processed data if some data were processed; 2) '0' string if no data were processed but link is alive; 3) 0 (zero) if underlying connection is closed. """ # FIXME: # When an error occurs we disconnect the transport directly. Client's # disconnect method will never be called. # Is this intended? # also look at transports start_disconnect() data = self.replace_non_character(data) for handler in self._cycleHandlers: handler(self) if len(self._pendingExceptions) > 0: _pendingException = self._pendingExceptions.pop() raise _pendingException[0], _pendingException[1], _pendingException[2] try: self.Stream.Parse(data) # end stream:stream tag received if self.Stream and self.Stream.has_received_endtag(): self._owner.disconnect(self.Stream.streamError) return 0 except ExpatError: log.error('Invalid XML received from server. Forcing disconnect.') self._owner.Connection.disconnect() return 0 except ValueError, e: log.debug('ValueError: %s' % str(e)) self._owner.Connection.pollend() return 0 if len(self._pendingExceptions) > 0: _pendingException = self._pendingExceptions.pop() raise _pendingException[0], _pendingException[1], _pendingException[2] if len(data) == 0: return '0' return len(data)
class BOSHDispatcher(XMPPDispatcher): def PlugIn(self, owner, after_SASL=False, old_features=None): self.old_features = old_features self.after_SASL = after_SASL XMPPDispatcher.PlugIn(self, owner) def StreamInit(self): """ Send an initial stream header """ self.Stream = simplexml.NodeBuilder() self.Stream.dispatch = self.dispatch self.Stream._dispatch_depth = 2 self.Stream.stream_header_received = self._check_stream_start self.Stream.features = self.old_features self._metastream = Node('stream:stream') self._metastream.setNamespace(self._owner.Namespace) self._metastream.setAttr('version', '1.0') self._metastream.setAttr('xmlns:stream', NS_STREAMS) self._metastream.setAttr('to', self._owner.Server) if locale.getdefaultlocale()[0]: self._metastream.setAttr('xml:lang', locale.getdefaultlocale()[0].split('_')[0]) self.restart = True self._owner.Connection.send_init(after_SASL=self.after_SASL) def StreamTerminate(self): """ Send a stream terminator """ self._owner.Connection.send_terminator() def ProcessNonBlocking(self, data=None): if self.restart: fromstream = self._metastream fromstream.setAttr('from', fromstream.getAttr('to')) fromstream.delAttr('to') data = '%s%s>%s' % (XML_DECLARATION, str(fromstream)[:-2], data) self.restart = False return XMPPDispatcher.ProcessNonBlocking(self, data) def dispatch(self, stanza, session=None, direct=0): if stanza.getName() == 'body' and stanza.getNamespace() == NS_HTTP_BIND: stanza_attrs = stanza.getAttrs() if 'authid' in stanza_attrs: # should be only in init response # auth module expects id of stream in document attributes self.Stream._document_attrs['id'] = stanza_attrs['authid'] self._owner.Connection.handle_body_attrs(stanza_attrs) children = stanza.getChildren() if children: for child in children: # if child doesn't have any ns specified, simplexml (or expat) # thinks it's of parent's (BOSH body) namespace, so we have to # rewrite it to jabber:client if child.getNamespace() == NS_HTTP_BIND: child.setNamespace(self._owner.defaultNamespace) XMPPDispatcher.dispatch(self, child, session, direct) else: XMPPDispatcher.dispatch(self, stanza, session, direct)