Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Wokkel as a submodule.

  • Loading branch information...
commit 51e9fb4ba3779e350961e5b9a4cd3e4be526de75 1 parent ea74bbf
@dustin authored
Showing with 5 additions and 7,159 deletions.
  1. +3 −0  .gitmodules
  2. +1 −0  lib/wokkel
  3. +0 −8 lib/wokkel/__init__.py
  4. +0 −187 lib/wokkel/client.py
  5. +0 −121 lib/wokkel/compat.py
  6. +0 −341 lib/wokkel/component.py
  7. +0 −484 lib/wokkel/data_form.py
  8. +0 −141 lib/wokkel/disco.py
  9. +0 −121 lib/wokkel/formats.py
  10. +0 −157 lib/wokkel/generic.py
  11. +0 −512 lib/wokkel/iwokkel.py
  12. +0 −1,038 lib/wokkel/pubsub.py
  13. +0 −531 lib/wokkel/server.py
  14. +0 −40 lib/wokkel/shim.py
  15. +0 −309 lib/wokkel/subprotocols.py
  16. +0 −6 lib/wokkel/test/__init__.py
  17. +0 −56 lib/wokkel/test/helpers.py
  18. +0 −72 lib/wokkel/test/test_client.py
  19. +0 −184 lib/wokkel/test/test_compat.py
  20. +0 −369 lib/wokkel/test/test_component.py
  21. +0 −180 lib/wokkel/test/test_data_form.py
  22. +0 −56 lib/wokkel/test/test_disco.py
  23. +0 −88 lib/wokkel/test/test_generic.py
  24. +0 −949 lib/wokkel/test/test_pubsub.py
  25. +0 −139 lib/wokkel/test/test_server.py
  26. +0 −98 lib/wokkel/test/test_shim.py
  27. +0 −453 lib/wokkel/test/test_subprotocols.py
  28. +0 −114 lib/wokkel/test/test_xmppim.py
  29. +0 −405 lib/wokkel/xmppim.py
  30. +1 −0  whatsup.tac
View
3  .gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/wokkel"]
+ path = lib/wokkel
+ url = git://github.com/dustin/wokkel.git
1  lib/wokkel
@@ -0,0 +1 @@
+Subproject commit 2ce0add5793051f841b502f99caff1c251c0ad05
View
8 lib/wokkel/__init__.py
@@ -1,8 +0,0 @@
-# Copyright (c) 2003-2007 Ralph Meijer
-# See LICENSE for details
-
-"""
-Wokkel.
-
-Support library for Twisted applications using XMPP protocols.
-"""
View
187 lib/wokkel/client.py
@@ -1,187 +0,0 @@
-# -*- test-case-name: wokkel.test.test_client -*-
-#
-# Copyright (c) 2003-2007 Ralph Meijer
-# See LICENSE for details.
-
-"""
-XMPP Client support.
-
-This module holds several improvements on top of Twisted's XMPP support
-that should probably eventually move there.
-"""
-
-from twisted.application import service
-from twisted.internet import defer, protocol, reactor
-from twisted.names.srvconnect import SRVConnector
-from twisted.words.protocols.jabber import client, sasl, xmlstream
-
-try:
- from twisted.words.xish.xmlstream import BootstrapMixin
-except ImportError:
- from wokkel.compat import BootstrapMixin
-
-from wokkel.subprotocols import StreamManager, XMPPHandler
-
-class CheckAuthInitializer(object):
- """
- Check what authentication methods are available.
- """
-
- def __init__(self, xs):
- self.xmlstream = xs
-
- def initialize(self):
- if (sasl.NS_XMPP_SASL, 'mechanisms') in self.xmlstream.features:
- inits = [(sasl.SASLInitiatingInitializer, True),
- (client.BindInitializer, True),
- (client.SessionInitializer, False)]
-
- for initClass, required in inits:
- init = initClass(self.xmlstream)
- init.required = required
- self.xmlstream.initializers.append(init)
- elif (client.NS_IQ_AUTH_FEATURE, 'auth') in self.xmlstream.features:
- self.xmlstream.initializers.append(
- client.IQAuthInitializer(self.xmlstream))
- else:
- raise Exception("No available authentication method found")
-
-
-class HybridAuthenticator(xmlstream.ConnectAuthenticator):
- """
- Initializes an XmlStream connecting to an XMPP server as a Client.
-
- This is similar to L{client.XMPPAuthenticator}, but also tries non-SASL
- autentication.
- """
-
- namespace = 'jabber:client'
-
- def __init__(self, jid, password):
- xmlstream.ConnectAuthenticator.__init__(self, jid.host)
- self.jid = jid
- self.password = password
-
- def associateWithStream(self, xs):
- xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
-
- tlsInit = xmlstream.TLSInitiatingInitializer(xs)
- xs.initializers = [client.CheckVersionInitializer(xs),
- tlsInit,
- CheckAuthInitializer(xs)]
-
-
-def HybridClientFactory(jid, password):
- """
- Client factory for XMPP 1.0.
-
- This is similar to L{client.XMPPClientFactory} but also tries non-SASL
- autentication.
- """
-
- a = HybridAuthenticator(jid, password)
- return xmlstream.XmlStreamFactory(a)
-
-
-class XMPPClient(StreamManager, service.Service):
- """
- Service that initiates an XMPP client connection.
- """
-
- def __init__(self, jid, password, host=None, port=5222):
- self.domain = jid.host
- self.host = host
- self.port = port
-
- factory = HybridClientFactory(jid, password)
-
- StreamManager.__init__(self, factory)
-
- def startService(self):
- service.Service.startService(self)
-
- self._connection = self._getConnection()
-
- def stopService(self):
- service.Service.stopService(self)
-
- self.factory.stopTrying()
- self._connection.disconnect()
-
- def initializationFailed(self, reason):
- """
- Called when stream initialization has failed.
-
- Stop the service (thereby disconnecting the current stream) and
- raise the exception.
- """
- self.stopService()
- reason.raiseException()
-
- def _getConnection(self):
- if self.host:
- return reactor.connectTCP(self.host, self.port, self.factory)
- else:
- c = SRVConnector(reactor, 'xmpp-client', self.domain, self.factory)
- c.connect()
- return c
-
-
-class DeferredClientFactory(BootstrapMixin, protocol.ClientFactory):
- protocol = xmlstream.XmlStream
-
- def __init__(self, jid, password):
- BootstrapMixin.__init__(self)
-
- self.jid = jid
- self.password = password
-
- deferred = defer.Deferred()
- self.deferred = deferred
- self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)
-
- class ConnectionInitializedHandler(XMPPHandler):
- def connectionInitialized(self):
- deferred.callback(None)
-
- self.streamManager = StreamManager(self)
- self.addHandler(ConnectionInitializedHandler())
-
-
- def buildProtocol(self, addr):
- """
- Create an instance of XmlStream.
-
- A new authenticator instance will be created and passed to the new
- XmlStream. Registered bootstrap event observers are installed as well.
- """
- self.authenticator = client.XMPPAuthenticator(self.jid, self.password)
- xs = self.protocol(self.authenticator)
- xs.factory = self
- self.installBootstraps(xs)
- return xs
-
-
- def clientConnectionFailed(self, connector, reason):
- self.deferred.errback(reason)
-
-
- def addHandler(self, handler):
- """
- Add a subprotocol handler to the stream manager.
- """
- self.streamManager.addHandler(handler)
-
-
- def removeHandler(self, handler):
- """
- Add a subprotocol handler to the stream manager.
- """
- self.streamManager.removeHandler(handler)
-
-
-def clientCreator(factory):
- domain = factory.authenticator.jid.host
- c = SRVConnector(reactor, 'xmpp-client', domain, factory)
- c.connect()
- return factory.deferred
View
121 lib/wokkel/compat.py
@@ -1,121 +0,0 @@
-# -*- test-case-name: wokkel.test.test_compat -*-
-#
-# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-from twisted.internet import protocol
-from twisted.words.protocols.jabber import xmlstream
-from twisted.words.xish import domish
-
-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 stanzaType:
- response['type'] = stanzaType
-
- return response
-
-
-
-class BootstrapMixin(object):
- """
- XmlStream factory mixin to install bootstrap event observers.
-
- This mixin is for factories providing
- L{IProtocolFactory<twisted.internet.interfaces.IProtocolFactory>} to make
- sure bootstrap event observers are set up on protocols, before incoming
- data is processed. Such protocols typically derive from
- L{utility.EventDispatcher}, like L{XmlStream}.
-
- You can set up bootstrap event observers using C{addBootstrap}. The
- C{event} and C{fn} parameters correspond with the C{event} and
- C{observerfn} arguments to L{utility.EventDispatcher.addObserver}.
-
- @ivar bootstraps: The list of registered bootstrap event observers.
- @type bootstrap: C{list}
- """
-
- def __init__(self):
- self.bootstraps = []
-
-
- def installBootstraps(self, dispatcher):
- """
- Install registered bootstrap observers.
-
- @param dispatcher: Event dispatcher to add the observers to.
- @type dispatcher: L{utility.EventDispatcher}
- """
- for event, fn in self.bootstraps:
- dispatcher.addObserver(event, fn)
-
-
- def addBootstrap(self, event, fn):
- """
- Add a bootstrap event handler.
- """
- self.bootstraps.append((event, fn))
-
-
- def removeBootstrap(self, event, fn):
- """
- Remove a bootstrap event handler.
- """
- self.bootstraps.remove((event, fn))
-
-
-
-class XmlStreamServerFactory(BootstrapMixin,
- protocol.ServerFactory):
- """
- Factory for Jabber XmlStream objects as a server.
-
- @since: 8.2.
- @ivar authenticatorFactory: Factory callable that takes no arguments, to
- create a fresh authenticator to be associated
- with the XmlStream.
- """
-
- protocol = xmlstream.XmlStream
-
- def __init__(self, authenticatorFactory):
- BootstrapMixin.__init__(self)
- self.authenticatorFactory = authenticatorFactory
-
-
- def buildProtocol(self, addr):
- """
- Create an instance of XmlStream.
-
- A new authenticator instance will be created and passed to the new
- XmlStream. Registered bootstrap event observers are installed as well.
- """
- authenticator = self.authenticatorFactory()
- xs = self.protocol(authenticator)
- xs.factory = self
- self.installBootstraps(xs)
- return xs
View
341 lib/wokkel/component.py
@@ -1,341 +0,0 @@
-# -*- test-case-name: wokkel.test.test_component -*-
-#
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-XMPP External Component utilities
-"""
-
-from twisted.application import service
-from twisted.internet import reactor
-from twisted.python import log
-from twisted.words.protocols.jabber.jid import internJID as JID
-from twisted.words.protocols.jabber import component, error, xmlstream
-from twisted.words.xish import domish
-
-from wokkel.generic import XmlPipe
-from wokkel.subprotocols import StreamManager
-
-NS_COMPONENT_ACCEPT = 'jabber:component:accept'
-
-class Component(StreamManager, service.Service):
- def __init__(self, host, port, jid, password):
- self.host = host
- self.port = port
-
- factory = component.componentFactory(jid, password)
-
- StreamManager.__init__(self, factory)
-
- def _authd(self, xs):
- old_send = xs.send
-
- def send(obj):
- if domish.IElement.providedBy(obj) and \
- not obj.getAttribute('from'):
- obj['from'] = self.xmlstream.thisEntity.full()
- old_send(obj)
-
- xs.send = send
- StreamManager._authd(self, xs)
-
- def initializationFailed(self, reason):
- """
- Called when stream initialization has failed.
-
- Stop the service (thereby disconnecting the current stream) and
- raise the exception.
- """
- self.stopService()
- reason.raiseException()
-
- def startService(self):
- service.Service.startService(self)
-
- self.factory.stopTrying()
- self._connection = self._getConnection()
-
- def stopService(self):
- service.Service.stopService(self)
-
- self._connection.disconnect()
-
- def _getConnection(self):
- return reactor.connectTCP(self.host, self.port, self.factory)
-
-
-
-class InternalComponent(xmlstream.XMPPHandlerCollection, service.Service):
- """
- Component service that connects directly to a router.
-
- Instead of opening a socket to connect to a router, like L{Component},
- components of this type connect to a router in the same process. This
- allows for one-process XMPP servers.
- """
-
- def __init__(self, router, domain):
- xmlstream.XMPPHandlerCollection.__init__(self)
- self.router = router
- self.domain = domain
-
- self.xmlstream = None
-
- def startService(self):
- """
- Create a XML pipe, connect to the router and setup handlers.
- """
- service.Service.startService(self)
-
- self.pipe = XmlPipe()
- self.xmlstream = self.pipe.source
- self.router.addRoute(self.domain, self.pipe.sink)
-
- for e in self:
- e.makeConnection(self.xmlstream)
- e.connectionInitialized()
-
-
- def stopService(self):
- """
- Disconnect from the router and handlers.
- """
- service.Service.stopService(self)
-
- self.router.removeRoute(self.domain, self.pipe.sink)
- self.pipe = None
- self.xmlstream = None
-
- for e in self:
- e.connectionLost(None)
-
-
- def addHandler(self, handler):
- """
- Add a new handler and connect it to the stream.
- """
- xmlstream.XMPPHandlerCollection.addHandler(self, handler)
-
- if self.xmlstream:
- handler.makeConnection(self.xmlstream)
- handler.connectionInitialized()
-
-
- def send(self, obj):
- """
- Send data to the XML stream, so it ends up at the router.
- """
- self.xmlstream.send(obj)
-
-
-
-class ListenComponentAuthenticator(xmlstream.ListenAuthenticator):
- """
- Authenticator for accepting components.
- """
- namespace = NS_COMPONENT_ACCEPT
-
- def __init__(self, secret):
- self.secret = secret
- xmlstream.ListenAuthenticator.__init__(self)
-
-
- def associateWithStream(self, xs):
- xs.version = (0, 0)
- xmlstream.ListenAuthenticator.associateWithStream(self, xs)
-
-
- def streamStarted(self, rootElement):
- xmlstream.ListenAuthenticator.streamStarted(self, rootElement)
-
- if rootElement.defaultUri != self.namespace:
- exc = error.StreamError('invalid-namespace')
- self.xmlstream.sendStreamError(exc)
- return
-
- # self.xmlstream.thisEntity is set to the address the component
- # wants to assume. This should probably be checked.
- if not self.xmlstream.thisEntity:
- exc = error.StreamError('improper-addressing')
- self.xmlstream.sendStreamError(exc)
- return
-
- self.xmlstream.sid = 'random' # FIXME
-
- self.xmlstream.sendHeader()
- self.xmlstream.addOnetimeObserver('/*', self.onElement)
-
-
- def onElement(self, element):
- if (element.uri, element.name) == (self.namespace, 'handshake'):
- self.onHandshake(unicode(element))
- else:
- exc = error.streamError('not-authorized')
- self.xmlstream.sendStreamError(exc)
-
-
- def onHandshake(self, handshake):
- calculatedHash = xmlstream.hashPassword(self.xmlstream.sid, self.secret)
- if handshake != calculatedHash:
- exc = error.StreamError('not-authorized', text='Invalid hash')
- self.xmlstream.sendStreamError(exc)
- else:
- self.xmlstream.send('<handshake/>')
- self.xmlstream.dispatch(self.xmlstream,
- xmlstream.STREAM_AUTHD_EVENT)
-
-
-
-class RouterService(service.Service):
- """
- XMPP Server's Router Service.
-
- This service connects the different components of the XMPP service and
- routes messages between them based on the given routing table.
-
- Connected components are trusted to have correct addressing in the
- stanzas they offer for routing.
-
- A route destination of C{None} adds a default route. Traffic for which no
- specific route exists, will be routed to this default route.
-
- @ivar routes: Routes based on the host part of JIDs. Maps host names to the
- L{EventDispatcher<utility.EventDispatcher>}s that should
- receive the traffic. A key of C{None} means the default
- route.
- @type routes: C{dict}
- """
-
- def __init__(self):
- self.routes = {}
-
-
- def addRoute(self, destination, xs):
- """
- Add a new route.
-
- The passed XML Stream C{xs} will have an observer for all stanzas
- added to route its outgoing traffic. In turn, traffic for
- C{destination} will be passed to this stream.
-
- @param destination: Destination of the route to be added as a host name
- or C{None} for the default route.
- @type destination: C{str} or C{NoneType}.
- @param xs: XML Stream to register the route for.
- @type xs: L{EventDispatcher<utility.EventDispatcher>}.
- """
- self.routes[destination] = xs
- xs.addObserver('/*', self.route)
-
-
- def removeRoute(self, destination, xs):
- """
- Remove a route.
-
- @param destination: Destination of the route that should be removed.
- @type destination: C{str}.
- @param xs: XML Stream to remove the route for.
- @type xs: L{EventDispatcher<utility.EventDispatcher>}.
- """
- xs.removeObserver('/*', self.route)
- if (xs == self.routes[destination]):
- del self.routes[destination]
-
-
- def route(self, stanza):
- """
- Route a stanza.
-
- @param stanza: The stanza to be routed.
- @type stanza: L{domish.Element}.
- """
- if not list(stanza.elements()):
- return
-
- destination = JID(stanza['to'])
-
- log.msg("Routing to %s: %r" % (destination.full(), stanza.toXml()))
-
- if destination.host in self.routes:
- self.routes[destination.host].send(stanza)
- else:
- self.routes[None].send(stanza)
-
-
-
-class ComponentServer(service.Service):
- """
- XMPP Component Server service.
-
- This service accepts XMPP external component connections and makes
- the router service route traffic for a component's bound domain
- to that component.
- """
-
- logTraffic = False
-
- def __init__(self, router, port=5347, secret='secret'):
- self.router = router
- self.port = port
- self.secret = secret
-
- def authenticatorFactory():
- return ListenComponentAuthenticator(self.secret)
-
- self.factory = xmlstream.XmlStreamServerFactory(authenticatorFactory)
- self.factory.addBootstrap(xmlstream.STREAM_CONNECTED_EVENT,
- self.makeConnection)
- self.factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT,
- self.connectionInitialized)
-
- self.serial = 0
-
-
- def startService(self):
- service.Service.startService(self)
- reactor.listenTCP(self.port, self.factory)
-
-
- def makeConnection(self, xs):
- """
- Called when a component connection was made.
-
- This enables traffic debugging on incoming streams.
- """
- xs.serial = self.serial
- self.serial += 1
-
- def logDataIn(buf):
- log.msg("RECV (%d): %r" % (xs.serial, buf))
-
- def logDataOut(buf):
- log.msg("SEND (%d): %r" % (xs.serial, buf))
-
- if self.logTraffic:
- xs.rawDataInFn = logDataIn
- xs.rawDataOutFn = logDataOut
-
- xs.addObserver(xmlstream.STREAM_ERROR_EVENT, self.onError)
-
-
- def connectionInitialized(self, xs):
- """
- Called when a component has succesfully authenticated.
-
- Add the component to the routing table and establish a handler
- for a closed connection.
- """
- destination = xs.thisEntity.host
-
- self.router.addRoute(destination, xs)
- xs.addObserver(xmlstream.STREAM_END_EVENT, self.connectionLost, 0,
- destination, xs)
-
-
- def onError(self, reason):
- log.err(reason, "Stream Error")
-
-
- def connectionLost(self, destination, xs, reason):
- self.router.removeRoute(destination, xs)
View
484 lib/wokkel/data_form.py
@@ -1,484 +0,0 @@
-# -*- test-case-name: wokkel.test.test_data_form -*-
-#
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-Data Forms.
-
-Support for Data Forms as described in
-U{XEP-0004<http://www.xmpp.org/extensions/xep-0004.html>}, along with support
-for Field Standardization for Data Forms as described in
-U{XEP-0068<http://www.xmpp.org/extensions/xep-0068.html>}.
-"""
-
-from twisted.words.protocols.jabber.jid import JID
-from twisted.words.xish import domish
-
-NS_X_DATA = 'jabber:x:data'
-
-
-
-class Error(Exception):
- """
- Data Forms error.
- """
-
-
-
-class FieldNameRequiredError(Error):
- """
- A field name is required for this field type.
- """
-
-
-
-class TooManyValuesError(Error):
- """
- This field is single-value.
- """
-
-
-
-class Option(object):
- """
- Data Forms field option.
-
- @ivar value: Value of this option.
- @type value: C{unicode}
- @ivar label: Optional label for this option.
- @type label: C{unicode} or C{NoneType}.
- """
-
- def __init__(self, value, label=None):
- self.value = value
- self.label = label
-
-
- def __repr__(self):
- r = ["Option(", repr(self.value)]
- if self.label:
- r.append(", ")
- r.append(repr(self.label))
- r.append(")")
- return u"".join(r)
-
-
- def toElement(self):
- """
- Return the DOM representation of this option.
-
- @rtype L{domish.Element}.
- """
- option = domish.Element((NS_X_DATA, 'option'))
- option.addElement('value', content=self.value)
- if self.label:
- option['label'] = self.label
- return option
-
- @staticmethod
- def fromElement(element):
- valueElements = list(domish.generateElementsQNamed(element.children,
- 'value', NS_X_DATA))
- if not valueElements:
- raise Error("Option has no value")
-
- label = element.getAttribute('label')
- return Option(unicode(valueElements[0]), label)
-
-
-class Field(object):
- """
- Data Forms field.
-
- @ivar fieldType: Type of this field. One of C{'boolean'}, C{'fixed'},
- C{'hidden'}, C{'jid-multi'}, C{'jid-single'},
- C{'list-multi'}, {'list-single'}, C{'text-multi'},
- C{'text-private'}, C{'text-single'}.
-
- The default is C{'text-single'}.
- @type fieldType: C{str}
- @ivar var: Field name. Optional if L{fieldType} is C{'fixed'}.
- @type var: C{str}
- @ivar label: Human readable label for this field.
- @type label: C{unicode}
- @ivar values: The values for this field, for multi-valued field
- types, as a list of C{bool}, C{unicode} or L{JID}.
- @type values: C{list}
- @ivar options: List of possible values to choose from in a response
- to this form as a list of L{Option}s.
- @type options: C{list}.
- @ivar desc: Human readable description for this field.
- @type desc: C{unicode}
- @ivar required: Whether the field is required to be provided in a
- response to this form.
- @type required: C{bool}.
- """
-
- def __init__(self, fieldType='text-single', var=None, value=None,
- values=None, options=None, label=None, desc=None,
- required=False):
- """
- Initialize this field.
-
- See the identically named instance variables for descriptions.
-
- If C{value} is not C{None}, it overrides C{values}, setting the
- given value as the only value for this field.
- """
-
- self.fieldType = fieldType
- self.var = var
- if value is not None:
- self.value = value
- else:
- self.values = values or []
-
- try:
- self.options = [Option(value, label)
- for value, label in options.iteritems()]
- except AttributeError:
- self.options = options or []
-
- self.label = label
- self.desc = desc
- self.required = required
-
-
- def __repr__(self):
- r = ["Field(fieldType=", repr(self.fieldType)]
- if self.var:
- r.append(", var=")
- r.append(repr(self.var))
- if self.label:
- r.append(", label=")
- r.append(repr(self.label))
- if self.desc:
- r.append(", desc=")
- r.append(repr(self.desc))
- if self.required:
- r.append(", required=")
- r.append(repr(self.required))
- if self.values:
- r.append(", values=")
- r.append(repr(self.values))
- if self.options:
- r.append(", options=")
- r.append(repr(self.options))
- r.append(")")
- return u"".join(r)
-
-
- def __value_set(self, value):
- """
- Setter of value property.
-
- Sets C{value} as the only element of L{values}.
-
- @type value: C{bool}, C{unicode} or L{JID}
- """
- self.values = [value]
-
-
- def __value_get(self):
- """
- Getter of value property.
-
- Returns the first element of L{values}, if present, or C{None}.
- """
-
- if self.values:
- return self.values[0]
- else:
- return None
-
-
- value = property(__value_get, __value_set, doc="""
- The value for this field, for single-valued field types.
-
- This is a special property accessing L{values}. Writing to this
- property empties L{values} and then sets the given value as the
- only element of L{values}. Reading from this propery returns the
- first element of L{values}.
- """)
-
-
- def typeCheck(self):
- """
- Check field properties agains the set field type.
- """
- if self.var is None and self.fieldType != 'fixed':
- raise FieldNameRequiredError()
-
- if self.values:
- if (self.fieldType not in ('hidden', 'jid-multi', 'list-multi',
- 'text-multi') and
- len(self.values) > 1):
- raise TooManyValuesError()
-
- newValues = []
- for value in self.values:
- if self.fieldType == 'boolean':
- # We send out the textual representation of boolean values
- value = bool(int(value))
- elif self.fieldType in ('jid-single', 'jid-multi'):
- value = value.full()
-
- newValues.append(value)
-
- self.values = newValues
-
- def toElement(self):
- """
- Return the DOM representation of this Field.
-
- @rtype L{domish.Element}.
- """
-
- self.typeCheck()
-
- field = domish.Element((NS_X_DATA, 'field'))
- field['type'] = self.fieldType
-
- if self.var is not None:
- field['var'] = self.var
-
- for value in self.values:
- if self.fieldType == 'boolean':
- value = unicode(value).lower()
- field.addElement('value', content=value)
-
- if self.fieldType in ('list-single', 'list-multi'):
- for option in self.options:
- field.addChild(option.toElement())
-
- if self.label is not None:
- field['label'] = self.label
-
- if self.desc is not None:
- field.addElement('desc', content=self.desc)
-
- if self.required:
- field.addElement('required')
-
- return field
-
-
- @staticmethod
- def _parse_desc(field, element):
- desc = unicode(element)
- if desc:
- field.desc = desc
-
-
- @staticmethod
- def _parse_option(field, element):
- field.options.append(Option.fromElement(element))
-
-
- @staticmethod
- def _parse_required(field, element):
- field.required = True
-
-
- @staticmethod
- def _parse_value(field, element):
- value = unicode(element)
- if field.fieldType == 'boolean':
- value = value.lower() in ('1', 'true')
- elif field.fieldType in ('jid-multi', 'jid-single'):
- value = JID(value)
- field.values.append(value)
-
-
- @staticmethod
- def fromElement(element):
- field = Field(None)
-
- for eAttr, fAttr in {'type': 'fieldType',
- 'var': 'var',
- 'label': 'label'}.iteritems():
- value = element.getAttribute(eAttr)
- if value:
- setattr(field, fAttr, value)
-
-
- for child in element.elements():
- if child.uri != NS_X_DATA:
- continue
-
- func = getattr(Field, '_parse_' + child.name, None)
- if func:
- func(field, child)
-
- return field
-
-
- @staticmethod
- def fromDict(dictionary):
- kwargs = dictionary.copy()
-
- if 'type' in dictionary:
- kwargs['fieldType'] = dictionary['type']
- del kwargs['type']
-
- if 'options' in dictionary:
- options = []
- for value, label in dictionary['options'].iteritems():
- options.append(Option(value, label))
- kwargs['options'] = options
-
- return Field(**kwargs)
-
-
-
-class Form(object):
- """
- Data Form.
-
- There are two similarly named properties of forms. The L{formType} is the
- the so-called type of the form, and is set as the C{'type'} attribute
- on the form's root element.
-
- The Field Standardization specification in XEP-0068, defines a way to
- provide a context for the field names used in this form, by setting a
- special hidden field named C{'FORM_TYPE'}, to put the names of all
- other fields in the namespace of the value of that field. This namespace
- is recorded in the L{formNamespace} instance variable.
-
- @ivar formType: Type of form. One of C{'form'}, C{'submit'}, {'cancel'},
- or {'result'}.
- @type formType: C{str}.
- @ivar formNamespace: The optional namespace of the field names for this
- form. This goes in the special field named
- C{'FORM_TYPE'}, if set.
- @type formNamespace: C{str}.
- @ivar fields: Dictionary of fields that have a name. Note that this is
- meant to be used for reading, only. One should use
- L{addField} for adding fields.
- @type fields: C{dict}
- """
-
- def __init__(self, formType, title=None, instructions=None,
- formNamespace=None, fields=None):
- self.formType = formType
- self.title = title
- self.instructions = instructions or []
- self.formNamespace = formNamespace
-
- self.fieldList = []
- self.fields = {}
-
- if fields:
- for field in fields:
- self.addField(field)
-
- def __repr__(self):
- r = ["Form(formType=", repr(self.formType)]
-
- if self.title:
- r.append(", title=")
- r.append(repr(self.title))
- if self.instructions:
- r.append(", instructions=")
- r.append(repr(self.instructions))
- if self.formNamespace:
- r.append(", formNamespace=")
- r.append(repr(self.formNamespace))
- if self.fields:
- r.append(", fields=")
- r.append(repr(self.fieldList))
- r.append(")")
- return u"".join(r)
-
-
- def addField(self, field):
- """
- Add a field to this form.
-
- Fields are added in order, and L{fields} is a dictionary of the
- named fields, that is kept in sync only if this method is used for
- adding new fields. Multiple fields with the same name are disallowed.
- """
- if field.var is not None:
- if field.var in self.fields:
- raise Error("Duplicate field %r" % field.var)
-
- self.fields[field.var] = field
-
- self.fieldList.append(field)
-
-
- def toElement(self):
- form = domish.Element((NS_X_DATA, 'x'))
- form['type'] = self.formType
-
- if self.title:
- form.addElement('title', content=self.title)
-
- for instruction in self.instructions:
- form.addElement('instruction', content=instruction)
-
- if self.formNamespace is not None:
- field = Field('hidden', 'FORM_TYPE', self.formNamespace)
- form.addChild(field.toElement())
-
- for field in self.fieldList:
- form.addChild(field.toElement())
-
- return form
-
-
- @staticmethod
- def _parse_title(form, element):
- title = unicode(element)
- if title:
- form.title = title
-
-
- @staticmethod
- def _parse_instructions(form, element):
- instructions = unicode(element)
- if instructions:
- form.instructions.append(instructions)
-
-
- @staticmethod
- def _parse_field(form, element):
- field = Field.fromElement(element)
- if (field.var == "FORM_TYPE" and
- field.fieldType == 'hidden' and
- field.value):
- form.formNamespace = field.value
- else:
- form.addField(field)
-
- @staticmethod
- def fromElement(element):
- if (element.uri, element.name) != ((NS_X_DATA, 'x')):
- raise Error("Element provided is not a Data Form")
-
- form = Form(element.getAttribute("type"))
-
- for child in element.elements():
- if child.uri != NS_X_DATA:
- continue
-
- func = getattr(Form, '_parse_' + child.name, None)
- if func:
- func(form, child)
-
- return form
-
- def getValues(self):
- values = {}
-
- for name, field in self.fields.iteritems():
- if len(field.values) > 1:
- value = field.values
- else:
- value = field.value
-
- values[name] = value
-
- return values
View
141 lib/wokkel/disco.py
@@ -1,141 +0,0 @@
-# -*- test-case-name: wokkel.test.test_disco -*-
-#
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-XMPP Service Discovery.
-
-The XMPP service discovery protocol is documented in
-U{XEP-0030<http://www.xmpp.org/extensions/xep-0030.html>}.
-"""
-
-from twisted.internet import defer
-from twisted.words.protocols.jabber import error, jid
-from twisted.words.xish import domish
-
-from wokkel.iwokkel import IDisco
-from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
-
-NS = 'http://jabber.org/protocol/disco'
-NS_INFO = NS + '#info'
-NS_ITEMS = NS + '#items'
-
-IQ_GET = '/iq[@type="get"]'
-DISCO_INFO = IQ_GET + '/query[@xmlns="' + NS_INFO + '"]'
-DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_ITEMS + '"]'
-
-class DiscoFeature(domish.Element):
- """
- Element representing an XMPP service discovery feature.
- """
-
- def __init__(self, feature):
- domish.Element.__init__(self, (NS_INFO, 'feature'),
- attribs={'var': feature})
-
-
-class DiscoIdentity(domish.Element):
- """
- Element representing an XMPP service discovery identity.
- """
-
- def __init__(self, category, type, name = None):
- domish.Element.__init__(self, (NS_INFO, 'identity'),
- attribs={'category': category,
- 'type': type})
- if name:
- self['name'] = name
-
-
-class DiscoItem(domish.Element):
- """
- Element representing an XMPP service discovery item.
- """
-
- def __init__(self, jid, node='', name=None):
- domish.Element.__init__(self, (NS_ITEMS, 'item'),
- attribs={'jid': jid.full()})
- if node:
- self['node'] = node
-
- if name:
- self['name'] = name
-
-
-class DiscoHandler(XMPPHandler, IQHandlerMixin):
- """
- Protocol implementation for XMPP Service Discovery.
-
- This handler will listen to XMPP service discovery requests and
- query the other handlers in L{parent} (see L{XMPPHandlerContainer}) for
- their identities, features and items according to L{IDisco}.
- """
-
- iqHandlers = {DISCO_INFO: '_onDiscoInfo',
- DISCO_ITEMS: '_onDiscoItems'}
-
- def connectionInitialized(self):
- self.xmlstream.addObserver(DISCO_INFO, self.handleRequest)
- self.xmlstream.addObserver(DISCO_ITEMS, self.handleRequest)
-
- def _error(self, failure):
- failure.trap(defer.FirstError)
- return failure.value.subFailure
-
- def _onDiscoInfo(self, iq):
- requestor = jid.internJID(iq["from"])
- target = jid.internJID(iq["to"])
- nodeIdentifier = iq.query.getAttribute("node", '')
-
- def toResponse(results):
- info = []
- for i in results:
- info.extend(i[1])
-
- if nodeIdentifier and not info:
- raise error.StanzaError('item-not-found')
- else:
- response = domish.Element((NS_INFO, 'query'))
-
- for item in info:
- response.addChild(item)
-
- return response
-
- dl = []
- for handler in self.parent:
- if IDisco.providedBy(handler):
- dl.append(handler.getDiscoInfo(requestor, target,
- nodeIdentifier))
-
- d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=1)
- d.addCallbacks(toResponse, self._error)
- return d
-
- def _onDiscoItems(self, iq):
- requestor = jid.internJID(iq["from"])
- target = jid.internJID(iq["to"])
- nodeIdentifier = iq.query.getAttribute("node", '')
-
- def toResponse(results):
- items = []
- for i in results:
- items.extend(i[1])
-
- response = domish.Element((NS_ITEMS, 'query'))
-
- for item in items:
- response.addChild(item)
-
- return response
-
- dl = []
- for handler in self.parent:
- if IDisco.providedBy(handler):
- dl.append(handler.getDiscoItems(requestor, target,
- nodeIdentifier))
-
- d = defer.DeferredList(dl, fireOnOneErrback=1, consumeErrors=1)
- d.addCallbacks(toResponse, self._error)
- return d
View
121 lib/wokkel/formats.py
@@ -1,121 +0,0 @@
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-NS_MOOD = 'http://jabber.org/protocol/mood'
-NS_TUNE = 'http://jabber.org/protocol/tune'
-
-class Mood:
- """
- User mood.
-
- This represents a user's mood, as defined in
- U{XEP-0107<http://www.xmpp.org/extensions/xep-0107.html>}.
-
- @ivar value: The mood value.
- @ivar text: The optional natural-language description of, or reason
- for the mood.
- """
-
- def __init__(self, value, text=None):
- self.value = value
- self.text = text
-
- def fromXml(self, element):
- """
- Get a Mood instance from an XML representation.
-
- This class method parses the given XML document into a L{Mood}
- instances.
-
- @param element: The XML mood document.
- @type element: object providing
- L{IElement<twisted.words.xish.domish.IElement>}
- @return: A L{Mood} instance or C{None} if C{element} was not a mood
- document or if there was no mood value element.
- """
- if element.uri != NS_MOOD or element.name != 'mood':
- return None
-
- value = None
- text = None
-
- for child in element.elements():
- if child.uri != NS_MOOD:
- continue
-
- if child.name == 'text':
- text = unicode(child)
- else:
- value = child.name
-
- if value:
- return Mood(value, text)
- else:
- return None
-
- fromXml = classmethod(fromXml)
-
-class Tune:
- """
- User tune.
-
- This represents a user's mood, as defined in
- U{XEP-0118<http://www.xmpp.org/extensions/xep-0118.html>}.
-
- @ivar artist: The artist or performer of the song or piece.
- @type artist: C{unicode}
- @ivar length: The duration of the song or piece in seconds.
- @type length: C{int}
- @ivar source: The collection (e.g. album) or other source.
- @type source: C{unicode}
- @ivar title: The title of the song or piece
- @type title: C{unicode}
- @ivar track: A unique identifier for the tune; e.g. the track number within
- the collection or the specific URI for the object.
- @type track: C{unicode}
- @ivar uri: A URI pointing to information about the song, collection, or
- artist.
- @type uri: C{str}
-
- """
-
- artist = None
- length = None
- source = None
- title = None
- track = None
- uri = None
-
- def fromXml(self, element):
- """
- Get a Tune instance from an XML representation.
-
- This class method parses the given XML document into a L{Tune}
- instances.
-
- @param element: The XML tune document.
- @type element: object providing
- L{IElement<twisted.words.xish.domish.IElement>}
- @return: A L{Tune} instance or C{None} if C{element} was not a tune
- document.
- """
- if element.uri != NS_TUNE or element.name != 'tune':
- return None
-
- tune = Tune()
-
- for child in element.elements():
- if child.uri != NS_TUNE:
- continue
-
- if child.name in ('artist', 'source', 'title', 'track', 'uri'):
- setattr(tune, child.name, unicode(child))
- elif child.name == 'length':
- try:
- tune.length = int(unicode(child))
- except ValueError:
- pass
-
- return tune
-
- fromXml = classmethod(fromXml)
View
157 lib/wokkel/generic.py
@@ -1,157 +0,0 @@
-# -*- test-case-name: wokkel.test.test_generic -*-
-#
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-Generic XMPP protocol helpers.
-"""
-
-from zope.interface import implements
-
-from twisted.internet import defer
-from twisted.words.protocols.jabber import error
-from twisted.words.xish import domish, utility
-
-from wokkel import disco
-from wokkel.compat import toResponse
-from wokkel.iwokkel import IDisco
-from wokkel.subprotocols import XMPPHandler
-
-IQ_GET = '/iq[@type="get"]'
-IQ_SET = '/iq[@type="set"]'
-
-NS_VERSION = 'jabber:iq:version'
-VERSION = IQ_GET + '/query[@xmlns="' + NS_VERSION + '"]'
-
-def parseXml(string):
- """
- Parse serialized XML into a DOM structure.
-
- @param string: The serialized XML to be parsed, UTF-8 encoded.
- @type string: C{str}.
- @return: The DOM structure, or C{None} on empty or incomplete input.
- @rtype: L{domish.Element}
- """
- roots = []
- results = []
- elementStream = domish.elementStream()
- elementStream.DocumentStartEvent = roots.append
- elementStream.ElementEvent = lambda elem: roots[0].addChild(elem)
- elementStream.DocumentEndEvent = lambda: results.append(roots[0])
- elementStream.parse(string)
- return results and results[0] or None
-
-
-
-def stripNamespace(rootElement):
- namespace = rootElement.uri
-
- def strip(element):
- if element.uri == namespace:
- element.uri = None
- if element.defaultUri == namespace:
- element.defaultUri = None
- for child in element.elements():
- strip(child)
-
- if namespace is not None:
- strip(rootElement)
-
- return rootElement
-
-
-
-class FallbackHandler(XMPPHandler):
- """
- XMPP subprotocol handler that catches unhandled iq requests.
-
- Unhandled iq requests are replied to with a service-unavailable stanza
- error.
- """
-
- def connectionInitialized(self):
- self.xmlstream.addObserver(IQ_SET, self.iqFallback, -1)
- self.xmlstream.addObserver(IQ_GET, self.iqFallback, -1)
-
- def iqFallback(self, iq):
- if iq.handled == True:
- return
-
- reply = error.StanzaError('service-unavailable')
- self.xmlstream.send(reply.toResponse(iq))
-
-
-
-class VersionHandler(XMPPHandler):
- """
- XMPP subprotocol handler for XMPP Software Version.
-
- This protocol is described in
- U{XEP-0092<http://www.xmpp.org/extensions/xep-0092.html>}.
- """
-
- implements(IDisco)
-
- def __init__(self, name, version):
- self.name = name
- self.version = version
-
- def connectionInitialized(self):
- self.xmlstream.addObserver(VERSION, self.onVersion)
-
- def onVersion(self, iq):
- response = toResponse(iq, "result")
-
- query = response.addElement((NS_VERSION, "query"))
- name = query.addElement("name", content=self.name)
- version = query.addElement("version", content=self.version)
- self.send(response)
-
- iq.handled = True
-
- def getDiscoInfo(self, requestor, target, node):
- info = set()
-
- if not node:
- info.add(disco.DiscoFeature(NS_VERSION))
-
- return defer.succeed(info)
-
- def getDiscoItems(self, requestor, target, node):
- return defer.succeed([])
-
-
-
-class XmlPipe(object):
- """
- XML stream pipe.
-
- Connects two objects that communicate stanzas through an XML stream like
- interface. Each of the ends of the pipe (sink and source) can be used to
- send XML stanzas to the other side, or add observers to process XML stanzas
- that were sent from the other side.
-
- XML pipes are usually used in place of regular XML streams that are
- transported over TCP. This is the reason for the use of the names source
- and sink for both ends of the pipe. The source side corresponds with the
- entity that initiated the TCP connection, whereas the sink corresponds with
- the entity that accepts that connection. In this object, though, the source
- and sink are treated equally.
-
- Unlike Jabber
- L{XmlStream<twisted.words.protocols.jabber.xmlstream.XmlStream>}s, the sink
- and source objects are assumed to represent an eternal connected and
- initialized XML stream. As such, events corresponding to connection,
- disconnection, initialization and stream errors are not dispatched or
- processed.
-
- @ivar source: Source XML stream.
- @ivar sink: Sink XML stream.
- """
-
- def __init__(self):
- self.source = utility.EventDispatcher()
- self.sink = utility.EventDispatcher()
- self.source.send = lambda obj: self.sink.dispatch(obj)
- self.sink.send = lambda obj: self.source.dispatch(obj)
View
512 lib/wokkel/iwokkel.py
@@ -1,512 +0,0 @@
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-Wokkel interfaces.
-"""
-
-from zope.interface import Attribute, Interface
-
-class IXMPPHandler(Interface):
- """
- Interface for XMPP protocol handlers.
-
- Objects that provide this interface can be added to a stream manager to
- handle of (part of) an XMPP extension protocol.
- """
-
- manager = Attribute("""XML stream manager""")
- xmlstream = Attribute("""The managed XML stream""")
-
- def setHandlerParent(parent):
- """
- Set the parent of the handler.
-
- @type parent: L{IXMPPHandlerCollection}
- """
-
- def disownHandlerParent(parent):
- """
- Remove the parent of the handler.
-
- @type parent: L{IXMPPHandlerCollection}
- """
-
- def makeConnection(xs):
- """
- A connection over the underlying transport of the XML stream has been
- established.
-
- At this point, no traffic has been exchanged over the XML stream
- given in C{xs}.
-
- This should setup L{xmlstream} and call L{connectionMade}.
-
- @type xs: L{XmlStream<twisted.words.protocols.jabber.XmlStream>}
- """
-
- def connectionMade():
- """
- Called after a connection has been established.
-
- This method can be used to change properties of the XML Stream, its
- authenticator or the stream manager prior to stream initialization
- (including authentication).
- """
-
- def connectionInitialized():
- """
- The XML stream has been initialized.
-
- At this point, authentication was successful, and XML stanzas can be
- exchanged over the XML stream L{xmlstream}. This method can be
- used to setup observers for incoming stanzas.
- """
-
- def connectionLost(reason):
- """
- The XML stream has been closed.
-
- Subsequent use of L{parent.send} will result in data being queued
- until a new connection has been established.
-
- @type reason: L{twisted.python.failure.Failure}
- """
-
-
-class IXMPPHandlerCollection(Interface):
- """
- Collection of handlers.
-
- Contain several handlers and manage their connection.
- """
-
- def __iter__():
- """
- Get an iterator over all child handlers.
- """
-
- def addHandler(handler):
- """
- Add a child handler.
-
- @type handler: L{IXMPPHandler}
- """
-
- def removeHandler(handler):
- """
- Remove a child handler.
-
- @type handler: L{IXMPPHandler}
- """
-
-
-class IDisco(Interface):
- """
- Interface for XMPP service discovery.
- """
-
- def getDiscoInfo(requestor, target, nodeIdentifier=''):
- """
- Get identity and features from this entity, node.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param target: The target entity to which the request is made.
- @type target: L{jid.JID}
- @param nodeIdentifier: The optional identifier of the node at this
- entity to retrieve the identify and features of.
- The default is C{''}, meaning the root node.
- @type nodeIdentifier: C{unicode}
- """
-
- def getDiscoItems(requestor, target, nodeIdentifier=''):
- """
- Get contained items for this entity, node.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param target: The target entity to which the request is made.
- @type target: L{jid.JID}
- @param nodeIdentifier: The optional identifier of the node at this
- entity to retrieve the identify and features of.
- The default is C{''}, meaning the root node.
- @type nodeIdentifier: C{unicode}
- """
-
-
-class IPubSubClient(Interface):
-
- def itemsReceived(event):
- """
- Called when an items notification has been received for a node.
-
- An item can be an element named C{item} or C{retract}. Respectively,
- they signal an item being published or retracted, optionally
- accompanied with an item identifier in the C{id} attribute.
-
- @param event: The items event.
- @type event: L{ItemsEvent<wokkel.pubsub.ItemsEvent>}
- """
-
-
- def deleteReceived(event):
- """
- Called when a deletion notification has been received for a node.
-
- @param event: The items event.
- @type event: L{ItemsEvent<wokkel.pubsub.DeleteEvent>}
- """
-
-
- def purgeReceived(event):
- """
- Called when a purge notification has been received for a node.
-
- Upon receiving this notification all items associated should be
- considered retracted.
-
- @param event: The items event.
- @type event: L{ItemsEvent<wokkel.pubsub.PurgeEvent>}
- """
-
- def createNode(service, nodeIdentifier=None):
- """
- Create a new publish subscribe node.
-
- @param service: The publish-subscribe service entity.
- @type service: L{jid.JID}
- @param nodeIdentifier: Optional suggestion for the new node's
- identifier. If omitted, the creation of an
- instant node will be attempted.
- @type nodeIdentifier: L{unicode}
- @return: a deferred that fires with the identifier of the newly created
- node. Note that this can differ from the suggested identifier
- if the publish subscribe service chooses to modify or ignore
- the suggested identifier.
- @rtype: L{defer.Deferred}
- """
-
- def deleteNode(service, nodeIdentifier):
- """
- Delete a node.
-
- @param service: The publish-subscribe service entity.
- @type service: L{jid.JID}
- @param nodeIdentifier: Identifier of the node to be deleted.
- @type nodeIdentifier: L{unicode}
- @rtype: L{defer.Deferred}
- """
-
- def subscribe(service, nodeIdentifier, subscriber):
- """
- Subscribe to a node with a given JID.
-
- @param service: The publish-subscribe service entity.
- @type service: L{jid.JID}
- @param nodeIdentifier: Identifier of the node to subscribe to.
- @type nodeIdentifier: L{unicode}
- @param subscriber: JID to subscribe to the node.
- @type subscriber: L{jid.JID}
- @rtype: L{defer.Deferred}
- """
-
- def unsubscribe(service, nodeIdentifier, subscriber):
- """
- Unsubscribe from a node with a given JID.
-
- @param service: The publish-subscribe service entity.
- @type service: L{jid.JID}
- @param nodeIdentifier: Identifier of the node to unsubscribe from.
- @type nodeIdentifier: L{unicode}
- @param subscriber: JID to unsubscribe from the node.
- @type subscriber: L{jid.JID}
- @rtype: L{defer.Deferred}
- """
-
- def publish(service, nodeIdentifier, items=[]):
- """
- Publish to a node.
-
- Node that the C{items} parameter is optional, because so-called
- transient, notification-only nodes do not use items and publish
- actions only signify a change in some resource.
-
- @param service: The publish-subscribe service entity.
- @type service: L{jid.JID}
- @param nodeIdentifier: Identifier of the node to publish to.
- @type nodeIdentifier: L{unicode}
- @param items: List of item elements.
- @type items: L{list} of L{Item}
- @rtype: L{defer.Deferred}
- """
-
-
-class IPubSubService(Interface):
- """
- Interface for an XMPP Publish Subscribe Service.
-
- All methods that are called as the result of an XMPP request are to
- return a deferred that fires when the requested action has been performed.
- Alternatively, exceptions maybe raised directly or by calling C{errback}
- on the returned deferred.
- """
-
- def notifyPublish(service, nodeIdentifier, notifications):
- """
- Send out notifications for a publish event.
-
- @param service: The entity the notifications will originate from.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node that was published
- to.
- @type nodeIdentifier: C{unicode}
- @param notifications: The notifications as tuples of subscriber, the
- list of subscriptions and the list of items to be
- notified.
- @type notifications: C{list} of (L{jid.JID}, C{list} of
- L{Subscription<wokkel.pubsub.Subscription>},
- C{list} of L{domish.Element})
- """
-
- def notifyDelete(service, nodeIdentifier, subscriptions):
- """
- Send out node deletion notifications.
-
- @param service: The entity the notifications will originate from.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node that was deleted.
- @type nodeIdentifier: C{unicode}
- @param subscriptions: The subscriptions for which a notification should
- be sent out.
- @type subscriptions: C{list} of L{jid.JID}
- """
-
- def publish(requestor, service, nodeIdentifier, items):
- """
- Called when a publish request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to publish to.
- @type nodeIdentifier: C{unicode}
- @param items: The items to be published as L{domish} elements.
- @type items: C{list} of C{domish.Element}
- @return: deferred that fires on success.
- @rtype: L{defer.Deferred}
- """
-
- def subscribe(requestor, service, nodeIdentifier, subscriber):
- """
- Called when a subscribe request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to subscribe to.
- @type nodeIdentifier: C{unicode}
- @param subscriber: The entity to be subscribed.
- @type subscriber: L{jid.JID}
- @return: A deferred that fires with a
- L{Subscription<wokkel.pubsub.Subscription>}.
- @rtype: L{defer.Deferred}
- """
-
- def unsubscribe(requestor, service, nodeIdentifier, subscriber):
- """
- Called when a subscribe request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to unsubscribe from.
- @type nodeIdentifier: C{unicode}
- @param subscriber: The entity to be unsubscribed.
- @type subscriber: L{jid.JID}
- @return: A deferred that fires with C{None} when unsubscription has
- succeeded.
- @rtype: L{defer.Deferred}
- """
-
- def subscriptions(requestor, service):
- """
- Called when a subscriptions retrieval request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @return: A deferred that fires with a C{list} of subscriptions as
- L{Subscription<wokkel.pubsub.Subscription>}.
- @rtype: L{defer.Deferred}
- """
-
- def affiliations(requestor, service):
- """
- Called when a affiliations retrieval request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @return: A deferred that fires with a C{list} of affiliations as
- C{tuple}s of (node identifier as C{unicode}, affiliation state
- as C{str}). The affiliation can be C{'owner'}, C{'publisher'},
- or C{'outcast'}.
- @rtype: L{defer.Deferred}
- """
-
- def create(requestor, service, nodeIdentifier):
- """
- Called when a node creation request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The suggestion for the identifier of the node to
- be created. If the request did not include a
- suggestion for the node identifier, the value
- is C{None}.
- @type nodeIdentifier: C{unicode} or C{NoneType}
- @return: A deferred that fires with a C{unicode} that represents
- the identifier of the new node.
- @rtype: L{defer.Deferred}
- """
-
- def getConfigurationOptions():
- """
- Retrieve all known node configuration options.
-
- The returned dictionary holds the possible node configuration options
- by option name. The value of each entry represents the specifics for
- that option in a dictionary:
-
- - C{'type'} (C{str}): The option's type (see
- L{Field<wokkel.data_form.Field>}'s doc string for possible values).
- - C{'label'} (C{unicode}): A human readable label for this option.
- - C{'options'} (C{dict}): Optional list of possible values for this
- option.
-
- Example::
-
- {
- "pubsub#persist_items":
- {"type": "boolean",
- "label": "Persist items to storage"},
- "pubsub#deliver_payloads":
- {"type": "boolean",
- "label": "Deliver payloads with event notifications"},
- "pubsub#send_last_published_item":
- {"type": "list-single",
- "label": "When to send the last published item",
- "options": {
- "never": "Never",
- "on_sub": "When a new subscription is processed"}
- }
- }
-
- @rtype: C{dict}.
- """
-
- def getDefaultConfiguration(requestor, service):
- """
- Called when a default node configuration request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @return: A deferred that fires with a C{dict} representing the default
- node configuration. Keys are C{str}s that represent the
- field name. Values can be of types C{unicode}, C{int} or
- C{bool}.
- @rtype: L{defer.Deferred}
- """
-
- def getConfiguration(requestor, service, nodeIdentifier):
- """
- Called when a node configuration retrieval request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to retrieve the
- configuration from.
- @type nodeIdentifier: C{unicode}
- @return: A deferred that fires with a C{dict} representing the node
- configuration. Keys are C{str}s that represent the field name.
- Values can be of types C{unicode}, C{int} or C{bool}.
- @rtype: L{defer.Deferred}
- """
-
- def setConfiguration(requestor, service, nodeIdentifier, options):
- """
- Called when a node configuration change request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to change the
- configuration of.
- @type nodeIdentifier: C{unicode}
- @return: A deferred that fires with C{None} when the node's
- configuration has been changed.
- @rtype: L{defer.Deferred}
- """
-
- def items(requestor, service, nodeIdentifier, maxItems, itemIdentifiers):
- """
- Called when a items retrieval request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to retrieve items
- from.
- @type nodeIdentifier: C{unicode}
- """
-
- def retract(requestor, service, nodeIdentifier, itemIdentifiers):
- """
- Called when a item retraction request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to retract items
- from.
- @type nodeIdentifier: C{unicode}
- """
-
- def purge(requestor, service, nodeIdentifier):
- """
- Called when a node purge request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to be purged.
- @type nodeIdentifier: C{unicode}
- """
-
- def delete(requestor, service, nodeIdentifier):
- """
- Called when a node deletion request has been received.
-
- @param requestor: The entity the request originated from.
- @type requestor: L{jid.JID}
- @param service: The entity the request was addressed to.
- @type service: L{jid.JID}
- @param nodeIdentifier: The identifier of the node to be delete.
- @type nodeIdentifier: C{unicode}
- """
View
1,038 lib/wokkel/pubsub.py
@@ -1,1038 +0,0 @@
-# -*- test-case-name: wokkel.test.test_pubsub -*-
-#
-# Copyright (c) 2003-2008 Ralph Meijer
-# See LICENSE for details.
-
-"""
-XMPP publish-subscribe protocol.
-
-This protocol is specified in
-U{XEP-0060<http://www.xmpp.org/extensions/xep-0060.html>}.
-"""
-
-from zope.interface import implements
-
-from twisted.internet import defer
-from twisted.words.protocols.jabber import jid, error, xmlstream
-from twisted.words.xish import domish
-
-from wokkel import disco, data_form, shim
-from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
-from wokkel.iwokkel import IPubSubClient, IPubSubService
-
-# Iq get and set XPath queries
-IQ_GET = '/iq[@type="get"]'
-IQ_SET = '/iq[@type="set"]'
-
-# Publish-subscribe namespaces
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
-NS_PUBSUB_EVENT = NS_PUBSUB + '#event'
-NS_PUBSUB_ERRORS = NS_PUBSUB + '#errors'
-NS_PUBSUB_OWNER = NS_PUBSUB + "#owner"
-NS_PUBSUB_NODE_CONFIG = NS_PUBSUB + "#node_config"
-NS_PUBSUB_META_DATA = NS_PUBSUB + "#meta-data"
-
-# In publish-subscribe namespace XPath query selector.
-IN_NS_PUBSUB = '[@xmlns="' + NS_PUBSUB + '"]'
-IN_NS_PUBSUB_OWNER = '[@xmlns="' + NS_PUBSUB_OWNER + '"]'
-
-# Publish-subscribe XPath queries
-PUBSUB_ELEMENT = '/pubsub' + IN_NS_PUBSUB
-PUBSUB_OWNER_ELEMENT = '/pubsub' + IN_NS_PUBSUB_OWNER
-PUBSUB_GET = IQ_GET + PUBSUB_ELEMENT
-PUBSUB_SET = IQ_SET + PUBSUB_ELEMENT
-PUBSUB_OWNER_GET = IQ_GET + PUBSUB_OWNER_ELEMENT
-PUBSUB_OWNER_SET = IQ_SET + PUBSUB_OWNER_ELEMENT
-
-# Publish-subscribe command XPath queries
-PUBSUB_PUBLISH = PUBSUB_SET + '/publish' + IN_NS_PUBSUB
-PUBSUB_CREATE = PUBSUB_SET + '/create' + IN_NS_PUBSUB
-PUBSUB_SUBSCRIBE = PUBSUB_SET + '/subscribe' + IN_NS_PUBSUB
-PUBSUB_UNSUBSCRIBE = PUBSUB_SET + '/unsubscribe' + IN_NS_PUBSUB
-PUBSUB_OPTIONS_GET = PUBSUB_GET + '/options' + IN_NS_PUBSUB
-PUBSUB_OPTIONS_SET = PUBSUB_SET + '/options' + IN_NS_PUBSUB
-PUBSUB_DEFAULT = PUBSUB_OWNER_GET + '/default' + IN_NS_PUBSUB_OWNER
-PUBSUB_CONFIGURE_GET = PUBSUB_OWNER_GET + '/configure' + IN_NS_PUBSUB_OWNER
-PUBSUB_CONFIGURE_SET = PUBSUB_OWNER_SET + '/configure' + IN_NS_PUBSUB_OWNER
-PUBSUB_SUBSCRIPTIONS = PUBSUB_GET + '/subscriptions' + IN_NS_PUBSUB
-PUBSUB_AFFILIATIONS = PUBSUB_GET + '/affiliations' + IN_NS_PUBSUB
-PUBSUB_AFFILIATIONS_GET = PUBSUB_OWNER_GET + '/affiliations' + \
- IN_NS_PUBSUB_OWNER
-PUBSUB_AFFILIATIONS_SET = PUBSUB_OWNER_SET + '/affiliations' + \
- IN_NS_PUBSUB_OWNER
-PUBSUB_SUBSCRIPTIONS_GET = PUBSUB_OWNER_GET + '/subscriptions' + \
- IN_NS_PUBSUB_OWNER
-PUBSUB_SUBSCRIPTIONS_SET = PUBSUB_OWNER_SET + '/subscriptions' + \
- IN_NS_PUBSUB_OWNER
-PUBSUB_ITEMS = PUBSUB_GET + '/items' + IN_NS_PUBSUB
-PUBSUB_RETRACT = PUBSUB_SET + '/retract' + IN_NS_PUBSUB
-PUBSUB_PURGE = PUBSUB_OWNER_SET + '/purge' + IN_NS_PUBSUB_OWNER
-PUBSUB_DELETE = PUBSUB_OWNER_SET + '/delete' + IN_NS_PUBSUB_OWNER
-
-class SubscriptionPending(Exception):
- """
- Raised when the requested subscription is pending acceptance.
- """
-
-
-
-class SubscriptionUnconfigured(Exception):
- """
- Raised when the requested subscription needs to be configured before
- becoming active.
- """
-
-
-
-class PubSubError(error.StanzaError):
- """
- Exception with publish-subscribe specific condition.
- """
- def __init__(self, condition, pubsubCondition, feature=None, text=None):
- appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition))
- if feature:
- appCondition['feature'] = feature
- error.StanzaError.__init__(self, condition,
- text=text,
- appCondition=appCondition)
-
-
-
-class BadRequest(PubSubError):
- """
- Bad request stanza error.
- """
- def __init__(self, pubsubCondition=None, text=None):
- PubSubError.__init__(self, 'bad-request', pubsubCondition, text)
-
-
-
-class Unsupported(PubSubError):
- def __init__(self, feature, text=None):
- PubSubError.__init__(self, 'feature-not-implemented',
- 'unsupported',
- feature,
- text)
-
-
-
-class Subscription(object):
- """
- A subscription to a node.
-
- @ivar nodeIdentifier: The identifier of the node subscribed to.
- The root node is denoted by C{None}.
- @ivar subscriber: The subscribing entity.
- @ivar state: The subscription state. One of C{'subscribed'}, C{'pending'},
- C{'unconfigured'}.
- @ivar options: Optional list of subscription options.
- @type options: C{dict}.
- """
-
- def __init__(self, nodeIdentifier, subscriber, state, options=None):
- self.nodeIdentifier = nodeIdentifier
- self.subscriber = subscriber
- self.state = state
- self.options = options or {}
-
-
-
-class Item(domish.Element):
- """
- Publish subscribe item.
-
- This behaves like an object providing L{domish.IElement}.
-
- Item payload can be added using C{addChild} or C{addRawXml}, or using the
- C{payload} keyword argument to C{__init__}.
- """
-
- def __init__(self, id=None, payload=None):
- """
- @param id: optional item identifier
- @type id: L{unicode}
- @param payload: optional item payload. Either as a domish element, or
- as serialized XML.
- @type payload: object providing L{domish.IElement} or L{unicode}.
- """
-
- domish.Element.__init__(self, (NS_PUBSUB, 'item'))
- if id is not None:
- self['id'] = id
- if payload is not None:
- if isinstance(payload, basestring):
- self.addRawXml(payload)
- else:
- self.addChild(payload)
-
-
-
-class _PubSubRequest(xmlstream.IQ):
- """
- Publish subscribe request.
-
- @ivar verb: Request verb
- @type verb: C{str}
- @ivar namespace: Request namespace.
- @type namespace: C{str}
- @ivar method: Type attribute of the IQ request. Either C{'set'} or C{'get'}
- @type method: C{str}
- @ivar command: Command element of the request. This is the direct child of
- the C{pubsub} element in the C{namespace} with the name
- C{verb}.
- """
-
- def __init__(self, xs, verb, namespace=NS_PUBSUB, method='set'):
- xmlstream.IQ.__init__(self, xs, method)
- self.addElement((namespace, 'pubsub'))
-
- self.command = self.pubsub.addElement(verb)
-
-
- def send(self, to):
- """
- Send out request.
-
- Extends L{xmlstream.IQ.send} by requiring the C{to} parameter to be
- a L{JID} instance.
-
- @param to: Entity to send the request to.
- @type to: L{JID}
- """
- destination = to.full()
- return xmlstream.IQ.send(self, destination)
-
-
-
-class PubSubEvent(object):
- """
- A publish subscribe event.
-
- @param sender: The entity from which the notification was received.
- @type sender: L{jid.JID}
- @param recipient: The entity to which the notification was sent.
- @type recipient: L{wokkel.pubsub.ItemsEvent}
- @param nodeIdentifier: Identifier of the node the event pertains to.
- @type nodeIdentifier: C{unicode}
- @param headers: SHIM headers, see L{wokkel.shim.extractHeaders}.
- @type headers: L{dict}
- """
-
- def __init__(self, sender, recipient, nodeIdentifier, headers):
- self.sender = sender
- self.recipient = recipient
- self.nodeIdentifier = nodeIdentifier
- self.headers = headers
-
-
-
-class ItemsEvent(PubSubEvent):
- """
- A publish-subscribe event that signifies new, updated and retracted items.
-
- @param items: List of received items as domish elements.
- @type items: C{list} of L{domish.Element}
- """
-
- def __init__(self, sender, recipient, nodeIdentifier, items, headers):
- PubSubEvent.__init__(self, sender, recipient, nodeIdentifier, headers)
- self.items = items
-
-
-
-class DeleteEvent(PubSubEvent):
- """
- A publish-subscribe event that signifies the deletion of a node.
- """
-
-
-
-class PurgeEvent(PubSubEvent):
- """
- A publish-subscribe event that signifies the purging of a node.
- """
-
-
-
-class PubSubClient(XMPPHandler):
- """
- Publish subscribe client protocol.
- """
-
- implements(IPubSubClient)
-
- def connectionInitialized(self):
- self.xmlstream.addObserver('/message/event[@xmlns="%s"]' %
- NS_PUBSUB_EVENT, self._onEvent)
-
-
- def _onEvent(self, message):
- try:
- sender = jid.JID(message["from"])
- recipient = jid.JID(message["to"])
- except KeyError:
- return
-
- actionElement = None
- for element in message.event.elements():
- if element.uri == NS_PUBSUB_EVENT:
- actionElement = element
-
- if not actionElement:
- return
-
- eventHandler = getattr(self, "_onEvent_%s" % actionElement.name, None)
-
- if eventHandler:
- headers = shim.extractHeaders(message)
- eventHandler(sender, recipient, actionElement, headers)
- message.handled = True
-
-
- def _onEvent_items(self, sender, recipient, action, headers):
- nodeIdentifier = action["node"]
-
- items = [element for element in action.elements()
- if element.name in ('item', 'retract')]
-
- event = ItemsEvent(sender, recipient, nodeIdentifier, items, headers)
- self.itemsReceived(event)
-
-
- def _onEvent_delete(self, sender, recipient, action, headers):
- nodeIdentifier = action["node"]
- event = DeleteEvent(sender, recipient, nodeIdentifier, headers)
- self.deleteReceived(event)
-
-
- def _onEvent_purge(self, sender, recipient, action, headers):
- nodeIdentifier = action["node"]
- event = PurgeEvent(sender, recipient, nodeIdentifier, headers)
- self.purgeReceived(event)
-
-
- def itemsReceived(self, event):
- pass
-
-
- def deleteReceived(self, event):
- pass
-
-
- def purgeReceived(self, event):
- pass
-
-
- def createNode(self, service, nodeIdentifier=None):
- """
- Create a publish subscribe node.
-