1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16  __version__="$Id" 
 17   
 18  """ 
 19  When your handler is called it is getting the session instance as the first argument. 
 20  This is the difference from xmpppy 0.1 where you got the "Client" instance. 
 21  With Session class you can have "multi-session" client instead of having 
 22  one client for each connection. Is is specifically important when you are 
 23  writing the server. 
 24  """ 
 25   
 26  from protocol import * 
 27   
 28   
 29  SOCKET_UNCONNECTED  =0 
 30  SOCKET_ALIVE        =1 
 31  SOCKET_DEAD         =2 
 32   
 33  STREAM__NOT_OPENED =1 
 34  STREAM__OPENED     =2 
 35  STREAM__CLOSING    =3 
 36  STREAM__CLOSED     =4 
 37   
 38  SESSION_NOT_AUTHED =1 
 39  SESSION_AUTHED     =2 
 40  SESSION_BOUND      =3 
 41  SESSION_OPENED     =4 
 42  SESSION_CLOSED     =5 
 43   
 45      """ 
 46      The Session class instance is used for storing all session-related info like  
 47      credentials, socket/xml stream/session state flags, roster items (in case of 
 48      client type connection) etc. 
 49      Session object have no means of discovering is any info is ready to be read. 
 50      Instead you should use poll() (recomended) or select() methods for this purpose. 
 51      Session can be one of two types: 'server' and 'client'. 'server' session handles 
 52      inbound connection and 'client' one used to create an outbound one. 
 53      Session instance have multitude of internal attributes. The most imporant is the 'peer' one. 
 54      It is set once the peer is authenticated (client). 
 55      """ 
 56 -    def __init__(self,socket,owner,xmlns=None,peer=None): 
  57          """ When the session is created it's type (client/server) is determined from the beginning. 
 58              socket argument is the pre-created socket-like object. 
 59              It must have the following methods: send, recv, fileno, close. 
 60              owner is the 'master' instance that have Dispatcher plugged into it and generally 
 61              will take care about all session events. 
 62              xmlns is the stream namespace that will be used. Client must set this argument 
 63              If server sets this argument than stream will be dropped if opened with some another namespace. 
 64              peer is the name of peer instance. This is the flag that differentiates client session from 
 65              server session. Client must set it to the name of the server that will be connected, server must 
 66              leave this argument alone. 
 67              """ 
 68          self.xmlns=xmlns 
 69          if peer: 
 70              self.TYP='client' 
 71              self.peer=peer 
 72              self._socket_state=SOCKET_UNCONNECTED 
 73          else: 
 74              self.TYP='server' 
 75              self.peer=None 
 76              self._socket_state=SOCKET_ALIVE 
 77          self._sock=socket 
 78          self._send=socket.send 
 79          self._recv=socket.recv 
 80          self.fileno=socket.fileno 
 81          self._registered=0 
 82   
 83          self.Dispatcher=owner.Dispatcher 
 84          self.DBG_LINE='session' 
 85          self.DEBUG=owner.Dispatcher.DEBUG 
 86          self._expected={} 
 87          self._owner=owner 
 88          if self.TYP=='server': self.ID=`random.random()`[2:] 
 89          else: self.ID=None 
 90   
 91          self.sendbuffer='' 
 92          self._stream_pos_queued=None 
 93          self._stream_pos_sent=0 
 94          self.deliver_key_queue=[] 
 95          self.deliver_queue_map={} 
 96          self.stanza_queue=[] 
 97   
 98          self._session_state=SESSION_NOT_AUTHED 
 99          self.waiting_features=[] 
100          for feature in [NS_TLS,NS_SASL,NS_BIND,NS_SESSION]: 
101              if feature in owner.features: self.waiting_features.append(feature) 
102          self.features=[] 
103          self.feature_in_process=None 
104          self.slave_session=None 
105          self.StartStream() 
 106   
122   
124          """ Reads all pending incoming data. 
125              Raises IOError on disconnection. 
126              Blocks until at least one byte is read.""" 
127          try: received = self._recv(10240) 
128          except: received = '' 
129   
130          if len(received):  
131              self.DEBUG(`self.fileno()`+' '+received,'got') 
132          else: 
133              self.DEBUG('Socket error while receiving data','error') 
134              self.set_socket_state(SOCKET_DEAD) 
135              raise IOError("Peer disconnected") 
136          return received 
 137   
139          """ Put chunk into "immidiatedly send" queue. 
140              Should only be used for auth/TLS stuff and like. 
141              If you just want to shedule regular stanza for delivery use enqueue method. 
142          """ 
143          if isinstance(chunk,Node): chunk = chunk.__str__().encode('utf-8') 
144          elif type(chunk)==type(u''): chunk = chunk.encode('utf-8') 
145          self.enqueue(chunk) 
 146   
148          """ Takes Protocol instance as argument. 
149              Puts stanza into "send" fifo queue. Items into the send queue are hold until 
150              stream authenticated. After that this method is effectively the same as "sendnow" method.""" 
151          if isinstance(stanza,Protocol): 
152              self.stanza_queue.append(stanza) 
153          else: self.sendbuffer+=stanza 
154          if self._socket_state>=SOCKET_ALIVE: self.push_queue() 
 155   
157          """ If stream is authenticated than move items from "send" queue to "immidiatedly send" queue. 
158              Else if the stream is failed then return all queued stanzas with error passed as argument. 
159              Otherwise do nothing.""" 
160           
161   
162          if self._stream_state>=STREAM__CLOSED or self._socket_state>=SOCKET_DEAD:  
163              self._owner.deactivatesession(self) 
164              for key in self.deliver_key_queue:                                           
165                  self._dispatch(Error(self.deliver_queue_map[key],failreason),trusted=1)  
166              for stanza in self.stanza_queue:                                             
167                  self._dispatch(Error(stanza,failreason),trusted=1)                       
168              self.deliver_queue_map,self.deliver_key_queue,self.stanza_queue={},[],[] 
169              return 
170          elif self._session_state>=SESSION_AUTHED:        
171               
172              for stanza in self.stanza_queue: 
173                  txt=stanza.__str__().encode('utf-8') 
174                  self.sendbuffer+=txt 
175                  self._stream_pos_queued+=len(txt)        
176                  self.deliver_queue_map[self._stream_pos_queued]=stanza      
177                  self.deliver_key_queue.append(self._stream_pos_queued) 
178              self.stanza_queue=[] 
 179               
180   
182          """ Put the "immidiatedly send" queue content on the wire. Blocks until at least one byte sent.""" 
183          if self.sendbuffer: 
184              try: 
185                   
186                  sent=self._send(self.sendbuffer)     
187              except: 
188                   
189                  self.set_socket_state(SOCKET_DEAD) 
190                  self.DEBUG("Socket error while sending data",'error') 
191                  return self.terminate_stream() 
192              self.DEBUG(`self.fileno()`+' '+self.sendbuffer[:sent],'sent') 
193              self._stream_pos_sent+=sent 
194              self.sendbuffer=self.sendbuffer[sent:] 
195              self._stream_pos_delivered=self._stream_pos_sent             
196              while self.deliver_key_queue and self._stream_pos_delivered>self.deliver_key_queue[0]: 
197                  del self.deliver_queue_map[self.deliver_key_queue[0]] 
198                  self.deliver_key_queue.remove(self.deliver_key_queue[0]) 
 199               
200   
202          """ This is callback that is used to pass the received stanza forth to owner's dispatcher 
203              _if_ the stream is authorised. Otherwise the stanza is just dropped. 
204              The 'trusted' argument is used to emulate stanza receive. 
205              This method is used internally. 
206          """ 
207          self._owner.packets+=1 
208          print self._owner.packets 
209          if self._stream_state==STREAM__OPENED or trusted:                
210              self.DEBUG(stanza.__str__(),'dispatch') 
211              stanza.trusted=trusted 
212              return self.Dispatcher.dispatch(stanza,self) 
 213   
215          """ This callback is used to detect the stream namespace of incoming stream. Used internally. """ 
216          if not attrs.has_key('id') or not attrs['id']: 
217              return self.terminate_stream(STREAM_INVALID_XML) 
218          self.ID=attrs['id'] 
219          if not attrs.has_key('version'): self._owner.Dialback(self) 
 220   
261   
266   
268          """ Declare some feature as illegal. Illegal features can not be used. 
269              Example: BIND feature becomes illegal after Non-SASL auth. """ 
270          if feature in self.waiting_features: self.waiting_features.remove(feature) 
 271   
282   
306   
315   
317          """ Declare some feature as "negotiating now" to prevent other features from start negotiating. """ 
318          if self.feature_in_process: raise "Starting feature %s over %s !"%(f,self.feature_in_process) 
319          self.feature_in_process=f 
 320   
322          """ Declare some feature as "negotiated" to allow other features start negotiating. """ 
323          if self.feature_in_process<>f: raise "Stopping feature %s instead of %s !"%(f,self.feature_in_process) 
324          self.feature_in_process=None 
 325   
327          """ Change the underlaying socket state. 
328              Socket starts with SOCKET_UNCONNECTED state 
329              and then proceeds (possibly) to SOCKET_ALIVE 
330              and then to SOCKET_DEAD """ 
331          if self._socket_state<newstate: self._socket_state=newstate 
 332   
334          """ Change the session state. 
335              Session starts with SESSION_NOT_AUTHED state 
336              and then comes through  
337              SESSION_AUTHED, SESSION_BOUND, SESSION_OPENED and SESSION_CLOSED states. 
338          """ 
339          if self._session_state<newstate: 
340              if self._session_state<SESSION_AUTHED and \ 
341                 newstate>=SESSION_AUTHED: self._stream_pos_queued=self._stream_pos_sent 
342              self._session_state=newstate 
 343   
345          """ Change the underlaying XML stream state 
346              Stream starts with STREAM__NOT_OPENED and then proceeds with 
347              STREAM__OPENED, STREAM__CLOSING and STREAM__CLOSED states. 
348              Note that some features (like TLS and SASL) 
349              requires stream re-start so this state can have non-linear changes. """ 
350          if self._stream_state<newstate: self._stream_state=newstate 
  351