Package pyxmpp :: Module streambase
[hide private]

Source Code for Module pyxmpp.streambase

  1  # 
  2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net> 
  3  # 
  4  # This program is free software; you can redistribute it and/or modify 
  5  # it under the terms of the GNU Lesser General Public License Version 
  6  # 2.1 as published by the Free Software Foundation. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, 
  9  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 11  # GNU Lesser General Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU Lesser General Public 
 14  # License along with this program; if not, write to the Free Software 
 15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 16  # 
 17  # pylint: disable-msg=W0201 
 18   
 19  """Core XMPP stream functionality. 
 20   
 21  Normative reference: 
 22    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__ 
 23  """ 
 24   
 25  __docformat__="restructuredtext en" 
 26   
 27  import libxml2 
 28  import socket 
 29  import os 
 30  import time 
 31  import random 
 32  import threading 
 33  import errno 
 34  import logging 
 35   
 36   
 37  from pyxmpp import xmlextra 
 38  from pyxmpp.expdict import ExpiringDictionary 
 39  from pyxmpp.utils import to_utf8, from_utf8 
 40  from pyxmpp.stanza import Stanza 
 41  from pyxmpp.error import StreamErrorNode 
 42  from pyxmpp.iq import Iq 
 43  from pyxmpp.presence import Presence 
 44  from pyxmpp.message import Message 
 45  from pyxmpp.jid import JID 
 46  from pyxmpp import resolver 
 47  from pyxmpp.stanzaprocessor import StanzaProcessor 
 48  from pyxmpp.exceptions import StreamError, StreamEncryptionRequired 
 49  from pyxmpp.exceptions import HostMismatch, ProtocolError 
 50  from pyxmpp.exceptions import DNSError, UnexpectedCNAMEError 
 51  from pyxmpp.exceptions import FatalStreamError, StreamParseError, StreamAuthenticationError 
 52   
 53  STREAM_NS="http://etherx.jabber.org/streams" 
 54  BIND_NS="urn:ietf:params:xml:ns:xmpp-bind" 
 55   
56 -def stanza_factory(xmlnode, stream = None):
57 """Creates Iq, Message or Presence object for XML stanza `xmlnode`""" 58 if xmlnode.name=="iq": 59 return Iq(xmlnode, stream = stream) 60 if xmlnode.name=="message": 61 return Message(xmlnode, stream = stream) 62 if xmlnode.name=="presence": 63 return Presence(xmlnode, stream = stream) 64 else: 65 return Stanza(xmlnode, stream = stream)
66
67 -class StreamBase(StanzaProcessor,xmlextra.StreamHandler):
68 """Base class for a generic XMPP stream. 69 70 Responsible for establishing connection, parsing the stream, dispatching 71 received stanzas to apopriate handlers and sending application's stanzas. 72 This doesn't provide any authentication or encryption (both required by 73 the XMPP specification) and is not usable on its own. 74 75 Whenever we say "stream" here we actually mean two streams 76 (incoming and outgoing) of one connections, as defined by the XMPP 77 specification. 78 79 :Ivariables: 80 - `lock`: RLock object used to synchronize access to Stream object. 81 - `features`: stream features as annouced by the initiator. 82 - `me`: local stream endpoint JID. 83 - `peer`: remote stream endpoint JID. 84 - `process_all_stanzas`: when `True` then all stanzas received are 85 considered local. 86 - `initiator`: `True` if local stream endpoint is the initiating entity. 87 - `owner`: `Client`, `Component` or similar object "owning" this stream. 88 - `_reader`: the stream reader object (push parser) for the stream. 89 """
90 - def __init__(self, default_ns, extra_ns = (), keepalive = 0, owner = None):
91 """Initialize Stream object 92 93 :Parameters: 94 - `default_ns`: stream's default namespace ("jabber:client" for 95 client, "jabber:server" for server, etc.) 96 - `extra_ns`: sequence of extra namespace URIs to be defined for 97 the stream. 98 - `keepalive`: keepalive output interval. 0 to disable. 99 - `owner`: `Client`, `Component` or similar object "owning" this stream. 100 """ 101 StanzaProcessor.__init__(self) 102 xmlextra.StreamHandler.__init__(self) 103 self.default_ns_uri=default_ns 104 if extra_ns: 105 self.extra_ns_uris=extra_ns 106 else: 107 self.extra_ns_uris=[] 108 self.keepalive=keepalive 109 self._reader_lock=threading.Lock() 110 self.process_all_stanzas=False 111 self.port=None 112 self._reset() 113 self.owner = owner 114 self.__logger=logging.getLogger("pyxmpp.Stream")
115
116 - def _reset(self):
117 """Reset `Stream` object state making it ready to handle new 118 connections.""" 119 self.doc_in=None 120 self.doc_out=None 121 self.socket=None 122 self._reader=None 123 self.addr=None 124 self.default_ns=None 125 self.extra_ns={} 126 self.stream_ns=None 127 self._reader=None 128 self.ioreader=None 129 self.me=None 130 self.peer=None 131 self.skip=False 132 self.stream_id=None 133 self._iq_response_handlers=ExpiringDictionary() 134 self._iq_get_handlers={} 135 self._iq_set_handlers={} 136 self._message_handlers=[] 137 self._presence_handlers=[] 138 self.eof=False 139 self.initiator=None 140 self.features=None 141 self.authenticated=False 142 self.peer_authenticated=False 143 self.auth_method_used=None 144 self.version=None 145 self.last_keepalive=False
146
147 - def __del__(self):
148 self.close()
149
150 - def _connect_socket(self,sock,to=None):
151 """Initialize stream on outgoing connection. 152 153 :Parameters: 154 - `sock`: connected socket for the stream 155 - `to`: name of the remote host 156 """ 157 self.eof=0 158 self.socket=sock 159 if to: 160 self.peer=JID(to) 161 else: 162 self.peer=None 163 self.initiator=1 164 self._send_stream_start() 165 self._make_reader()
166
167 - def connect(self,addr,port,service=None,to=None):
168 """Establish XMPP connection with given address. 169 170 [initiating entity only] 171 172 :Parameters: 173 - `addr`: peer name or IP address 174 - `port`: port number to connect to 175 - `service`: service name (to be resolved using SRV DNS records) 176 - `to`: peer name if different than `addr` 177 """ 178 self.lock.acquire() 179 try: 180 return self._connect(addr,port,service,to) 181 finally: 182 self.lock.release()
183
184 - def _connect(self, addr, port, service = None, to = None):
185 """Same as `Stream.connect` but assume `self.lock` is acquired.""" 186 if to is None: 187 to = from_utf8(addr) 188 allow_cname = True 189 if service is not None: 190 self.state_change("resolving srv", (addr, service)) 191 addrs = resolver.resolve_srv(addr, service) 192 if not addrs: 193 addrs = [(addr, port)] 194 else: 195 allow_cname = False 196 else: 197 addrs=[(addr, port)] 198 exception = None 199 for addr, port in addrs: 200 if type(addr) in (str, unicode): 201 self.state_change("resolving", addr) 202 s=None 203 while True: 204 try: 205 addresses = resolver.getaddrinfo(addr, port, None, 206 socket.SOCK_STREAM, allow_cname = allow_cname) 207 break 208 except UnexpectedCNAMEError, err: 209 self.__logger.warning(str(err)) 210 allow_cname = True 211 continue 212 except DNSError, err: 213 self.__logger.debug(str(err)) 214 exception = err 215 addresses = [] 216 break 217 for res in addresses: 218 family, socktype, proto, _unused, sockaddr = res 219 try: 220 s=socket.socket(family,socktype,proto) 221 self.state_change("connecting",sockaddr) 222 s.connect(sockaddr) 223 self.state_change("connected",sockaddr) 224 except socket.error, err: 225 exception = err 226 self.__logger.debug("Connect to %r failed" % (sockaddr,)) 227 if s: 228 s.close() 229 s=None 230 continue 231 break 232 if s: 233 break 234 if not s: 235 if exception: 236 raise exception 237 else: 238 raise FatalStreamError,"Cannot connect" 239 240 self.addr=addr 241 self.port=port 242 self._connect_socket(s, to) 243 self.last_keepalive=time.time()
244
245 - def accept(self,sock,myname):
246 """Accept incoming connection. 247 248 [receiving entity only] 249 250 :Parameters: 251 - `sock`: a listening socket. 252 - `myname`: local stream endpoint name.""" 253 self.lock.acquire() 254 try: 255 return self._accept(sock,myname) 256 finally: 257 self.lock.release()
258
259 - def _accept(self,sock,myname):
260 """Same as `Stream.accept` but assume `self.lock` is acquired.""" 261 self.eof=0 262 self.socket,addr=sock.accept() 263 self.__logger.debug("Connection from: %r" % (addr,)) 264 self.addr,self.port=addr 265 if myname: 266 self.me=JID(myname) 267 else: 268 self.me=None 269 self.initiator=0 270 self._make_reader() 271 self.last_keepalive=time.time()
272
273 - def disconnect(self):
274 """Gracefully close the connection.""" 275 self.lock.acquire() 276 try: 277 return self._disconnect() 278 finally: 279 self.lock.release()
280
281 - def _disconnect(self):
282 """Same as `Stream.disconnect` but assume `self.lock` is acquired.""" 283 if self.doc_out: 284 self._send_stream_end()
285
286 - def _post_connect(self):
287 """Called when connection is established. 288 289 This method is supposed to be overriden in derived classes.""" 290 pass
291
292 - def _post_auth(self):
293 """Called when connection is authenticated. 294 295 This method is supposed to be overriden in derived classes.""" 296 pass
297
298 - def state_change(self,state,arg):
299 """Called when connection state is changed. 300 301 This method is supposed to be overriden in derived classes 302 or replaced by an application. 303 304 It may be used to display the connection progress.""" 305 self.__logger.debug("State: %s: %r" % (state,arg))
306
307 - def close(self):
308 """Forcibly close the connection and clear the stream state.""" 309 self.lock.acquire() 310 try: 311 return self._close() 312 finally: 313 self.lock.release()
314
315 - def _close(self):
316 """Same as `Stream.close` but assume `self.lock` is acquired.""" 317 self._disconnect() 318 if self.doc_in: 319 self.doc_in=None 320 if self.features: 321 self.features=None 322 self._reader=None 323 self.stream_id=None 324 if self.socket: 325 self.socket.close() 326 self._reset()
327
328 - def _make_reader(self):
329 """Create ne `xmlextra.StreamReader` instace as `self._reader`.""" 330 self._reader=xmlextra.StreamReader(self)
331
332 - def stream_start(self,doc):
333 """Process <stream:stream> (stream start) tag received from peer. 334 335 :Parameters: 336 - `doc`: document created by the parser""" 337 self.doc_in=doc 338 self.__logger.debug("input document: %r" % (self.doc_in.serialize(),)) 339 340 try: 341 r=self.doc_in.getRootElement() 342 if r.ns().getContent() != STREAM_NS: 343 self._send_stream_error("invalid-namespace") 344 raise FatalStreamError,"Invalid namespace." 345 except libxml2.treeError: 346 self._send_stream_error("invalid-namespace") 347 raise FatalStreamError,"Couldn't get the namespace." 348 349 self.version=r.prop("version") 350 if self.version and self.version!="1.0": 351 self._send_stream_error("unsupported-version") 352 raise FatalStreamError,"Unsupported protocol version." 353 354 to_from_mismatch=0 355 if self.initiator: 356 self.stream_id=r.prop("id") 357 peer = from_utf8(r.prop("from")) 358 if peer: 359 peer = JID(peer) 360 if self.peer: 361 if peer and peer!=self.peer: 362 self.__logger.debug("peer hostname mismatch:" 363 " %r != %r" % (peer,self.peer)) 364 to_from_mismatch=1 365 else: 366 self.peer=peer 367 else: 368 to = from_utf8(r.prop("to")) 369 if to: 370 to=self.check_to(to) 371 if not to: 372 self._send_stream_error("host-unknown") 373 raise FatalStreamError,'Bad "to"' 374 self.me=JID(to) 375 self._send_stream_start(self.generate_id()) 376 self._send_stream_features() 377 self.state_change("fully connected",self.peer) 378 self._post_connect() 379 380 if not self.version: 381 self.state_change("fully connected",self.peer) 382 self._post_connect() 383 384 if to_from_mismatch: 385 raise HostMismatch
386
387 - def stream_end(self, _unused):
388 """Process </stream:stream> (stream end) tag received from peer. 389 390 :Parameters: 391 - `_unused`: document created by the parser""" 392 self.__logger.debug("Stream ended") 393 self.eof=1 394 if self.doc_out: 395 self._send_stream_end() 396 if self.doc_in: 397 self.doc_in=None 398 self._reader=None 399 if self.features: 400 self.features=None 401 self.state_change("disconnected",self.peer)
402
403 - def stanza_start(self,doc,node):
404 """Process stanza (first level child element of the stream) start tag 405 -- do nothing. 406 407 :Parameters: 408 - `doc`: parsed document 409 - `node`: stanza's full XML 410 """ 411 pass
412
413 - def stanza(self, _unused, node):
414 """Process stanza (first level child element of the stream). 415 416 :Parameters: 417 - `_unused`: parsed document 418 - `node`: stanza's full XML 419 """ 420 self._process_node(node)
421
422 - def error(self,descr):
423 """Handle stream XML parse error. 424 425 :Parameters: 426 - `descr`: error description 427 """ 428 raise StreamParseError,descr
429
430 - def _send_stream_end(self):
431 """Send stream end tag.""" 432 self.doc_out.getRootElement().addContent(" ") 433 s=self.doc_out.getRootElement().serialize(encoding="UTF-8") 434 end=s.rindex("<") 435 try: 436 self._write_raw(s[end:]) 437 except (IOError,SystemError,socket.error),e: 438 self.__logger.debug("Sending stream closing tag failed:"+str(e)) 439 self.doc_out.freeDoc() 440 self.doc_out=None 441 if self.features: 442 self.features=None
443
444 - def _send_stream_start(self,sid=None):
445 """Send stream start tag.""" 446 if self.doc_out: 447 raise StreamError,"Stream start already sent" 448 self.doc_out=libxml2.newDoc("1.0") 449 root=self.doc_out.newChild(None, "stream", None) 450 self.stream_ns=root.newNs(STREAM_NS,"stream") 451 root.setNs(self.stream_ns) 452 self.default_ns=root.newNs(self.default_ns_uri,None) 453 for prefix,uri in self.extra_ns: 454 self.extra_ns[uri]=root.newNs(uri,prefix) 455 if self.peer and self.initiator: 456 root.setProp("to",self.peer.as_utf8()) 457 if self.me and not self.initiator: 458 root.setProp("from",self.me.as_utf8()) 459 root.setProp("version","1.0") 460 if sid: 461 root.setProp("id",sid) 462 self.stream_id=sid 463 sr=self.doc_out.serialize(encoding="UTF-8") 464 self._write_raw(sr[:sr.find("/>")]+">")
465
466 - def _send_stream_error(self,condition):
467 """Send stream error element. 468 469 :Parameters: 470 - `condition`: stream error condition name, as defined in the 471 XMPP specification.""" 472 if not self.doc_out: 473 self._send_stream_start() 474 e=StreamErrorNode(condition) 475 e.xmlnode.setNs(self.stream_ns) 476 self._write_raw(e.serialize()) 477 e.free() 478 self._send_stream_end()
479
480 - def _restart_stream(self):
481 """Restart the stream as needed after SASL and StartTLS negotiation.""" 482 self._reader=None 483 #self.doc_out.freeDoc() 484 self.doc_out=None 485 #self.doc_in.freeDoc() # memleak, but the node which caused the restart 486 # will be freed after this function returns 487 self.doc_in=None 488 self.features=None 489 if self.initiator: 490 self._send_stream_start(self.stream_id) 491 self._make_reader()
492
493 - def _make_stream_features(self):
494 """Create the <features/> element for the stream. 495 496 [receving entity only] 497 498 :returns: new <features/> element node.""" 499 root=self.doc_out.getRootElement() 500 features=root.newChild(root.ns(),"features",None) 501 return features
502
503 - def _send_stream_features(self):
504 """Send stream <features/>. 505 506 [receiving entity only]""" 507 self.features=self._make_stream_features() 508 self._write_raw(self.features.serialize(encoding="UTF-8"))
509
510 - def write_raw(self,data):
511 """Write raw data to the stream socket. 512 513 :Parameters: 514 - `data`: data to send""" 515 self.lock.acquire() 516 try: 517 return self._write_raw(data) 518 finally: 519 self.lock.release()
520
521 - def _write_raw(self,data):
522 """Same as `Stream.write_raw` but assume `self.lock` is acquired.""" 523 logging.getLogger("pyxmpp.Stream.out").debug("OUT: %r",data) 524 try: 525 while data: 526 sent = self.socket.send(data) 527 data = data[sent:] 528 except (IOError,OSError,socket.error),e: 529 raise FatalStreamError("IO Error: "+str(e))
530
531 - def _write_node(self,xmlnode):
532 """Write XML `xmlnode` to the stream. 533 534 :Parameters: 535 - `xmlnode`: XML node to send.""" 536 if self.eof or not self.socket or not self.doc_out: 537 self.__logger.debug("Dropping stanza: %r" % (xmlnode,)) 538 return 539 xmlnode=xmlnode.docCopyNode(self.doc_out,1) 540 self.doc_out.addChild(xmlnode) 541 try: 542 ns = xmlnode.ns() 543 except libxml2.treeError: 544 ns = None 545 if ns and ns.content == xmlextra.COMMON_NS: 546 xmlextra.replace_ns(xmlnode, ns, self.default_ns) 547 s = xmlextra.safe_serialize(xmlnode) 548 self._write_raw(s) 549 xmlnode.unlinkNode() 550 xmlnode.freeNode()
551
552 - def send(self,stanza):
553 """Write stanza to the stream. 554 555 :Parameters: 556 - `stanza`: XMPP stanza to send.""" 557 self.lock.acquire() 558 try: 559 return self._send(stanza) 560 finally: 561 self.lock.release()
562
563 - def _send(self,stanza):
564 """Same as `Stream.send` but assume `self.lock` is acquired.""" 565 if not self.version: 566 try: 567 err = stanza.get_error() 568 except ProtocolError: 569 err = None 570 if err: 571 err.downgrade() 572 self.fix_out_stanza(stanza) 573 self._write_node(stanza.xmlnode)
574
575 - def idle(self):
576 """Do some housekeeping (cache expiration, timeout handling). 577 578 This method should be called periodically from the application's 579 main loop.""" 580 self.lock.acquire() 581 try: 582 return self._idle() 583 finally: 584 self.lock.release()
585
586 - def _idle(self):
587 """Same as `Stream.idle` but assume `self.lock` is acquired.""" 588 self._iq_response_handlers.expire() 589 if not self.socket or self.eof: 590 return 591 now=time.time() 592 if self.keepalive and now-self.last_keepalive>=self.keepalive: 593 self._write_raw(" ") 594 self.last_keepalive=now
595
596 - def fileno(self):
597 """Return filedescriptor of the stream socket.""" 598 self.lock.acquire() 599 try: 600 return self.socket.fileno() 601 finally: 602 self.lock.release()
603
604 - def loop(self,timeout):
605 """Simple "main loop" for the stream.""" 606 self.lock.acquire() 607 try: 608 while not self.eof and self.socket is not None: 609 act=self._loop_iter(timeout) 610 if not act: 611 self._idle() 612 finally: 613 self.lock.release()
614
615 - def loop_iter(self,timeout):
616 """Single iteration of a simple "main loop" for the stream.""" 617 self.lock.acquire() 618 try: 619 return self._loop_iter(timeout) 620 finally: 621 self.lock.release()
622
623 - def _loop_iter(self,timeout):
624 """Same as `Stream.loop_iter` but assume `self.lock` is acquired.""" 625 import select 626 self.lock.release() 627 try: 628 if not self.socket: 629 time.sleep(timeout) 630 return False 631 try: 632 ifd, _unused, efd = select.select( [self.socket], [], [self.socket], timeout ) 633 except select.error,e: 634 if e.args[0]!=errno.EINTR: 635 raise 636 ifd, _unused, efd=[], [], [] 637 finally: 638 self.lock.acquire() 639 if self.socket in ifd or self.socket in efd: 640 self._process() 641 return True 642 else: 643 return False
644
645 - def process(self):
646 """Process stream's pending events. 647 648 Should be called whenever there is input available 649 on `self.fileno()` socket descriptor. Is called by 650 `self.loop_iter`.""" 651 self.lock.acquire() 652 try: 653 self._process() 654 finally: 655 self.lock.release()
656
657 - def _process(self):
658 """Same as `Stream.process` but assume `self.lock` is acquired.""" 659 try: 660 try: 661 self._read() 662 except (xmlextra.error,),e: 663 self.__logger.exception("Exception during read()") 664 raise StreamParseError(unicode(e)) 665 except: 666 raise 667 except (IOError,OSError,socket.error),e: 668 self.close() 669 raise FatalStreamError("IO Error: "+str(e)) 670 except (FatalStreamError,KeyboardInterrupt,SystemExit),e: 671 self.close() 672 raise
673
674 - def _read(self):
675 """Read data pending on the stream socket and pass it to the parser.""" 676 self.__logger.debug("StreamBase._read(), socket: %r",self.socket) 677 if self.eof: 678 return 679 try: 680 r=self.socket.recv(1024) 681 except socket.error,e: 682 if e.args[0]!=errno.EINTR: 683 raise 684 return 685 self._feed_reader(r)
686
687 - def _feed_reader(self,data):
688 """Feed the stream reader with data received. 689 690 If `data` is None or empty, then stream end (peer disconnected) is 691 assumed and the stream is closed. 692 693 :Parameters: 694 - `data`: data received from the stream socket. 695 :Types: 696 - `data`: `unicode` 697 """ 698 logging.getLogger("pyxmpp.Stream.in").debug("IN: %r",data) 699 if data: 700 try: 701 r=self._reader.feed(data) 702 while r: 703 r=self._reader.feed("") 704 if r is None: 705 self.eof=1 706 self.disconnect() 707 except StreamParseError: 708 self._send_stream_error("xml-not-well-formed") 709 raise 710 else: 711 self.eof=1 712 self.disconnect() 713 if self.eof: 714 self.stream_end(None)
715
716 - def _process_node(self,xmlnode):
717 """Process first level element of the stream. 718 719 The element may be stream error or features, StartTLS 720 request/response, SASL request/response or a stanza. 721 722 :Parameters: 723 - `xmlnode`: XML node describing the element 724 """ 725 ns_uri=xmlnode.ns().getContent() 726 if ns_uri=="http://etherx.jabber.org/streams": 727 self._process_stream_node(xmlnode) 728 return 729 730 if ns_uri==self.default_ns_uri: 731 stanza=stanza_factory(xmlnode, self) 732 self.lock.release() 733 try: 734 self.process_stanza(stanza) 735 finally: 736 self.lock.acquire() 737 stanza.free() 738 else: 739 self.__logger.debug("Unhandled node: %r" % (xmlnode.serialize(),))
740
741 - def _process_stream_node(self,xmlnode):
742 """Process first level stream-namespaced element of the stream. 743 744 The element may be stream error or stream features. 745 746 :Parameters: 747 - `xmlnode`: XML node describing the element 748 """ 749 if xmlnode.name=="error": 750 e=StreamErrorNode(xmlnode) 751 self.lock.release() 752 try: 753 self.process_stream_error(e) 754 finally: 755 self.lock.acquire() 756 e.free() 757 return 758 elif xmlnode.name=="features": 759 self.__logger.debug("Got stream features") 760 self.__logger.debug("Node: %r" % (xmlnode,)) 761 self.features=xmlnode.copyNode(1) 762 self.doc_in.addChild(self.features) 763 self._got_features() 764 return 765 766 self.__logger.debug("Unhandled stream node: %r" % (xmlnode.serialize(),))
767
768 - def process_stream_error(self,err):
769 """Process stream error element received. 770 771 :Types: 772 - `err`: `StreamErrorNode` 773 774 :Parameters: 775 - `err`: error received 776 """ 777 778 self.__logger.debug("Unhandled stream error: condition: %s %r" 779 % (err.get_condition().name,err.serialize()))
780
781 - def check_to(self,to):
782 """Check "to" attribute of received stream header. 783 784 :return: `to` if it is equal to `self.me`, None otherwise. 785 786 Should be overriden in derived classes which require other logic 787 for handling that attribute.""" 788 if to!=self.me: 789 return None 790 return to
791
792 - def generate_id(self):
793 """Generate a random and unique stream ID. 794 795 :return: the id string generated.""" 796 return "%i-%i-%s" % (os.getpid(),time.time(),str(random.random())[2:])
797
798 - def _got_features(self):
799 """Process incoming <stream:features/> element. 800 801 [initiating entity only] 802 803 The received features node is available in `self.features`.""" 804 ctxt = self.doc_in.xpathNewContext() 805 ctxt.setContextNode(self.features) 806 ctxt.xpathRegisterNs("stream",STREAM_NS) 807 ctxt.xpathRegisterNs("bind",BIND_NS) 808 bind_n=None 809 try: 810 bind_n=ctxt.xpathEval("bind:bind") 811 finally: 812 ctxt.xpathFreeContext() 813 814 if self.authenticated: 815 if bind_n: 816 self.bind(self.me.resource) 817 else: 818 self.state_change("authorized",self.me)
819
820 - def bind(self,resource):
821 """Bind to a resource. 822 823 [initiating entity only] 824 825 :Parameters: 826 - `resource`: the resource name to bind to. 827 828 XMPP stream is authenticated for bare JID only. To use 829 the full JID it must be bound to a resource. 830 """ 831 iq=Iq(stanza_type="set") 832 q=iq.new_query(BIND_NS, u"bind") 833 if resource: 834 q.newTextChild(None,"resource",to_utf8(resource)) 835 self.state_change("binding",resource) 836 self.set_response_handlers(iq,self._bind_success,self._bind_error) 837 self.send(iq) 838 iq.free()
839
840 - def _bind_success(self,stanza):
841 """Handle resource binding success. 842 843 [initiating entity only] 844 845 :Parameters: 846 - `stanza`: <iq type="result"/> stanza received. 847 848 Set `self.me` to the full JID negotiated.""" 849 jid_n=stanza.xpath_eval("bind:bind/bind:jid",{"bind":BIND_NS}) 850 if jid_n: 851 self.me=JID(jid_n[0].getContent().decode("utf-8")) 852 self.state_change("authorized",self.me)
853
854 - def _bind_error(self,stanza):
855 """Handle resource binding success. 856 857 [initiating entity only] 858 859 :raise FatalStreamError:""" 860 raise FatalStreamError,"Resource binding failed"
861
862 - def connected(self):
863 """Check if stream is connected. 864 865 :return: True if stream connection is active.""" 866 if self.doc_in and self.doc_out and not self.eof: 867 return True 868 else: 869 return False
870 871 # vi: sts=4 et sw=4 872