source: wokkel/generic.py @ 55:a8cda961ee7e

Last change on this file since 55:a8cda961ee7e was 55:a8cda961ee7e, checked in by Ralph Meijer <ralphm@…>, 13 years ago

Implement server-to-server dialback protocol and stream management.

Author: ralphm.
Fixes #33.

This also abstracts the concept of a client factory that fires a deferred.

File size: 5.6 KB
Line 
1# -*- test-case-name: wokkel.test.test_generic -*-
2#
3# Copyright (c) 2003-2008 Ralph Meijer
4# See LICENSE for details.
5
6"""
7Generic XMPP protocol helpers.
8"""
9
10from zope.interface import implements
11
12from twisted.internet import defer, protocol
13from twisted.words.protocols.jabber import error, xmlstream
14from twisted.words.protocols.jabber.xmlstream import toResponse
15from twisted.words.xish import domish, utility
16
17try:
18    from twisted.words.xish.xmlstream import BootstrapMixin
19except ImportError:
20    from wokkel.compat import BootstrapMixin
21
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
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
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
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
90
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):
109        response = toResponse(iq, "result")
110
111        query = response.addElement((NS_VERSION, "query"))
112        name = query.addElement("name", content=self.name)
113        version = query.addElement("version", content=self.version)
114        self.send(response)
115
116        iq.handled = True
117
118    def getDiscoInfo(self, requestor, target, node):
119        info = set()
120
121        if not node:
122            info.add(disco.DiscoFeature(NS_VERSION))
123
124        return defer.succeed(info)
125
126    def getDiscoItems(self, requestor, target, node):
127        return defer.succeed([])
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)
163
164
165class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
166    protocol = xmlstream.XmlStream
167
168    def __init__(self, authenticator):
169        BootstrapMixin.__init__(self)
170
171        self.authenticator = authenticator
172
173        deferred = defer.Deferred()
174        self.deferred = deferred
175        self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.deferred.callback)
176        self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)
177
178
179    def buildProtocol(self, addr):
180        """
181        Create an instance of XmlStream.
182
183        A new authenticator instance will be created and passed to the new
184        XmlStream. Registered bootstrap event observers are installed as well.
185        """
186        xs = self.protocol(self.authenticator)
187        xs.factory = self
188        self.installBootstraps(xs)
189        return xs
190
191
192    def clientConnectionFailed(self, connector, reason):
193        self.deferred.errback(reason)
Note: See TracBrowser for help on using the repository browser.