source: wokkel/generic.py @ 195:ff66e31c8607

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

imported patch py3-generic.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 __future__ import division, absolute_import
11
12from zope.interface import implementer
13
14from twisted.internet import defer, protocol
15from twisted.python import reflect
16from twisted.python.deprecate import deprecated
17from twisted.python.versions import Version
18from twisted.words.protocols.jabber import error, jid, xmlstream
19from twisted.words.protocols.jabber.xmlstream import toResponse
20from twisted.words.xish import domish, utility
21from twisted.words.xish.xmlstream import BootstrapMixin
22
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
91@implementer(IDisco)
92class VersionHandler(XMPPHandler):
93    """
94    XMPP subprotocol handler for XMPP Software Version.
95
96    This protocol is described in
97    U{XEP-0092<http://xmpp.org/extensions/xep-0092.html>}.
98    """
99
100    def __init__(self, name, version):
101        self.name = name
102        self.version = version
103
104    def connectionInitialized(self):
105        self.xmlstream.addObserver(VERSION, self.onVersion)
106
107    def onVersion(self, iq):
108        response = toResponse(iq, "result")
109
110        query = response.addElement((NS_VERSION, "query"))
111        query.addElement("name", content=self.name)
112        query.addElement("version", content=self.version)
113        self.send(response)
114
115        iq.handled = True
116
117    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
118        info = set()
119
120        if not nodeIdentifier:
121            from wokkel import disco
122            info.add(disco.DiscoFeature(NS_VERSION))
123
124        return defer.succeed(info)
125
126    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
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
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    recipient = None
177    sender = None
178    stanzaKind = None
179    stanzaID = None
180    stanzaType = None
181
182    def __init__(self, recipient=None, sender=None):
183        self.recipient = recipient
184        self.sender = sender
185
186
187    @classmethod
188    def fromElement(Class, element):
189        """
190        Create a stanza from a L{domish.Element}.
191        """
192        stanza = Class()
193        stanza.parseElement(element)
194        return stanza
195
196
197    def parseElement(self, element):
198        """
199        Parse the stanza element.
200
201        This is called with the stanza's element when a L{Stanza} is
202        created using L{fromElement}. It parses the stanza's core attributes
203        (addressing, type and id), strips the namespace from the stanza
204        element for easier transport across streams and passes on
205        child elements for further parsing.
206
207        Child element parsers are defined by providing a C{childParsers}
208        attribute on a subclass, as a mapping from (URI, name) to the name
209        of the handler on C{self}. C{parseElement} will accumulate
210        C{childParsers} from its class hierarchy, iterate over the child
211        elements and pass it to matching handlers based on the child element's
212        URI and name. The special key of C{None} can be used to pass all
213        child elements to.
214        """
215        if element.hasAttribute('from'):
216            self.sender = jid.internJID(element['from'])
217        if element.hasAttribute('to'):
218            self.recipient = jid.internJID(element['to'])
219        self.stanzaType = element.getAttribute('type')
220        self.stanzaID = element.getAttribute('id')
221
222        # Save element
223        stripNamespace(element)
224        self.element = element
225
226        # accumulate all childHandlers in the class hierarchy of Class
227        handlers = {}
228        reflect.accumulateClassDict(self.__class__, 'childParsers', handlers)
229
230        for child in element.elements():
231            try:
232                handler = handlers[child.uri, child.name]
233            except KeyError:
234                try:
235                    handler = handlers[None]
236                except KeyError:
237                    continue
238
239            getattr(self, handler)(child)
240
241
242    def toElement(self):
243        element = domish.Element((None, self.stanzaKind))
244        if self.sender is not None:
245            element['from'] = self.sender.full()
246        if self.recipient is not None:
247            element['to'] = self.recipient.full()
248        if self.stanzaType:
249            element['type'] = self.stanzaType
250        if self.stanzaID:
251            element['id'] = self.stanzaID
252        return element
253
254
255
256class ErrorStanza(Stanza):
257
258    def parseElement(self, element):
259        Stanza.parseElement(self, element)
260        self.exception = error.exceptionFromStanza(element)
261
262
263
264class Request(Stanza):
265    """
266    IQ request stanza.
267
268    This is a base class for IQ get or set stanzas, to be used with
269    L{wokkel.subprotocols.StreamManager.request}.
270    """
271
272    stanzaKind = 'iq'
273    stanzaType = 'get'
274    timeout = None
275
276    childParsers = {None: 'parseRequest'}
277
278    def __init__(self, recipient=None, sender=None, stanzaType=None):
279        Stanza.__init__(self, recipient=recipient, sender=sender)
280        if stanzaType is not None:
281            self.stanzaType = stanzaType
282
283
284    def parseRequest(self, element):
285        """
286        Called with the request's child element for parsing.
287
288        When a request instance is created using L{fromElement}, this method
289        is called with the child element of the iq. Override this method for
290        parsing the request's payload.
291        """
292
293
294    def toElement(self):
295        element = Stanza.toElement(self)
296
297        if not self.stanzaID:
298            element.addUniqueId()
299            self.stanzaID = element['id']
300
301        return element
302
303
304
305class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
306    protocol = xmlstream.XmlStream
307
308    def __init__(self, authenticator):
309        BootstrapMixin.__init__(self)
310
311        self.authenticator = authenticator
312
313        deferred = defer.Deferred()
314        self.deferred = deferred
315        self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.deferred.callback)
316        self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)
317
318
319    def buildProtocol(self, addr):
320        """
321        Create an instance of XmlStream.
322
323        A new authenticator instance will be created and passed to the new
324        XmlStream. Registered bootstrap event observers are installed as well.
325        """
326        xs = self.protocol(self.authenticator)
327        xs.factory = self
328        self.installBootstraps(xs)
329        return xs
330
331
332    def clientConnectionFailed(self, connector, reason):
333        self.deferred.errback(reason)
334
335
336
337@deprecated(Version("Wokkel", 0, 8, 0), "unicode.encode('idna')")
338def prepareIDNName(name):
339    """
340    Encode a unicode IDN Domain Name into its ACE equivalent.
341
342    This will encode the domain labels, separated by allowed dot code points,
343    to their ASCII Compatible Encoding (ACE) equivalent, using punycode. The
344    result is an ASCII byte string of the encoded labels, separated by the
345    standard full stop.
346    """
347    return name.encode('idna')
Note: See TracBrowser for help on using the repository browser.