Index: third_party/twisted_8_1/twisted/words/protocols/jabber/xmlstream.py |
diff --git a/third_party/twisted_8_1/twisted/words/protocols/jabber/xmlstream.py b/third_party/twisted_8_1/twisted/words/protocols/jabber/xmlstream.py |
deleted file mode 100644 |
index fe0d1429fbbac1ada26b6de325390146c5543dd8..0000000000000000000000000000000000000000 |
--- a/third_party/twisted_8_1/twisted/words/protocols/jabber/xmlstream.py |
+++ /dev/null |
@@ -1,1078 +0,0 @@ |
-# -*- test-case-name: twisted.words.test.test_jabberxmlstream -*- |
-# |
-# Copyright (c) 2001-2008 Twisted Matrix Laboratories. |
-# See LICENSE for details. |
- |
-""" |
-XMPP XML Streams |
- |
-Building blocks for setting up XML Streams, including helping classes for |
-doing authentication on either client or server side, and working with XML |
-Stanzas. |
-""" |
- |
-from zope.interface import directlyProvides, implements |
- |
-from twisted.internet import defer |
-from twisted.internet.error import ConnectionLost |
-from twisted.python import failure, log |
-from twisted.words.protocols.jabber import error, ijabber, jid |
-from twisted.words.xish import domish, xmlstream |
-from twisted.words.xish.xmlstream import STREAM_CONNECTED_EVENT |
-from twisted.words.xish.xmlstream import STREAM_START_EVENT |
-from twisted.words.xish.xmlstream import STREAM_END_EVENT |
-from twisted.words.xish.xmlstream import STREAM_ERROR_EVENT |
- |
-try: |
- from twisted.internet import ssl |
-except ImportError: |
- ssl = None |
-if ssl and not ssl.supported: |
- ssl = None |
- |
-STREAM_AUTHD_EVENT = intern("//event/stream/authd") |
-INIT_FAILED_EVENT = intern("//event/xmpp/initfailed") |
- |
-NS_STREAMS = 'http://etherx.jabber.org/streams' |
-NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls' |
- |
-Reset = object() |
- |
-def hashPassword(sid, password): |
- """ |
- Create a SHA1-digest string of a session identifier and password. |
- """ |
- import sha |
- return sha.new("%s%s" % (sid, password)).hexdigest() |
- |
- |
- |
-class Authenticator: |
- """ |
- Base class for business logic of initializing an XmlStream |
- |
- Subclass this object to enable an XmlStream to initialize and authenticate |
- to different types of stream hosts (such as clients, components, etc.). |
- |
- Rules: |
- 1. The Authenticator MUST dispatch a L{STREAM_AUTHD_EVENT} when the |
- stream has been completely initialized. |
- 2. The Authenticator SHOULD reset all state information when |
- L{associateWithStream} is called. |
- 3. The Authenticator SHOULD override L{streamStarted}, and start |
- initialization there. |
- |
- @type xmlstream: L{XmlStream} |
- @ivar xmlstream: The XmlStream that needs authentication |
- |
- @note: the term authenticator is historical. Authenticators perform |
- all steps required to prepare the stream for the exchange |
- of XML stanzas. |
- """ |
- |
- def __init__(self): |
- self.xmlstream = None |
- |
- |
- def connectionMade(self): |
- """ |
- Called by the XmlStream when the underlying socket connection is |
- in place. |
- |
- This allows the Authenticator to send an initial root element, if it's |
- connecting, or wait for an inbound root from the peer if it's accepting |
- the connection. |
- |
- Subclasses can use self.xmlstream.send() to send any initial data to |
- the peer. |
- """ |
- |
- |
- def streamStarted(self, rootElement): |
- """ |
- Called by the XmlStream when the stream has started. |
- |
- A stream is considered to have started when the start tag of the root |
- element has been received. |
- |
- This examines L{rootElement} to see if there is a version attribute. |
- If absent, C{0.0} is assumed per RFC 3920. Subsequently, the |
- minimum of the version from the received stream header and the |
- value stored in L{xmlstream} is taken and put back in {xmlstream}. |
- |
- Extensions of this method can extract more information from the |
- stream header and perform checks on them, optionally sending |
- stream errors and closing the stream. |
- """ |
- if rootElement.hasAttribute("version"): |
- version = rootElement["version"].split(".") |
- try: |
- version = (int(version[0]), int(version[1])) |
- except (IndexError, ValueError): |
- version = (0, 0) |
- else: |
- version = (0, 0) |
- |
- self.xmlstream.version = min(self.xmlstream.version, version) |
- |
- |
- def associateWithStream(self, xmlstream): |
- """ |
- Called by the XmlStreamFactory when a connection has been made |
- to the requested peer, and an XmlStream object has been |
- instantiated. |
- |
- The default implementation just saves a handle to the new |
- XmlStream. |
- |
- @type xmlstream: L{XmlStream} |
- @param xmlstream: The XmlStream that will be passing events to this |
- Authenticator. |
- |
- """ |
- self.xmlstream = xmlstream |
- |
- |
- |
-class ConnectAuthenticator(Authenticator): |
- """ |
- Authenticator for initiating entities. |
- """ |
- |
- namespace = None |
- |
- def __init__(self, otherHost): |
- self.otherHost = otherHost |
- |
- |
- def connectionMade(self): |
- self.xmlstream.namespace = self.namespace |
- self.xmlstream.otherEntity = jid.internJID(self.otherHost) |
- self.xmlstream.sendHeader() |
- |
- |
- def initializeStream(self): |
- """ |
- Perform stream initialization procedures. |
- |
- An L{XmlStream} holds a list of initializer objects in its |
- C{initializers} attribute. This method calls these initializers in |
- order and dispatches the C{STREAM_AUTHD_EVENT} event when the list has |
- been successfully processed. Otherwise it dispatches the |
- C{INIT_FAILED_EVENT} event with the failure. |
- |
- Initializers may return the special L{Reset} object to halt the |
- initialization processing. It signals that the current initializer was |
- successfully processed, but that the XML Stream has been reset. An |
- example is the TLSInitiatingInitializer. |
- """ |
- |
- def remove_first(result): |
- self.xmlstream.initializers.pop(0) |
- |
- return result |
- |
- def do_next(result): |
- """ |
- Take the first initializer and process it. |
- |
- On success, the initializer is removed from the list and |
- then next initializer will be tried. |
- """ |
- |
- if result is Reset: |
- return None |
- |
- try: |
- init = self.xmlstream.initializers[0] |
- except IndexError: |
- self.xmlstream.dispatch(self.xmlstream, STREAM_AUTHD_EVENT) |
- return None |
- else: |
- d = defer.maybeDeferred(init.initialize) |
- d.addCallback(remove_first) |
- d.addCallback(do_next) |
- return d |
- |
- d = defer.succeed(None) |
- d.addCallback(do_next) |
- d.addErrback(self.xmlstream.dispatch, INIT_FAILED_EVENT) |
- |
- |
- def streamStarted(self, rootElement): |
- """ |
- Called by the XmlStream when the stream has started. |
- |
- This extends L{Authenticator.streamStarted} to extract further stream |
- headers from L{rootElement}, optionally wait for stream features being |
- received and then call C{initializeStream}. |
- """ |
- |
- Authenticator.streamStarted(self, rootElement) |
- |
- self.xmlstream.sid = rootElement.getAttribute("id") |
- |
- if rootElement.hasAttribute("from"): |
- self.xmlstream.otherEntity = jid.internJID(rootElement["from"]) |
- |
- # Setup observer for stream features, if applicable |
- if self.xmlstream.version >= (1, 0): |
- def onFeatures(element): |
- features = {} |
- for feature in element.elements(): |
- features[(feature.uri, feature.name)] = feature |
- |
- self.xmlstream.features = features |
- self.initializeStream() |
- |
- self.xmlstream.addOnetimeObserver('/features[@xmlns="%s"]' % |
- NS_STREAMS, |
- onFeatures) |
- else: |
- self.initializeStream() |
- |
- |
- |
-class ListenAuthenticator(Authenticator): |
- """ |
- Authenticator for receiving entities. |
- """ |
- |
- namespace = None |
- |
- def associateWithStream(self, xmlstream): |
- """ |
- Called by the XmlStreamFactory when a connection has been made. |
- |
- Extend L{Authenticator.associateWithStream} to set the L{XmlStream} |
- to be non-initiating. |
- """ |
- Authenticator.associateWithStream(self, xmlstream) |
- self.xmlstream.initiating = False |
- |
- |
- def streamStarted(self, rootElement): |
- """ |
- Called by the XmlStream when the stream has started. |
- |
- This extends L{Authenticator.streamStarted} to extract further |
- information from the stream headers from L{rootElement}. |
- """ |
- Authenticator.streamStarted(self, rootElement) |
- |
- self.xmlstream.namespace = rootElement.defaultUri |
- |
- if rootElement.hasAttribute("to"): |
- self.xmlstream.thisEntity = jid.internJID(rootElement["to"]) |
- |
- self.xmlstream.prefixes = {} |
- for prefix, uri in rootElement.localPrefixes.iteritems(): |
- self.xmlstream.prefixes[uri] = prefix |
- |
- |
- |
-class FeatureNotAdvertized(Exception): |
- """ |
- Exception indicating a stream feature was not advertized, while required by |
- the initiating entity. |
- """ |
- |
- |
- |
-class BaseFeatureInitiatingInitializer(object): |
- """ |
- Base class for initializers with a stream feature. |
- |
- This assumes the associated XmlStream represents the initiating entity |
- of the connection. |
- |
- @cvar feature: tuple of (uri, name) of the stream feature root element. |
- @type feature: tuple of (L{str}, L{str}) |
- @ivar required: whether the stream feature is required to be advertized |
- by the receiving entity. |
- @type required: L{bool} |
- """ |
- |
- implements(ijabber.IInitiatingInitializer) |
- |
- feature = None |
- required = False |
- |
- def __init__(self, xs): |
- self.xmlstream = xs |
- |
- |
- def initialize(self): |
- """ |
- Initiate the initialization. |
- |
- Checks if the receiving entity advertizes the stream feature. If it |
- does, the initialization is started. If it is not advertized, and the |
- C{required} instance variable is L{True}, it raises |
- L{FeatureNotAdvertized}. Otherwise, the initialization silently |
- succeeds. |
- """ |
- |
- if self.feature in self.xmlstream.features: |
- return self.start() |
- elif self.required: |
- raise FeatureNotAdvertized |
- else: |
- return None |
- |
- |
- def start(self): |
- """ |
- Start the actual initialization. |
- |
- May return a deferred for asynchronous initialization. |
- """ |
- |
- |
- |
-class TLSError(Exception): |
- """ |
- TLS base exception. |
- """ |
- |
- |
- |
-class TLSFailed(TLSError): |
- """ |
- Exception indicating failed TLS negotiation |
- """ |
- |
- |
- |
-class TLSRequired(TLSError): |
- """ |
- Exception indicating required TLS negotiation. |
- |
- This exception is raised when the receiving entity requires TLS |
- negotiation and the initiating does not desire to negotiate TLS. |
- """ |
- |
- |
- |
-class TLSNotSupported(TLSError): |
- """ |
- Exception indicating missing TLS support. |
- |
- This exception is raised when the initiating entity wants and requires to |
- negotiate TLS when the OpenSSL library is not available. |
- """ |
- |
- |
- |
-class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer): |
- """ |
- TLS stream initializer for the initiating entity. |
- |
- It is strongly required to include this initializer in the list of |
- initializers for an XMPP stream. By default it will try to negotiate TLS. |
- An XMPP server may indicate that TLS is required. If TLS is not desired, |
- set the C{wanted} attribute to False instead of removing it from the list |
- of initializers, so a proper exception L{TLSRequired} can be raised. |
- |
- @cvar wanted: indicates if TLS negotiation is wanted. |
- @type wanted: L{bool} |
- """ |
- |
- feature = (NS_XMPP_TLS, 'starttls') |
- wanted = True |
- _deferred = None |
- |
- def onProceed(self, obj): |
- """ |
- Proceed with TLS negotiation and reset the XML stream. |
- """ |
- |
- self.xmlstream.removeObserver('/failure', self.onFailure) |
- ctx = ssl.CertificateOptions() |
- self.xmlstream.transport.startTLS(ctx) |
- self.xmlstream.reset() |
- self.xmlstream.sendHeader() |
- self._deferred.callback(Reset) |
- |
- |
- def onFailure(self, obj): |
- self.xmlstream.removeObserver('/proceed', self.onProceed) |
- self._deferred.errback(TLSFailed()) |
- |
- |
- def start(self): |
- """ |
- Start TLS negotiation. |
- |
- This checks if the receiving entity requires TLS, the SSL library is |
- available and uses the C{required} and C{wanted} instance variables to |
- determine what to do in the various different cases. |
- |
- For example, if the SSL library is not available, and wanted and |
- required by the user, it raises an exception. However if it is not |
- required by both parties, initialization silently succeeds, moving |
- on to the next step. |
- """ |
- if self.wanted: |
- if ssl is None: |
- if self.required: |
- return defer.fail(TLSNotSupported()) |
- else: |
- return defer.succeed(None) |
- else: |
- pass |
- elif self.xmlstream.features[self.feature].required: |
- return defer.fail(TLSRequired()) |
- else: |
- return defer.succeed(None) |
- |
- self._deferred = defer.Deferred() |
- self.xmlstream.addOnetimeObserver("/proceed", self.onProceed) |
- self.xmlstream.addOnetimeObserver("/failure", self.onFailure) |
- self.xmlstream.send(domish.Element((NS_XMPP_TLS, "starttls"))) |
- return self._deferred |
- |
- |
- |
-class XmlStream(xmlstream.XmlStream): |
- """ |
- XMPP XML Stream protocol handler. |
- |
- @ivar version: XML stream version as a tuple (major, minor). Initially, |
- this is set to the minimally supported version. Upon |
- receiving the stream header of the peer, it is set to the |
- minimum of that value and the version on the received |
- header. |
- @type version: (L{int}, L{int}) |
- @ivar namespace: default namespace URI for stream |
- @type namespace: L{str} |
- @ivar thisEntity: JID of this entity |
- @type thisEntity: L{JID} |
- @ivar otherEntity: JID of the peer entity |
- @type otherEntity: L{JID} |
- @ivar sid: session identifier |
- @type sid: L{str} |
- @ivar initiating: True if this is the initiating stream |
- @type initiating: L{bool} |
- @ivar features: map of (uri, name) to stream features element received from |
- the receiving entity. |
- @type features: L{dict} of (L{str}, L{str}) to L{domish.Element}. |
- @ivar prefixes: map of URI to prefixes that are to appear on stream |
- header. |
- @type prefixes: L{dict} of L{str} to L{str} |
- @ivar initializers: list of stream initializer objects |
- @type initializers: L{list} of objects that provide L{IInitializer} |
- @ivar authenticator: associated authenticator that uses C{initializers} to |
- initialize the XML stream. |
- """ |
- |
- version = (1, 0) |
- namespace = 'invalid' |
- thisEntity = None |
- otherEntity = None |
- sid = None |
- initiating = True |
- |
- _headerSent = False # True if the stream header has been sent |
- |
- def __init__(self, authenticator): |
- xmlstream.XmlStream.__init__(self) |
- |
- self.prefixes = {NS_STREAMS: 'stream'} |
- self.authenticator = authenticator |
- self.initializers = [] |
- self.features = {} |
- |
- # Reset the authenticator |
- authenticator.associateWithStream(self) |
- |
- |
- def _callLater(self, *args, **kwargs): |
- from twisted.internet import reactor |
- return reactor.callLater(*args, **kwargs) |
- |
- |
- def reset(self): |
- """ |
- Reset XML Stream. |
- |
- Resets the XML Parser for incoming data. This is to be used after |
- successfully negotiating a new layer, e.g. TLS and SASL. Note that |
- registered event observers will continue to be in place. |
- """ |
- self._headerSent = False |
- self._initializeStream() |
- |
- |
- def onStreamError(self, errelem): |
- """ |
- Called when a stream:error element has been received. |
- |
- Dispatches a L{STREAM_ERROR_EVENT} event with the error element to |
- allow for cleanup actions and drops the connection. |
- |
- @param errelem: The received error element. |
- @type errelem: L{domish.Element} |
- """ |
- self.dispatch(failure.Failure(error.exceptionFromStreamError(errelem)), |
- STREAM_ERROR_EVENT) |
- self.transport.loseConnection() |
- |
- |
- def sendHeader(self): |
- """ |
- Send stream header. |
- """ |
- # set up optional extra namespaces |
- localPrefixes = {} |
- for uri, prefix in self.prefixes.iteritems(): |
- if uri != NS_STREAMS: |
- localPrefixes[prefix] = uri |
- |
- rootElement = domish.Element((NS_STREAMS, 'stream'), self.namespace, |
- localPrefixes=localPrefixes) |
- |
- if self.otherEntity: |
- rootElement['to'] = self.otherEntity.userhost() |
- |
- if self.thisEntity: |
- rootElement['from'] = self.thisEntity.userhost() |
- |
- if not self.initiating and self.sid: |
- rootElement['id'] = self.sid |
- |
- if self.version >= (1, 0): |
- rootElement['version'] = "%d.%d" % self.version |
- |
- self.send(rootElement.toXml(prefixes=self.prefixes, closeElement=0)) |
- self._headerSent = True |
- |
- |
- def sendFooter(self): |
- """ |
- Send stream footer. |
- """ |
- self.send('</stream:stream>') |
- |
- |
- def sendStreamError(self, streamError): |
- """ |
- Send stream level error. |
- |
- If we are the receiving entity, and haven't sent the header yet, |
- we sent one first. |
- |
- After sending the stream error, the stream is closed and the transport |
- connection dropped. |
- |
- @param streamError: stream error instance |
- @type streamError: L{error.StreamError} |
- """ |
- if not self._headerSent and not self.initiating: |
- self.sendHeader() |
- |
- if self._headerSent: |
- self.send(streamError.getElement()) |
- self.sendFooter() |
- |
- self.transport.loseConnection() |
- |
- |
- def send(self, obj): |
- """ |
- Send data over the stream. |
- |
- This overrides L{xmlstream.Xmlstream.send} to use the default namespace |
- of the stream header when serializing L{domish.IElement}s. It is |
- assumed that if you pass an object that provides L{domish.IElement}, |
- it represents a direct child of the stream's root element. |
- """ |
- if domish.IElement.providedBy(obj): |
- obj = obj.toXml(prefixes=self.prefixes, |
- defaultUri=self.namespace, |
- prefixesInScope=self.prefixes.values()) |
- |
- xmlstream.XmlStream.send(self, obj) |
- |
- |
- def connectionMade(self): |
- """ |
- Called when a connection is made. |
- |
- Notifies the authenticator when a connection has been made. |
- """ |
- xmlstream.XmlStream.connectionMade(self) |
- self.authenticator.connectionMade() |
- |
- |
- def onDocumentStart(self, rootElement): |
- """ |
- Called when the stream header has been received. |
- |
- Extracts the header's C{id} and C{version} attributes from the root |
- element. The C{id} attribute is stored in our C{sid} attribute and the |
- C{version} attribute is parsed and the minimum of the version we sent |
- and the parsed C{version} attribute is stored as a tuple (major, minor) |
- in this class' C{version} attribute. If no C{version} attribute was |
- present, we assume version 0.0. |
- |
- If appropriate (we are the initiating stream and the minimum of our and |
- the other party's version is at least 1.0), a one-time observer is |
- registered for getting the stream features. The registered function is |
- C{onFeatures}. |
- |
- Ultimately, the authenticator's C{streamStarted} method will be called. |
- |
- @param rootElement: The root element. |
- @type rootElement: L{domish.Element} |
- """ |
- xmlstream.XmlStream.onDocumentStart(self, rootElement) |
- |
- # Setup observer for stream errors |
- self.addOnetimeObserver("/error[@xmlns='%s']" % NS_STREAMS, |
- self.onStreamError) |
- |
- self.authenticator.streamStarted(rootElement) |
- |
- |
- |
-class XmlStreamFactory(xmlstream.XmlStreamFactory): |
- """ |
- Factory for Jabber XmlStream objects as a reconnecting client. |
- |
- Note that this differs from L{xmlstream.XmlStreamFactory} in that |
- it generates Jabber specific L{XmlStream} instances that have |
- authenticators. |
- """ |
- |
- protocol = XmlStream |
- |
- def __init__(self, authenticator): |
- xmlstream.XmlStreamFactory.__init__(self, authenticator) |
- self.authenticator = authenticator |
- |
- |
- |
-class TimeoutError(Exception): |
- """ |
- Exception raised when no IQ response has been received before the |
- configured timeout. |
- """ |
- |
- |
- |
-def upgradeWithIQResponseTracker(xs): |
- """ |
- Enhances an XmlStream for iq response tracking. |
- |
- This makes an L{XmlStream} object provide L{IIQResponseTracker}. When a |
- response is an error iq stanza, the deferred has its errback invoked with a |
- failure that holds a L{StanzaException<error.StanzaException>} that is |
- easier to examine. |
- """ |
- def callback(iq): |
- """ |
- Handle iq response by firing associated deferred. |
- """ |
- if getattr(iq, 'handled', False): |
- return |
- |
- try: |
- d = xs.iqDeferreds[iq["id"]] |
- except KeyError: |
- pass |
- else: |
- del xs.iqDeferreds[iq["id"]] |
- iq.handled = True |
- if iq['type'] == 'error': |
- d.errback(error.exceptionFromStanza(iq)) |
- else: |
- d.callback(iq) |
- |
- |
- def disconnected(_): |
- """ |
- Make sure deferreds do not linger on after disconnect. |
- |
- This errbacks all deferreds of iq's for which no response has been |
- received with a L{ConnectionLost} failure. Otherwise, the deferreds |
- will never be fired. |
- """ |
- iqDeferreds = xs.iqDeferreds |
- xs.iqDeferreds = {} |
- for d in iqDeferreds.itervalues(): |
- d.errback(ConnectionLost()) |
- |
- xs.iqDeferreds = {} |
- xs.iqDefaultTimeout = getattr(xs, 'iqDefaultTimeout', None) |
- xs.addObserver(xmlstream.STREAM_END_EVENT, disconnected) |
- xs.addObserver('/iq[@type="result"]', callback) |
- xs.addObserver('/iq[@type="error"]', callback) |
- directlyProvides(xs, ijabber.IIQResponseTracker) |
- |
- |
- |
-class IQ(domish.Element): |
- """ |
- Wrapper for an iq stanza. |
- |
- Iq stanzas are used for communications with a request-response behaviour. |
- Each iq request is associated with an XML stream and has its own unique id |
- to be able to track the response. |
- |
- @ivar timeout: if set, a timeout period after which the deferred returned |
- by C{send} will have its errback called with a |
- L{TimeoutError} failure. |
- @type timeout: C{float} |
- """ |
- |
- timeout = None |
- |
- def __init__(self, xmlstream, stanzaType="set"): |
- """ |
- @type xmlstream: L{xmlstream.XmlStream} |
- @param xmlstream: XmlStream to use for transmission of this IQ |
- |
- @type stanzaType: L{str} |
- @param stanzaType: IQ type identifier ('get' or 'set') |
- """ |
- domish.Element.__init__(self, (None, "iq")) |
- self.addUniqueId() |
- self["type"] = stanzaType |
- self._xmlstream = xmlstream |
- |
- |
- def send(self, to=None): |
- """ |
- Send out this iq. |
- |
- Returns a deferred that is fired when an iq response with the same id |
- is received. Result responses will be passed to the deferred callback. |
- Error responses will be transformed into a |
- L{StanzaError<error.StanzaError>} and result in the errback of the |
- deferred being invoked. |
- |
- @rtype: L{defer.Deferred} |
- """ |
- if to is not None: |
- self["to"] = to |
- |
- if not ijabber.IIQResponseTracker.providedBy(self._xmlstream): |
- upgradeWithIQResponseTracker(self._xmlstream) |
- |
- d = defer.Deferred() |
- self._xmlstream.iqDeferreds[self['id']] = d |
- |
- timeout = self.timeout or self._xmlstream.iqDefaultTimeout |
- if timeout is not None: |
- def onTimeout(): |
- del self._xmlstream.iqDeferreds[self['id']] |
- d.errback(TimeoutError("IQ timed out")) |
- |
- call = self._xmlstream._callLater(timeout, onTimeout) |
- |
- def cancelTimeout(result): |
- if call.active(): |
- call.cancel() |
- |
- return result |
- |
- d.addBoth(cancelTimeout) |
- |
- self._xmlstream.send(self) |
- return d |
- |
- |
- |
-def toResponse(stanza, stanzaType=None): |
- """ |
- Create a response stanza from another stanza. |
- |
- This takes the addressing and id attributes from a stanza to create a (new, |
- empty) response stanza. The addressing attributes are swapped and the id |
- copied. Optionally, the stanza type of the response can be specified. |
- |
- @param stanza: the original stanza |
- @type stanza: L{domish.Element} |
- @param stanzaType: optional response stanza type |
- @type stanzaType: C{str} |
- @return: the response stanza. |
- @rtype: L{domish.Element} |
- """ |
- |
- toAddr = stanza.getAttribute('from') |
- fromAddr = stanza.getAttribute('to') |
- stanzaID = stanza.getAttribute('id') |
- |
- response = domish.Element((None, stanza.name)) |
- if toAddr: |
- response['to'] = toAddr |
- if fromAddr: |
- response['from'] = fromAddr |
- if stanzaID: |
- response['id'] = stanzaID |
- if type: |
- response['type'] = stanzaType |
- |
- return response |
- |
- |
- |
-class XMPPHandler(object): |
- """ |
- XMPP protocol handler. |
- |
- Classes derived from this class implement (part of) one or more XMPP |
- extension protocols, and are referred to as a subprotocol implementation. |
- """ |
- |
- implements(ijabber.IXMPPHandler) |
- |
- def __init__(self): |
- self.parent = None |
- self.xmlstream = None |
- |
- |
- def setHandlerParent(self, parent): |
- self.parent = parent |
- self.parent.addHandler(self) |
- |
- |
- def disownHandlerParent(self, parent): |
- self.parent.removeHandler(self) |
- self.parent = None |
- |
- |
- def makeConnection(self, xs): |
- self.xmlstream = xs |
- self.connectionMade() |
- |
- |
- def connectionMade(self): |
- """ |
- Called after a connection has been established. |
- |
- Can be overridden to perform work before stream initialization. |
- """ |
- |
- |
- def connectionInitialized(self): |
- """ |
- The XML stream has been initialized. |
- |
- Can be overridden to perform work after stream initialization, e.g. to |
- set up observers and start exchanging XML stanzas. |
- """ |
- |
- |
- def connectionLost(self, reason): |
- """ |
- The XML stream has been closed. |
- |
- This method can be extended to inspect the C{reason} argument and |
- act on it. |
- """ |
- self.xmlstream = None |
- |
- |
- def send(self, obj): |
- """ |
- Send data over the managed XML stream. |
- |
- @note: The stream manager maintains a queue for data sent using this |
- method when there is no current initialized XML stream. This |
- data is then sent as soon as a new stream has been established |
- and initialized. Subsequently, L{connectionInitialized} will be |
- called again. If this queueing is not desired, use C{send} on |
- C{self.xmlstream}. |
- |
- @param obj: data to be sent over the XML stream. This is usually an |
- object providing L{domish.IElement}, or serialized XML. See |
- L{xmlstream.XmlStream} for details. |
- """ |
- self.parent.send(obj) |
- |
- |
- |
-class XMPPHandlerCollection(object): |
- """ |
- Collection of XMPP subprotocol handlers. |
- |
- This allows for grouping of subprotocol handlers, but is not an |
- L{XMPPHandler} itself, so this is not recursive. |
- |
- @ivar handlers: List of protocol handlers. |
- @type handlers: L{list} of objects providing |
- L{IXMPPHandler} |
- """ |
- |
- implements(ijabber.IXMPPHandlerCollection) |
- |
- def __init__(self): |
- self.handlers = [] |
- |
- |
- def __iter__(self): |
- """ |
- Act as a container for handlers. |
- """ |
- return iter(self.handlers) |
- |
- |
- def addHandler(self, handler): |
- """ |
- Add protocol handler. |
- |
- Protocol handlers are expected to provide L{ijabber.IXMPPHandler}. |
- """ |
- self.handlers.append(handler) |
- |
- |
- def removeHandler(self, handler): |
- """ |
- Remove protocol handler. |
- """ |
- self.handlers.remove(handler) |
- |
- |
- |
-class StreamManager(XMPPHandlerCollection): |
- """ |
- Business logic representing a managed XMPP connection. |
- |
- This maintains a single XMPP connection and provides facilities for packet |
- routing and transmission. Business logic modules are objects providing |
- L{ijabber.IXMPPHandler} (like subclasses of L{XMPPHandler}), and added |
- using L{addHandler}. |
- |
- @ivar xmlstream: currently managed XML stream |
- @type xmlstream: L{XmlStream} |
- @ivar logTraffic: if true, log all traffic. |
- @type logTraffic: L{bool} |
- @ivar _initialized: Whether the stream represented by L{xmlstream} has |
- been initialized. This is used when caching outgoing |
- stanzas. |
- @type _initialized: C{bool} |
- @ivar _packetQueue: internal buffer of unsent data. See L{send} for details. |
- @type _packetQueue: L{list} |
- """ |
- |
- logTraffic = False |
- |
- def __init__(self, factory): |
- XMPPHandlerCollection.__init__(self) |
- self.xmlstream = None |
- self._packetQueue = [] |
- self._initialized = False |
- |
- factory.addBootstrap(STREAM_CONNECTED_EVENT, self._connected) |
- factory.addBootstrap(STREAM_AUTHD_EVENT, self._authd) |
- factory.addBootstrap(INIT_FAILED_EVENT, self.initializationFailed) |
- factory.addBootstrap(STREAM_END_EVENT, self._disconnected) |
- self.factory = factory |
- |
- |
- def addHandler(self, handler): |
- """ |
- Add protocol handler. |
- |
- When an XML stream has already been established, the handler's |
- C{connectionInitialized} will be called to get it up to speed. |
- """ |
- XMPPHandlerCollection.addHandler(self, handler) |
- |
- # get protocol handler up to speed when a connection has already |
- # been established |
- if self.xmlstream and self._initialized: |
- handler.makeConnection(self.xmlstream) |
- handler.connectionInitialized() |
- |
- |
- def _connected(self, xs): |
- """ |
- Called when the transport connection has been established. |
- |
- Here we optionally set up traffic logging (depending on L{logTraffic}) |
- and call each handler's C{makeConnection} method with the L{XmlStream} |
- instance. |
- """ |
- def logDataIn(buf): |
- log.msg("RECV: %r" % buf) |
- |
- def logDataOut(buf): |
- log.msg("SEND: %r" % buf) |
- |
- if self.logTraffic: |
- xs.rawDataInFn = logDataIn |
- xs.rawDataOutFn = logDataOut |
- |
- self.xmlstream = xs |
- |
- for e in self: |
- e.makeConnection(xs) |
- |
- |
- def _authd(self, xs): |
- """ |
- Called when the stream has been initialized. |
- |
- Send out cached stanzas and call each handler's |
- C{connectionInitialized} method. |
- """ |
- # Flush all pending packets |
- for p in self._packetQueue: |
- xs.send(p) |
- self._packetQueue = [] |
- self._initialized = True |
- |
- # Notify all child services which implement |
- # the IService interface |
- for e in self: |
- e.connectionInitialized() |
- |
- |
- def initializationFailed(self, reason): |
- """ |
- Called when stream initialization has failed. |
- |
- Stream initialization has halted, with the reason indicated by |
- C{reason}. It may be retried by calling the authenticator's |
- C{initializeStream}. See the respective authenticators for details. |
- |
- @param reason: A failure instance indicating why stream initialization |
- failed. |
- @type reason: L{failure.Failure} |
- """ |
- |
- |
- def _disconnected(self, _): |
- """ |
- Called when the stream has been closed. |
- |
- From this point on, the manager doesn't interact with the |
- L{XmlStream} anymore and notifies each handler that the connection |
- was lost by calling its C{connectionLost} method. |
- """ |
- self.xmlstream = None |
- self._initialized = False |
- |
- # Notify all child services which implement |
- # the IService interface |
- for e in self: |
- e.connectionLost(None) |
- |
- |
- def send(self, obj): |
- """ |
- Send data over the XML stream. |
- |
- When there is no established XML stream, the data is queued and sent |
- out when a new XML stream has been established and initialized. |
- |
- @param obj: data to be sent over the XML stream. See |
- L{xmlstream.XmlStream.send} for details. |
- """ |
- if self._initialized: |
- self.xmlstream.send(obj) |
- else: |
- self._packetQueue.append(obj) |