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

Source Code for Module nbxmpp.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.52 2006/01/09 22:08:57 normanr Exp $ 
  16   
  17  """ 
  18  Protocol module contains tools that are needed for processing of xmpp-related 
  19  data structures, including jabber-objects like JID or different stanzas and 
  20  sub- stanzas) handling routines 
  21  """ 
  22   
  23  from simplexml import Node, NodeBuilder 
  24  import time 
  25  import string 
  26  import hashlib 
  27   
28 -def ascii_upper(s):
29 trans_table = string.maketrans(string.ascii_lowercase, 30 string.ascii_uppercase) 31 return s.translate(trans_table)
32 33 NS_ACTIVITY = 'http://jabber.org/protocol/activity' # XEP-0108 34 NS_ADDRESS = 'http://jabber.org/protocol/address' # XEP-0033 35 NS_AGENTS = 'jabber:iq:agents' 36 NS_AMP = 'http://jabber.org/protocol/amp' 37 NS_AMP_ERRORS = NS_AMP + '#errors' 38 NS_ARCHIVE = 'urn:xmpp:archive' # XEP-0136 39 NS_ARCHIVE_AUTO = NS_ARCHIVE + ':auto' # XEP-0136 40 NS_ARCHIVE_MANAGE = NS_ARCHIVE + ':manage' # XEP-0136 41 NS_ARCHIVE_MANUAL = NS_ARCHIVE + ':manual' # XEP-0136 42 NS_ARCHIVE_PREF = NS_ARCHIVE + ':pref' 43 NS_ATOM = 'http://www.w3.org/2005/Atom' 44 NS_ATTENTION = 'urn:xmpp:attention:0' # XEP-0224 45 NS_AUTH = 'jabber:iq:auth' 46 NS_AVATAR = 'http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' 47 NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind' 48 NS_BLOCKING = 'urn:xmpp:blocking' # XEP-0191 49 NS_BOB = 'urn:xmpp:bob' # XEP-0231 50 NS_BOOKMARKS = 'storage:bookmarks' # XEP-0048 51 NS_BROWSE = 'jabber:iq:browse' 52 NS_BROWSING = 'http://jabber.org/protocol/browsing' # XEP-0195 53 NS_BYTESTREAM = 'http://jabber.org/protocol/bytestreams' # XEP-0065 54 NS_CAPS = 'http://jabber.org/protocol/caps' # XEP-0115 55 NS_CAPTCHA = 'urn:xmpp:captcha' # XEP-0158 56 NS_CARBONS = 'urn:xmpp:carbons:2' # XEP-0280 57 NS_CHATSTATES = 'http://jabber.org/protocol/chatstates' # XEP-0085 58 NS_CHATTING = 'http://jabber.org/protocol/chatting' # XEP-0194 59 NS_CLIENT = 'jabber:client' 60 NS_CONDITIONS = 'urn:xmpp:muc:conditions:0' # XEP-0306 61 NS_COMMANDS = 'http://jabber.org/protocol/commands' 62 NS_COMPONENT_ACCEPT = 'jabber:component:accept' 63 NS_COMPONENT_1 = 'http://jabberd.jabberstudio.org/ns/component/1.0' 64 NS_COMPRESS = 'http://jabber.org/protocol/compress' # XEP-0138 65 NS_CONFERENCE = 'jabber:x:conference' 66 NS_CORRECT = 'urn:xmpp:message-correct:0' # XEP-0308 67 NS_DATA = 'jabber:x:data' # XEP-0004 68 NS_DATA_MEDIA = 'urn:xmpp:media-element' # XEP-0221 69 NS_DELAY = 'jabber:x:delay' 70 NS_DELAY2 = 'urn:xmpp:delay' 71 NS_DIALBACK = 'jabber:server:dialback' 72 NS_DISCO = 'http://jabber.org/protocol/disco' 73 NS_DISCO_INFO = NS_DISCO + '#info' 74 NS_DISCO_ITEMS = NS_DISCO + '#items' 75 NS_ENCRYPTED = 'jabber:x:encrypted' # XEP-0027 76 NS_ESESSION = 'http://www.xmpp.org/extensions/xep-0116.html#ns' 77 NS_ESESSION_INIT = 'http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 78 NS_EVENT = 'jabber:x:event' # XEP-0022 79 NS_FEATURE = 'http://jabber.org/protocol/feature-neg' 80 NS_FILE = 'http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096 81 NS_FORWARD = 'urn:xmpp:forward:0' # XEP-0297 82 NS_GAMING = 'http://jabber.org/protocol/gaming' # XEP-0196 83 NS_GATEWAY = 'jabber:iq:gateway' # XEP-0100 84 NS_GEOLOC = 'http://jabber.org/protocol/geoloc' # XEP-0080 85 NS_GROUPCHAT = 'gc-1.0' 86 NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth' # XEP-0070 87 NS_HTTP_BIND = 'http://jabber.org/protocol/httpbind' # XEP-0124 88 NS_IBB = 'http://jabber.org/protocol/ibb' 89 NS_INVISIBLE = 'presence-invisible' # Jabberd2 90 NS_IQ = 'iq' # Jabberd2 91 NS_JINGLE ='urn:xmpp:jingle:1' # XEP-0166 92 NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1' # XEP-0166 93 NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1' # XEP-0167 94 NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio' # XEP-0167 95 NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video' # XEP-0167 96 NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:3' # XEP-0234 97 NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EXPERIMENTAL security layer of jingle 98 NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 99 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 100 NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 101 NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' # XEP-0261 102 NS_LAST = 'jabber:iq:last' 103 NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080 104 NS_MAM = 'urn:xmpp:mam:tmp' # XEP-0313 105 NS_MESSAGE = 'message' # Jabberd2 106 NS_MOOD = 'http://jabber.org/protocol/mood' # XEP-0107 107 NS_MUC = 'http://jabber.org/protocol/muc' 108 NS_MUC_USER = NS_MUC + '#user' 109 NS_MUC_ADMIN = NS_MUC + '#admin' 110 NS_MUC_OWNER = NS_MUC + '#owner' 111 NS_MUC_UNIQUE = NS_MUC + '#unique' 112 NS_MUC_CONFIG = NS_MUC + '#roomconfig' 113 NS_NICK = 'http://jabber.org/protocol/nick' # XEP-0172 114 NS_OFFLINE = 'http://www.jabber.org/jeps/jep-0030.html' # XEP-0013 115 NS_PHYSLOC = 'http://jabber.org/protocol/physloc' # XEP-0112 116 NS_PING = 'urn:xmpp:ping' # XEP-0199 117 NS_PRESENCE = 'presence' # Jabberd2 118 NS_PRIVACY = 'jabber:iq:privacy' 119 NS_PRIVATE = 'jabber:iq:private' 120 NS_PROFILE = 'http://jabber.org/protocol/profile' # XEP-0154 121 NS_PUBSUB = 'http://jabber.org/protocol/pubsub' # XEP-0060 122 NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' 123 NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060 124 NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' # XEP-0060 125 NS_REGISTER = 'jabber:iq:register' 126 NS_ROSTER = 'jabber:iq:roster' 127 NS_ROSTERNOTES = 'storage:rosternotes' 128 NS_ROSTERX = 'http://jabber.org/protocol/rosterx' # XEP-0144 129 NS_ROSTER_VER = 'urn:xmpp:features:rosterver' # XEP-0273 130 NS_RPC = 'jabber:iq:rpc' # XEP-0009 131 NS_RSM = 'http://jabber.org/protocol/rsm' 132 NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl' 133 NS_SECLABEL = 'urn:xmpp:sec-label:0' 134 NS_SECLABEL_CATALOG = 'urn:xmpp:sec-label:catalog:2' 135 NS_SEARCH = 'jabber:iq:search' 136 NS_SERVER = 'jabber:server' 137 NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session' 138 NS_SI = 'http://jabber.org/protocol/si' # XEP-0096 139 NS_SI_PUB = 'http://jabber.org/protocol/sipub' # XEP-0137 140 NS_SIGNED = 'jabber:x:signed' # XEP-0027 141 NS_SSN = 'urn:xmpp:ssn' # XEP-0155 142 NS_STANZA_CRYPTO = 'http://www.xmpp.org/extensions/xep-0200.html#ns' # XEP-0200 143 NS_STANZAS = 'urn:ietf:params:xml:ns:xmpp-stanzas' 144 NS_STREAM = 'http://affinix.com/jabber/stream' 145 NS_STREAMS = 'http://etherx.jabber.org/streams' 146 NS_TIME = 'jabber:iq:time' # XEP-0900 147 NS_TIME_REVISED = 'urn:xmpp:time' # XEP-0202 148 NS_TLS = 'urn:ietf:params:xml:ns:xmpp-tls' 149 NS_TUNE = 'http://jabber.org/protocol/tune' # XEP-0118 150 NS_VACATION = 'http://jabber.org/protocol/vacation' 151 NS_VCARD = 'vcard-temp' 152 NS_GMAILNOTIFY = 'google:mail:notify' 153 NS_GTALKSETTING = 'google:setting' 154 NS_VCARD_UPDATE = NS_VCARD + ':x:update' 155 NS_VERSION = 'jabber:iq:version' 156 NS_VIEWING = 'http://jabber.org/protocol/viewing' # XEP--197 157 NS_WAITINGLIST = 'http://jabber.org/protocol/waitinglist' # XEP-0130 158 NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' # XEP-0071 159 NS_XHTML = 'http://www.w3.org/1999/xhtml' # " 160 NS_X_OOB = 'jabber:x:oob' # XEP-0066 161 NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout' # XEP-0141 162 NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122 163 NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams' 164 NS_RECEIPTS = 'urn:xmpp:receipts' 165 NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2' # XEP-0189 166 NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2' 167 NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2' 168 NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198 169 NS_HASHES = 'urn:xmpp:hashes:1' # XEP-300 170 NS_HASHES_MD5 = 'urn:xmpp:hash-function-textual-names:md5' 171 NS_HASHES_SHA1 = 'urn:xmpp:hash-function-textual-names:sha-1' 172 NS_HASHES_SHA256 = 'urn:xmpp:hash-function-textual-names:sha-256' 173 NS_HASHES_SHA512 = 'urn:xmpp:hash-function-textual-names:sha-512' 174 175 #xmpp_stream_error_conditions = ''' 176 #bad-format -- -- -- The entity has sent XML that cannot be processed. 177 #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. 178 #conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. 179 #connection-timeout -- -- -- The entity has not generated any traffic over the stream for some period of time. 180 #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. 181 #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. 182 #improper-addressing -- -- -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value). 183 #internal-server-error -- -- -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream. 184 #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. 185 #invalid-id -- -- -- The stream ID or dialback ID is invalid or does not match an ID previously provided. 186 #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". 187 #invalid-xml -- -- -- The entity has sent invalid XML over the stream to a server that performs validation. 188 #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. 189 #policy-violation -- -- -- The entity has violated some local service policy. 190 #remote-connection-failed -- -- -- The server is unable to properly connect to a remote resource that is required for authentication or authorization. 191 #resource-constraint -- -- -- The server lacks the system resources necessary to service the stream. 192 #restricted-xml -- -- -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character. 193 #see-other-host -- -- -- The server will not provide service to the initiating entity but is redirecting traffic to another host. 194 #system-shutdown -- -- -- The server is being shut down and all active streams are being closed. 195 #undefined-condition -- -- -- The error condition is not one of those defined by the other conditions in this list. 196 #unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server. 197 #unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server. 198 #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. 199 #xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.''' 200 201 #xmpp_stanza_error_conditions = ''' 202 #bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. 203 #conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. 204 #feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. 205 #forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action. 206 #gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address. 207 #internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error. 208 #item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found. 209 #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. 210 #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. 211 #not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action. 212 #not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials. 213 #payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required. 214 #recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable. 215 #redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity. 216 #registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required. 217 #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. 218 #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. 219 #resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request. 220 #service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service. 221 #subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. 222 #undefined-condition -- 500 -- -- Undefined Condition 223 #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).''' 224 225 #sasl_error_conditions = ''' 226 #aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element. 227 #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. 228 #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. 229 #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. 230 #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. 231 #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. 232 #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.''' 233 234 #ERRORS, _errorcodes, loc = {}, {}, {} 235 #for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', 236 #xmpp_stream_error_conditions), (NS_STANZAS, 'ERR', xmpp_stanza_error_conditions), 237 #(NS_SASL, 'SASL', sasl_error_conditions)): 238 #for err in errpool.split('\n')[1:]: 239 #cond, code, typ, text = err.split(' -- ') 240 #name = errname + '_' + ascii_upper(cond).replace('-', '_') 241 #locals()[name] = ns + ' ' + cond 242 #loc[name] = ns + ' ' + cond 243 #ERRORS[ns + ' ' + cond] = [code, typ, text] 244 #if code: 245 #_errorcodes[code] = cond 246 #del ns, errname, errpool, err, cond, code, typ, text 247 #import pprint 248 #pprint.pprint(ERRORS) 249 #pprint.pprint(_errorcodes) 250 #for (k, v) in loc.items(): 251 #print('%s = \'%s\'' % (k, v)) 252 253 ERRORS = { 254 'urn:ietf:params:xml:ns:xmpp-sasl aborted': ['', 255 '', 256 'The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.'], 257 'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding': ['', 258 '', 259 '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.'], 260 'urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid': ['', 261 '', 262 '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.'], 263 'urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism': ['', 264 '', 265 '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.'], 266 'urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak': ['', 267 '', 268 '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.'], 269 'urn:ietf:params:xml:ns:xmpp-sasl not-authorized': ['', 270 '', 271 '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.'], 272 'urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure': ['', 273 '', 274 'The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.'], 275 'urn:ietf:params:xml:ns:xmpp-stanzas bad-request': ['400', 276 'modify', 277 'The sender has sent XML that is malformed or that cannot be processed.'], 278 'urn:ietf:params:xml:ns:xmpp-stanzas conflict': ['409', 279 'cancel', 280 'Access cannot be granted because an existing resource or session exists with the same name or address.'], 281 'urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented': ['501', 282 'cancel', 283 'The feature requested is not implemented by the recipient or server and therefore cannot be processed.'], 284 'urn:ietf:params:xml:ns:xmpp-stanzas forbidden': ['403', 285 'auth', 286 'The requesting entity does not possess the required permissions to perform the action.'], 287 'urn:ietf:params:xml:ns:xmpp-stanzas gone': ['302', 288 'modify', 289 'The recipient or server can no longer be contacted at this address.'], 290 'urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error': ['500', 291 'wait', 292 'The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.'], 293 'urn:ietf:params:xml:ns:xmpp-stanzas item-not-found': ['404', 294 'cancel', 295 'The addressed JID or item requested cannot be found.'], 296 'urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed': ['400', 297 'modify', 298 "The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme."], 299 'urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable': ['406', 300 'cancel', 301 '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.'], 302 'urn:ietf:params:xml:ns:xmpp-stanzas not-allowed': ['405', 303 'cancel', 304 'The recipient or server does not allow any entity to perform the action.'], 305 'urn:ietf:params:xml:ns:xmpp-stanzas not-authorized': ['401', 306 'auth', 307 'The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.'], 308 'urn:ietf:params:xml:ns:xmpp-stanzas payment-required': ['402', 309 'auth', 310 'The requesting entity is not authorized to access the requested service because payment is required.'], 311 'urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable': ['404', 312 'wait', 313 'The intended recipient is temporarily unavailable.'], 314 'urn:ietf:params:xml:ns:xmpp-stanzas redirect': ['302', 315 'modify', 316 'The recipient or server is redirecting requests for this information to another entity.'], 317 'urn:ietf:params:xml:ns:xmpp-stanzas registration-required': ['407', 318 'auth', 319 'The requesting entity is not authorized to access the requested service because registration is required.'], 320 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found': ['404', 321 'cancel', 322 'A remote server or service specified as part or all of the JID of the intended recipient does not exist.'], 323 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout': ['504', 324 'wait', 325 '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.'], 326 'urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint': ['500', 327 'wait', 328 'The server or recipient lacks the system resources necessary to service the request.'], 329 'urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable': ['503', 330 'cancel', 331 'The server or recipient does not currently provide the requested service.'], 332 'urn:ietf:params:xml:ns:xmpp-stanzas subscription-required': ['407', 333 'auth', 334 'The requesting entity is not authorized to access the requested service because a subscription is required.'], 335 'urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition': ['500', 336 '', 337 'Undefined Condition'], 338 'urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request': ['400', 339 'wait', 340 'The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).'], 341 'urn:ietf:params:xml:ns:xmpp-streams bad-format': ['', 342 '', 343 'The entity has sent XML that cannot be processed.'], 344 'urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix': ['', 345 '', 346 'The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.'], 347 'urn:ietf:params:xml:ns:xmpp-streams conflict': ['', 348 '', 349 'The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.'], 350 'urn:ietf:params:xml:ns:xmpp-streams connection-timeout': ['', 351 '', 352 'The entity has not generated any traffic over the stream for some period of time.'], 353 'urn:ietf:params:xml:ns:xmpp-streams host-gone': ['', 354 '', 355 "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."], 356 'urn:ietf:params:xml:ns:xmpp-streams host-unknown': ['', 357 '', 358 "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."], 359 'urn:ietf:params:xml:ns:xmpp-streams improper-addressing': ['', 360 '', 361 "A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value)."], 362 'urn:ietf:params:xml:ns:xmpp-streams internal-server-error': ['', 363 '', 364 'The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.'], 365 'urn:ietf:params:xml:ns:xmpp-streams invalid-from': ['cancel', 366 '', 367 "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."], 368 'urn:ietf:params:xml:ns:xmpp-streams invalid-id': ['', 369 '', 370 'The stream ID or dialback ID is invalid or does not match an ID previously provided.'], 371 'urn:ietf:params:xml:ns:xmpp-streams invalid-namespace': ['', 372 '', 373 '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".'], 374 'urn:ietf:params:xml:ns:xmpp-streams invalid-xml': ['', 375 '', 376 'The entity has sent invalid XML over the stream to a server that performs validation.'], 377 'urn:ietf:params:xml:ns:xmpp-streams not-authorized': ['', 378 '', 379 '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.'], 380 'urn:ietf:params:xml:ns:xmpp-streams policy-violation': ['', 381 '', 382 'The entity has violated some local service policy.'], 383 'urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed': ['', 384 '', 385 'The server is unable to properly connect to a remote resource that is required for authentication or authorization.'], 386 'urn:ietf:params:xml:ns:xmpp-streams resource-constraint': ['', 387 '', 388 'The server lacks the system resources necessary to service the stream.'], 389 'urn:ietf:params:xml:ns:xmpp-streams restricted-xml': ['', 390 '', 391 'The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.'], 392 'urn:ietf:params:xml:ns:xmpp-streams see-other-host': ['', 393 '', 394 'The server will not provide service to the initiating entity but is redirecting traffic to another host.'], 395 'urn:ietf:params:xml:ns:xmpp-streams system-shutdown': ['', 396 '', 397 'The server is being shut down and all active streams are being closed.'], 398 'urn:ietf:params:xml:ns:xmpp-streams undefined-condition': ['', 399 '', 400 'The error condition is not one of those defined by the other conditions in this list.'], 401 'urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding': ['', 402 '', 403 'The initiating entity has encoded the stream in an encoding that is not supported by the server.'], 404 'urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type': ['', 405 '', 406 'The initiating entity has sent a first-level child of the stream that is not supported by the server.'], 407 'urn:ietf:params:xml:ns:xmpp-streams unsupported-version': ['', 408 '', 409 "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."], 410 'urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed': ['', 411 '', 412 'The initiating entity has sent XML that is not well-formed.'] 413 } 414 415 _errorcodes = { 416 '302': 'redirect', 417 '400': 'unexpected-request', 418 '401': 'not-authorized', 419 '402': 'payment-required', 420 '403': 'forbidden', 421 '404': 'remote-server-not-found', 422 '405': 'not-allowed', 423 '406': 'not-acceptable', 424 '407': 'subscription-required', 425 '409': 'conflict', 426 '500': 'undefined-condition', 427 '501': 'feature-not-implemented', 428 '503': 'service-unavailable', 429 '504': 'remote-server-timeout', 430 'cancel': 'invalid-from' 431 } 432 433 STREAM_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-streams not-authorized' 434 STREAM_REMOTE_CONNECTION_FAILED = 'urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed' 435 SASL_MECHANISM_TOO_WEAK = 'urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak' 436 STREAM_XML_NOT_WELL_FORMED = 'urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed' 437 ERR_JID_MALFORMED = 'urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed' 438 STREAM_SEE_OTHER_HOST = 'urn:ietf:params:xml:ns:xmpp-streams see-other-host' 439 STREAM_BAD_NAMESPACE_PREFIX = 'urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix' 440 ERR_SERVICE_UNAVAILABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable' 441 STREAM_CONNECTION_TIMEOUT = 'urn:ietf:params:xml:ns:xmpp-streams connection-timeout' 442 STREAM_UNSUPPORTED_VERSION = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-version' 443 STREAM_IMPROPER_ADDRESSING = 'urn:ietf:params:xml:ns:xmpp-streams improper-addressing' 444 STREAM_UNDEFINED_CONDITION = 'urn:ietf:params:xml:ns:xmpp-streams undefined-condition' 445 SASL_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-sasl not-authorized' 446 ERR_GONE = 'urn:ietf:params:xml:ns:xmpp-stanzas gone' 447 SASL_TEMPORARY_AUTH_FAILURE = 'urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure' 448 ERR_REMOTE_SERVER_NOT_FOUND = 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found' 449 ERR_UNEXPECTED_REQUEST = 'urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request' 450 ERR_RECIPIENT_UNAVAILABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable' 451 ERR_CONFLICT = 'urn:ietf:params:xml:ns:xmpp-stanzas conflict' 452 STREAM_SYSTEM_SHUTDOWN = 'urn:ietf:params:xml:ns:xmpp-streams system-shutdown' 453 STREAM_BAD_FORMAT = 'urn:ietf:params:xml:ns:xmpp-streams bad-format' 454 ERR_SUBSCRIPTION_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas subscription-required' 455 STREAM_INTERNAL_SERVER_ERROR = 'urn:ietf:params:xml:ns:xmpp-streams internal-server-error' 456 ERR_NOT_AUTHORIZED = 'urn:ietf:params:xml:ns:xmpp-stanzas not-authorized' 457 SASL_ABORTED = 'urn:ietf:params:xml:ns:xmpp-sasl aborted' 458 ERR_REGISTRATION_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas registration-required' 459 ERR_INTERNAL_SERVER_ERROR = 'urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error' 460 SASL_INCORRECT_ENCODING = 'urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding' 461 STREAM_HOST_GONE = 'urn:ietf:params:xml:ns:xmpp-streams host-gone' 462 STREAM_POLICY_VIOLATION = 'urn:ietf:params:xml:ns:xmpp-streams policy-violation' 463 STREAM_INVALID_XML = 'urn:ietf:params:xml:ns:xmpp-streams invalid-xml' 464 STREAM_CONFLICT = 'urn:ietf:params:xml:ns:xmpp-streams conflict' 465 STREAM_RESOURCE_CONSTRAINT = 'urn:ietf:params:xml:ns:xmpp-streams resource-constraint' 466 STREAM_UNSUPPORTED_ENCODING = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding' 467 ERR_NOT_ALLOWED = 'urn:ietf:params:xml:ns:xmpp-stanzas not-allowed' 468 ERR_ITEM_NOT_FOUND = 'urn:ietf:params:xml:ns:xmpp-stanzas item-not-found' 469 ERR_NOT_ACCEPTABLE = 'urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable' 470 STREAM_INVALID_FROM = 'urn:ietf:params:xml:ns:xmpp-streams invalid-from' 471 ERR_FEATURE_NOT_IMPLEMENTED = 'urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented' 472 ERR_BAD_REQUEST = 'urn:ietf:params:xml:ns:xmpp-stanzas bad-request' 473 STREAM_INVALID_ID = 'urn:ietf:params:xml:ns:xmpp-streams invalid-id' 474 STREAM_HOST_UNKNOWN = 'urn:ietf:params:xml:ns:xmpp-streams host-unknown' 475 ERR_UNDEFINED_CONDITION = 'urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition' 476 SASL_INVALID_MECHANISM = 'urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism' 477 STREAM_RESTRICTED_XML = 'urn:ietf:params:xml:ns:xmpp-streams restricted-xml' 478 ERR_RESOURCE_CONSTRAINT = 'urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint' 479 ERR_REMOTE_SERVER_TIMEOUT = 'urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout' 480 SASL_INVALID_AUTHZID = 'urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid' 481 ERR_PAYMENT_REQUIRED = 'urn:ietf:params:xml:ns:xmpp-stanzas payment-required' 482 STREAM_INVALID_NAMESPACE = 'urn:ietf:params:xml:ns:xmpp-streams invalid-namespace' 483 ERR_REDIRECT = 'urn:ietf:params:xml:ns:xmpp-stanzas redirect' 484 STREAM_UNSUPPORTED_STANZA_TYPE = 'urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type' 485 ERR_FORBIDDEN = 'urn:ietf:params:xml:ns:xmpp-stanzas forbidden' 486
487 -def isResultNode(node):
488 """ 489 Return true if the node is a positive reply 490 """ 491 return node and node.getType() == 'result'
492
493 -def isErrorNode(node):
494 """ 495 Return true if the node is a negative reply 496 """ 497 return node and node.getType() == 'error'
498
499 -class NodeProcessed(Exception):
500 """ 501 Exception that should be raised by handler when the handling should be 502 stopped 503 """ 504 pass
505
506 -class StreamError(Exception):
507 """ 508 Base exception class for stream errors 509 """ 510 pass
511
512 -class BadFormat(StreamError):
513 pass
514
515 -class BadNamespacePrefix(StreamError):
516 pass
517
518 -class Conflict(StreamError):
519 pass
520
521 -class ConnectionTimeout(StreamError):
522 pass
523
524 -class HostGone(StreamError):
525 pass
526
527 -class HostUnknown(StreamError):
528 pass
529
530 -class ImproperAddressing(StreamError):
531 pass
532
533 -class InternalServerError(StreamError):
534 pass
535
536 -class InvalidFrom(StreamError):
537 pass
538
539 -class InvalidID(StreamError):
540 pass
541
542 -class InvalidNamespace(StreamError):
543 pass
544
545 -class InvalidXML(StreamError):
546 pass
547
548 -class NotAuthorized(StreamError):
549 pass
550
551 -class PolicyViolation(StreamError):
552 pass
553
554 -class RemoteConnectionFailed(StreamError):
555 pass
556
557 -class ResourceConstraint(StreamError):
558 pass
559
560 -class RestrictedXML(StreamError):
561 pass
562
563 -class SeeOtherHost(StreamError):
564 pass
565
566 -class SystemShutdown(StreamError):
567 pass
568
569 -class UndefinedCondition(StreamError):
570 pass
571
572 -class UnsupportedEncoding(StreamError):
573 pass
574
575 -class UnsupportedStanzaType(StreamError):
576 pass
577
578 -class UnsupportedVersion(StreamError):
579 pass
580
581 -class XMLNotWellFormed(StreamError):
582 pass
583 584 stream_exceptions = {'bad-format': BadFormat, 585 'bad-namespace-prefix': BadNamespacePrefix, 586 'conflict': Conflict, 587 'connection-timeout': ConnectionTimeout, 588 'host-gone': HostGone, 589 'host-unknown': HostUnknown, 590 'improper-addressing': ImproperAddressing, 591 'internal-server-error': InternalServerError, 592 'invalid-from': InvalidFrom, 593 'invalid-id': InvalidID, 594 'invalid-namespace': InvalidNamespace, 595 'invalid-xml': InvalidXML, 596 'not-authorized': NotAuthorized, 597 'policy-violation': PolicyViolation, 598 'remote-connection-failed': RemoteConnectionFailed, 599 'resource-constraint': ResourceConstraint, 600 'restricted-xml': RestrictedXML, 601 'see-other-host': SeeOtherHost, 602 'system-shutdown': SystemShutdown, 603 'undefined-condition': UndefinedCondition, 604 'unsupported-encoding': UnsupportedEncoding, 605 'unsupported-stanza-type': UnsupportedStanzaType, 606 'unsupported-version': UnsupportedVersion, 607 'xml-not-well-formed': XMLNotWellFormed} 608
609 -class JID:
610 """ 611 JID can be built from string, modified, compared, serialised into string 612 """ 613
614 - def __init__(self, jid=None, node='', domain='', resource=''):
615 """ 616 JID can be specified as string (jid argument) or as separate parts 617 618 Examples: 619 JID('node@domain/resource') 620 JID(node='node',domain='domain.org') 621 """ 622 if not jid and not domain: 623 raise ValueError('JID must contain at least domain name') 624 elif type(jid) == type(self): 625 self.node, self.domain = jid.node, jid.domain 626 self.resource = jid.resource 627 elif domain: 628 self.node, self.domain, self.resource = node, domain, resource 629 else: 630 if jid.find('@') + 1: 631 self.node, jid = jid.split('@', 1) 632 else: 633 self.node = '' 634 if jid.find('/')+1: 635 self.domain, self.resource = jid.split('/', 1) 636 else: 637 self.domain, self.resource = jid, ''
638
639 - def getNode(self):
640 """ 641 Return the node part of the JID 642 """ 643 return self.node
644
645 - def setNode(self, node):
646 """ 647 Set the node part of the JID to new value. Specify None to remove 648 the node part 649 """ 650 self.node = node.lower()
651
652 - def getDomain(self):
653 """ 654 Return the domain part of the JID 655 """ 656 return self.domain
657
658 - def setDomain(self, domain):
659 """ 660 Set the domain part of the JID to new value 661 """ 662 self.domain = domain.lower()
663
664 - def getResource(self):
665 """ 666 Return the resource part of the JID 667 """ 668 return self.resource
669
670 - def setResource(self, resource):
671 """ 672 Set the resource part of the JID to new value. Specify None to remove the 673 resource part 674 """ 675 self.resource = resource
676
677 - def getStripped(self):
678 """ 679 Return the bare representation of JID. I.e. string value w/o resource 680 """ 681 return self.__str__(0)
682
683 - def __eq__(self, other):
684 """ 685 Compare the JID to another instance or to string for equality 686 """ 687 try: 688 other = JID(other) 689 except ValueError: 690 return 0 691 return self.resource == other.resource and \ 692 self.__str__(0) == other.__str__(0)
693
694 - def __ne__(self, other):
695 """ 696 Compare the JID to another instance or to string for non-equality 697 """ 698 return not self.__eq__(other)
699
700 - def bareMatch(self, other):
701 """ 702 Compare the node and domain parts of the JID's for equality 703 """ 704 return self.__str__(0) == JID(other).__str__(0)
705
706 - def __str__(self, wresource=1):
707 """ 708 Serialise JID into string 709 """ 710 if self.node: 711 jid = self.node + '@' + self.domain 712 else: 713 jid = self.domain 714 if wresource and self.resource: 715 return jid + '/' + self.resource 716 return jid
717
718 - def __hash__(self):
719 """ 720 Produce hash of the JID, Allows to use JID objects as keys of the 721 dictionary 722 """ 723 return hash(str(self))
724
725 -class BOSHBody(Node):
726 """ 727 <body> tag that wraps usual XMPP stanzas in XMPP over BOSH 728 """ 729
730 - def __init__(self, attrs={}, payload=[], node=None):
731 Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) 732 self.setNamespace(NS_HTTP_BIND)
733 734
735 -class Protocol(Node):
736 """ 737 A "stanza" object class. Contains methods that are common for presences, iqs 738 and messages 739 """ 740
741 - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, 742 payload=[], timestamp=None, xmlns=None, node=None):
743 """ 744 Constructor, name is the name of the stanza 745 i.e. 'message' or 'presence'or 'iq' 746 747 to is the value of 'to' attribure, 'typ' - 'type' attribute 748 frn - from attribure, attrs - other attributes mapping, 749 payload - same meaning as for simplexml payload definition 750 timestamp - the time value that needs to be stamped over stanza 751 xmlns - namespace of top stanza node 752 node - parsed or unparsed stana to be taken as prototype. 753 """ 754 if not attrs: 755 attrs = {} 756 if to: 757 attrs['to'] = to 758 if frm: 759 attrs['from'] = frm 760 if typ: 761 attrs['type'] = typ 762 Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) 763 if not node and xmlns: 764 self.setNamespace(xmlns) 765 if self['to']: 766 self.setTo(self['to']) 767 if self['from']: 768 self.setFrom(self['from']) 769 if node and type(self) == type(node) and \ 770 self.__class__ == node.__class__ and self.attrs.has_key('id'): 771 del self.attrs['id'] 772 self.timestamp = None 773 for d in self.getTags('delay', namespace=NS_DELAY2): 774 try: 775 if d.getAttr('stamp') < self.getTimestamp2(): 776 self.setTimestamp(d.getAttr('stamp')) 777 except Exception: 778 pass 779 if not self.timestamp: 780 for x in self.getTags('x', namespace=NS_DELAY): 781 try: 782 if x.getAttr('stamp') < self.getTimestamp(): 783 self.setTimestamp(x.getAttr('stamp')) 784 except Exception: 785 pass 786 if timestamp is not None: 787 self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
788
789 - def getTo(self):
790 """ 791 Return value of the 'to' attribute 792 """ 793 try: 794 return self['to'] 795 except: 796 return None
797
798 - def getFrom(self):
799 """ 800 Return value of the 'from' attribute 801 """ 802 try: 803 return self['from'] 804 except: 805 return None
806
807 - def getTimestamp(self):
808 """ 809 Return the timestamp in the 'yyyymmddThhmmss' format 810 """ 811 if self.timestamp: 812 return self.timestamp 813 return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
814
815 - def getTimestamp2(self):
816 """ 817 Return the timestamp in the 'yyyymmddThhmmss' format 818 """ 819 if self.timestamp: 820 return self.timestamp 821 return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
822
823 - def getID(self):
824 """ 825 Return the value of the 'id' attribute 826 """ 827 return self.getAttr('id')
828
829 - def setTo(self, val):
830 """ 831 Set the value of the 'to' attribute 832 """ 833 self.setAttr('to', JID(val))
834
835 - def getType(self):
836 """ 837 Return the value of the 'type' attribute 838 """ 839 return self.getAttr('type')
840
841 - def setFrom(self, val):
842 """ 843 Set the value of the 'from' attribute 844 """ 845 self.setAttr('from', JID(val))
846
847 - def setType(self, val):
848 """ 849 Set the value of the 'type' attribute 850 """ 851 self.setAttr('type', val)
852
853 - def setID(self, val):
854 """ 855 Set the value of the 'id' attribute 856 """ 857 self.setAttr('id', val)
858
859 - def getError(self):
860 """ 861 Return the error-condition (if present) or the textual description 862 of the error (otherwise) 863 """ 864 errtag = self.getTag('error') 865 if errtag: 866 for tag in errtag.getChildren(): 867 if tag.getName() != 'text': 868 return tag.getName() 869 return errtag.getData()
870
871 - def getErrorMsg(self):
872 """ 873 Return the textual description of the error (if present) 874 or the error condition 875 """ 876 errtag = self.getTag('error') 877 if errtag: 878 for tag in errtag.getChildren(): 879 if tag.getName() == 'text': 880 return tag.getData() 881 return self.getError()
882
883 - def getErrorCode(self):
884 """ 885 Return the error code. Obsolete. 886 """ 887 return self.getTagAttr('error', 'code')
888
889 - def getStatusConditions(self):
890 """ 891 Return the status conditions list as defined in XEP-0306. 892 """ 893 conds = [] 894 condtag = self.getTag('conditions', namespace=NS_CONDITIONS) 895 if condtag: 896 for tag in condtag.getChildren(): 897 conds.append(tag.getName()) 898 return conds
899
900 - def setError(self, error, code=None):
901 """ 902 Set the error code. Obsolete. Use error-conditions instead 903 """ 904 if code: 905 if str(code) in _errorcodes.keys(): 906 error = ErrorNode(_errorcodes[str(code)], text=error) 907 else: 908 error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, 909 typ='cancel', text=error) 910 elif type(error) in [type(''), type(u'')]: 911 error=ErrorNode(error) 912 self.setType('error') 913 self.addChild(node=error)
914
915 - def setTimestamp(self, val=None):
916 """ 917 Set the timestamp. timestamp should be the yyyymmddThhmmss string 918 """ 919 if not val: 920 val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) 921 self.timestamp=val 922 self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY)
923
924 - def getProperties(self):
925 """ 926 Return the list of namespaces to which belongs the direct childs of element 927 """ 928 props = [] 929 for child in self.getChildren(): 930 prop = child.getNamespace() 931 if prop not in props: 932 props.append(prop) 933 return props
934
935 - def __setitem__(self, item, val):
936 """ 937 Set the item 'item' to the value 'val' 938 """ 939 if item in ['to', 'from']: 940 val = JID(val) 941 return self.setAttr(item, val)
942 943
944 -class Message(Protocol):
945 """ 946 XMPP Message stanza - "push" mechanism 947 """ 948
949 - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, 950 attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, 951 node=None):
952 """ 953 You can specify recipient, text of message, type of message any 954 additional attributes, sender of the message, any additional payload 955 (f.e. jabber:x:delay element) and namespace in one go. 956 957 Alternatively you can pass in the other XML object as the 'node' 958 parameted to replicate it as message 959 """ 960 Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, 961 payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 962 if body: 963 self.setBody(body) 964 if xhtml: 965 self.setXHTML(xhtml) 966 if subject is not None: 967 self.setSubject(subject)
968
969 - def getBody(self):
970 """ 971 Return text of the message 972 """ 973 return self.getTagData('body')
974
975 - def getXHTML(self, xmllang=None):
976 """ 977 Return serialized xhtml-im element text of the message 978 979 TODO: Returning a DOM could make rendering faster. 980 """ 981 xhtml = self.getTag('html') 982 if xhtml: 983 if xmllang: 984 body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) 985 else: 986 body = xhtml.getTag('body') 987 return str(body) 988 return None
989
990 - def getSubject(self):
991 """ 992 Return subject of the message 993 """ 994 return self.getTagData('subject')
995
996 - def getThread(self):
997 """ 998 Return thread of the message 999 """ 1000 return self.getTagData('thread')
1001
1002 - def setBody(self, val):
1003 """ 1004 Set the text of the message""" 1005 self.setTagData('body', val)
1006
1007 - def setXHTML(self, val, xmllang=None):
1008 """ 1009 Sets the xhtml text of the message (XEP-0071). The parameter is the 1010 "inner html" to the body. 1011 """ 1012 try: 1013 if xmllang: 1014 dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' \ 1015 % (NS_XHTML, xmllang, val)).getDom() 1016 else: 1017 dom = NodeBuilder('<body xmlns="%s">%s</body>' % (NS_XHTML, 1018 val), 0).getDom() 1019 if self.getTag('html'): 1020 self.getTag('html').addChild(node=dom) 1021 else: 1022 self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) 1023 except Exception, e: 1024 print "Error", e
1025 # FIXME: log. we could not set xhtml (parse error, whatever) 1026
1027 - def setSubject(self, val):
1028 """ 1029 Set the subject of the message 1030 """ 1031 self.setTagData('subject', val)
1032
1033 - def setThread(self, val):
1034 """ 1035 Set the thread of the message 1036 """ 1037 self.setTagData('thread', val)
1038
1039 - def buildReply(self, text=None):
1040 """ 1041 Builds and returns another message object with specified text. The to, 1042 from, thread and type properties of new message are pre-set as reply to 1043 this message 1044 """ 1045 m = Message(to=self.getFrom(), frm=self.getTo(), body=text, 1046 typ=self.getType()) 1047 th = self.getThread() 1048 if th: 1049 m.setThread(th) 1050 return m
1051
1052 - def getStatusCode(self):
1053 """ 1054 Return the status code of the message (for groupchat config change) 1055 """ 1056 attrs = [] 1057 for xtag in self.getTags('x'): 1058 for child in xtag.getTags('status'): 1059 attrs.append(child.getAttr('code')) 1060 return attrs
1061
1062 -class Presence(Protocol):
1063
1064 - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, 1065 attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, 1066 node=None):
1067 """ 1068 You can specify recipient, type of message, priority, show and status 1069 values any additional attributes, sender of the presence, timestamp, any 1070 additional payload (f.e. jabber:x:delay element) and namespace in one go. 1071 Alternatively you can pass in the other XML object as the 'node' 1072 parameted to replicate it as presence 1073 """ 1074 Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, 1075 payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 1076 if priority: 1077 self.setPriority(priority) 1078 if show: 1079 self.setShow(show) 1080 if status: 1081 self.setStatus(status)
1082
1083 - def getPriority(self):
1084 """ 1085 Return the priority of the message 1086 """ 1087 return self.getTagData('priority')
1088
1089 - def getShow(self):
1090 """ 1091 Return the show value of the message 1092 """ 1093 return self.getTagData('show')
1094
1095 - def getStatus(self):
1096 """ 1097 Return the status string of the message 1098 """ 1099 return self.getTagData('status')
1100
1101 - def setPriority(self, val):
1102 """ 1103 Set the priority of the message 1104 """ 1105 self.setTagData('priority', val)
1106
1107 - def setShow(self, val):
1108 """ 1109 Set the show value of the message 1110 """ 1111 self.setTagData('show', val)
1112
1113 - def setStatus(self, val):
1114 """ 1115 Set the status string of the message 1116 """ 1117 self.setTagData('status', val)
1118
1119 - def _muc_getItemAttr(self, tag, attr):
1120 for xtag in self.getTags('x'): 1121 if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): 1122 continue 1123 for child in xtag.getTags(tag): 1124 return child.getAttr(attr)
1125
1126 - def _muc_getSubTagDataAttr(self, tag, attr):
1127 for xtag in self.getTags('x'): 1128 if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): 1129 continue 1130 for child in xtag.getTags('item'): 1131 for cchild in child.getTags(tag): 1132 return cchild.getData(), cchild.getAttr(attr) 1133 return None, None
1134
1135 - def getRole(self):
1136 """ 1137 Return the presence role (for groupchat) 1138 """ 1139 return self._muc_getItemAttr('item', 'role')
1140
1141 - def getAffiliation(self):
1142 """ 1143 Return the presence affiliation (for groupchat) 1144 """ 1145 return self._muc_getItemAttr('item', 'affiliation')
1146
1147 - def getNewNick(self):
1148 """ 1149 Return the status code of the presence (for groupchat) 1150 """ 1151 return self._muc_getItemAttr('item', 'nick')
1152
1153 - def getJid(self):
1154 """ 1155 Return the presence jid (for groupchat) 1156 """ 1157 return self._muc_getItemAttr('item', 'jid')
1158
1159 - def getReason(self):
1160 """ 1161 Returns the reason of the presence (for groupchat) 1162 """ 1163 return self._muc_getSubTagDataAttr('reason', '')[0]
1164
1165 - def getActor(self):
1166 """ 1167 Return the reason of the presence (for groupchat) 1168 """ 1169 return self._muc_getSubTagDataAttr('actor', 'jid')[1]
1170
1171 - def getStatusCode(self):
1172 """ 1173 Return the status code of the presence (for groupchat) 1174 """ 1175 attrs = [] 1176 for xtag in self.getTags('x'): 1177 for child in xtag.getTags('status'): 1178 attrs.append(child.getAttr('code')) 1179 return attrs
1180
1181 -class Iq(Protocol):
1182 """ 1183 XMPP Iq object - get/set dialog mechanism 1184 """ 1185
1186 - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, 1187 payload=[], xmlns=NS_CLIENT, node=None):
1188 """ 1189 You can specify type, query namespace any additional attributes, 1190 recipient of the iq, sender of the iq, any additional payload (f.e. 1191 jabber:x:data node) and namespace in one go. 1192 1193 Alternatively you can pass in the other XML object as the 'node' 1194 parameted to replicate it as an iq 1195 """ 1196 Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, 1197 xmlns=xmlns, node=node) 1198 if payload: 1199 self.setQueryPayload(payload) 1200 if queryNS: 1201 self.setQueryNS(queryNS)
1202
1203 - def getQuery(self):
1204 """ 1205 Return the IQ's child element if it exists, None otherwise. 1206 """ 1207 children = self.getChildren() 1208 if children and self.getType() != 'error' and \ 1209 children[0].getName() != 'error': 1210 return children[0]
1211
1212 - def getQueryNS(self):
1213 """ 1214 Return the namespace of the 'query' child element 1215 """ 1216 tag = self.getQuery() 1217 if tag: 1218 return tag.getNamespace()
1219
1220 - def getQuerynode(self):
1221 """ 1222 Return the 'node' attribute value of the 'query' child element 1223 """ 1224 tag = self.getQuery() 1225 if tag: 1226 return tag.getAttr('node')
1227
1228 - def getQueryPayload(self):
1229 """ 1230 Return the 'query' child element payload 1231 """ 1232 tag = self.getQuery() 1233 if tag: 1234 return tag.getPayload()
1235
1236 - def getQueryChildren(self):
1237 """ 1238 Return the 'query' child element child nodes 1239 """ 1240 tag = self.getQuery() 1241 if tag: 1242 return tag.getChildren()
1243
1244 - def setQuery(self, name=None):
1245 """ 1246 Change the name of the query node, creating it if needed. Keep the 1247 existing name if none is given (use 'query' if it's a creation). 1248 Return the query node. 1249 """ 1250 query = self.getQuery() 1251 if query is None: 1252 query = self.addChild('query') 1253 if name is not None: 1254 query.setName(name) 1255 return query
1256
1257 - def setQueryNS(self, namespace):
1258 """ 1259 Set the namespace of the 'query' child element 1260 """ 1261 self.setQuery().setNamespace(namespace)
1262
1263 - def setQueryPayload(self, payload):
1264 """ 1265 Set the 'query' child element payload 1266 """ 1267 self.setQuery().setPayload(payload)
1268
1269 - def setQuerynode(self, node):
1270 """ 1271 Set the 'node' attribute value of the 'query' child element 1272 """ 1273 self.setQuery().setAttr('node', node)
1274
1275 - def buildReply(self, typ):
1276 """ 1277 Build and return another Iq object of specified type. The to, from and 1278 query child node of new Iq are pre-set as reply to this Iq. 1279 """ 1280 iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), 1281 attrs={'id': self.getID()}) 1282 iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS()) 1283 return iq
1284
1285 -class Hashes(Node):
1286 """ 1287 Hash elements for various XEPs as defined in XEP-300 1288 """ 1289 1290 """ 1291 RECOMENDED HASH USE: 1292 Algorithm Support 1293 MD2 MUST NOT 1294 MD4 MUST NOT 1295 MD5 MAY 1296 SHA-1 MUST 1297 SHA-256 MUST 1298 SHA-512 SHOULD 1299 """ 1300 1301 supported = ('md5', 'sha-1', 'sha-256', 'sha-512') 1302
1303 - def __init__(self, nsp=NS_HASHES):
1304 Node.__init__(self, None, {}, [], None, None, False, None) 1305 self.setNamespace(nsp) 1306 self.setName('hash')
1307
1308 - def calculateHash(self, algo, file_string):
1309 """ 1310 Calculate the hash and add it. It is preferable doing it here 1311 instead of doing it all over the place in Gajim. 1312 """ 1313 hl = None 1314 hash_ = None 1315 # file_string can be a string or a file 1316 if type(file_string) == str: # if it is a string 1317 if algo == 'sha-1': 1318 hl = hashlib.sha1() 1319 elif algo == 'md5': 1320 hl = hashlib.md5() 1321 elif algo == 'sha-256': 1322 hl = hashlib.sha256() 1323 elif algo == 'sha-512': 1324 hl = hashlib.sha512() 1325 if hl: 1326 hl.update(file_string) 1327 hash_ = hl.hexdigest() 1328 else: # if it is a file 1329 if algo == 'sha-1': 1330 hl = hashlib.sha1() 1331 elif algo == 'md5': 1332 hl = hashlib.md5() 1333 elif algo == 'sha-256': 1334 hl = hashlib.sha256() 1335 elif algo == 'sha-512': 1336 hl = hashlib.sha512() 1337 if hl: 1338 for line in file_string: 1339 hl.update(line) 1340 hash_ = hl.hexdigest() 1341 return hash_
1342
1343 - def addHash(self, hash_, algo):
1344 self.setAttr('algo', algo) 1345 self.setData(hash_)
1346
1347 -class Acks(Node):
1348 """ 1349 Acknowledgement elements for Stream Management 1350 """
1351 - def __init__(self, nsp=NS_STREAM_MGMT):
1352 Node.__init__(self, None, {}, [], None, None, False, None) 1353 self.setNamespace(nsp)
1354
1355 - def buildAnswer(self, handled):
1356 """ 1357 handled is the number of stanzas handled 1358 """ 1359 self.setName('a') 1360 self.setAttr('h', handled)
1361
1362 - def buildRequest(self):
1363 self.setName('r')
1364
1365 - def buildEnable(self, resume=False):
1366 self.setName('enable') 1367 if resume: 1368 self.setAttr('resume', 'true')
1369
1370 - def buildResume(self, handled, previd):
1371 self.setName('resume') 1372 self.setAttr('h', handled) 1373 self.setAttr('previd', previd)
1374
1375 -class ErrorNode(Node):
1376 """ 1377 XMPP-style error element 1378 1379 In the case of stanza error should be attached to XMPP stanza. 1380 In the case of stream-level errors should be used separately. 1381 """ 1382
1383 - def __init__(self, name, code=None, typ=None, text=None):
1384 """ 1385 Mandatory parameter: name - name of error condition. 1386 Optional parameters: code, typ, text. 1387 Used for backwards compartibility with older jabber protocol. 1388 """ 1389 if name in ERRORS: 1390 cod, type_, txt = ERRORS[name] 1391 ns = name.split()[0] 1392 else: 1393 cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' 1394 if typ: 1395 type_ = typ 1396 if code: 1397 cod = code 1398 if text: 1399 txt = text 1400 Node.__init__(self, 'error', {}, [Node(name)]) 1401 if type_: 1402 self.setAttr('type', type_) 1403 if not cod: 1404 self.setName('stream:error') 1405 if txt: 1406 self.addChild(node=Node(ns + ' text', {}, [txt])) 1407 if cod: 1408 self.setAttr('code', cod)
1409
1410 -class Error(Protocol):
1411 """ 1412 Used to quickly transform received stanza into error reply 1413 """ 1414
1415 - def __init__(self, node, error, reply=1):
1416 """ 1417 Create error reply basing on the received 'node' stanza and the 'error' 1418 error condition 1419 1420 If the 'node' is not the received stanza but locally created ('to' and 1421 'from' fields needs not swapping) specify the 'reply' argument as false. 1422 """ 1423 if reply: 1424 Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) 1425 else: 1426 Protocol.__init__(self, node=node) 1427 self.setError(error) 1428 if node.getType() == 'error': 1429 self.__str__ = self.__dupstr__
1430
1431 - def __dupstr__(self, dup1=None, dup2=None):
1432 """ 1433 Dummy function used as preventor of creating error node in reply to error 1434 node. I.e. you will not be able to serialise "double" error into string. 1435 """ 1436 return ''
1437
1438 -class DataField(Node):
1439 """ 1440 This class is used in the DataForm class to describe the single data item 1441 1442 If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then 1443 you will need to work with instances of this class. 1444 """ 1445
1446 - def __init__(self, name=None, value=None, typ=None, required=0, desc=None, 1447 options=[], node=None):
1448 """ 1449 Create new data field of specified name,value and type 1450 1451 Also 'required','desc' and 'options' fields can be set. Alternatively 1452 other XML object can be passed in as the 'node' parameted 1453 to replicate it as a new datafiled. 1454 """ 1455 Node.__init__(self, 'field', node=node) 1456 if name: 1457 self.setVar(name) 1458 if isinstance(value, (list, tuple)): 1459 self.setValues(value) 1460 elif value: 1461 self.setValue(value) 1462 if typ: 1463 self.setType(typ) 1464 elif not typ and not node: 1465 self.setType('text-single') 1466 if required: 1467 self.setRequired(required) 1468 if desc: 1469 self.setDesc(desc) 1470 if options: 1471 self.setOptions(options)
1472
1473 - def setRequired(self, req=1):
1474 """ 1475 Change the state of the 'required' flag 1476 """ 1477 if req: 1478 self.setTag('required') 1479 else: 1480 try: 1481 self.delChild('required') 1482 except ValueError: 1483 return
1484
1485 - def isRequired(self):
1486 """ 1487 Return in this field a required one 1488 """ 1489 return self.getTag('required')
1490
1491 - def setDesc(self, desc):
1492 """ 1493 Set the description of this field 1494 """ 1495 self.setTagData('desc', desc)
1496
1497 - def getDesc(self):
1498 """ 1499 Return the description of this field 1500 """ 1501 return self.getTagData('desc')
1502
1503 - def setValue(self, val):
1504 """ 1505 Set the value of this field 1506 """ 1507 self.setTagData('value', val)
1508
1509 - def getValue(self):
1510 return self.getTagData('value')
1511
1512 - def setValues(self, lst):
1513 """ 1514 Set the values of this field as values-list. Replaces all previous filed 1515 values! If you need to just add a value - use addValue method 1516 """ 1517 while self.getTag('value'): 1518 self.delChild('value') 1519 for val in lst: 1520 self.addValue(val)
1521
1522 - def addValue(self, val):
1523 """ 1524 Add one more value to this field. Used in 'get' iq's or such 1525 """ 1526 self.addChild('value', {}, [val])
1527
1528 - def getValues(self):
1529 """ 1530 Return the list of values associated with this field 1531 """ 1532 ret = [] 1533 for tag in self.getTags('value'): 1534 ret.append(tag.getData()) 1535 return ret
1536
1537 - def getOptions(self):
1538 """ 1539 Return label-option pairs list associated with this field 1540 """ 1541 ret = [] 1542 for tag in self.getTags('option'): 1543 ret.append([tag.getAttr('label'), tag.getTagData('value')]) 1544 return ret
1545
1546 - def setOptions(self, lst):
1547 """ 1548 Set label-option pairs list associated with this field 1549 """ 1550 while self.getTag('option'): 1551 self.delChild('option') 1552 for opt in lst: 1553 self.addOption(opt)
1554
1555 - def addOption(self, opt):
1556 """ 1557 Add one more label-option pair to this field 1558 """ 1559 if isinstance(opt, basestring): 1560 self.addChild('option').setTagData('value', opt) 1561 else: 1562 self.addChild('option', {'label': opt[0]}).setTagData('value', 1563 opt[1])
1564
1565 - def getType(self):
1566 """ 1567 Get type of this field 1568 """ 1569 return self.getAttr('type')
1570
1571 - def setType(self, val):
1572 """ 1573 Set type of this field 1574 """ 1575 return self.setAttr('type', val)
1576
1577 - def getVar(self):
1578 """ 1579 Get 'var' attribute value of this field 1580 """ 1581 return self.getAttr('var')
1582
1583 - def setVar(self, val):
1584 """ 1585 Set 'var' attribute value of this field 1586 """ 1587 return self.setAttr('var', val)
1588
1589 -class DataForm(Node):
1590 """ 1591 Used for manipulating dataforms in XMPP 1592 1593 Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many 1594 other applications. 1595 """
1596 - def __init__(self, typ=None, data=[], title=None, node=None):
1597 """ 1598 Create new dataform of type 'typ'. 'data' is the list of DataField 1599 instances that this dataform contains, 'title' - the title string. You 1600 can specify the 'node' argument as the other node to be used as base for 1601 constructing this dataform 1602 1603 title and instructions is optional and SHOULD NOT contain newlines. 1604 Several instructions MAY be present. 1605 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) 1606 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. 1607 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. 1608 'title' MAY be included in forms of type "form" and "result" 1609 """ 1610 Node.__init__(self, 'x', node=node) 1611 if node: 1612 newkids = [] 1613 for n in self.getChildren(): 1614 if n.getName() == 'field': 1615 newkids.append(DataField(node=n)) 1616 else: 1617 newkids.append(n) 1618 self.kids = newkids 1619 if typ: 1620 self.setType(typ) 1621 self.setNamespace(NS_DATA) 1622 if title: 1623 self.setTitle(title) 1624 if isinstance(data, dict): 1625 newdata = [] 1626 for name in data.keys(): 1627 newdata.append(DataField(name, data[name])) 1628 data = newdata 1629 for child in data: 1630 if isinstance(child, basestring): 1631 self.addInstructions(child) 1632 elif child.__class__.__name__ == 'DataField': 1633 self.kids.append(child) 1634 else: 1635 self.kids.append(DataField(node=child))
1636
1637 - def getType(self):
1638 """ 1639 Return the type of dataform 1640 """ 1641 return self.getAttr('type')
1642
1643 - def setType(self, typ):
1644 """ 1645 Set the type of dataform 1646 """ 1647 self.setAttr('type', typ)
1648
1649 - def getTitle(self):
1650 """ 1651 Return the title of dataform 1652 """ 1653 return self.getTagData('title')
1654
1655 - def setTitle(self, text):
1656 """ 1657 Set the title of dataform 1658 """ 1659 self.setTagData('title', text)
1660
1661 - def getInstructions(self):
1662 """ 1663 Return the instructions of dataform 1664 """ 1665 return self.getTagData('instructions')
1666
1667 - def setInstructions(self, text):
1668 """ 1669 Set the instructions of dataform 1670 """ 1671 self.setTagData('instructions', text)
1672
1673 - def addInstructions(self, text):
1674 """ 1675 Add one more instruction to the dataform 1676 """ 1677 self.addChild('instructions', {}, [text])
1678
1679 - def getField(self, name):
1680 """ 1681 Return the datafield object with name 'name' (if exists) 1682 """ 1683 return self.getTag('field', attrs={'var': name})
1684
1685 - def setField(self, name):
1686 """ 1687 Create if nessessary or get the existing datafield object with name 1688 'name' and return it 1689 """ 1690 f = self.getField(name) 1691 if f: 1692 return f 1693 return self.addChild(node=DataField(name))
1694
1695 - def asDict(self):
1696 """ 1697 Represent dataform as simple dictionary mapping of datafield names to 1698 their values 1699 """ 1700 ret = {} 1701 for field in self.getTags('field'): 1702 name = field.getAttr('var') 1703 typ = field.getType() 1704 if isinstance(typ, basestring) and typ.endswith('-multi'): 1705 val = [] 1706 for i in field.getTags('value'): 1707 val.append(i.getData()) 1708 else: 1709 val = field.getTagData('value') 1710 ret[name] = val 1711 if self.getTag('instructions'): 1712 ret['instructions'] = self.getInstructions() 1713 return ret
1714
1715 - def __getitem__(self, name):
1716 """ 1717 Simple dictionary interface for getting datafields values by their names 1718 """ 1719 item = self.getField(name) 1720 if item: 1721 return item.getValue() 1722 raise IndexError('No such field')
1723
1724 - def __setitem__(self, name, val):
1725 """ 1726 Simple dictionary interface for setting datafields values by their names 1727 """ 1728 return self.setField(name).setValue(val)
1729