1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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'
34 NS_ADDRESS = 'http://jabber.org/protocol/address'
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'
39 NS_ARCHIVE_AUTO = NS_ARCHIVE + ':auto'
40 NS_ARCHIVE_MANAGE = NS_ARCHIVE + ':manage'
41 NS_ARCHIVE_MANUAL = NS_ARCHIVE + ':manual'
42 NS_ARCHIVE_PREF = NS_ARCHIVE + ':pref'
43 NS_ATOM = 'http://www.w3.org/2005/Atom'
44 NS_ATTENTION = 'urn:xmpp:attention:0'
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'
49 NS_BOOKMARKS = 'storage:bookmarks'
50 NS_BROWSE = 'jabber:iq:browse'
51 NS_BROWSING = 'http://jabber.org/protocol/browsing'
52 NS_BYTESTREAM = 'http://jabber.org/protocol/bytestreams'
53 NS_CAPS = 'http://jabber.org/protocol/caps'
54 NS_CAPTCHA = 'urn:xmpp:captcha'
55 NS_CARBONS = 'urn:xmpp:carbons:1'
56 NS_CHATSTATES = 'http://jabber.org/protocol/chatstates'
57 NS_CHATTING = 'http://jabber.org/protocol/chatting'
58 NS_CLIENT = 'jabber:client'
59 NS_CONDITIONS = 'urn:xmpp:muc:conditions:0'
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'
64 NS_CONFERENCE = 'jabber:x:conference'
65 NS_DATA = 'jabber:x:data'
66 NS_DATA_MEDIA = 'urn:xmpp:media-element'
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'
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'
76 NS_EVENT = 'jabber:x:event'
77 NS_FEATURE = 'http://jabber.org/protocol/feature-neg'
78 NS_FILE = 'http://jabber.org/protocol/si/profile/file-transfer'
79 NS_FORWARD = 'urn:xmpp:forward:0'
80 NS_GAMING = 'http://jabber.org/protocol/gaming'
81 NS_GATEWAY = 'jabber:iq:gateway'
82 NS_GEOLOC = 'http://jabber.org/protocol/geoloc'
83 NS_GROUPCHAT = 'gc-1.0'
84 NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth'
85 NS_HTTP_BIND = 'http://jabber.org/protocol/httpbind'
86 NS_IBB = 'http://jabber.org/protocol/ibb'
87 NS_INVISIBLE = 'presence-invisible'
88 NS_IQ = 'iq'
89 NS_JINGLE ='urn:xmpp:jingle:1'
90 NS_JINGLE_ERRORS = 'urn:xmpp:jingle:errors:1'
91 NS_JINGLE_RTP = 'urn:xmpp:jingle:apps:rtp:1'
92 NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio'
93 NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video'
94 NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:3'
95 NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0'
96 NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1'
97 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1'
98 NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1'
99 NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1'
100 NS_LAST = 'jabber:iq:last'
101 NS_LOCATION = 'http://jabber.org/protocol/geoloc'
102 NS_MESSAGE = 'message'
103 NS_MOOD = 'http://jabber.org/protocol/mood'
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'
111 NS_OFFLINE = 'http://www.jabber.org/jeps/jep-0030.html'
112 NS_PHYSLOC = 'http://jabber.org/protocol/physloc'
113 NS_PING = 'urn:xmpp:ping'
114 NS_PRESENCE = 'presence'
115 NS_PRIVACY = 'jabber:iq:privacy'
116 NS_PRIVATE = 'jabber:iq:private'
117 NS_PROFILE = 'http://jabber.org/protocol/profile'
118 NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
119 NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event'
120 NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options'
121 NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner'
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'
126 NS_ROSTER_VER = 'urn:xmpp:features:rosterver'
127 NS_RPC = 'jabber:iq:rpc'
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'
136 NS_SI_PUB = 'http://jabber.org/protocol/sipub'
137 NS_SIGNED = 'jabber:x:signed'
138 NS_SSN = 'urn:xmpp:ssn'
139 NS_STANZA_CRYPTO = 'http://www.xmpp.org/extensions/xep-0200.html#ns'
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'
144 NS_TIME_REVISED = 'urn:xmpp:time'
145 NS_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
146 NS_TUNE = 'http://jabber.org/protocol/tune'
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'
154 NS_WAITINGLIST = 'http://jabber.org/protocol/waitinglist'
155 NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im'
156 NS_XHTML = 'http://www.w3.org/1999/xhtml'
157 NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout'
158 NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate'
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'
162 NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2'
163 NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2'
164 NS_STREAM_MGMT = 'urn:xmpp:sm:2'
165 NS_HASHES = 'urn:xmpp:hashes:0'
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
244 """
245 Return true if the node is a positive reply
246 """
247 return node and node.getType() == 'result'
248
250 """
251 Return true if the node is a negative reply
252 """
253 return node and node.getType() == 'error'
254
256 """
257 Exception that should be raised by handler when the handling should be
258 stopped
259 """
260 pass
261
263 """
264 Base exception class for stream errors
265 """
266 pass
267
270
273
276
279
282
285
288
291
294
297
300
303
306
309
312
315
318
321
324
327
330
333
336
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
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
396 """
397 Return the node part of the JID
398 """
399 return self.node
400
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
421 """
422 Return the resource part of the JID
423 """
424 return self.resource
425
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
434 """
435 Return the bare representation of JID. I.e. string value w/o resource
436 """
437 return self.__str__(0)
438
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
451 """
452 Compare the JID to another instance or to string for non-equality
453 """
454 return not self.__eq__(other)
455
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
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
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
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)
544
546 """
547 Return value of the 'to' attribute
548 """
549 try:
550 return self['to']
551 except:
552 return None
553
555 """
556 Return value of the 'from' attribute
557 """
558 try:
559 return self['from']
560 except:
561 return None
562
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
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
580 """
581 Return the value of the 'id' attribute
582 """
583 return self.getAttr('id')
584
586 """
587 Set the value of the 'to' attribute
588 """
589 self.setAttr('to', JID(val))
590
592 """
593 Return the value of the 'type' attribute
594 """
595 return self.getAttr('type')
596
598 """
599 Set the value of the 'from' attribute
600 """
601 self.setAttr('from', JID(val))
602
604 """
605 Set the value of the 'type' attribute
606 """
607 self.setAttr('type', val)
608
610 """
611 Set the value of the 'id' attribute
612 """
613 self.setAttr('id', val)
614
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
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
640 """
641 Return the error code. Obsolete.
642 """
643 return self.getTagAttr('error', 'code')
644
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
670
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
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
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
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
726 """
727 Return text of the message
728 """
729 return self.getTagData('body')
730
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
747 """
748 Return subject of the message
749 """
750 return self.getTagData('subject')
751
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
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
782
784 """
785 Set the subject of the message
786 """
787 self.setTagData('subject', val)
788
790 """
791 Set the thread of the message
792 """
793 self.setTagData('thread', val)
794
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
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
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
840 """
841 Return the priority of the message
842 """
843 return self.getTagData('priority')
844
846 """
847 Return the show value of the message
848 """
849 return self.getTagData('show')
850
852 """
853 Return the status string of the message
854 """
855 return self.getTagData('status')
856
858 """
859 Set the priority of the message
860 """
861 self.setTagData('priority', val)
862
864 """
865 Set the show value of the message
866 """
867 self.setTagData('show', val)
868
870 """
871 Set the status string of the message
872 """
873 self.setTagData('status', val)
874
881
890
892 """
893 Return the presence role (for groupchat)
894 """
895 return self._muc_getItemAttr('item', 'role')
896
898 """
899 Return the presence affiliation (for groupchat)
900 """
901 return self._muc_getItemAttr('item', 'affiliation')
902
904 """
905 Return the status code of the presence (for groupchat)
906 """
907 return self._muc_getItemAttr('item', 'nick')
908
910 """
911 Return the presence jid (for groupchat)
912 """
913 return self._muc_getItemAttr('item', 'jid')
914
916 """
917 Returns the reason of the presence (for groupchat)
918 """
919 return self._muc_getSubTagDataAttr('reason', '')[0]
920
922 """
923 Return the reason of the presence (for groupchat)
924 """
925 return self._muc_getSubTagDataAttr('actor', 'jid')[1]
926
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
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
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
969 """
970 Return the namespace of the 'query' child element
971 """
972 tag = self.getQuery()
973 if tag:
974 return tag.getNamespace()
975
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
985 """
986 Return the 'query' child element payload
987 """
988 tag = self.getQuery()
989 if tag:
990 return tag.getPayload()
991
993 """
994 Return the 'query' child element child nodes
995 """
996 tag = self.getQuery()
997 if tag:
998 return tag.getChildren()
999
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
1014 """
1015 Set the namespace of the 'query' child element
1016 """
1017 self.setQuery().setNamespace(namespace)
1018
1020 """
1021 Set the 'query' child element payload
1022 """
1023 self.setQuery().setPayload(payload)
1024
1026 """
1027 Set the 'node' attribute value of the 'query' child element
1028 """
1029 self.setQuery().setAttr('node', node)
1030
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
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
1063
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
1072 if type(file_string) == str:
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:
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
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
1113 """
1114 Acknowledgement elements for Stream Management
1115 """
1119
1121 """
1122 handled is the number of stanzas handled
1123 """
1124 self.setName('a')
1125 self.setAttr('h', handled)
1126
1129
1131 self.setName('enable')
1132 if resume:
1133 self.setAttr('resume', 'true')
1134
1139
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
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
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
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
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
1251 """
1252 Return in this field a required one
1253 """
1254 return self.getTag('required')
1255
1257 """
1258 Set the description of this field
1259 """
1260 self.setTagData('desc', desc)
1261
1263 """
1264 Return the description of this field
1265 """
1266 return self.getTagData('desc')
1267
1269 """
1270 Set the value of this field
1271 """
1272 self.setTagData('value', val)
1273
1276
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
1288 """
1289 Add one more value to this field. Used in 'get' iq's or such
1290 """
1291 self.addChild('value', {}, [val])
1292
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
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
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
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
1331 """
1332 Get type of this field
1333 """
1334 return self.getAttr('type')
1335
1337 """
1338 Set type of this field
1339 """
1340 return self.setAttr('type', val)
1341
1343 """
1344 Get 'var' attribute value of this field
1345 """
1346 return self.getAttr('var')
1347
1349 """
1350 Set 'var' attribute value of this field
1351 """
1352 return self.setAttr('var', val)
1353
1494