]> code.delx.au - pymsnt/blob - src/disco.py
Reimport and tags (0.10.1)
[pymsnt] / src / disco.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 import utils
5 if(utils.checkTwisted()):
6 from twisted.xish.domish import Element
7 from twisted.words.protocols.jabber import jid
8 else:
9 from tlib.domish import Element
10 from tlib.jabber import jid
11 from twisted.internet.defer import Deferred
12 from twisted.internet import reactor
13 from debug import LogEvent, INFO, WARN, ERROR
14 import sys
15 import config
16 import legacy
17 import lang
18
19 XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"
20 DISCO = "http://jabber.org/protocol/disco"
21 DISCO_ITEMS = DISCO + "#items"
22 DISCO_INFO = DISCO + "#info"
23 COMMANDS = "http://jabber.org/protocol/commands"
24 CAPS = "http://jabber.org/protocol/caps"
25 SUBSYNC = "http://jabber.org/protocol/roster-subsync"
26 MUC = "http://jabber.org/protocol/muc"
27 MUC_USER = MUC + "#user"
28 IQGATEWAY = "jabber:iq:gateway"
29 IQVERSION = "jabber:iq:version"
30 IQREGISTER = "jabber:iq:register"
31 IQROSTER = "jabber:iq:roster"
32 IQAVATAR = "jabber:iq:avatar"
33 XCONFERENCE = "jabber:x:conference"
34 XEVENT = "jabber:x:event"
35 XDELAY = "jabber:x:delay"
36 XAVATAR = "jabber:x:avatar"
37 STORAGEAVATAR = "storage:client:avatar"
38 XVCARDUPDATE = "vcard-temp:x:update"
39 VCARDTEMP = "vcard-temp"
40
41
42
43
44 class ServerDiscovery:
45 """ Handles everything IQ related. You can send IQ stanzas and receive a Deferred
46 to notify you when a response comes, or if there's a timeout.
47 Also manages discovery for server & client """
48
49 # TODO rename this file & class to something more sensible
50
51 def __init__ (self, pytrans):
52 LogEvent(INFO)
53 self.pytrans = pytrans
54 self.identities = {}
55 self.features = {}
56 self.nodes = {}
57 self.deferredIqs = {} # A dict indexed by (jid, id) of deferreds to fire
58
59 self.addFeature(DISCO, None, config.jid)
60 self.addFeature(DISCO, None, "USER")
61
62 def sendIq(self, el, timeout=15):
63 """ Used for sending IQ packets.
64 The id attribute for the IQ will be autogenerated if it is not there yet.
65 Returns a deferred which will fire with the matching IQ response as it's sole argument. """
66 def checkDeferred():
67 if(not d.called):
68 d.errback()
69 del self.deferredIqs[(jid, ID)]
70
71 jid = el.getAttribute("to")
72 ID = el.getAttribute("id")
73 if(not ID):
74 ID = self.pytrans.makeMessageID()
75 el.attributes["id"] = ID
76 self.pytrans.send(el)
77 d = Deferred()
78 self.deferredIqs[(jid, ID)] = d
79 reactor.callLater(timeout, checkDeferred)
80 return d
81
82 def addIdentity(self, category, ctype, name, jid):
83 """ Adds an identity to this JID's discovery profile. If jid == "USER" then MSN users will get this identity. """
84 LogEvent(INFO)
85 if not self.identities.has_key(jid):
86 self.identities[jid] = []
87 self.identities[jid].append((category, ctype, name))
88
89 def addFeature(self, var, handler, jid):
90 """ Adds a feature to this JID's discovery profile. If jid == "USER" then MSN users will get this feature. """
91 LogEvent(INFO)
92 if not self.features.has_key(jid):
93 self.features[jid] = []
94 self.features[jid].append((var, handler))
95
96 def addNode(self, node, handler, name, jid, rootnode):
97 """ Adds a node to this JID's discovery profile. If jid == "USER" then MSN users will get this node. """
98 LogEvent(INFO)
99 if not self.nodes.has_key(jid):
100 self.nodes[jid] = {}
101 self.nodes[jid][node] = (handler, name, rootnode)
102
103 def onIq(self, el):
104 """ Decides what to do with an IQ """
105 fro = el.getAttribute("from")
106 to = el.getAttribute("to")
107 ID = el.getAttribute("id")
108 iqType = el.getAttribute("type")
109 ulang = utils.getLang(el)
110 try: # Stringprep
111 froj = jid.JID(fro)
112 to = jid.JID(to).full()
113 except Exception, e:
114 LogEvent(WARN, "", "Dropping IQ because of stringprep error")
115
116 # Check if it's a response to a send IQ
117 if self.deferredIqs.has_key((fro, ID)) and (iqType == "error" or iqType == "result"):
118 LogEvent(INFO, "", "Doing callback")
119 self.deferredIqs[(fro, ID)].callback(el)
120 del self.deferredIqs[(fro, ID)]
121 return
122
123 if not (iqType == "get" or iqType == "set"): return # Not interested
124
125 LogEvent(INFO, "", "Looking for handler")
126
127 for query in el.elements():
128 xmlns = query.defaultUri
129 node = query.getAttribute("node")
130
131 if xmlns.startswith(DISCO) and node:
132 if self.nodes.has_key(to) and self.nodes[to].has_key(node) and self.nodes[to][node][0]:
133 self.nodes[to][node][0](el)
134 return
135 else:
136 # If the node we're browsing wasn't found, fall through and display the root disco
137 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
138 return
139 elif xmlns == DISCO_INFO:
140 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
141 return
142 elif xmlns == DISCO_ITEMS:
143 self.sendDiscoItemsResponse(to=fro, ID=ID, ulang=ulang, jid=to)
144 return
145
146 if to.find('@') > 0:
147 searchjid = "USER"
148 else:
149 searchjid = to
150 for (feature, handler) in self.features.get(searchjid, []):
151 if feature == xmlns and handler:
152 LogEvent(INFO, "Handler found")
153 handler(el)
154 return
155
156 # Still hasn't been handled
157 LogEvent(WARN, "", "Unknown Iq request")
158 self.sendIqError(to=fro, fro=to, ID=ID, xmlns=DISCO, etype="cancel", condition="feature-not-implemented")
159
160 def sendDiscoInfoResponse(self, to, ID, ulang, jid):
161 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
162 LogEvent(INFO)
163 iq = Element((None, "iq"))
164 iq.attributes["type"] = "result"
165 iq.attributes["from"] = jid
166 iq.attributes["to"] = to
167 if(ID):
168 iq.attributes["id"] = ID
169 query = iq.addElement("query")
170 query.attributes["xmlns"] = DISCO_INFO
171
172 searchjid = jid
173 if(jid.find('@') > 0): searchjid = "USER"
174 # Add any identities
175 for (category, ctype, name) in self.identities.get(searchjid, []):
176 identity = query.addElement("identity")
177 identity.attributes["category"] = category
178 identity.attributes["type"] = ctype
179 identity.attributes["name"] = name
180
181 # Add any supported features
182 for (var, handler) in self.features.get(searchjid, []):
183 feature = query.addElement("feature")
184 feature.attributes["var"] = var
185
186 self.pytrans.send(iq)
187
188 def sendDiscoItemsResponse(self, to, ID, ulang, jid):
189 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
190 LogEvent(INFO)
191 iq = Element((None, "iq"))
192 iq.attributes["type"] = "result"
193 iq.attributes["from"] = jid
194 iq.attributes["to"] = to
195 if(ID):
196 iq.attributes["id"] = ID
197 query = iq.addElement("query")
198 query.attributes["xmlns"] = DISCO_ITEMS
199
200 searchjid = jid
201 if(jid.find('@') > 0): searchjid = "USER"
202 for node in self.nodes.get(searchjid, []):
203 handler, name, rootnode = self.nodes[jid][node]
204 if rootnode:
205 name = getattr(lang.get(ulang), name)
206 item = query.addElement("item")
207 item.attributes["jid"] = jid
208 item.attributes["node"] = node
209 item.attributes["name"] = name
210
211 self.pytrans.send(iq)
212
213
214 def sendIqError(self, to, fro, ID, xmlns, etype, condition):
215 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
216 el = Element((None, "iq"))
217 el.attributes["to"] = to
218 el.attributes["from"] = fro
219 if(ID):
220 el.attributes["id"] = ID
221 el.attributes["type"] = "error"
222 error = el.addElement("error")
223 error.attributes["type"] = etype
224 error.attributes["code"] = str(utils.errorCodeMap[condition])
225 cond = error.addElement(condition)
226 self.pytrans.send(el)
227
228