source: wokkel/generic.py @ 130:45c3ce4fe4eb

Last change on this file since 130:45c3ce4fe4eb was 57:bcc9bc7a0929, checked in by Ralph Meijer <ralphm@…>, 13 years ago

Add a PubSubRequest? class, to parse and render publish-subscribe requests.

Author: ralphm.
Fixes #45.

File size: 6.4 KB
RevLine 
[20]1# -*- test-case-name: wokkel.test.test_generic -*-
2#
3# Copyright (c) 2003-2008 Ralph Meijer
[1]4# See LICENSE for details.
5
6"""
7Generic XMPP protocol helpers.
8"""
9
10from zope.interface import implements
11
[55]12from twisted.internet import defer, protocol
[57]13from twisted.words.protocols.jabber import error, jid, xmlstream
[20]14from twisted.words.protocols.jabber.xmlstream import toResponse
[35]15from twisted.words.xish import domish, utility
[1]16
[55]17try:
18    from twisted.words.xish.xmlstream import BootstrapMixin
19except ImportError:
20    from wokkel.compat import BootstrapMixin
21
[1]22from wokkel import disco
23from wokkel.iwokkel import IDisco
24from wokkel.subprotocols import XMPPHandler
25
26IQ_GET = '/iq[@type="get"]'
27IQ_SET = '/iq[@type="set"]'
28
29NS_VERSION = 'jabber:iq:version'
30VERSION = IQ_GET + '/query[@xmlns="' + NS_VERSION + '"]'
31
[24]32def parseXml(string):
33    """
34    Parse serialized XML into a DOM structure.
35
36    @param string: The serialized XML to be parsed, UTF-8 encoded.
37    @type string: C{str}.
38    @return: The DOM structure, or C{None} on empty or incomplete input.
39    @rtype: L{domish.Element}
40    """
41    roots = []
42    results = []
43    elementStream = domish.elementStream()
44    elementStream.DocumentStartEvent = roots.append
45    elementStream.ElementEvent = lambda elem: roots[0].addChild(elem)
46    elementStream.DocumentEndEvent = lambda: results.append(roots[0])
47    elementStream.parse(string)
48    return results and results[0] or None
49
50
51
[30]52def stripNamespace(rootElement):
53    namespace = rootElement.uri
54
55    def strip(element):
56        if element.uri == namespace:
57            element.uri = None
58            if element.defaultUri == namespace:
59                element.defaultUri = None
60            for child in element.elements():
61                strip(child)
62
63    if namespace is not None:
64        strip(rootElement)
65
66    return rootElement
67
68
69
[1]70class FallbackHandler(XMPPHandler):
71    """
72    XMPP subprotocol handler that catches unhandled iq requests.
73
74    Unhandled iq requests are replied to with a service-unavailable stanza
75    error.
76    """
77
78    def connectionInitialized(self):
79        self.xmlstream.addObserver(IQ_SET, self.iqFallback, -1)
80        self.xmlstream.addObserver(IQ_GET, self.iqFallback, -1)
81
82    def iqFallback(self, iq):
83        if iq.handled == True:
84            return
85
86        reply = error.StanzaError('service-unavailable')
87        self.xmlstream.send(reply.toResponse(iq))
88
89
[20]90
[1]91class VersionHandler(XMPPHandler):
92    """
93    XMPP subprotocol handler for XMPP Software Version.
94
95    This protocol is described in
96    U{XEP-0092<http://www.xmpp.org/extensions/xep-0092.html>}.
97    """
98
99    implements(IDisco)
100
101    def __init__(self, name, version):
102        self.name = name
103        self.version = version
104
105    def connectionInitialized(self):
106        self.xmlstream.addObserver(VERSION, self.onVersion)
107
108    def onVersion(self, iq):
[20]109        response = toResponse(iq, "result")
[1]110
[20]111        query = response.addElement((NS_VERSION, "query"))
[1]112        name = query.addElement("name", content=self.name)
[20]113        version = query.addElement("version", content=self.version)
114        self.send(response)
[1]115
116        iq.handled = True
117
[6]118    def getDiscoInfo(self, requestor, target, node):
[11]119        info = set()
120
[1]121        if not node:
[11]122            info.add(disco.DiscoFeature(NS_VERSION))
123
124        return defer.succeed(info)
[1]125
[6]126    def getDiscoItems(self, requestor, target, node):
[1]127        return defer.succeed([])
[35]128
129
130
131class XmlPipe(object):
132    """
133    XML stream pipe.
134
135    Connects two objects that communicate stanzas through an XML stream like
136    interface. Each of the ends of the pipe (sink and source) can be used to
137    send XML stanzas to the other side, or add observers to process XML stanzas
138    that were sent from the other side.
139
140    XML pipes are usually used in place of regular XML streams that are
141    transported over TCP. This is the reason for the use of the names source
142    and sink for both ends of the pipe. The source side corresponds with the
143    entity that initiated the TCP connection, whereas the sink corresponds with
144    the entity that accepts that connection. In this object, though, the source
145    and sink are treated equally.
146
147    Unlike Jabber
148    L{XmlStream<twisted.words.protocols.jabber.xmlstream.XmlStream>}s, the sink
149    and source objects are assumed to represent an eternal connected and
150    initialized XML stream. As such, events corresponding to connection,
151    disconnection, initialization and stream errors are not dispatched or
152    processed.
153
154    @ivar source: Source XML stream.
155    @ivar sink: Sink XML stream.
156    """
157
158    def __init__(self):
159        self.source = utility.EventDispatcher()
160        self.sink = utility.EventDispatcher()
161        self.source.send = lambda obj: self.sink.dispatch(obj)
162        self.sink.send = lambda obj: self.source.dispatch(obj)
[55]163
164
[57]165
166class Stanza(object):
167    """
168    Abstract representation of a stanza.
169
170    @ivar sender: The sending entity.
171    @type sender: L{jid.JID}
172    @ivar recipient: The receiving entity.
173    @type recipient: L{jid.JID}
174    """
175
176    sender = None
177    recipient = None
178    stanzaType = None
179
180    @classmethod
181    def fromElement(Class, element):
182        stanza = Class()
183        stanza.parseElement(element)
184        return stanza
185
186
187    def parseElement(self, element):
188        self.sender = jid.internJID(element['from'])
189        if element.hasAttribute('from'):
190            self.sender = jid.internJID(element['from'])
191        if element.hasAttribute('to'):
192            self.recipient = jid.internJID(element['to'])
193        self.stanzaType = element.getAttribute('type')
194
195
[55]196class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
197    protocol = xmlstream.XmlStream
198
199    def __init__(self, authenticator):
200        BootstrapMixin.__init__(self)
201
202        self.authenticator = authenticator
203
204        deferred = defer.Deferred()
205        self.deferred = deferred
206        self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.deferred.callback)
207        self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)
208
209
210    def buildProtocol(self, addr):
211        """
212        Create an instance of XmlStream.
213
214        A new authenticator instance will be created and passed to the new
215        XmlStream. Registered bootstrap event observers are installed as well.
216        """
217        xs = self.protocol(self.authenticator)
218        xs.factory = self
219        self.installBootstraps(xs)
220        return xs
221
222
223    def clientConnectionFailed(self, connector, reason):
224        self.deferred.errback(reason)
Note: See TracBrowser for help on using the repository browser.