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