File: generic.py

package info (click to toggle)
python-tx-xmpp 0.10.1.post1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,468 kB
  • sloc: python: 12,915; makefile: 3
file content (318 lines) | stat: -rw-r--r-- 9,809 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# -*- test-case-name: tx_xmpp.test.test.test_generic -*-
#
# Copyright (c) Ralph Meijer.
# See LICENSE for details.

"""
Generic XMPP protocol helpers.
"""

from __future__ import division, absolute_import

from zope.interface import implementer

from twisted.internet import defer, protocol
from twisted.python import reflect
from twisted.words.protocols.jabber import error, jid, xmlstream
from twisted.words.protocols.jabber.xmlstream import toResponse
from twisted.words.xish import domish, utility
from twisted.words.xish.xmlstream import BootstrapMixin

from .itx_xmpp import IDisco
from .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: L{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))


@implementer(IDisco)
class VersionHandler(XMPPHandler):
    """
    XMPP subprotocol handler for XMPP Software Version.

    This protocol is described in
    U{XEP-0092<http://xmpp.org/extensions/xep-0092.html>}.
    """

    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"))
        query.addElement("name", content=self.name)
        query.addElement("version", content=self.version)
        self.send(response)

        iq.handled = True

    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
        info = set()

        if not nodeIdentifier:
            from . import disco

            info.add(disco.DiscoFeature(NS_VERSION))

        return defer.succeed(info)

    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
        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)


class Stanza(object):
    """
    Abstract representation of a stanza.

    @ivar sender: The sending entity.
    @type sender: L{jid.JID}
    @ivar recipient: The receiving entity.
    @type recipient: L{jid.JID}
    """

    recipient = None
    sender = None
    stanzaKind = None
    stanzaID = None
    stanzaType = None

    def __init__(self, recipient=None, sender=None):
        self.recipient = recipient
        self.sender = sender

    @classmethod
    def fromElement(Class, element):
        """
        Create a stanza from a L{domish.Element}.
        """
        stanza = Class()
        stanza.parseElement(element)
        return stanza

    def parseElement(self, element):
        """
        Parse the stanza element.

        This is called with the stanza's element when a L{Stanza} is
        created using L{fromElement}. It parses the stanza's core attributes
        (addressing, type and id), strips the namespace from the stanza
        element for easier transport across streams and passes on
        child elements for further parsing.

        Child element parsers are defined by providing a C{childParsers}
        attribute on a subclass, as a mapping from (URI, name) to the name
        of the handler on C{self}. C{parseElement} will accumulate
        C{childParsers} from its class hierarchy, iterate over the child
        elements and pass it to matching handlers based on the child element's
        URI and name. The special key of C{None} can be used to pass all
        child elements to.
        """
        if element.hasAttribute("from"):
            self.sender = jid.internJID(element["from"])
        if element.hasAttribute("to"):
            self.recipient = jid.internJID(element["to"])
        self.stanzaType = element.getAttribute("type")
        self.stanzaID = element.getAttribute("id")

        # Save element
        stripNamespace(element)
        self.element = element

        # accumulate all childHandlers in the class hierarchy of Class
        handlers = {}
        reflect.accumulateClassDict(self.__class__, "childParsers", handlers)

        for child in element.elements():
            try:
                handler = handlers[child.uri, child.name]
            except KeyError:
                try:
                    handler = handlers[None]
                except KeyError:
                    continue

            getattr(self, handler)(child)

    def toElement(self):
        element = domish.Element((None, self.stanzaKind))
        if self.sender is not None:
            element["from"] = self.sender.full()
        if self.recipient is not None:
            element["to"] = self.recipient.full()
        if self.stanzaType:
            element["type"] = self.stanzaType
        if self.stanzaID:
            element["id"] = self.stanzaID
        return element


class ErrorStanza(Stanza):

    def parseElement(self, element):
        Stanza.parseElement(self, element)
        self.exception = error.exceptionFromStanza(element)


class Request(Stanza):
    """
    IQ request stanza.

    This is a base class for IQ get or set stanzas, to be used with
    L{tx_xmpp.subprotocols.StreamManager.request}.
    """

    stanzaKind = "iq"
    stanzaType = "get"
    timeout = None

    childParsers = {None: "parseRequest"}

    def __init__(self, recipient=None, sender=None, stanzaType=None):
        Stanza.__init__(self, recipient=recipient, sender=sender)
        if stanzaType is not None:
            self.stanzaType = stanzaType

    def parseRequest(self, element):
        """
        Called with the request's child element for parsing.

        When a request instance is created using L{fromElement}, this method
        is called with the child element of the iq. Override this method for
        parsing the request's payload.
        """

    def toElement(self):
        element = Stanza.toElement(self)

        if not self.stanzaID:
            element.addUniqueId()
            self.stanzaID = element["id"]

        return element


class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
    protocol = xmlstream.XmlStream

    def __init__(self, authenticator):
        BootstrapMixin.__init__(self)

        self.authenticator = authenticator

        deferred = defer.Deferred()
        self.deferred = deferred
        self.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.deferred.callback)
        self.addBootstrap(xmlstream.INIT_FAILED_EVENT, deferred.errback)

    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.
        """
        xs = self.protocol(self.authenticator)
        xs.factory = self
        self.installBootstraps(xs)
        return xs

    def clientConnectionFailed(self, connector, reason):
        self.deferred.errback(reason)