[1] | 1 | # -*- test-case-name: wokkel.test.test_disco -*- |
---|
| 2 | # |
---|
[31] | 3 | # Copyright (c) 2003-2008 Ralph Meijer |
---|
[1] | 4 | # See LICENSE for details. |
---|
| 5 | |
---|
| 6 | """ |
---|
| 7 | XMPP Service Discovery. |
---|
| 8 | |
---|
| 9 | The XMPP service discovery protocol is documented in |
---|
| 10 | U{XEP-0030<http://www.xmpp.org/extensions/xep-0030.html>}. |
---|
| 11 | """ |
---|
| 12 | |
---|
| 13 | from twisted.internet import defer |
---|
| 14 | from twisted.words.protocols.jabber import error, jid |
---|
| 15 | from twisted.words.xish import domish |
---|
| 16 | |
---|
| 17 | from wokkel.iwokkel import IDisco |
---|
| 18 | from wokkel.subprotocols import IQHandlerMixin, XMPPHandler |
---|
| 19 | |
---|
[46] | 20 | NS_DISCO = 'http://jabber.org/protocol/disco' |
---|
| 21 | NS_DISCO_INFO = NS_DISCO + '#info' |
---|
| 22 | NS_DISCO_ITEMS = NS_DISCO + '#items' |
---|
[1] | 23 | |
---|
| 24 | IQ_GET = '/iq[@type="get"]' |
---|
[46] | 25 | DISCO_INFO = IQ_GET + '/query[@xmlns="' + NS_DISCO_INFO + '"]' |
---|
| 26 | DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_DISCO_ITEMS + '"]' |
---|
[1] | 27 | |
---|
| 28 | class DiscoFeature(domish.Element): |
---|
| 29 | """ |
---|
| 30 | Element representing an XMPP service discovery feature. |
---|
| 31 | """ |
---|
| 32 | |
---|
| 33 | def __init__(self, feature): |
---|
[46] | 34 | domish.Element.__init__(self, (NS_DISCO_INFO, 'feature'), |
---|
[1] | 35 | attribs={'var': feature}) |
---|
| 36 | |
---|
| 37 | |
---|
| 38 | class DiscoIdentity(domish.Element): |
---|
| 39 | """ |
---|
| 40 | Element representing an XMPP service discovery identity. |
---|
| 41 | """ |
---|
| 42 | |
---|
| 43 | def __init__(self, category, type, name = None): |
---|
[46] | 44 | domish.Element.__init__(self, (NS_DISCO_INFO, 'identity'), |
---|
[1] | 45 | attribs={'category': category, |
---|
| 46 | 'type': type}) |
---|
| 47 | if name: |
---|
| 48 | self['name'] = name |
---|
| 49 | |
---|
| 50 | |
---|
| 51 | class DiscoItem(domish.Element): |
---|
| 52 | """ |
---|
| 53 | Element representing an XMPP service discovery item. |
---|
| 54 | """ |
---|
| 55 | |
---|
[30] | 56 | def __init__(self, jid, node='', name=None): |
---|
[46] | 57 | domish.Element.__init__(self, (NS_DISCO_ITEMS, 'item'), |
---|
[1] | 58 | attribs={'jid': jid.full()}) |
---|
| 59 | if node: |
---|
| 60 | self['node'] = node |
---|
| 61 | |
---|
| 62 | if name: |
---|
| 63 | self['name'] = name |
---|
| 64 | |
---|
| 65 | |
---|
[46] | 66 | |
---|
[1] | 67 | class DiscoHandler(XMPPHandler, IQHandlerMixin): |
---|
| 68 | """ |
---|
| 69 | Protocol implementation for XMPP Service Discovery. |
---|
| 70 | |
---|
| 71 | This handler will listen to XMPP service discovery requests and |
---|
[5] | 72 | query the other handlers in L{parent} (see L{XMPPHandlerContainer}) for |
---|
[1] | 73 | their identities, features and items according to L{IDisco}. |
---|
| 74 | """ |
---|
| 75 | |
---|
| 76 | iqHandlers = {DISCO_INFO: '_onDiscoInfo', |
---|
| 77 | DISCO_ITEMS: '_onDiscoItems'} |
---|
| 78 | |
---|
| 79 | def connectionInitialized(self): |
---|
| 80 | self.xmlstream.addObserver(DISCO_INFO, self.handleRequest) |
---|
| 81 | self.xmlstream.addObserver(DISCO_ITEMS, self.handleRequest) |
---|
| 82 | |
---|
| 83 | |
---|
| 84 | def _onDiscoInfo(self, iq): |
---|
[46] | 85 | """ |
---|
| 86 | Called for incoming disco info requests. |
---|
| 87 | |
---|
| 88 | @param iq: The request iq element. |
---|
| 89 | @type iq: L{Element<twisted.words.xish.domish.Element>} |
---|
| 90 | """ |
---|
[6] | 91 | requestor = jid.internJID(iq["from"]) |
---|
[1] | 92 | target = jid.internJID(iq["to"]) |
---|
[30] | 93 | nodeIdentifier = iq.query.getAttribute("node", '') |
---|
[1] | 94 | |
---|
[46] | 95 | def toResponse(info): |
---|
[1] | 96 | if nodeIdentifier and not info: |
---|
| 97 | raise error.StanzaError('item-not-found') |
---|
| 98 | else: |
---|
[46] | 99 | response = domish.Element((NS_DISCO_INFO, 'query')) |
---|
| 100 | if nodeIdentifier: |
---|
| 101 | response['node'] = nodeIdentifier |
---|
[1] | 102 | |
---|
| 103 | for item in info: |
---|
| 104 | response.addChild(item) |
---|
| 105 | |
---|
| 106 | return response |
---|
| 107 | |
---|
[46] | 108 | d = self.info(requestor, target, nodeIdentifier) |
---|
| 109 | d.addCallback(toResponse) |
---|
[1] | 110 | return d |
---|
| 111 | |
---|
[46] | 112 | |
---|
[1] | 113 | def _onDiscoItems(self, iq): |
---|
[46] | 114 | """ |
---|
| 115 | Called for incoming disco items requests. |
---|
| 116 | |
---|
| 117 | @param iq: The request iq element. |
---|
| 118 | @type iq: L{Element<twisted.words.xish.domish.Element>} |
---|
| 119 | """ |
---|
[6] | 120 | requestor = jid.internJID(iq["from"]) |
---|
[1] | 121 | target = jid.internJID(iq["to"]) |
---|
[30] | 122 | nodeIdentifier = iq.query.getAttribute("node", '') |
---|
[1] | 123 | |
---|
[46] | 124 | def toResponse(items): |
---|
| 125 | response = domish.Element((NS_DISCO_ITEMS, 'query')) |
---|
| 126 | if nodeIdentifier: |
---|
| 127 | response['node'] = nodeIdentifier |
---|
[1] | 128 | |
---|
| 129 | for item in items: |
---|
| 130 | response.addChild(item) |
---|
| 131 | |
---|
| 132 | return response |
---|
| 133 | |
---|
[46] | 134 | d = self.items(requestor, target, nodeIdentifier) |
---|
| 135 | d.addCallback(toResponse) |
---|
| 136 | return d |
---|
[1] | 137 | |
---|
[46] | 138 | |
---|
| 139 | def _gatherResults(self, deferredList): |
---|
| 140 | """ |
---|
| 141 | Gather results from a list of deferreds. |
---|
| 142 | |
---|
| 143 | Similar to L{defer.gatherResults}, but flattens the returned results, |
---|
| 144 | consumes errors after the first one and fires the errback of the |
---|
| 145 | returned deferred with the failure of the first deferred that fires its |
---|
| 146 | errback. |
---|
| 147 | |
---|
| 148 | @param deferredList: List of deferreds for which the results should be |
---|
| 149 | gathered. |
---|
| 150 | @type deferredList: C{list} |
---|
| 151 | @return: Deferred that fires with a list of gathered results. |
---|
| 152 | @rtype: L{defer.Deferred} |
---|
| 153 | """ |
---|
| 154 | def cb(resultList): |
---|
| 155 | results = [] |
---|
| 156 | for success, value in resultList: |
---|
| 157 | results.extend(value) |
---|
| 158 | return results |
---|
| 159 | |
---|
| 160 | def eb(failure): |
---|
| 161 | failure.trap(defer.FirstError) |
---|
| 162 | return failure.value.subFailure |
---|
| 163 | |
---|
| 164 | d = defer.DeferredList(deferredList, fireOnOneErrback=1, |
---|
| 165 | consumeErrors=1) |
---|
| 166 | d.addCallbacks(cb, eb) |
---|
[1] | 167 | return d |
---|
[46] | 168 | |
---|
| 169 | |
---|
| 170 | def info(self, requestor, target, nodeIdentifier): |
---|
| 171 | """ |
---|
| 172 | Inspect all sibling protocol handlers for disco info. |
---|
| 173 | |
---|
| 174 | Calls the L{getDiscoInfo<IDisco.getDiscoInfo>} method on all child |
---|
| 175 | handlers of the parent, that provide L{IDisco}. |
---|
| 176 | |
---|
| 177 | @param requestor: The entity that sent the request. |
---|
| 178 | @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} |
---|
| 179 | @param target: The entity the request was sent to. |
---|
| 180 | @type target: L{JID<twisted.words.protocols.jabber.jid.JID>} |
---|
| 181 | @param nodeIdentifier: The optional node being queried, or C{''}. |
---|
| 182 | @type nodeIdentifier: C{unicode} |
---|
| 183 | @return: Deferred with the gathered results from sibling handlers. |
---|
| 184 | @rtype: L{defer.Deferred} |
---|
| 185 | """ |
---|
| 186 | dl = [handler.getDiscoInfo(requestor, target, nodeIdentifier) |
---|
| 187 | for handler in self.parent |
---|
| 188 | if IDisco.providedBy(handler)] |
---|
| 189 | return self._gatherResults(dl) |
---|
| 190 | |
---|
| 191 | |
---|
| 192 | def items(self, requestor, target, nodeIdentifier): |
---|
| 193 | """ |
---|
| 194 | Inspect all sibling protocol handlers for disco items. |
---|
| 195 | |
---|
| 196 | Calls the L{getDiscoItems<IDisco.getDiscoItems>} method on all child |
---|
| 197 | handlers of the parent, that provide L{IDisco}. |
---|
| 198 | |
---|
| 199 | @param requestor: The entity that sent the request. |
---|
| 200 | @type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>} |
---|
| 201 | @param target: The entity the request was sent to. |
---|
| 202 | @type target: L{JID<twisted.words.protocols.jabber.jid.JID>} |
---|
| 203 | @param nodeIdentifier: The optional node being queried, or C{''}. |
---|
| 204 | @type nodeIdentifier: C{unicode} |
---|
| 205 | @return: Deferred with the gathered results from sibling handlers. |
---|
| 206 | @rtype: L{defer.Deferred} |
---|
| 207 | """ |
---|
| 208 | dl = [handler.getDiscoItems(requestor, target, nodeIdentifier) |
---|
| 209 | for handler in self.parent |
---|
| 210 | if IDisco.providedBy(handler)] |
---|
| 211 | return self._gatherResults(dl) |
---|