]>
code.delx.au - pymsnt/blob - 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
5 if(utils
.checkTwisted()):
6 from twisted
.xish
.domish
import Element
7 from twisted
.words
.protocols
.jabber
import jid
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
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"
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 """
49 # TODO rename this file & class to something more sensible
51 def __init__ (self
, pytrans
):
53 self
.pytrans
= pytrans
57 self
.deferredIqs
= {} # A dict indexed by (jid, id) of deferreds to fire
59 self
.addFeature(DISCO
, None, config
.jid
)
60 self
.addFeature(DISCO
, None, "USER")
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. """
69 del self
.deferredIqs
[(jid
, ID
)]
71 jid
= el
.getAttribute("to")
72 ID
= el
.getAttribute("id")
74 ID
= self
.pytrans
.makeMessageID()
75 el
.attributes
["id"] = ID
78 self
.deferredIqs
[(jid
, ID
)] = d
79 reactor
.callLater(timeout
, checkDeferred
)
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. """
85 if not self
.identities
.has_key(jid
):
86 self
.identities
[jid
] = []
87 self
.identities
[jid
].append((category
, ctype
, name
))
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. """
92 if not self
.features
.has_key(jid
):
93 self
.features
[jid
] = []
94 self
.features
[jid
].append((var
, handler
))
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. """
99 if not self
.nodes
.has_key(jid
):
101 self
.nodes
[jid
][node
] = (handler
, name
, rootnode
)
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
)
112 to
= jid
.JID(to
).full()
114 LogEvent(WARN
, "", "Dropping IQ because of stringprep error")
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
)]
123 if not (iqType
== "get" or iqType
== "set"): return # Not interested
125 LogEvent(INFO
, "", "Looking for handler")
127 for query
in el
.elements():
128 xmlns
= query
.defaultUri
129 node
= query
.getAttribute("node")
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
)
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
)
139 elif xmlns
== DISCO_INFO
:
140 self
.sendDiscoInfoResponse(to
=fro
, ID
=ID
, ulang
=ulang
, jid
=to
)
142 elif xmlns
== DISCO_ITEMS
:
143 self
.sendDiscoItemsResponse(to
=fro
, ID
=ID
, ulang
=ulang
, jid
=to
)
150 for (feature
, handler
) in self
.features
.get(searchjid
, []):
151 if feature
== xmlns
and handler
:
152 LogEvent(INFO
, "Handler found")
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")
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. """
163 iq
= Element((None, "iq"))
164 iq
.attributes
["type"] = "result"
165 iq
.attributes
["from"] = jid
166 iq
.attributes
["to"] = to
168 iq
.attributes
["id"] = ID
169 query
= iq
.addElement("query")
170 query
.attributes
["xmlns"] = DISCO_INFO
173 if(jid
.find('@') > 0): searchjid
= "USER"
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
181 # Add any supported features
182 for (var
, handler
) in self
.features
.get(searchjid
, []):
183 feature
= query
.addElement("feature")
184 feature
.attributes
["var"] = var
186 self
.pytrans
.send(iq
)
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. """
191 iq
= Element((None, "iq"))
192 iq
.attributes
["type"] = "result"
193 iq
.attributes
["from"] = jid
194 iq
.attributes
["to"] = to
196 iq
.attributes
["id"] = ID
197 query
= iq
.addElement("query")
198 query
.attributes
["xmlns"] = DISCO_ITEMS
201 if(jid
.find('@') > 0): searchjid
= "USER"
202 for node
in self
.nodes
.get(searchjid
, []):
203 handler
, name
, rootnode
= self
.nodes
[jid
][node
]
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
211 self
.pytrans
.send(iq
)
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
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
)