Package xmpp :: Module protocol
[hide private]
[frames] | no frames]

Source Code for Module xmpp.protocol

  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.58 2007/05/13 17:55:46 normanr 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' 
 62  NS_AVATAR           ='jabber:iq:avatar'                                     # XEP-0008 (historical) 
 63  NS_BIND             ='urn:ietf:params:xml:ns:xmpp-bind' 
 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' 
 69  NS_COMMANDS         ='http://jabber.org/protocol/commands' 
 70  NS_COMPONENT_ACCEPT ='jabber:component:accept' 
 71  NS_COMPONENT_1      ='http://jabberd.jabberstudio.org/ns/component/1.0' 
 72  NS_COMPRESS         ='http://jabber.org/protocol/compress'                  # XEP-0138 
 73  NS_DATA             ='jabber:x:data'                                        # XEP-0004 
 74  NS_DELAY            ='jabber:x:delay' 
 75  NS_DIALBACK         ='jabber:server:dialback' 
 76  NS_DISCO            ='http://jabber.org/protocol/disco'                     # XEP-0030 
 77  NS_DISCO_INFO                   =NS_DISCO+'#info'                           # XEP-0030 
 78  NS_DISCO_ITEMS                  =NS_DISCO+'#items'                          # XEP-0030 
 79  NS_ENCRYPTED        ='jabber:x:encrypted'                                   # XEP-0027 
 80  NS_EVENT            ='jabber:x:event'                                       # XEP-0022 
 81  NS_FEATURE          ='http://jabber.org/protocol/feature-neg'   
 82  NS_FILE             ='http://jabber.org/protocol/si/profile/file-transfer'  # XEP-0096 
 83  NS_GATEWAY          ='jabber:iq:gateway' 
 84  NS_GEOLOC           ='http://jabber.org/protocol/geoloc'                    # XEP-0080 
 85  NS_GROUPCHAT        ='gc-1.0' 
 86  NS_HTTP_BIND        ='http://jabber.org/protocol/httpbind'                  # XEP-0124 
 87  NS_IBB              ='http://jabber.org/protocol/ibb' 
 88  NS_INVISIBLE        ='presence-invisible'                                   # Jabberd2 
 89  NS_IQ               ='iq'                                                   # Jabberd2 
 90  NS_LAST             ='jabber:iq:last' 
 91  NS_MESSAGE          ='message'                                              # Jabberd2 
 92  NS_MOOD             ='http://jabber.org/protocol/mood'                      # XEP-0107 
 93  NS_MUC              ='http://jabber.org/protocol/muc'                       # XEP-0045 
 94  NS_MUC_ADMIN                    =NS_MUC+'#admin'                            # XEP-0045 
 95  NS_MUC_OWNER                    =NS_MUC+'#owner'                            # XEP-0045 
 96  NS_MUC_UNIQUE                   =NS_MUC+'#unique'                           # XEP-0045 
 97  NS_MUC_USER                     =NS_MUC+'#user'                             # XEP-0045 
 98  NS_MUC_REGISTER                 =NS_MUC+'#register'                         # XEP-0045 
 99  NS_MUC_REQUEST                  =NS_MUC+'#request'                          # XEP-0045 
100  NS_MUC_ROOMCONFIG               =NS_MUC+'#roomconfig'                       # XEP-0045 
101  NS_MUC_ROOMINFO                 =NS_MUC+'#roominfo'                         # XEP-0045 
102  NS_MUC_ROOMS                    =NS_MUC+'#rooms'                            # XEP-0045 
103  NS_MUC_TRAFIC                   =NS_MUC+'#traffic'                          # XEP-0045 
104  NS_OFFLINE          ='http://jabber.org/protocol/offline'                   # XEP-0013 
105  NS_PHYSLOC          ='http://jabber.org/protocol/physloc'                   # XEP-0112 
106  NS_PRESENCE         ='presence'                                             # Jabberd2 
107  NS_PRIVACY          ='jabber:iq:privacy' 
108  NS_PRIVATE          ='jabber:iq:private' 
109  NS_PUBSUB           ='http://jabber.org/protocol/pubsub'                    # XEP-0060 
110  NS_REGISTER         ='jabber:iq:register' 
111  NS_ROSTER           ='jabber:iq:roster' 
112  NS_ROSTERX          ='http://jabber.org/protocol/rosterx'                   # XEP-0144 
113  NS_RPC              ='jabber:iq:rpc'                                        # XEP-0009 
114  NS_SASL             ='urn:ietf:params:xml:ns:xmpp-sasl' 
115  NS_SEARCH           ='jabber:iq:search' 
116  NS_SERVER           ='jabber:server' 
117  NS_SESSION          ='urn:ietf:params:xml:ns:xmpp-session' 
118  NS_SI               ='http://jabber.org/protocol/si'                        # XEP-0096 
119  NS_SI_PUB           ='http://jabber.org/protocol/sipub'                     # XEP-0137 
120  NS_SIGNED           ='jabber:x:signed'                                      # XEP-0027 
121  NS_STANZAS          ='urn:ietf:params:xml:ns:xmpp-stanzas' 
122  NS_STREAMS          ='http://etherx.jabber.org/streams' 
123  NS_TIME             ='jabber:iq:time' 
124  NS_TLS              ='urn:ietf:params:xml:ns:xmpp-tls' 
125  NS_VACATION         ='http://jabber.org/protocol/vacation' 
126  NS_VCARD            ='vcard-temp' 
127  NS_VERSION          ='jabber:iq:version' 
128  NS_WAITINGLIST      ='http://jabber.org/protocol/waitinglist'               # XEP-0130 
129  NS_XHTML_IM         ='http://jabber.org/protocol/xhtml-im'                  # XEP-0071 
130  NS_DATA_LAYOUT      ='http://jabber.org/protocol/xdata-layout'              # XEP-0141 
131  NS_DATA_VALIDATE    ='http://jabber.org/protocol/xdata-validate'            # XEP-0122 
132  NS_XMPP_STREAMS     ='urn:ietf:params:xml:ns:xmpp-streams' 
133   
134  xmpp_stream_error_conditions=""" 
135  bad-format --  --  -- The entity has sent XML that cannot be processed. 
136  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. 
137  conflict --  --  -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. 
138  connection-timeout --  --  -- The entity has not generated any traffic over the stream for some period of time. 
139  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. 
140  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. 
141  improper-addressing --  --  -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value). 
142  internal-server-error --  --  -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream. 
143  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. 
144  invalid-id --  --  -- The stream ID or dialback ID is invalid or does not match an ID previously provided. 
145  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". 
146  invalid-xml --  --  -- The entity has sent invalid XML over the stream to a server that performs validation. 
147  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. 
148  policy-violation --  --  -- The entity has violated some local service policy. 
149  remote-connection-failed --  --  -- The server is unable to properly connect to a remote resource that is required for authentication or authorization. 
150  resource-constraint --  --  -- The server lacks the system resources necessary to service the stream. 
151  restricted-xml --  --  -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character. 
152  see-other-host --  --  -- The server will not provide service to the initiating entity but is redirecting traffic to another host. 
153  system-shutdown --  --  -- The server is being shut down and all active streams are being closed. 
154  undefined-condition --  --  -- The error condition is not one of those defined by the other conditions in this list. 
155  unsupported-encoding --  --  -- The initiating entity has encoded the stream in an encoding that is not supported by the server. 
156  unsupported-stanza-type --  --  -- The initiating entity has sent a first-level child of the stream that is not supported by the server. 
157  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. 
158  xml-not-well-formed --  --  -- The initiating entity has sent XML that is not well-formed.""" 
159  xmpp_stanza_error_conditions=""" 
160  bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. 
161  conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. 
162  feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. 
163  forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action. 
164  gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address. 
165  internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error. 
166  item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found. 
167  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. 
168  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. 
169  not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action. 
170  not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials. 
171  payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required. 
172  recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable. 
173  redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity. 
174  registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required. 
175  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. 
176  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. 
177  resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request. 
178  service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service. 
179  subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. 
180  undefined-condition -- 500 --  --  
181  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).""" 
182  sasl_error_conditions=""" 
183  aborted --  --  -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element. 
184  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. 
185  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. 
186  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. 
187  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. 
188  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. 
189  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.""" 
190   
191  ERRORS,_errorcodes={},{} 
192  for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions), 
193                             (NS_STANZAS     ,'ERR'   ,xmpp_stanza_error_conditions), 
194                             (NS_SASL        ,'SASL'  ,sasl_error_conditions)]: 
195      for err in errpool.split('\n')[1:]: 
196          cond,code,typ,text=err.split(' -- ') 
197          name=errname+'_'+cond.upper().replace('-','_') 
198          locals()[name]=ns+' '+cond 
199          ERRORS[ns+' '+cond]=[code,typ,text] 
200          if code: _errorcodes[code]=cond 
201  del ns,errname,errpool,err,cond,code,typ,text 
202   
203 -def isResultNode(node):
204 """ Returns true if the node is a positive reply. """ 205 return node and node.getType()=='result'
206 -def isErrorNode(node):
207 """ Returns true if the node is a negative reply. """ 208 return node and node.getType()=='error'
209
210 -class NodeProcessed(Exception):
211 """ Exception that should be raised by handler when the handling should be stopped. """
212 -class StreamError(Exception):
213 """ Base exception class for stream errors."""
214 -class BadFormat(StreamError): pass
215 -class BadNamespacePrefix(StreamError): pass
216 -class Conflict(StreamError): pass
217 -class ConnectionTimeout(StreamError): pass
218 -class HostGone(StreamError): pass
219 -class HostUnknown(StreamError): pass
220 -class ImproperAddressing(StreamError): pass
221 -class InternalServerError(StreamError): pass
222 -class InvalidFrom(StreamError): pass
223 -class InvalidID(StreamError): pass
224 -class InvalidNamespace(StreamError): pass
225 -class InvalidXML(StreamError): pass
226 -class NotAuthorized(StreamError): pass
227 -class PolicyViolation(StreamError): pass
228 -class RemoteConnectionFailed(StreamError): pass
229 -class ResourceConstraint(StreamError): pass
230 -class RestrictedXML(StreamError): pass
231 -class SeeOtherHost(StreamError): pass
232 -class SystemShutdown(StreamError): pass
233 -class UndefinedCondition(StreamError): pass
234 -class UnsupportedEncoding(StreamError): pass
235 -class UnsupportedStanzaType(StreamError): pass
236 -class UnsupportedVersion(StreamError): pass
237 -class XMLNotWellFormed(StreamError): pass
238 239 stream_exceptions = {'bad-format': BadFormat, 240 'bad-namespace-prefix': BadNamespacePrefix, 241 'conflict': Conflict, 242 'connection-timeout': ConnectionTimeout, 243 'host-gone': HostGone, 244 'host-unknown': HostUnknown, 245 'improper-addressing': ImproperAddressing, 246 'internal-server-error': InternalServerError, 247 'invalid-from': InvalidFrom, 248 'invalid-id': InvalidID, 249 'invalid-namespace': InvalidNamespace, 250 'invalid-xml': InvalidXML, 251 'not-authorized': NotAuthorized, 252 'policy-violation': PolicyViolation, 253 'remote-connection-failed': RemoteConnectionFailed, 254 'resource-constraint': ResourceConstraint, 255 'restricted-xml': RestrictedXML, 256 'see-other-host': SeeOtherHost, 257 'system-shutdown': SystemShutdown, 258 'undefined-condition': UndefinedCondition, 259 'unsupported-encoding': UnsupportedEncoding, 260 'unsupported-stanza-type': UnsupportedStanzaType, 261 'unsupported-version': UnsupportedVersion, 262 'xml-not-well-formed': XMLNotWellFormed} 263
264 -class JID:
265 """ JID object. JID can be built from string, modified, compared, serialised into string. """
266 - def __init__(self, jid=None, node='', domain='', resource=''):
267 """ Constructor. JID can be specified as string (jid argument) or as separate parts. 268 Examples: 269 JID('node@domain/resource') 270 JID(node='node',domain='domain.org') 271 """ 272 if not jid and not domain: raise ValueError('JID must contain at least domain name') 273 elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource 274 elif domain: self.node,self.domain,self.resource=node,domain,resource 275 else: 276 if jid.find('@')+1: self.node,jid=jid.split('@',1) 277 else: self.node='' 278 if jid.find('/')+1: self.domain,self.resource=jid.split('/',1) 279 else: self.domain,self.resource=jid,''
280 - def getNode(self):
281 """ Return the node part of the JID """ 282 return self.node
283 - def setNode(self,node):
284 """ Set the node part of the JID to new value. Specify None to remove the node part.""" 285 self.node=node.lower()
286 - def getDomain(self):
287 """ Return the domain part of the JID """ 288 return self.domain
289 - def setDomain(self,domain):
290 """ Set the domain part of the JID to new value.""" 291 self.domain=domain.lower()
292 - def getResource(self):
293 """ Return the resource part of the JID """ 294 return self.resource
295 - def setResource(self,resource):
296 """ Set the resource part of the JID to new value. Specify None to remove the resource part.""" 297 self.resource=resource
298 - def getStripped(self):
299 """ Return the bare representation of JID. I.e. string value w/o resource. """ 300 return self.__str__(0)
301 - def __eq__(self, other):
302 """ Compare the JID to another instance or to string for equality. """ 303 try: other=JID(other) 304 except ValueError: return 0 305 return self.resource==other.resource and self.__str__(0) == other.__str__(0)
306 - def __ne__(self, other):
307 """ Compare the JID to another instance or to string for non-equality. """ 308 return not self.__eq__(other)
309 - def bareMatch(self, other):
310 """ Compare the node and domain parts of the JID's for equality. """ 311 return self.__str__(0) == JID(other).__str__(0)
312 - def __str__(self,wresource=1):
313 """ Serialise JID into string. """ 314 if self.node: jid=self.node+'@'+self.domain 315 else: jid=self.domain 316 if wresource and self.resource: return jid+'/'+self.resource 317 return jid
318 - def __hash__(self):
319 """ Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """ 320 return hash(self.__str__())
321
322 -class Protocol(Node):
323 """ A "stanza" object class. Contains methods that are common for presences, iqs and messages. """
324 - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
325 """ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'. 326 to is the value of 'to' attribure, 'typ' - 'type' attribute 327 frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition 328 timestamp - the time value that needs to be stamped over stanza 329 xmlns - namespace of top stanza node 330 node - parsed or unparsed stana to be taken as prototype. 331 """ 332 if not attrs: attrs={} 333 if to: attrs['to']=to 334 if frm: attrs['from']=frm 335 if typ: attrs['type']=typ 336 Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) 337 if not node and xmlns: self.setNamespace(xmlns) 338 if self['to']: self.setTo(self['to']) 339 if self['from']: self.setFrom(self['from']) 340 if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id'] 341 self.timestamp=None 342 for x in self.getTags('x',namespace=NS_DELAY): 343 try: 344 if not self.getTimestamp() or x.getAttr('stamp')<self.getTimestamp(): self.setTimestamp(x.getAttr('stamp')) 345 except: pass 346 if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
347 - def getTo(self):
348 """ Return value of the 'to' attribute. """ 349 try: return self['to'] 350 except: return None
351 - def getFrom(self):
352 """ Return value of the 'from' attribute. """ 353 try: return self['from'] 354 except: return None
355 - def getTimestamp(self):
356 """ Return the timestamp in the 'yyyymmddThhmmss' format. """ 357 return self.timestamp
358 - def getID(self):
359 """ Return the value of the 'id' attribute. """ 360 return self.getAttr('id')
361 - def setTo(self,val):
362 """ Set the value of the 'to' attribute. """ 363 self.setAttr('to', JID(val))
364 - def getType(self):
365 """ Return the value of the 'type' attribute. """ 366 return self.getAttr('type')
367 - def setFrom(self,val):
368 """ Set the value of the 'from' attribute. """ 369 self.setAttr('from', JID(val))
370 - def setType(self,val):
371 """ Set the value of the 'type' attribute. """ 372 self.setAttr('type', val)
373 - def setID(self,val):
374 """ Set the value of the 'id' attribute. """ 375 self.setAttr('id', val)
376 - def getError(self):
377 """ Return the error-condition (if present) or the textual description of the error (otherwise). """ 378 errtag=self.getTag('error') 379 if errtag: 380 for tag in errtag.getChildren(): 381 if tag.getName()<>'text': return tag.getName() 382 return errtag.getData()
383 - def getErrorCode(self):
384 """ Return the error code. Obsolette. """ 385 return self.getTagAttr('error','code')
386 - def setError(self,error,code=None):
387 """ Set the error code. Obsolette. Use error-conditions instead. """ 388 if code: 389 if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error) 390 else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error) 391 elif type(error) in [type(''),type(u'')]: error=ErrorNode(error) 392 self.setType('error') 393 self.addChild(node=error)
394 - def setTimestamp(self,val=None):
395 """Set the timestamp. timestamp should be the yyyymmddThhmmss string.""" 396 if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) 397 self.timestamp=val 398 self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
399 - def getProperties(self):
400 """ Return the list of namespaces to which belongs the direct childs of element""" 401 props=[] 402 for child in self.getChildren(): 403 prop=child.getNamespace() 404 if prop not in props: props.append(prop) 405 return props
406 - def __setitem__(self,item,val):
407 """ Set the item 'item' to the value 'val'.""" 408 if item in ['to','from']: val=JID(val) 409 return self.setAttr(item,val)
410
411 -class Message(Protocol):
412 """ XMPP Message stanza - "push" mechanism."""
413 - def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
414 """ Create message object. You can specify recipient, text of message, type of message 415 any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. 416 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """ 417 Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 418 if body: self.setBody(body) 419 if subject: self.setSubject(subject)
420 - def getBody(self):
421 """ Returns text of the message. """ 422 return self.getTagData('body')
423 - def getSubject(self):
424 """ Returns subject of the message. """ 425 return self.getTagData('subject')
426 - def getThread(self):
427 """ Returns thread of the message. """ 428 return self.getTagData('thread')
429 - def setBody(self,val):
430 """ Sets the text of the message. """ 431 self.setTagData('body',val)
432 - def setSubject(self,val):
433 """ Sets the subject of the message. """ 434 self.setTagData('subject',val)
435 - def setThread(self,val):
436 """ Sets the thread of the message. """ 437 self.setTagData('thread',val)
438 - def buildReply(self,text=None):
439 """ Builds and returns another message object with specified text. 440 The to, from and thread properties of new message are pre-set as reply to this message. """ 441 m=Message(to=self.getFrom(),frm=self.getTo(),body=text) 442 th=self.getThread() 443 if th: m.setThread(th) 444 return m
445
446 -class Presence(Protocol):
447 """ XMPP Presence object."""
448 - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
449 """ Create presence object. You can specify recipient, type of message, priority, show and status values 450 any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go. 451 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """ 452 Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 453 if priority: self.setPriority(priority) 454 if show: self.setShow(show) 455 if status: self.setStatus(status)
456 - def getPriority(self):
457 """ Returns the priority of the message. """ 458 return self.getTagData('priority')
459 - def getShow(self):
460 """ Returns the show value of the message. """ 461 return self.getTagData('show')
462 - def getStatus(self):
463 """ Returns the status string of the message. """ 464 return self.getTagData('status')
465 - def setPriority(self,val):
466 """ Sets the priority of the message. """ 467 self.setTagData('priority',val)
468 - def setShow(self,val):
469 """ Sets the show value of the message. """ 470 self.setTagData('show',val)
471 - def setStatus(self,val):
472 """ Sets the status string of the message. """ 473 self.setTagData('status',val)
474
475 - def _muc_getItemAttr(self,tag,attr):
476 for xtag in self.getTags('x'): 477 for child in xtag.getTags(tag): 478 return child.getAttr(attr)
479 - def _muc_getSubTagDataAttr(self,tag,attr):
480 for xtag in self.getTags('x'): 481 for child in xtag.getTags('item'): 482 for cchild in child.getTags(tag): 483 return cchild.getData(),cchild.getAttr(attr) 484 return None,None
485 - def getRole(self):
486 """Returns the presence role (for groupchat)""" 487 return self._muc_getItemAttr('item','role')
488 - def getAffiliation(self):
489 """Returns the presence affiliation (for groupchat)""" 490 return self._muc_getItemAttr('item','affiliation')
491 - def getNick(self):
492 """Returns the nick value (for nick change in groupchat)""" 493 return self._muc_getItemAttr('item','nick')
494 - def getJid(self):
495 """Returns the presence jid (for groupchat)""" 496 return self._muc_getItemAttr('item','jid')
497 - def getReason(self):
498 """Returns the reason of the presence (for groupchat)""" 499 return self._muc_getSubTagDataAttr('reason','')[0]
500 - def getActor(self):
501 """Returns the reason of the presence (for groupchat)""" 502 return self._muc_getSubTagDataAttr('actor','jid')[1]
503 - def getStatusCode(self):
504 """Returns the status code of the presence (for groupchat)""" 505 return self._muc_getItemAttr('status','code')
506
507 -class Iq(Protocol):
508 """ XMPP Iq object - get/set dialog mechanism. """
509 - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
510 """ Create Iq object. You can specify type, query namespace 511 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. 512 Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """ 513 Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) 514 if payload: self.setQueryPayload(payload) 515 if queryNS: self.setQueryNS(queryNS)
516 - def getQueryNS(self):
517 """ Return the namespace of the 'query' child element.""" 518 tag=self.getTag('query') 519 if tag: return tag.getNamespace()
520 - def getQuerynode(self):
521 """ Return the 'node' attribute value of the 'query' child element.""" 522 return self.getTagAttr('query','node')
523 - def getQueryPayload(self):
524 """ Return the 'query' child element payload.""" 525 tag=self.getTag('query') 526 if tag: return tag.getPayload()
527 - def getQueryChildren(self):
528 """ Return the 'query' child element child nodes.""" 529 tag=self.getTag('query') 530 if tag: return tag.getChildren()
531 - def setQueryNS(self,namespace):
532 """ Set the namespace of the 'query' child element.""" 533 self.setTag('query').setNamespace(namespace)
534 - def setQueryPayload(self,payload):
535 """ Set the 'query' child element payload.""" 536 self.setTag('query').setPayload(payload)
537 - def setQuerynode(self,node):
538 """ Set the 'node' attribute value of the 'query' child element.""" 539 self.setTagAttr('query','node',node)
540 - def buildReply(self,typ):
541 """ Builds and returns another Iq object of specified type. 542 The to, from and query child node of new Iq are pre-set as reply to this Iq. """ 543 iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()}) 544 if self.getTag('query'): iq.setQueryNS(self.getQueryNS()) 545 return iq
546
547 -class ErrorNode(Node):
548 """ XMPP-style error element. 549 In the case of stanza error should be attached to XMPP stanza. 550 In the case of stream-level errors should be used separately. """
551 - def __init__(self,name,code=None,typ=None,text=None):
552 """ Create new error node object. 553 Mandatory parameter: name - name of error condition. 554 Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.""" 555 if ERRORS.has_key(name): 556 cod,type,txt=ERRORS[name] 557 ns=name.split()[0] 558 else: cod,ns,type,txt='500',NS_STANZAS,'cancel','' 559 if typ: type=typ 560 if code: cod=code 561 if text: txt=text 562 Node.__init__(self,'error',{},[Node(name)]) 563 if type: self.setAttr('type',type) 564 if not cod: self.setName('stream:error') 565 if txt: self.addChild(node=Node(ns+' text',{},[txt])) 566 if cod: self.setAttr('code',cod)
567
568 -class Error(Protocol):
569 """ Used to quickly transform received stanza into error reply."""
570 - def __init__(self,node,error,reply=1):
571 """ Create error reply basing on the received 'node' stanza and the 'error' error condition. 572 If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping) 573 specify the 'reply' argument as false.""" 574 if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node) 575 else: Protocol.__init__(self,node=node) 576 self.setError(error) 577 if node.getType()=='error': self.__str__=self.__dupstr__
578 - def __dupstr__(self,dup1=None,dup2=None):
579 """ Dummy function used as preventor of creating error node in reply to error node. 580 I.e. you will not be able to serialise "double" error into string. 581 """ 582 return ''
583
584 -class DataField(Node):
585 """ This class is used in the DataForm class to describe the single data item. 586 If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) 587 then you will need to work with instances of this class. """
588 - def __init__(self,name=None,value=None,typ=None,required=0,label=None,desc=None,options=[],node=None):
589 """ Create new data field of specified name,value and type. 590 Also 'required','desc' and 'options' fields can be set. 591 Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled. 592 """ 593 Node.__init__(self,'field',node=node) 594 if name: self.setVar(name) 595 if type(value) in [list,tuple]: self.setValues(value) 596 elif value: self.setValue(value) 597 if typ: self.setType(typ) 598 elif not typ and not node: self.setType('text-single') 599 if required: self.setRequired(required) 600 if label: self.setLabel(label) 601 if desc: self.setDesc(desc) 602 if options: self.setOptions(options)
603 - def setRequired(self,req=1):
604 """ Change the state of the 'required' flag. """ 605 if req: self.setTag('required') 606 else: 607 try: self.delChild('required') 608 except ValueError: return
609 - def isRequired(self):
610 """ Returns in this field a required one. """ 611 return self.getTag('required')
612 - def setLabel(self,label):
613 """ Set the label of this field. """ 614 self.setAttr('label',label)
615 - def getLabel(self):
616 """ Return the label of this field. """ 617 return self.getAttr('label')
618 - def setDesc(self,desc):
619 """ Set the description of this field. """ 620 self.setTagData('desc',desc)
621 - def getDesc(self):
622 """ Return the description of this field. """ 623 return self.getTagData('desc')
624 - def setValue(self,val):
625 """ Set the value of this field. """ 626 self.setTagData('value',val)
627 - def getValue(self):
628 return self.getTagData('value')
629 - def setValues(self,lst):
630 """ Set the values of this field as values-list. 631 Replaces all previous filed values! If you need to just add a value - use addValue method.""" 632 while self.getTag('value'): self.delChild('value') 633 for val in lst: self.addValue(val)
634 - def addValue(self,val):
635 """ Add one more value to this field. Used in 'get' iq's or such.""" 636 self.addChild('value',{},[val])
637 - def getValues(self):
638 """ Return the list of values associated with this field.""" 639 ret=[] 640 for tag in self.getTags('value'): ret.append(tag.getData()) 641 return ret
642 - def getOptions(self):
643 """ Return label-option pairs list associated with this field.""" 644 ret=[] 645 for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')]) 646 return ret
647 - def setOptions(self,lst):
648 """ Set label-option pairs list associated with this field.""" 649 while self.getTag('option'): self.delChild('option') 650 for opt in lst: self.addOption(opt)
651 - def addOption(self,opt):
652 """ Add one more label-option pair to this field.""" 653 if type(opt) in [str,unicode]: self.addChild('option').setTagData('value',opt) 654 else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
655 - def getType(self):
656 """ Get type of this field. """ 657 return self.getAttr('type')
658 - def setType(self,val):
659 """ Set type of this field. """ 660 return self.setAttr('type',val)
661 - def getVar(self):
662 """ Get 'var' attribute value of this field. """ 663 return self.getAttr('var')
664 - def setVar(self,val):
665 """ Set 'var' attribute value of this field. """ 666 return self.setAttr('var',val)
667
668 -class DataForm(Node):
669 """ DataForm class. Used for manipulating dataforms in XMPP. 670 Relevant XEPs: 0004, 0068, 0122. 671 Can be used in disco, pub-sub and many other applications."""
672 - def __init__(self, typ=None, data=[], title=None, node=None):
673 """ 674 Create new dataform of type 'typ'. 'data' is the list of DataField 675 instances that this dataform contains, 'title' - the title string. 676 You can specify the 'node' argument as the other node to be used as 677 base for constructing this dataform. 678 679 title and instructions is optional and SHOULD NOT contain newlines. 680 Several instructions MAY be present. 681 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) 682 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. 683 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. 684 'title' MAY be included in forms of type "form" and "result" 685 """ 686 Node.__init__(self,'x',node=node) 687 if node: 688 newkids=[] 689 for n in self.getChildren(): 690 if n.getName()=='field': newkids.append(DataField(node=n)) 691 else: newkids.append(n) 692 self.kids=newkids 693 if typ: self.setType(typ) 694 self.setNamespace(NS_DATA) 695 if title: self.setTitle(title) 696 if type(data)==type({}): 697 newdata=[] 698 for name in data.keys(): newdata.append(DataField(name,data[name])) 699 data=newdata 700 for child in data: 701 if type(child) in [type(''),type(u'')]: self.addInstructions(child) 702 elif child.__class__.__name__=='DataField': self.kids.append(child) 703 else: self.kids.append(DataField(node=child))
704 - def getType(self):
705 """ Return the type of dataform. """ 706 return self.getAttr('type')
707 - def setType(self,typ):
708 """ Set the type of dataform. """ 709 self.setAttr('type',typ)
710 - def getTitle(self):
711 """ Return the title of dataform. """ 712 return self.getTagData('title')
713 - def setTitle(self,text):
714 """ Set the title of dataform. """ 715 self.setTagData('title',text)
716 - def getInstructions(self):
717 """ Return the instructions of dataform. """ 718 return self.getTagData('instructions')
719 - def setInstructions(self,text):
720 """ Set the instructions of dataform. """ 721 self.setTagData('instructions',text)
722 - def addInstructions(self,text):
723 """ Add one more instruction to the dataform. """ 724 self.addChild('instructions',{},[text])
725 - def getField(self,name):
726 """ Return the datafield object with name 'name' (if exists). """ 727 return self.getTag('field',attrs={'var':name})
728 - def setField(self,name):
729 """ Create if nessessary or get the existing datafield object with name 'name' and return it. """ 730 f=self.getField(name) 731 if f: return f 732 return self.addChild(node=DataField(name))
733 - def asDict(self):
734 """ Represent dataform as simple dictionary mapping of datafield names to their values.""" 735 ret={} 736 for field in self.getTags('field'): 737 name=field.getAttr('var') 738 typ=field.getType() 739 if isinstance(typ,(str,unicode)) and typ[-6:]=='-multi': 740 val=[] 741 for i in field.getTags('value'): val.append(i.getData()) 742 else: val=field.getTagData('value') 743 ret[name]=val 744 if self.getTag('instructions'): ret['instructions']=self.getInstructions() 745 return ret
746 - def __getitem__(self,name):
747 """ Simple dictionary interface for getting datafields values by their names.""" 748 item=self.getField(name) 749 if item: return item.getValue() 750 raise IndexError('No such field')
751 - def __setitem__(self,name,val):
752 """ Simple dictionary interface for setting datafields values by their names.""" 753 return self.setField(name).setValue(val)
754