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