]> code.delx.au - pymsnt/blob - src/disco.py
File transfer nearly working...
[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 SI = "http://jabber.org/protocol/si"
29 FT = "http://jabber.org/protocol/si/profile/file-transfer"
30 S5B = "http://jabber.org/protocol/bytestreams"
31 IBB = "http://jabber.org/protocol/ibb"
32 IQGATEWAY = "jabber:iq:gateway"
33 IQVERSION = "jabber:iq:version"
34 IQREGISTER = "jabber:iq:register"
35 IQROSTER = "jabber:iq:roster"
36 IQAVATAR = "jabber:iq:avatar"
37 IQOOB = "jabber:iq:oob"
38 XOOB = "jabber:x:oob"
39 XCONFERENCE = "jabber:x:conference"
40 XEVENT = "jabber:x:event"
41 XDELAY = "jabber:x:delay"
42 XAVATAR = "jabber:x:avatar"
43 STORAGEAVATAR = "storage:client:avatar"
44 XVCARDUPDATE = "vcard-temp:x:update"
45 VCARDTEMP = "vcard-temp"
46
47
48
49
50 class ServerDiscovery:
51 """ Handles everything IQ related. You can send IQ stanzas and receive a Deferred
52 to notify you when a response comes, or if there's a timeout.
53 Also manages discovery for server & client """
54
55 # TODO rename this file & class to something more sensible
56
57 def __init__ (self, pytrans):
58 LogEvent(INFO)
59 self.pytrans = pytrans
60 self.identities = {}
61 self.features = {}
62 self.nodes = {}
63 self.deferredIqs = {} # A dict indexed by (jid, id) of deferreds to fire
64
65 self.addFeature(DISCO, None, config.jid)
66 self.addFeature(DISCO, None, "USER")
67
68 def sendIq(self, el, timeout=15):
69 """ Used for sending IQ packets.
70 The id attribute for the IQ will be autogenerated if it is not there yet.
71 Returns a deferred which will fire with the matching IQ response as it's sole argument. """
72 def checkDeferred():
73 if(not d.called):
74 d.errback()
75 del self.deferredIqs[(jid, ID)]
76
77 jid = el.getAttribute("to")
78 ID = el.getAttribute("id")
79 if(not ID):
80 ID = self.pytrans.makeMessageID()
81 el.attributes["id"] = ID
82 self.pytrans.send(el)
83 d = Deferred()
84 self.deferredIqs[(jid, ID)] = d
85 reactor.callLater(timeout, checkDeferred)
86 return d
87
88 def addIdentity(self, category, ctype, name, jid):
89 """ Adds an identity to this JID's discovery profile. If jid == "USER" then MSN users will get this identity. """
90 LogEvent(INFO)
91 if not self.identities.has_key(jid):
92 self.identities[jid] = []
93 self.identities[jid].append((category, ctype, name))
94
95 def addFeature(self, var, handler, jid):
96 """ Adds a feature to this JID's discovery profile. If jid == "USER" then MSN users will get this feature. """
97 LogEvent(INFO)
98 if not self.features.has_key(jid):
99 self.features[jid] = []
100 self.features[jid].append((var, handler))
101
102 def addNode(self, node, handler, name, jid, rootnode):
103 """ Adds a node to this JID's discovery profile. If jid == "USER" then MSN users will get this node. """
104 LogEvent(INFO)
105 if not self.nodes.has_key(jid):
106 self.nodes[jid] = {}
107 self.nodes[jid][node] = (handler, name, rootnode)
108
109 def onIq(self, el):
110 """ Decides what to do with an IQ """
111 fro = el.getAttribute("from")
112 to = el.getAttribute("to")
113 ID = el.getAttribute("id")
114 iqType = el.getAttribute("type")
115 ulang = utils.getLang(el)
116 try: # Stringprep
117 froj = jid.JID(fro)
118 to = jid.JID(to).full()
119 except Exception, e:
120 LogEvent(WARN, "", "Dropping IQ because of stringprep error")
121
122 # Check if it's a response to a send IQ
123 if self.deferredIqs.has_key((fro, ID)) and (iqType == "error" or iqType == "result"):
124 LogEvent(INFO, "", "Doing callback")
125 self.deferredIqs[(fro, ID)].callback(el)
126 del self.deferredIqs[(fro, ID)]
127 return
128
129 if not (iqType == "get" or iqType == "set"): return # Not interested
130
131 LogEvent(INFO, "", "Looking for handler")
132
133 for query in el.elements():
134 xmlns = query.defaultUri
135 node = query.getAttribute("node")
136
137 if xmlns.startswith(DISCO) and node:
138 if self.nodes.has_key(to) and self.nodes[to].has_key(node) and self.nodes[to][node][0]:
139 self.nodes[to][node][0](el)
140 return
141 else:
142 # If the node we're browsing wasn't found, fall through and display the root disco
143 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
144 return
145 elif xmlns == DISCO_INFO:
146 self.sendDiscoInfoResponse(to=fro, ID=ID, ulang=ulang, jid=to)
147 return
148 elif xmlns == DISCO_ITEMS:
149 self.sendDiscoItemsResponse(to=fro, ID=ID, ulang=ulang, jid=to)
150 return
151
152 if to.find('@') > 0:
153 searchjid = "USER"
154 else:
155 searchjid = to
156 for (feature, handler) in self.features.get(searchjid, []):
157 if feature == xmlns and handler:
158 LogEvent(INFO, "Handler found")
159 handler(el)
160 return
161
162 # Still hasn't been handled
163 LogEvent(WARN, "", "Unknown Iq request")
164 self.sendIqError(to=fro, fro=to, ID=ID, xmlns=DISCO, etype="cancel", condition="feature-not-implemented")
165
166 def sendDiscoInfoResponse(self, to, ID, ulang, jid):
167 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
168 LogEvent(INFO)
169 iq = Element((None, "iq"))
170 iq.attributes["type"] = "result"
171 iq.attributes["from"] = jid
172 iq.attributes["to"] = to
173 if(ID):
174 iq.attributes["id"] = ID
175 query = iq.addElement("query")
176 query.attributes["xmlns"] = DISCO_INFO
177
178 searchjid = jid
179 if(jid.find('@') > 0): searchjid = "USER"
180 # Add any identities
181 for (category, ctype, name) in self.identities.get(searchjid, []):
182 identity = query.addElement("identity")
183 identity.attributes["category"] = category
184 identity.attributes["type"] = ctype
185 identity.attributes["name"] = name
186
187 # Add any supported features
188 for (var, handler) in self.features.get(searchjid, []):
189 feature = query.addElement("feature")
190 feature.attributes["var"] = var
191
192 self.pytrans.send(iq)
193
194 def sendDiscoItemsResponse(self, to, ID, ulang, jid):
195 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
196 LogEvent(INFO)
197 iq = Element((None, "iq"))
198 iq.attributes["type"] = "result"
199 iq.attributes["from"] = jid
200 iq.attributes["to"] = to
201 if(ID):
202 iq.attributes["id"] = ID
203 query = iq.addElement("query")
204 query.attributes["xmlns"] = DISCO_ITEMS
205
206 searchjid = jid
207 if(jid.find('@') > 0): searchjid = "USER"
208 for node in self.nodes.get(searchjid, []):
209 handler, name, rootnode = self.nodes[jid][node]
210 if rootnode:
211 name = getattr(lang.get(ulang), name)
212 item = query.addElement("item")
213 item.attributes["jid"] = jid
214 item.attributes["node"] = node
215 item.attributes["name"] = name
216
217 self.pytrans.send(iq)
218
219
220 def sendIqError(self, to, fro, ID, xmlns, etype, condition):
221 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
222 el = Element((None, "iq"))
223 el.attributes["to"] = to
224 el.attributes["from"] = fro
225 if(ID):
226 el.attributes["id"] = ID
227 el.attributes["type"] = "error"
228 error = el.addElement("error")
229 error.attributes["type"] = etype
230 error.attributes["code"] = str(utils.errorCodeMap[condition])
231 cond = error.addElement(condition)
232 self.pytrans.send(el)
233
234
235 class DiscoRequest:
236 def __init__(self, pytrans, jid):
237 LogEvent(INFO)
238 self.pytrans, self.jid = pytrans, jid
239
240 def doDisco(self):
241 ID = self.pytrans.makeMessageID()
242 iq = Element((None, "iq"))
243 iq.attributes["to"] = self.jid
244 iq.attributes["from"] = config.jid
245 iq.attributes["type"] = "get"
246 query = iq.addElement("query")
247 query.attributes["xmlns"] = DISCO_INFO
248
249 d = self.pytrans.discovery.sendIq(iq)
250 d.addCallback(self.discoResponse)
251 d.addErrback(self.discoFail)
252 return d
253
254 def discoResponse(self, el):
255 iqType = el.getAttribute("type")
256 if iqType != "result":
257 return []
258
259 fro = el.getAttribute("from")
260
261 features = []
262
263 query = el.getElement("query")
264 if not query:
265 return []
266
267 for child in query.elements():
268 if child.name == "feature":
269 features.append(child.getAttribute("var"))
270
271 return features
272
273 def discoFail(self):
274 return []
275
276
277