]>
code.delx.au - pymsnt/blob - src/disco.py
1 # Copyright 2004-2006 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
4 from debug
import LogEvent
, INFO
, WARN
, ERROR
6 from twisted
. internet
. defer
import Deferred
7 from twisted
. internet
import reactor
8 from twisted
. words
. xish
. domish
import Element
9 from twisted
. words
. protocols
. jabber
. jid
import internJID
17 XMPP_STANZAS
= "urn:ietf:params:xml:ns:xmpp-stanzas"
18 DISCO
= "http://jabber.org/protocol/disco"
19 DISCO_ITEMS
= DISCO
+ "#items"
20 DISCO_INFO
= DISCO
+ "#info"
21 COMMANDS
= "http://jabber.org/protocol/commands"
22 CAPS
= "http://jabber.org/protocol/caps"
23 SUBSYNC
= "http://delx.cjb.net/protocol/roster-subsync"
24 MUC
= "http://jabber.org/protocol/muc"
25 MUC_USER
= MUC
+ "#user"
26 FEATURE_NEG
= "http://jabber.org/protocol/feature-neg"
27 SI
= "http://jabber.org/protocol/si"
28 FT
= "http://jabber.org/protocol/si/profile/file-transfer"
29 S5B
= "http://jabber.org/protocol/bytestreams"
30 IBB
= "http://jabber.org/protocol/ibb"
31 IQGATEWAY
= "jabber:iq:gateway"
32 IQVERSION
= "jabber:iq:version"
33 IQREGISTER
= "jabber:iq:register"
34 IQROSTER
= "jabber:iq:roster"
35 IQAVATAR
= "jabber:iq:avatar"
36 IQOOB
= "jabber:iq:oob"
38 XCONFERENCE
= "jabber:x:conference"
39 XEVENT
= "jabber:x:event"
40 XDELAY
= "jabber:x:delay"
41 XAVATAR
= "jabber:x:avatar"
42 XDATA
= "jabber:x:data"
43 STORAGEAVATAR
= "storage:client:avatar"
44 XVCARDUPDATE
= "vcard-temp:x:update"
45 VCARDTEMP
= "vcard-temp"
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 """
55 # TODO rename this file & class to something more sensible
57 def __init__ ( self
, pytrans
):
59 self
. pytrans
= pytrans
63 self
. deferredIqs
= {} # A dict indexed by (jid, id) of deferreds to fire
65 self
. addFeature ( DISCO
, None , config
. jid
)
66 self
. addFeature ( DISCO
, None , "USER" )
67 self
. addFeature ( DISCO
, None , "ROOM" )
69 def _makeSearchJID ( self
, jid
):
75 elif config
. compjid
and to
== config
. compjid
:
80 def sendIq ( self
, el
, timeout
= 15 ):
81 """ Used for sending IQ packets.
82 The id attribute for the IQ will be autogenerated if it is not there yet.
83 Returns a deferred which will fire with the matching IQ response as it's sole argument. """
86 d
. errback ( Exception ( "Timeout" ))
87 del self
. deferredIqs
[( jid
, ID
)]
89 jid
= el
. getAttribute ( "to" )
90 ID
= el
. getAttribute ( "id" )
92 ID
= self
. pytrans
. makeMessageID ()
93 el
. attributes
[ "id" ] = ID
96 self
. deferredIqs
[( jid
, ID
)] = d
97 reactor
. callLater ( timeout
, checkDeferred
)
100 def addIdentity ( self
, category
, ctype
, name
, jid
):
101 """ Adds an identity to this JID's discovery profile. If jid == "USER" then MSN users will get this identity, jid == "ROOM" is for groupchat rooms. """
103 if not self
. identities
. has_key ( jid
):
104 self
. identities
[ jid
] = []
105 self
. identities
[ jid
]. append (( category
, ctype
, name
))
107 def addFeature ( self
, var
, handler
, jid
):
108 """ Adds a feature to this JID's discovery profile. If jid == "USER" then MSN users will get this feature, jid == "ROOM" is for groupchat rooms. """
110 if not self
. features
. has_key ( jid
):
111 self
. features
[ jid
] = []
112 self
. features
[ jid
]. append (( var
, handler
))
114 def addNode ( self
, node
, handler
, name
, jid
, rootnode
):
115 """ Adds a node to this JID's discovery profile. If jid == "USER" then MSN users will get this node, jid == "ROOM" is for groupchat rooms. """
117 if not self
. nodes
. has_key ( jid
):
119 self
. nodes
[ jid
][ node
] = ( handler
, name
, rootnode
)
122 """ Decides what to do with an IQ """
123 fro
= el
. getAttribute ( "from" )
124 to
= el
. getAttribute ( "to" )
125 ID
= el
. getAttribute ( "id" )
126 iqType
= el
. getAttribute ( "type" )
127 ulang
= utils
. getLang ( el
)
129 froj
= internJID ( fro
)
130 to
= internJID ( to
). full ()
132 LogEvent ( WARN
, "" , "Dropping IQ because of stringprep error" )
134 # Check if it's a response to a send IQ
135 if self
. deferredIqs
. has_key (( fro
, ID
)) and ( iqType
== "error" or iqType
== "result" ):
136 LogEvent ( INFO
, "" , "Doing callback" )
137 self
. deferredIqs
[( fro
, ID
)]. callback ( el
)
138 del self
. deferredIqs
[( fro
, ID
)]
141 if not ( iqType
== "get" or iqType
== "set" ): return # Not interested
143 LogEvent ( INFO
, "" , "Looking for handler" )
145 for query
in el
. elements ():
147 node
= query
. getAttribute ( "node" )
149 if xmlns
. startswith ( DISCO
) and node
:
150 if self
. nodes
. has_key ( to
) and self
. nodes
[ to
]. has_key ( node
) and self
. nodes
[ to
][ node
][ 0 ]:
151 self
. nodes
[ to
][ node
][ 0 ]( el
)
154 # If the node we're browsing wasn't found, fall through and display the root disco
155 self
. sendDiscoInfoResponse ( to
= fro
, ID
= ID
, ulang
= ulang
, jid
= to
)
157 elif xmlns
== DISCO_INFO
:
158 self
. sendDiscoInfoResponse ( to
= fro
, ID
= ID
, ulang
= ulang
, jid
= to
)
160 elif xmlns
== DISCO_ITEMS
:
161 self
. sendDiscoItemsResponse ( to
= fro
, ID
= ID
, ulang
= ulang
, jid
= to
)
164 for ( feature
, handler
) in self
. features
. get ( self
._ makeSearchJID
( to
), []):
165 if feature
== xmlns
and handler
:
166 LogEvent ( INFO
, "Handler found" )
170 # Still hasn't been handled
171 LogEvent ( WARN
, "" , "Unknown Iq request" )
172 self
. sendIqError ( to
= fro
, fro
= to
, ID
= ID
, xmlns
= DISCO
, etype
= "cancel" , condition
= "feature-not-implemented" )
174 def sendDiscoInfoResponse ( self
, to
, ID
, ulang
, jid
):
175 """ Send a service discovery disco#info stanza to the given 'to'. 'jid' is the JID that was queried. """
177 iq
= Element (( None , "iq" ))
178 iq
. attributes
[ "type" ] = "result"
179 iq
. attributes
[ "from" ] = jid
180 iq
. attributes
[ "to" ] = to
182 iq
. attributes
[ "id" ] = ID
183 query
= iq
. addElement ( "query" )
184 query
. attributes
[ "xmlns" ] = DISCO_INFO
186 searchjid
= self
._ makeSearchJID
( jid
)
189 for ( category
, ctype
, name
) in self
. identities
. get ( searchjid
, []):
190 identity
= query
. addElement ( "identity" )
191 identity
. attributes
[ "category" ] = category
192 identity
. attributes
[ "type" ] = ctype
193 identity
. attributes
[ "name" ] = name
195 # Add any supported features
196 for ( var
, handler
) in self
. features
. get ( searchjid
, []):
197 feature
= query
. addElement ( "feature" )
198 feature
. attributes
[ "var" ] = var
200 self
. pytrans
. send ( iq
)
202 def sendDiscoItemsResponse ( self
, to
, ID
, ulang
, jid
):
203 """ Send a service discovery disco#items stanza to the given 'to'. 'jid' is the JID that was queried. """
205 iq
= Element (( None , "iq" ))
206 iq
. attributes
[ "type" ] = "result"
207 iq
. attributes
[ "from" ] = jid
208 iq
. attributes
[ "to" ] = to
210 iq
. attributes
[ "id" ] = ID
211 query
= iq
. addElement ( "query" )
212 query
. attributes
[ "xmlns" ] = DISCO_ITEMS
214 searchjid
= self
._ makeSearchJID
( jid
)
215 for node
in self
. nodes
. get ( searchjid
, []):
216 handler
, name
, rootnode
= self
. nodes
[ jid
][ node
]
218 name
= getattr ( lang
. get ( ulang
), name
)
219 item
= query
. addElement ( "item" )
220 item
. attributes
[ "jid" ] = jid
221 item
. attributes
[ "node" ] = node
222 item
. attributes
[ "name" ] = name
224 self
. pytrans
. send ( iq
)
227 def sendIqError ( self
, to
, fro
, ID
, xmlns
, etype
, condition
):
228 """ Sends an IQ error response. See the XMPP RFC for details on the fields. """
229 el
= Element (( None , "iq" ))
230 el
. attributes
[ "to" ] = to
231 el
. attributes
[ "from" ] = fro
233 el
. attributes
[ "id" ] = ID
234 el
. attributes
[ "type" ] = "error"
235 error
= el
. addElement ( "error" )
236 error
. attributes
[ "type" ] = etype
237 error
. attributes
[ "code" ] = str ( utils
. errorCodeMap
[ condition
])
238 cond
= error
. addElement ( condition
)
239 cond
. attributes
[ "xmlns" ] = XMPP_STANZAS
240 self
. pytrans
. send ( el
)
244 def __init__ ( self
, pytrans
, jid
):
246 self
. pytrans
, self
. jid
= pytrans
, jid
249 ID
= self
. pytrans
. makeMessageID ()
250 iq
= Element (( None , "iq" ))
251 iq
. attributes
[ "to" ] = self
. jid
252 iq
. attributes
[ "from" ] = config
. jid
253 iq
. attributes
[ "type" ] = "get"
254 query
= iq
. addElement ( "query" )
255 query
. attributes
[ "xmlns" ] = DISCO_INFO
257 d
= self
. pytrans
. discovery
. sendIq ( iq
)
258 d
. addCallback ( self
. discoResponse
)
259 d
. addErrback ( self
. discoFail
)
262 def discoResponse ( self
, el
):
263 iqType
= el
. getAttribute ( "type" )
264 if iqType
!= "result" :
267 fro
= el
. getAttribute ( "from" )
271 for child
in el
. elements ():
272 if child
. name
== "query" :
278 for child
in query
. elements ():
279 if child
. name
== "feature" :
280 features
. append ( child
. getAttribute ( "var" ))
284 def discoFail ( self
, err
):