remove no longer needed logging
[uccdoor.git] / xmpp / protocol.py
1 ##   protocol.py 
2 ##
3 ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
4 ##
5 ##   This program is free software; you can redistribute it and/or modify
6 ##   it under the terms of the GNU General Public License as published by
7 ##   the Free Software Foundation; either version 2, or (at your option)
8 ##   any later version.
9 ##
10 ##   This program is distributed in the hope that it will be useful,
11 ##   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ##   GNU General Public License for more details.
14
15 # $Id: protocol.py,v 1.60 2009/04/07 11:14:28 snakeru Exp $
16
17 """
18 Protocol module contains tools that is needed for processing of 
19 xmpp-related data structures.
20 """
21
22 from simplexml import Node,ustr
23 import time
24 NS_ACTIVITY         ='http://jabber.org/protocol/activity'                  # XEP-0108
25 NS_ADDRESS          ='http://jabber.org/protocol/address'                   # XEP-0033
26 NS_ADMIN            ='http://jabber.org/protocol/admin'                     # XEP-0133
27 NS_ADMIN_ADD_USER               =NS_ADMIN+'#add-user'                       # XEP-0133
28 NS_ADMIN_DELETE_USER            =NS_ADMIN+'#delete-user'                    # XEP-0133
29 NS_ADMIN_DISABLE_USER           =NS_ADMIN+'#disable-user'                   # XEP-0133
30 NS_ADMIN_REENABLE_USER          =NS_ADMIN+'#reenable-user'                  # XEP-0133
31 NS_ADMIN_END_USER_SESSION       =NS_ADMIN+'#end-user-session'               # XEP-0133
32 NS_ADMIN_GET_USER_PASSWORD      =NS_ADMIN+'#get-user-password'              # XEP-0133
33 NS_ADMIN_CHANGE_USER_PASSWORD   =NS_ADMIN+'#change-user-password'           # XEP-0133
34 NS_ADMIN_GET_USER_ROSTER        =NS_ADMIN+'#get-user-roster'                # XEP-0133
35 NS_ADMIN_GET_USER_LASTLOGIN     =NS_ADMIN+'#get-user-lastlogin'             # XEP-0133
36 NS_ADMIN_USER_STATS             =NS_ADMIN+'#user-stats'                     # XEP-0133
37 NS_ADMIN_EDIT_BLACKLIST         =NS_ADMIN+'#edit-blacklist'                 # XEP-0133
38 NS_ADMIN_EDIT_WHITELIST         =NS_ADMIN+'#edit-whitelist'                 # XEP-0133
39 NS_ADMIN_REGISTERED_USERS_NUM   =NS_ADMIN+'#get-registered-users-num'       # XEP-0133
40 NS_ADMIN_DISABLED_USERS_NUM     =NS_ADMIN+'#get-disabled-users-num'         # XEP-0133
41 NS_ADMIN_ONLINE_USERS_NUM       =NS_ADMIN+'#get-online-users-num'           # XEP-0133
42 NS_ADMIN_ACTIVE_USERS_NUM       =NS_ADMIN+'#get-active-users-num'           # XEP-0133
43 NS_ADMIN_IDLE_USERS_NUM         =NS_ADMIN+'#get-idle-users-num'             # XEP-0133
44 NS_ADMIN_REGISTERED_USERS_LIST  =NS_ADMIN+'#get-registered-users-list'      # XEP-0133
45 NS_ADMIN_DISABLED_USERS_LIST    =NS_ADMIN+'#get-disabled-users-list'        # XEP-0133
46 NS_ADMIN_ONLINE_USERS_LIST      =NS_ADMIN+'#get-online-users-list'          # XEP-0133
47 NS_ADMIN_ACTIVE_USERS_LIST      =NS_ADMIN+'#get-active-users-list'          # XEP-0133
48 NS_ADMIN_IDLE_USERS_LIST        =NS_ADMIN+'#get-idle-users-list'            # XEP-0133
49 NS_ADMIN_ANNOUNCE               =NS_ADMIN+'#announce'                       # XEP-0133
50 NS_ADMIN_SET_MOTD               =NS_ADMIN+'#set-motd'                       # XEP-0133
51 NS_ADMIN_EDIT_MOTD              =NS_ADMIN+'#edit-motd'                      # XEP-0133
52 NS_ADMIN_DELETE_MOTD            =NS_ADMIN+'#delete-motd'                    # XEP-0133
53 NS_ADMIN_SET_WELCOME            =NS_ADMIN+'#set-welcome'                    # XEP-0133
54 NS_ADMIN_DELETE_WELCOME         =NS_ADMIN+'#delete-welcome'                 # XEP-0133
55 NS_ADMIN_EDIT_ADMIN             =NS_ADMIN+'#edit-admin'                     # XEP-0133
56 NS_ADMIN_RESTART                =NS_ADMIN+'#restart'                        # XEP-0133
57 NS_ADMIN_SHUTDOWN               =NS_ADMIN+'#shutdown'                       # XEP-0133
58 NS_AGENTS           ='jabber:iq:agents'                                     # XEP-0094 (historical)
59 NS_AMP              ='http://jabber.org/protocol/amp'                       # XEP-0079
60 NS_AMP_ERRORS                   =NS_AMP+'#errors'                           # XEP-0079
61 NS_AUTH             ='jabber:iq:auth'                                       # XEP-0078
62 NS_AVATAR           ='jabber:iq:avatar'                                     # XEP-0008 (historical)
63 NS_BIND             ='urn:ietf:params:xml:ns:xmpp-bind'                     # RFC 3920
64 NS_BROWSE           ='jabber:iq:browse'                                     # XEP-0011 (historical)
65 NS_BYTESTREAM       ='http://jabber.org/protocol/bytestreams'               # XEP-0065
66 NS_CAPS             ='http://jabber.org/protocol/caps'                      # XEP-0115
67 NS_CHATSTATES       ='http://jabber.org/protocol/chatstates'                # XEP-0085
68 NS_CLIENT           ='jabber:client'                                        # RFC 3921
69 NS_COMMANDS         ='http://jabber.org/protocol/commands'                  # XEP-0050
70 NS_COMPONENT_ACCEPT ='jabber:component:accept'                              # XEP-0114
71 NS_COMPONENT_1      ='http://jabberd.jabberstudio.org/ns/component/1.0'     # Jabberd2
72 NS_COMPRESS         ='http://jabber.org/protocol/compress'                  # XEP-0138
73 NS_DATA             ='jabber:x:data'                                        # XEP-0004
74 NS_DATA_LAYOUT      ='http://jabber.org/protocol/xdata-layout'              # XEP-0141
75 NS_DATA_VALIDATE    ='http://jabber.org/protocol/xdata-validate'            # XEP-0122
76 NS_DELAY            ='jabber:x:delay'                                       # XEP-0091 (deprecated)
77 NS_DIALBACK         ='jabber:server:dialback'                               # RFC 3921
78 NS_DISCO            ='http://jabber.org/protocol/disco'                     # XEP-0030
79 NS_DISCO_INFO                   =NS_DISCO+'#info'                           # XEP-0030
80 NS_DISCO_ITEMS                  =NS_DISCO+'#items'                          # XEP-0030
81 NS_ENCRYPTED        ='jabber:x:encrypted'                                   # XEP-0027
82 NS_EVENT            ='jabber:x:event'                                       # XEP-0022 (deprecated)
83 NS_FEATURE          ='http://jabber.org/protocol/feature-neg'               # XEP-0020
84 NS_FILE             ='http://jabber.org/protocol/si/profile/file-transfer'  # XEP-0096
85 NS_GATEWAY          ='jabber:iq:gateway'                                    # XEP-0100
86 NS_GEOLOC           ='http://jabber.org/protocol/geoloc'                    # XEP-0080
87 NS_GROUPCHAT        ='gc-1.0'                                               # XEP-0045
88 NS_HTTP_BIND        ='http://jabber.org/protocol/httpbind'                  # XEP-0124
89 NS_IBB              ='http://jabber.org/protocol/ibb'                       # XEP-0047
90 NS_INVISIBLE        ='presence-invisible'                                   # Jabberd2
91 NS_IQ               ='iq'                                                   # Jabberd2
92 NS_LAST             ='jabber:iq:last'                                       # XEP-0012
93 NS_MESSAGE          ='message'                                              # Jabberd2
94 NS_MOOD             ='http://jabber.org/protocol/mood'                      # XEP-0107
95 NS_MUC              ='http://jabber.org/protocol/muc'                       # XEP-0045
96 NS_MUC_ADMIN                    =NS_MUC+'#admin'                            # XEP-0045
97 NS_MUC_OWNER                    =NS_MUC+'#owner'                            # XEP-0045
98 NS_MUC_UNIQUE                   =NS_MUC+'#unique'                           # XEP-0045
99 NS_MUC_USER                     =NS_MUC+'#user'                             # XEP-0045
100 NS_MUC_REGISTER                 =NS_MUC+'#register'                         # XEP-0045
101 NS_MUC_REQUEST                  =NS_MUC+'#request'                          # XEP-0045
102 NS_MUC_ROOMCONFIG               =NS_MUC+'#roomconfig'                       # XEP-0045
103 NS_MUC_ROOMINFO                 =NS_MUC+'#roominfo'                         # XEP-0045
104 NS_MUC_ROOMS                    =NS_MUC+'#rooms'                            # XEP-0045
105 NS_MUC_TRAFIC                   =NS_MUC+'#traffic'                          # XEP-0045
106 NS_NICK             ='http://jabber.org/protocol/nick'                      # XEP-0172
107 NS_OFFLINE          ='http://jabber.org/protocol/offline'                   # XEP-0013
108 NS_PHYSLOC          ='http://jabber.org/protocol/physloc'                   # XEP-0112
109 NS_PRESENCE         ='presence'                                             # Jabberd2
110 NS_PRIVACY          ='jabber:iq:privacy'                                    # RFC 3921
111 NS_PRIVATE          ='jabber:iq:private'                                    # XEP-0049
112 NS_PUBSUB           ='http://jabber.org/protocol/pubsub'                    # XEP-0060
113 NS_REGISTER         ='jabber:iq:register'                                   # XEP-0077
114 NS_RC               ='http://jabber.org/protocol/rc'                        # XEP-0146
115 NS_ROSTER           ='jabber:iq:roster'                                     # RFC 3921
116 NS_ROSTERX          ='http://jabber.org/protocol/rosterx'                   # XEP-0144
117 NS_RPC              ='jabber:iq:rpc'                                        # XEP-0009
118 NS_SASL             ='urn:ietf:params:xml:ns:xmpp-sasl'                     # RFC 3920
119 NS_SEARCH           ='jabber:iq:search'                                     # XEP-0055
120 NS_SERVER           ='jabber:server'                                        # RFC 3921
121 NS_SESSION          ='urn:ietf:params:xml:ns:xmpp-session'                  # RFC 3921
122 NS_SI               ='http://jabber.org/protocol/si'                        # XEP-0096
123 NS_SI_PUB           ='http://jabber.org/protocol/sipub'                     # XEP-0137
124 NS_SIGNED           ='jabber:x:signed'                                      # XEP-0027
125 NS_STANZAS          ='urn:ietf:params:xml:ns:xmpp-stanzas'                  # RFC 3920
126 NS_STREAMS          ='http://etherx.jabber.org/streams'                     # RFC 3920
127 NS_TIME             ='jabber:iq:time'                                       # XEP-0090 (deprecated)
128 NS_TLS              ='urn:ietf:params:xml:ns:xmpp-tls'                      # RFC 3920
129 NS_VACATION         ='http://jabber.org/protocol/vacation'                  # XEP-0109
130 NS_VCARD            ='vcard-temp'                                           # XEP-0054
131 NS_VCARD_UPDATE     ='vcard-temp:x:update'                                  # XEP-0153
132 NS_VERSION          ='jabber:iq:version'                                    # XEP-0092
133 NS_WAITINGLIST      ='http://jabber.org/protocol/waitinglist'               # XEP-0130
134 NS_XHTML_IM         ='http://jabber.org/protocol/xhtml-im'                  # XEP-0071
135 NS_XMPP_STREAMS     ='urn:ietf:params:xml:ns:xmpp-streams'                  # RFC 3920
136
137 xmpp_stream_error_conditions="""
138 bad-format --  --  -- The entity has sent XML that cannot be processed.
139 bad-namespace-prefix --  --  -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
140 conflict --  --  -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
141 connection-timeout --  --  -- The entity has not generated any traffic over the stream for some period of time.
142 host-gone --  --  -- The value of the 'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server.
143 host-unknown --  --  -- The value of the 'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server.
144 improper-addressing --  --  -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value).
145 internal-server-error --  --  -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.
146 invalid-from -- cancel --  -- The JID or hostname provided in a 'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization.
147 invalid-id --  --  -- The stream ID or dialback ID is invalid or does not match an ID previously provided.
148 invalid-namespace --  --  -- The streams namespace name is something other than "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback".
149 invalid-xml --  --  -- The entity has sent invalid XML over the stream to a server that performs validation.
150 not-authorized --  --  -- The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation.
151 policy-violation --  --  -- The entity has violated some local service policy.
152 remote-connection-failed --  --  -- The server is unable to properly connect to a remote resource that is required for authentication or authorization.
153 resource-constraint --  --  -- The server lacks the system resources necessary to service the stream.
154 restricted-xml --  --  -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.
155 see-other-host --  --  -- The server will not provide service to the initiating entity but is redirecting traffic to another host.
156 system-shutdown --  --  -- The server is being shut down and all active streams are being closed.
157 undefined-condition --  --  -- The error condition is not one of those defined by the other conditions in this list.
158 unsupported-encoding --  --  -- The initiating entity has encoded the stream in an encoding that is not supported by the server.
159 unsupported-stanza-type --  --  -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
160 unsupported-version --  --  -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.
161 xml-not-well-formed --  --  -- The initiating entity has sent XML that is not well-formed."""
162 xmpp_stanza_error_conditions="""
163 bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
164 conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
165 feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
166 forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action.
167 gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address.
168 internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.
169 item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found.
170 jid-malformed -- 400 -- modify -- The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme.
171 not-acceptable -- 406 -- cancel -- The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server.
172 not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action.
173 not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.
174 payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required.
175 recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable.
176 redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity.
177 registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required.
178 remote-server-not-found -- 404 -- cancel -- A remote server or service specified as part or all of the JID of the intended recipient does not exist.
179 remote-server-timeout -- 504 -- wait -- A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time.
180 resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request.
181 service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.
182 subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
183 undefined-condition -- 500 --  -- 
184 unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."""
185 sasl_error_conditions="""
186 aborted --  --  -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
187 incorrect-encoding --  --  -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.
188 invalid-authzid --  --  -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.
189 invalid-mechanism --  --  -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element.
190 mechanism-too-weak --  --  -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data.
191 not-authorized --  --  -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.
192 temporary-auth-failure --  --  -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element."""
193
194 ERRORS,_errorcodes={},{}
195 for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
196                            (NS_STANZAS     ,'ERR'   ,xmpp_stanza_error_conditions),
197                            (NS_SASL        ,'SASL'  ,sasl_error_conditions)]:
198     for err in errpool.split('\n')[1:]:
199         cond,code,typ,text=err.split(' -- ')
200         name=errname+'_'+cond.upper().replace('-','_')
201         locals()[name]=ns+' '+cond
202         ERRORS[ns+' '+cond]=[code,typ,text]
203         if code: _errorcodes[code]=cond
204 del ns,errname,errpool,err,cond,code,typ,text
205
206 def isResultNode(node):
207     """ Returns true if the node is a positive reply. """
208     return node and node.getType()=='result'
209 def isErrorNode(node):
210     """ Returns true if the node is a negative reply. """
211     return node and node.getType()=='error'
212
213 class NodeProcessed(Exception):
214     """ Exception that should be raised by handler when the handling should be stopped. """
215 class StreamError(Exception):
216     """ Base exception class for stream errors."""
217 class BadFormat(StreamError): pass
218 class BadNamespacePrefix(StreamError): pass
219 class Conflict(StreamError): pass
220 class ConnectionTimeout(StreamError): pass
221 class HostGone(StreamError): pass
222 class HostUnknown(StreamError): pass
223 class ImproperAddressing(StreamError): pass
224 class InternalServerError(StreamError): pass
225 class InvalidFrom(StreamError): pass
226 class InvalidID(StreamError): pass
227 class InvalidNamespace(StreamError): pass
228 class InvalidXML(StreamError): pass
229 class NotAuthorized(StreamError): pass
230 class PolicyViolation(StreamError): pass
231 class RemoteConnectionFailed(StreamError): pass
232 class ResourceConstraint(StreamError): pass
233 class RestrictedXML(StreamError): pass
234 class SeeOtherHost(StreamError): pass
235 class SystemShutdown(StreamError): pass
236 class UndefinedCondition(StreamError): pass
237 class UnsupportedEncoding(StreamError): pass
238 class UnsupportedStanzaType(StreamError): pass
239 class UnsupportedVersion(StreamError): pass
240 class XMLNotWellFormed(StreamError): pass
241
242 stream_exceptions = {'bad-format': BadFormat,
243                      'bad-namespace-prefix': BadNamespacePrefix,
244                      'conflict': Conflict,
245                      'connection-timeout': ConnectionTimeout,
246                      'host-gone': HostGone,
247                      'host-unknown': HostUnknown,
248                      'improper-addressing': ImproperAddressing,
249                      'internal-server-error': InternalServerError,
250                      'invalid-from': InvalidFrom,
251                      'invalid-id': InvalidID,
252                      'invalid-namespace': InvalidNamespace,
253                      'invalid-xml': InvalidXML,
254                      'not-authorized': NotAuthorized,
255                      'policy-violation': PolicyViolation,
256                      'remote-connection-failed': RemoteConnectionFailed,
257                      'resource-constraint': ResourceConstraint,
258                      'restricted-xml': RestrictedXML,
259                      'see-other-host': SeeOtherHost,
260                      'system-shutdown': SystemShutdown,
261                      'undefined-condition': UndefinedCondition,
262                      'unsupported-encoding': UnsupportedEncoding,
263                      'unsupported-stanza-type': UnsupportedStanzaType,
264                      'unsupported-version': UnsupportedVersion,
265                      'xml-not-well-formed': XMLNotWellFormed}
266
267 class JID:
268     """ JID object. JID can be built from string, modified, compared, serialised into string. """
269     def __init__(self, jid=None, node='', domain='', resource=''):
270         """ Constructor. JID can be specified as string (jid argument) or as separate parts.
271             Examples:
272             JID('node@domain/resource')
273             JID(node='node',domain='domain.org')
274         """
275         if not jid and not domain: raise ValueError('JID must contain at least domain name')
276         elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
277         elif domain: self.node,self.domain,self.resource=node,domain,resource
278         else:
279             if jid.find('@')+1: self.node,jid=jid.split('@',1)
280             else: self.node=''
281             if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
282             else: self.domain,self.resource=jid,''
283     def getNode(self):
284         """ Return the node part of the JID """
285         return self.node
286     def setNode(self,node):
287         """ Set the node part of the JID to new value. Specify None to remove the node part."""
288         self.node=node.lower()
289     def getDomain(self):
290         """ Return the domain part of the JID """
291         return self.domain
292     def setDomain(self,domain):
293         """ Set the domain part of the JID to new value."""
294         self.domain=domain.lower()
295     def getResource(self):
296         """ Return the resource part of the JID """
297         return self.resource
298     def setResource(self,resource):
299         """ Set the resource part of the JID to new value. Specify None to remove the resource part."""
300         self.resource=resource
301     def getStripped(self):
302         """ Return the bare representation of JID. I.e. string value w/o resource. """
303         return self.__str__(0)
304     def __eq__(self, other):
305         """ Compare the JID to another instance or to string for equality. """
306         try: other=JID(other)
307         except ValueError: return 0
308         return self.resource==other.resource and self.__str__(0) == other.__str__(0)
309     def __ne__(self, other):
310         """ Compare the JID to another instance or to string for non-equality. """
311         return not self.__eq__(other)
312     def bareMatch(self, other):
313         """ Compare the node and domain parts of the JID's for equality. """
314         return self.__str__(0) == JID(other).__str__(0)
315     def __str__(self,wresource=1):
316         """ Serialise JID into string. """
317         if self.node: jid=self.node+'@'+self.domain
318         else: jid=self.domain
319         if wresource and self.resource: return jid+'/'+self.resource
320         return jid
321     def __hash__(self):
322         """ Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """
323         return hash(self.__str__())
324
325 class Protocol(Node):
326     """ A "stanza" object class. Contains methods that are common for presences, iqs and messages. """
327     def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
328         """ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
329             to is the value of 'to' attribure, 'typ' - 'type' attribute
330             frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
331             timestamp - the time value that needs to be stamped over stanza
332             xmlns - namespace of top stanza node
333             node - parsed or unparsed stana to be taken as prototype.
334         """
335         if not attrs: attrs={}
336         if to: attrs['to']=to
337         if frm: attrs['from']=frm
338         if typ: attrs['type']=typ
339         Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
340         if not node and xmlns: self.setNamespace(xmlns)
341         if self['to']: self.setTo(self['to'])
342         if self['from']: self.setFrom(self['from'])
343         if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id']
344         self.timestamp=None
345         for x in self.getTags('x',namespace=NS_DELAY):
346             try:
347                 if not self.getTimestamp() or x.getAttr('stamp')<self.getTimestamp(): self.setTimestamp(x.getAttr('stamp'))
348             except: pass
349         if timestamp is not None: self.setTimestamp(timestamp)  # To auto-timestamp stanza just pass timestamp=''
350     def getTo(self):
351         """ Return value of the 'to' attribute. """
352         try: return self['to']
353         except: return None
354     def getFrom(self):
355         """ Return value of the 'from' attribute. """
356         try: return self['from']
357         except: return None
358     def getTimestamp(self):
359         """ Return the timestamp in the 'yyyymmddThhmmss' format. """
360         return self.timestamp
361     def getID(self):
362         """ Return the value of the 'id' attribute. """
363         return self.getAttr('id')
364     def setTo(self,val):
365         """ Set the value of the 'to' attribute. """
366         self.setAttr('to', JID(val))
367     def getType(self):
368         """ Return the value of the 'type' attribute. """
369         return self.getAttr('type')
370     def setFrom(self,val):
371         """ Set the value of the 'from' attribute. """
372         self.setAttr('from', JID(val))
373     def setType(self,val):
374         """ Set the value of the 'type' attribute. """
375         self.setAttr('type', val)
376     def setID(self,val):
377         """ Set the value of the 'id' attribute. """
378         self.setAttr('id', val)
379     def getError(self):
380         """ Return the error-condition (if present) or the textual description of the error (otherwise). """
381         errtag=self.getTag('error')
382         if errtag:
383             for tag in errtag.getChildren():
384                 if tag.getName()<>'text': return tag.getName()
385             return errtag.getData()
386     def getErrorCode(self):
387         """ Return the error code. Obsolette. """
388         return self.getTagAttr('error','code')
389     def setError(self,error,code=None):
390         """ Set the error code. Obsolette. Use error-conditions instead. """
391         if code:
392             if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
393             else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
394         elif type(error) in [type(''),type(u'')]: error=ErrorNode(error)
395         self.setType('error')
396         self.addChild(node=error)
397     def setTimestamp(self,val=None):
398         """Set the timestamp. timestamp should be the yyyymmddThhmmss string."""
399         if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
400         self.timestamp=val
401         self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
402     def getProperties(self):
403         """ Return the list of namespaces to which belongs the direct childs of element"""
404         props=[]
405         for child in self.getChildren():
406             prop=child.getNamespace()
407             if prop not in props: props.append(prop)
408         return props
409     def __setitem__(self,item,val):
410         """ Set the item 'item' to the value 'val'."""
411         if item in ['to','from']: val=JID(val)
412         return self.setAttr(item,val)
413
414 class Message(Protocol):
415     """ XMPP Message stanza - "push" mechanism."""
416     def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
417         """ Create message object. You can specify recipient, text of message, type of message
418             any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
419             Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
420         Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
421         if body: self.setBody(body)
422         if subject: self.setSubject(subject)
423     def getBody(self):
424         """ Returns text of the message. """
425         return self.getTagData('body')
426     def getSubject(self):
427         """ Returns subject of the message. """
428         return self.getTagData('subject')
429     def getThread(self):
430         """ Returns thread of the message. """
431         return self.getTagData('thread')
432     def setBody(self,val):
433         """ Sets the text of the message. """
434         self.setTagData('body',val)
435     def setSubject(self,val):
436         """ Sets the subject of the message. """
437         self.setTagData('subject',val)
438     def setThread(self,val):
439         """ Sets the thread of the message. """
440         self.setTagData('thread',val)
441     def buildReply(self,text=None):
442         """ Builds and returns another message object with specified text.
443             The to, from and thread properties of new message are pre-set as reply to this message. """
444         m=Message(to=self.getFrom(),frm=self.getTo(),body=text)
445         th=self.getThread()
446         if th: m.setThread(th)
447         return m
448
449 class Presence(Protocol):
450     """ XMPP Presence object."""
451     def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
452         """ Create presence object. You can specify recipient, type of message, priority, show and status values
453             any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
454             Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """
455         Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
456         if priority: self.setPriority(priority)
457         if show: self.setShow(show)
458         if status: self.setStatus(status)
459     def getPriority(self):
460         """ Returns the priority of the message. """
461         return self.getTagData('priority')
462     def getShow(self):
463         """ Returns the show value of the message. """
464         return self.getTagData('show')
465     def getStatus(self):
466         """ Returns the status string of the message. """
467         return self.getTagData('status')
468     def setPriority(self,val):
469         """ Sets the priority of the message. """
470         self.setTagData('priority',val)
471     def setShow(self,val):
472         """ Sets the show value of the message. """
473         self.setTagData('show',val)
474     def setStatus(self,val):
475         """ Sets the status string of the message. """
476         self.setTagData('status',val)
477
478     def _muc_getItemAttr(self,tag,attr):
479         for xtag in self.getTags('x'):
480             for child in xtag.getTags(tag):
481                 return child.getAttr(attr)
482     def _muc_getSubTagDataAttr(self,tag,attr):
483         for xtag in self.getTags('x'):
484             for child in xtag.getTags('item'):
485                 for cchild in child.getTags(tag):
486                     return cchild.getData(),cchild.getAttr(attr)
487         return None,None
488     def getRole(self):
489         """Returns the presence role (for groupchat)"""
490         return self._muc_getItemAttr('item','role')
491     def getAffiliation(self):
492         """Returns the presence affiliation (for groupchat)"""
493         return self._muc_getItemAttr('item','affiliation')
494     def getNick(self):
495         """Returns the nick value (for nick change in groupchat)"""
496         return self._muc_getItemAttr('item','nick')
497     def getJid(self):
498         """Returns the presence jid (for groupchat)"""
499         return self._muc_getItemAttr('item','jid')
500     def getReason(self):
501         """Returns the reason of the presence (for groupchat)"""
502         return self._muc_getSubTagDataAttr('reason','')[0]
503     def getActor(self):
504         """Returns the reason of the presence (for groupchat)"""
505         return self._muc_getSubTagDataAttr('actor','jid')[1]
506     def getStatusCode(self):
507         """Returns the status code of the presence (for groupchat)"""
508         return self._muc_getItemAttr('status','code')
509
510 class Iq(Protocol): 
511     """ XMPP Iq object - get/set dialog mechanism. """
512     def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
513         """ Create Iq object. You can specify type, query namespace
514             any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
515             Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """
516         Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
517         if payload: self.setQueryPayload(payload)
518         if queryNS: self.setQueryNS(queryNS)
519     def getQueryNS(self):
520         """ Return the namespace of the 'query' child element."""
521         tag=self.getTag('query')
522         if tag: return tag.getNamespace()
523     def getQuerynode(self):
524         """ Return the 'node' attribute value of the 'query' child element."""
525         return self.getTagAttr('query','node')
526     def getQueryPayload(self):
527         """ Return the 'query' child element payload."""
528         tag=self.getTag('query')
529         if tag: return tag.getPayload()
530     def getQueryChildren(self):
531         """ Return the 'query' child element child nodes."""
532         tag=self.getTag('query')
533         if tag: return tag.getChildren()
534     def setQueryNS(self,namespace):
535         """ Set the namespace of the 'query' child element."""
536         self.setTag('query').setNamespace(namespace)
537     def setQueryPayload(self,payload):
538         """ Set the 'query' child element payload."""
539         self.setTag('query').setPayload(payload)
540     def setQuerynode(self,node):
541         """ Set the 'node' attribute value of the 'query' child element."""
542         self.setTagAttr('query','node',node)
543     def buildReply(self,typ):
544         """ Builds and returns another Iq object of specified type.
545             The to, from and query child node of new Iq are pre-set as reply to this Iq. """
546         iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
547         if self.getTag('query'): iq.setQueryNS(self.getQueryNS())
548         return iq
549
550 class ErrorNode(Node):
551     """ XMPP-style error element.
552         In the case of stanza error should be attached to XMPP stanza.
553         In the case of stream-level errors should be used separately. """
554     def __init__(self,name,code=None,typ=None,text=None):
555         """ Create new error node object.
556             Mandatory parameter: name - name of error condition.
557             Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol."""
558         if ERRORS.has_key(name):
559             cod,type,txt=ERRORS[name]
560             ns=name.split()[0]
561         else: cod,ns,type,txt='500',NS_STANZAS,'cancel',''
562         if typ: type=typ
563         if code: cod=code
564         if text: txt=text
565         Node.__init__(self,'error',{},[Node(name)])
566         if type: self.setAttr('type',type)
567         if not cod: self.setName('stream:error')
568         if txt: self.addChild(node=Node(ns+' text',{},[txt]))
569         if cod: self.setAttr('code',cod)
570
571 class Error(Protocol):
572     """ Used to quickly transform received stanza into error reply."""
573     def __init__(self,node,error,reply=1):
574         """ Create error reply basing on the received 'node' stanza and the 'error' error condition.
575             If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
576             specify the 'reply' argument as false."""
577         if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
578         else: Protocol.__init__(self,node=node)
579         self.setError(error)
580         if node.getType()=='error': self.__str__=self.__dupstr__
581     def __dupstr__(self,dup1=None,dup2=None):
582         """ Dummy function used as preventor of creating error node in reply to error node.
583             I.e. you will not be able to serialise "double" error into string.
584         """
585         return ''
586
587 class DataField(Node):
588     """ This class is used in the DataForm class to describe the single data item.
589         If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) 
590         then you will need to work with instances of this class. """
591     def __init__(self,name=None,value=None,typ=None,required=0,label=None,desc=None,options=[],node=None):
592         """ Create new data field of specified name,value and type.
593             Also 'required','desc' and 'options' fields can be set.
594             Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
595             """
596         Node.__init__(self,'field',node=node)
597         if name: self.setVar(name)
598         if type(value) in [list,tuple]: self.setValues(value)
599         elif value: self.setValue(value)
600         if typ: self.setType(typ)
601         elif not typ and not node: self.setType('text-single')
602         if required: self.setRequired(required)
603         if label: self.setLabel(label)
604         if desc: self.setDesc(desc)
605         if options: self.setOptions(options)
606     def setRequired(self,req=1):
607         """ Change the state of the 'required' flag. """
608         if req: self.setTag('required')
609         else:
610             try: self.delChild('required')
611             except ValueError: return
612     def isRequired(self):
613         """ Returns in this field a required one. """
614         return self.getTag('required')
615     def setLabel(self,label):
616         """ Set the label of this field. """
617         self.setAttr('label',label)
618     def getLabel(self):
619         """ Return the label of this field. """
620         return self.getAttr('label')
621     def setDesc(self,desc):
622         """ Set the description of this field. """
623         self.setTagData('desc',desc)
624     def getDesc(self):
625         """ Return the description of this field. """
626         return self.getTagData('desc')
627     def setValue(self,val):
628         """ Set the value of this field. """
629         self.setTagData('value',val)
630     def getValue(self):
631         return self.getTagData('value')
632     def setValues(self,lst):
633         """ Set the values of this field as values-list.
634             Replaces all previous filed values! If you need to just add a value - use addValue method."""
635         while self.getTag('value'): self.delChild('value')
636         for val in lst: self.addValue(val)
637     def addValue(self,val):
638         """ Add one more value to this field. Used in 'get' iq's or such."""
639         self.addChild('value',{},[val])
640     def getValues(self):
641         """ Return the list of values associated with this field."""
642         ret=[]
643         for tag in self.getTags('value'): ret.append(tag.getData())
644         return ret
645     def getOptions(self):
646         """ Return label-option pairs list associated with this field."""
647         ret=[]
648         for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
649         return ret
650     def setOptions(self,lst):
651         """ Set label-option pairs list associated with this field."""
652         while self.getTag('option'): self.delChild('option')
653         for opt in lst: self.addOption(opt)
654     def addOption(self,opt):
655         """ Add one more label-option pair to this field."""
656         if type(opt) in [str,unicode]: self.addChild('option').setTagData('value',opt)
657         else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
658     def getType(self):
659         """ Get type of this field. """
660         return self.getAttr('type')
661     def setType(self,val):
662         """ Set type of this field. """
663         return self.setAttr('type',val)
664     def getVar(self):
665         """ Get 'var' attribute value of this field. """
666         return self.getAttr('var')
667     def setVar(self,val):
668         """ Set 'var' attribute value of this field. """
669         return self.setAttr('var',val)
670
671 class DataReported(Node):
672     """ This class is used in the DataForm class to describe the 'reported data field' data items which are used in
673         'multiple item form results' (as described in XEP-0004).
674         Represents the fields that will be returned from a search. This information is useful when
675         you try to use the jabber:iq:search namespace to return dynamic form information.
676         """
677     def __init__(self,node=None):
678         """ Create new empty 'reported data' field. However, note that, according XEP-0004:
679             * It MUST contain one or more DataFields.
680             * Contained DataFields SHOULD possess a 'type' and 'label' attribute in addition to 'var' attribute
681             * Contained DataFields SHOULD NOT contain a <value/> element.
682             Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
683             dataitem.
684         """
685         Node.__init__(self,'reported',node=node)
686         if node:
687             newkids=[]
688             for n in self.getChildren():
689                 if    n.getName()=='field': newkids.append(DataField(node=n))
690                 else: newkids.append(n)
691             self.kids=newkids
692     def getField(self,name):
693         """ Return the datafield object with name 'name' (if exists). """
694         return self.getTag('field',attrs={'var':name})
695     def setField(self,name,typ=None,label=None):
696         """ Create if nessessary or get the existing datafield object with name 'name' and return it.
697             If created, attributes 'type' and 'label' are applied to new datafield."""
698         f=self.getField(name)
699         if f: return f
700         return self.addChild(node=DataField(name,None,typ,0,label))
701     def asDict(self):
702         """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
703         ret={}
704         for field in self.getTags('field'):
705             name=field.getAttr('var')
706             typ=field.getType()
707             if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi':
708                 val=[]
709                 for i in field.getTags('value'): val.append(i.getData())
710             else: val=field.getTagData('value')
711             ret[name]=val
712         if self.getTag('instructions'): ret['instructions']=self.getInstructions()
713         return ret
714     def __getitem__(self,name):
715         """ Simple dictionary interface for getting datafields values by their names."""
716         item=self.getField(name)
717         if item: return item.getValue()
718         raise IndexError('No such field')
719     def __setitem__(self,name,val):
720         """ Simple dictionary interface for setting datafields values by their names."""
721         return self.setField(name).setValue(val)
722
723 class DataItem(Node):
724     """ This class is used in the DataForm class to describe data items which are used in 'multiple
725         item form results' (as described in XEP-0004).
726         """
727     def __init__(self,node=None):
728         """ Create new empty data item. However, note that, according XEP-0004, DataItem MUST contain ALL
729             DataFields described in DataReported.
730             Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
731             dataitem.
732             """
733         Node.__init__(self,'item',node=node)
734         if node:
735             newkids=[]
736             for n in self.getChildren():
737                 if    n.getName()=='field': newkids.append(DataField(node=n))
738                 else: newkids.append(n)
739             self.kids=newkids
740     def getField(self,name):
741         """ Return the datafield object with name 'name' (if exists). """
742         return self.getTag('field',attrs={'var':name})
743     def setField(self,name):
744         """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
745         f=self.getField(name)
746         if f: return f
747         return self.addChild(node=DataField(name))
748     def asDict(self):
749         """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
750         ret={}
751         for field in self.getTags('field'):
752             name=field.getAttr('var')
753             typ=field.getType()
754             if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi':
755                 val=[]
756                 for i in field.getTags('value'): val.append(i.getData())
757             else: val=field.getTagData('value')
758             ret[name]=val
759         if self.getTag('instructions'): ret['instructions']=self.getInstructions()
760         return ret
761     def __getitem__(self,name):
762         """ Simple dictionary interface for getting datafields values by their names."""
763         item=self.getField(name)
764         if item: return item.getValue()
765         raise IndexError('No such field')
766     def __setitem__(self,name,val):
767         """ Simple dictionary interface for setting datafields values by their names."""
768         return self.setField(name).setValue(val)
769
770 class DataForm(Node):
771     """ DataForm class. Used for manipulating dataforms in XMPP.
772         Relevant XEPs: 0004, 0068, 0122.
773         Can be used in disco, pub-sub and many other applications."""
774     def __init__(self, typ=None, data=[], title=None, node=None):
775         """
776             Create new dataform of type 'typ'; 'data' is the list of DataReported,
777             DataItem and DataField instances that this dataform contains; 'title'
778             is the title string.
779             You can specify the 'node' argument as the other node to be used as
780             base for constructing this dataform.
781
782             title and instructions is optional and SHOULD NOT contain newlines.
783             Several instructions MAY be present.
784             'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
785             'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
786             'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
787             'title' MAY be included in forms of type "form" and "result"
788         """
789         Node.__init__(self,'x',node=node)
790         if node:
791             newkids=[]
792             for n in self.getChildren():
793                 if   n.getName()=='field': newkids.append(DataField(node=n))
794                 elif n.getName()=='item': newkids.append(DataItem(node=n))
795                 elif n.getName()=='reported': newkids.append(DataReported(node=n))
796                 else: newkids.append(n)
797             self.kids=newkids
798         if typ: self.setType(typ)
799         self.setNamespace(NS_DATA)
800         if title: self.setTitle(title)
801         if type(data)==type({}):
802             newdata=[]
803             for name in data.keys(): newdata.append(DataField(name,data[name]))
804             data=newdata
805         for child in data:
806             if type(child) in [type(''),type(u'')]: self.addInstructions(child)
807             elif child.__class__.__name__=='DataField': self.kids.append(child)
808             elif child.__class__.__name__=='DataItem': self.kids.append(child)
809             elif child.__class__.__name__=='DataReported': self.kids.append(child)
810             else: self.kids.append(DataField(node=child))
811     def getType(self):
812         """ Return the type of dataform. """
813         return self.getAttr('type')
814     def setType(self,typ):
815         """ Set the type of dataform. """
816         self.setAttr('type',typ)
817     def getTitle(self):
818         """ Return the title of dataform. """
819         return self.getTagData('title')
820     def setTitle(self,text):
821         """ Set the title of dataform. """
822         self.setTagData('title',text)
823     def getInstructions(self):
824         """ Return the instructions of dataform. """
825         return self.getTagData('instructions')
826     def setInstructions(self,text):
827         """ Set the instructions of dataform. """
828         self.setTagData('instructions',text)
829     def addInstructions(self,text):
830         """ Add one more instruction to the dataform. """
831         self.addChild('instructions',{},[text])
832     def getField(self,name):
833         """ Return the datafield object with name 'name' (if exists). """
834         return self.getTag('field',attrs={'var':name})
835     def setField(self,name):
836         """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
837         f=self.getField(name)
838         if f: return f
839         return self.addChild(node=DataField(name))
840     def asDict(self):
841         """ Represent dataform as simple dictionary mapping of datafield names to their values."""
842         ret={}
843         for field in self.getTags('field'):
844             name=field.getAttr('var')
845             typ=field.getType()
846             if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi':
847                 val=[]
848                 for i in field.getTags('value'): val.append(i.getData())
849             else: val=field.getTagData('value')
850             ret[name]=val
851         if self.getTag('instructions'): ret['instructions']=self.getInstructions()
852         return ret
853     def __getitem__(self,name):
854         """ Simple dictionary interface for getting datafields values by their names."""
855         item=self.getField(name)
856         if item: return item.getValue()
857         raise IndexError('No such field')
858     def __setitem__(self,name,val):
859         """ Simple dictionary interface for setting datafields values by their names."""
860         return self.setField(name).setValue(val)

UCC git Repository :: git.ucc.asn.au