| 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)
|
|
|