source: wokkel/generic.py @ 194:f1b586df427b

Last change on this file since 194:f1b586df427b was 194:f1b586df427b, checked in by Ralph Meijer <ralphm@…>, 4 years ago

imported patch py3-subprotocols.patch

  • Property exe set to *
File size: 10.1 KB
Line 
1# -*- test-case-name: wokkel.test.test_generic -*-
2#
3# Copyright (c) Ralph Meijer.
4# See LICENSE for details.
5
6"""
7Generic XMPP protocol helpers.
8"""
9
10from zope.interface import implementer
11
12from twisted.internet import defer, protocol
13from twisted.python import reflect
14from twisted.python.deprecate import deprecated
15from twisted.python.versions import Version
16from twisted.words.protocols.jabber import error, jid, xmlstream
17from twisted.words.protocols.jabber.xmlstream import toResponse
18from twisted.words.xish import domish, utility
19from twisted.words.xish.xmlstream import BootstrapMixin
20
21from wokkel.iwokkel import IDisco
22from wokkel.subprotocols import XMPPHandler
23
24IQ_GET = '/iq[@type="get"]'
25IQ_SET = '/iq[@type="set"]'
26
27NS_VERSION = 'jabber:iq:version'
28VERSION = IQ_GET + '/query[@xmlns="' + NS_VERSION + '"]'
29
30def parseXml(string):
31    """
32    Parse serialized XML into a DOM structure.
33
34    @param string: The serialized XML to be parsed, UTF-8 encoded.
35    @type string: C{str}.
36    @return: The DOM structure, or C{None} on empty or incomplete input.
37    @rtype: L{domish.Element}
38    """
39    roots = []
40    results = []
41    elementStream = domish.elementStream()
42    elementStream.DocumentStartEvent = roots.append
43    elementStream.ElementEvent = lambda elem: roots[0].addChild(elem)
44    elementStream.DocumentEndEvent = lambda: results.append(roots[0])
45    elementStream.parse(string)
46    return results and results[0] or None
47
48
49
50def stripNamespace(rootElement):
51    namespace = rootElement.uri
52
53    def strip(element):
54        if element.uri == namespace:
55            element.uri = None
56            if element.defaultUri == namespace:
57                element.defaultUri = None
58            for child in element.elements():
59                strip(child)
60
61    if namespace is not None:
62        strip(rootElement)
63
64    return rootElement
65
66
67
68class FallbackHandler(XMPPHandler):
69    """
70    XMPP subprotocol handler that catches unhandled iq requests.
71
72    Unhandled iq requests are replied to with a service-unavailable stanza
73    error.
74    """
75
76    def connectionInitialized(self):
77        self.xmlstream.addObserver(IQ_SET, self.iqFallback, -1)
78        self.xmlstream.addObserver(IQ_GET, self.iqFallback, -1)
79
80    def iqFallback(self, iq):
81        if iq.handled == True:
82            return
83
84        reply = error.StanzaError('service-unavailable')
85        self.xmlstream.send(reply.toResponse(iq))
86
87
88
89@implementer(IDisco)
90class VersionHandler(XMPPHandler):
91    """
92    XMPP subprotocol handler for XMPP Software Version.
93
94    This protocol is described in
95    U{XEP-0092<http://xmpp.org/extensions/xep-0092.html>}.
96    """
97
98    def __init__(self, name, version):
99        self.name = name
100        self.version = version
101
102    def connectionInitialized(self):
103        self.xmlstream.addObserver(VERSION, self.onVersion)
104
105    def onVersion(self, iq):
106        response = toResponse(iq, "result")
107
108        query = response.addElement((NS_VERSION, "query"))
109        query.addElement("name", content=self.name)
110        query.addElement("version", content=self.version)
111        self.send(response)
112
113        iq.handled = True
114
115    def getDiscoInfo(self, requestor, target, node):
116        info = set()
117
118        if not node:
119            from wokkel import disco
120            info.add(disco.DiscoFeature(NS_VERSION))
121
122        return defer.succeed(info)
123
124    def getDiscoItems(self, requestor, target, node):
125        return defer.succeed([])
126
127
128
129class XmlPipe(object):
130    """
131    XML stream pipe.
132
133    Connects two objects that communicate stanzas through an XML stream like
134    interface. Each of the ends of the pipe (sink and source) can be used to
135    send XML stanzas to the other side, or add observers to process XML stanzas
136    that were sent from the other side.
137
138    XML pipes are usually used in place of regular XML streams that are
139    transported over TCP. This is the reason for the use of the names source
140    and sink for both ends of the pipe. The source side corresponds with the
141    entity that initiated the TCP connection, whereas the sink corresponds with
142    the entity that accepts that connection. In this object, though, the source
143    and sink are treated equally.
144
145    Unlike Jabber
146    L{XmlStream<twisted.words.protocols.jabber.xmlstream.XmlStream>}s, the sink
147    and source objects are assumed to represent an eternal connected and
148    initialized XML stream. As such, events corresponding to connection,
149    disconnection, initialization and stream errors are not dispatched or
150    processed.
151
152    @ivar source: Source XML stream.
153    @ivar sink: Sink XML stream.
154    """
155
156    def __init__(self):
157        self.source = utility.EventDispatcher()
158        self.sink = utility.EventDispatcher()
159        self.source.send = lambda obj: self.sink.dispatch(obj)
160        self.sink.send = lambda obj: self.source.dispatch(obj)
161
162
163
164class Stanza(object):
165    """
166    Abstract representation of a stanza.
167
168    @ivar sender: The sending entity.
169    @type sender: L{jid.JID}
170    @ivar recipient: The receiving entity.
171    @type recipient: L{jid.JID}
172    """
173
174    recipient = None
175    sender = None
176    stanzaKind = None
177    stanzaID = None
178    stanzaType = None
179
180    def __init__(self, recipient=None, sender=None):
181        self.recipient = recipient
182        self.sender = sender
183
184
185    @classmethod
186    def fromElement(Class, element):
187        """
188        Create a stanza from a L{domish.Element}.
189        """
190        stanza = Class()
191        stanza.parseElement(element)
192        return stanza
193
194
195    def parseElement(self, element):
196        """
197        Parse the stanza element.
198
199        This is called with the stanza's element when a L{Stanza} is
200        created using L{fromElement}. It parses the stanza's core attributes
201        (addressing, type and id), strips the namespace from the stanza
202        element for easier transport across streams and passes on
203        child elements for further parsing.
204
205        Child element parsers are defined by providing a C{childParsers}
206        attribute on a subclass, as a mapping from (URI, name) to the name
207        of the handler on C{self}. C{parseElement} will accumulate
208        C{childParsers} from its class hierarchy, iterate over the child
209        elements and pass it to matching handlers based on the child element's
210        URI and name. The special key of C{None} can be used to pass all
211        child elements to.
212        """
213        if element.hasAttribute('from'):
214            self.sender = jid.internJID(element['from'])
215        if element.hasAttribute('to'):
216            self.recipient = jid.internJID(element['to'])
217        self.stanzaType = element.getAttribute('type')
218        self.stanzaID = element.getAttribute('id')
219
220        # Save element
221        stripNamespace(element)
222        self.element = element
223
224        # accumulate all childHandlers in the class hierarchy of Class
225        handlers = {}
226        reflect.accumulateClassDict(self.__class__, 'childParsers', handlers)
227
228        for child in element.elements():
229            try:
230                handler = handlers[child.uri, child.name]
231            except KeyError:
232                try:
233                    handler = handlers[None]
234                except KeyError:
235                    continue
236
237            getattr(self, handler)(child)
238
239
240    def toElement(self):
241        element = domish.Element((None, self.stanzaKind))
242        if self.sender is not None:
243            element['from'] = self.sender.full()
244        if self.recipient is not None:
245            element['to'] = self.recipient.full()
246        if self.stanzaType:
247            element['type'] = self.stanzaType
248        if self.stanzaID:
249            element['id'] = self.stanzaID
250        return element
251
252
253
254class ErrorStanza(Stanza):
255
256    def parseElement(self, element):
257        Stanza.parseElement(self, element)
258        self.exception = error.exceptionFromStanza(element)
259
260
261
262class Request(Stanza):
263    """
264    IQ request stanza.
265
266    This is a base class for IQ get or set stanzas, to be used with
267    L{wokkel.subprotocols.StreamManager.request}.
268    """
269
270    stanzaKind = 'iq'
271    stanzaType = 'get'
272    timeout = None
273
274    childParsers = {None: 'parseRequest'}
275
276    def __init__(self, recipient=None, sender=None, stanzaType=None):
277        Stanza.__init__(self, recipient=recipient, sender=sender)
278        if stanzaType is not None:
279            self.stanzaType = stanzaType
280
281
282    def parseRequest(self, element):
283        """
284        Called with the request's child element for parsing.
285
286        When a request instance is created using L{fromElement}, this method
287        is called with the child element of the iq. Override this method for
288        parsing the request's payload.
289        """
290
291
292    def toElement(self):
293        element = Stanza.toElement(self)
294
295        if not self.stanzaID:
296            element.addUniqueId()
297            self.stanzaID = element['id']
298
299        return element
300
301
302
303class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
304    protocol = xmlstream.XmlStream
305
306    def __init__(self, authenticator):
307        BootstrapMixin.__init__(self)
308
309        self.authenticator = authenticator
310
311        deferred = defer.Deferred()
312        self.deferred = deferred
313        self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.deferred.callback)
314        self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)
315
316
317    def buildProtocol(self, addr):
318        """
319        Create an instance of XmlStream.
320
321        A new authenticator instance will be created and passed to the new
322        XmlStream. Registered bootstrap event observers are installed as well.
323        """
324        xs = self.protocol(self.authenticator)
325        xs.factory = self
326        self.installBootstraps(xs)
327        return xs
328
329
330    def clientConnectionFailed(self, connector, reason):
331        self.deferred.errback(reason)
332
333
334
335@deprecated(Version("Wokkel", 0, 8, 0), "unicode.encode('idna')")
336def prepareIDNName(name):
337    """
338    Encode a unicode IDN Domain Name into its ACE equivalent.
339
340    This will encode the domain labels, separated by allowed dot code points,
341    to their ASCII Compatible Encoding (ACE) equivalent, using punycode. The
342    result is an ASCII byte string of the encoded labels, separated by the
343    standard full stop.
344    """
345    return name.encode('idna')
Note: See TracBrowser for help on using the repository browser.