Package pyxmpp :: Module streambase
[hide private]

Source Code for Module pyxmpp.streambase

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