Package pyxmpp :: Package jabber :: Module client
[hide private]

Source Code for Module pyxmpp.jabber.client

  1  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net> 
  2  # 
  3  # This program is free software; you can redistribute it and/or modify 
  4  # it under the terms of the GNU Lesser General Public License Version 
  5  # 2.1 as published by the Free Software Foundation. 
  6  # 
  7  # This program is distributed in the hope that it will be useful, 
  8  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 10  # GNU Lesser General Public License for more details. 
 11  # 
 12  # You should have received a copy of the GNU Lesser General Public 
 13  # License along with this program; if not, write to the Free Software 
 14  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 15  # 
 16  """Basic Jabber client functionality implementation. 
 17   
 18  Extends `pyxmpp.client` interface with legacy authentication 
 19  and basic Service Discovery handling. 
 20   
 21  Normative reference: 
 22    - `JEP 78 <http://www.jabber.org/jeps/jep-0078.html>`__ 
 23    - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__ 
 24  """ 
 25   
 26  __revision__="$Id: client.py 714 2010-04-05 10:20:10Z jajcus $" 
 27  __docformat__="restructuredtext en" 
 28   
 29  import logging 
 30   
 31  from pyxmpp.jabber.clientstream import LegacyClientStream 
 32  from pyxmpp.jabber.disco import DISCO_ITEMS_NS,DISCO_INFO_NS 
 33  from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoIdentity 
 34  from pyxmpp.jabber import disco 
 35  from pyxmpp.client import Client 
 36  from pyxmpp.stanza import Stanza 
 37  from pyxmpp.cache import CacheSuite 
 38  from pyxmpp.utils import from_utf8 
 39  from pyxmpp.interfaces import IFeaturesProvider 
 40   
41 -class JabberClient(Client):
42 """Base class for a Jabber client. 43 44 :Ivariables: 45 - `disco_items`: default Disco#items reply for a query to an empty node. 46 - `disco_info`: default Disco#info reply for a query to an empty node -- 47 provides information about the client and its supported fetures. 48 - `disco_identity`: default identity of the default `disco_info`. 49 - `register`: when `True` than registration will be started instead of authentication. 50 :Types: 51 - `disco_items`: `DiscoItems` 52 - `disco_info`: `DiscoInfo` 53 - `register`: `bool` 54 """
55 - def __init__(self,jid=None, password=None, server=None, port=5222, 56 auth_methods=("sasl:DIGEST-MD5","digest"), 57 tls_settings=None, keepalive=0, 58 disco_name=u"pyxmpp based Jabber client", disco_category=u"client", 59 disco_type=u"pc"):
60 """Initialize a JabberClient object. 61 62 :Parameters: 63 - `jid`: user full JID for the connection. 64 - `password`: user password. 65 - `server`: server to use. If not given then address will be derived form the JID. 66 - `port`: port number to use. If not given then address will be derived form the JID. 67 - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms 68 in the list should be prefixed with "sasl:" string. 69 - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. 70 - `keepalive`: keepalive output interval. 0 to disable. 71 - `disco_name`: name of the client identity in the disco#info 72 replies. 73 - `disco_category`: category of the client identity in the disco#info 74 replies. The default of u'client' should be the right choice in 75 most cases. 76 - `disco_type`: type of the client identity in the disco#info 77 replies. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__ 78 :Types: 79 - `jid`: `pyxmpp.JID` 80 - `password`: `unicode` 81 - `server`: `unicode` 82 - `port`: `int` 83 - `auth_methods`: sequence of `str` 84 - `tls_settings`: `pyxmpp.TLSSettings` 85 - `keepalive`: `int` 86 - `disco_name`: `unicode` 87 - `disco_category`: `unicode` 88 - `disco_type`: `unicode` 89 """ 90 91 Client.__init__(self,jid,password,server,port,auth_methods,tls_settings,keepalive) 92 self.stream_class = LegacyClientStream 93 self.disco_items=DiscoItems() 94 self.disco_info=DiscoInfo() 95 self.disco_identity=DiscoIdentity(self.disco_info, 96 disco_name, disco_category, disco_type) 97 self.register_feature(u"dnssrv") 98 self.register_feature(u"stringprep") 99 self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s") 100 self.cache = CacheSuite(max_items = 1000) 101 self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient")
102 103 # public methods 104
105 - def connect(self, register = False):
106 """Connect to the server and set up the stream. 107 108 Set `self.stream` and notify `self.state_changed` when connection 109 succeeds. Additionally, initialize Disco items and info of the client. 110 """ 111 Client.connect(self, register) 112 if register: 113 self.stream.registration_callback = self.process_registration_form
114
115 - def register_feature(self, feature_name):
116 """Register a feature to be announced by Service Discovery. 117 118 :Parameters: 119 - `feature_name`: feature namespace or name. 120 :Types: 121 - `feature_name`: `unicode`""" 122 self.disco_info.add_feature(feature_name)
123
124 - def unregister_feature(self, feature_name):
125 """Unregister a feature to be announced by Service Discovery. 126 127 :Parameters: 128 - `feature_name`: feature namespace or name. 129 :Types: 130 - `feature_name`: `unicode`""" 131 self.disco_info.remove_feature(feature_name)
132
133 - def submit_registration_form(self, form):
134 """Submit a registration form 135 136 :Parameters: 137 - `form`: the form to submit 138 :Types: 139 - `form`: `pyxmpp.jabber.dataforms.Form`""" 140 self.stream.submit_registration_form(form)
141 142 # private methods
143 - def __disco_info(self,iq):
144 """Handle a disco#info request. 145 146 `self.disco_get_info` method will be used to prepare the query response. 147 148 :Parameters: 149 - `iq`: the IQ stanza received. 150 :Types: 151 - `iq`: `pyxmpp.iq.Iq`""" 152 q=iq.get_query() 153 if q.hasProp("node"): 154 node=from_utf8(q.prop("node")) 155 else: 156 node=None 157 info=self.disco_get_info(node,iq) 158 if isinstance(info,DiscoInfo): 159 resp=iq.make_result_response() 160 self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s" 161 % (iq.serialize(),resp.serialize(),info.xmlnode.serialize())) 162 resp.set_content(info.xmlnode.copyNode(1)) 163 elif isinstance(info,Stanza): 164 resp=info 165 else: 166 resp=iq.make_error_response("item-not-found") 167 self.__logger.debug("Disco-info response: %s" % (resp.serialize(),)) 168 self.stream.send(resp)
169
170 - def __disco_items(self,iq):
171 """Handle a disco#items request. 172 173 `self.disco_get_items` method will be used to prepare the query response. 174 175 :Parameters: 176 - `iq`: the IQ stanza received. 177 :Types: 178 - `iq`: `pyxmpp.iq.Iq`""" 179 q=iq.get_query() 180 if q.hasProp("node"): 181 node=from_utf8(q.prop("node")) 182 else: 183 node=None 184 items=self.disco_get_items(node,iq) 185 if isinstance(items,DiscoItems): 186 resp=iq.make_result_response() 187 self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s" 188 % (iq.serialize(),resp.serialize(),items.xmlnode.serialize())) 189 resp.set_content(items.xmlnode.copyNode(1)) 190 elif isinstance(items,Stanza): 191 resp=items 192 else: 193 resp=iq.make_error_response("item-not-found") 194 self.__logger.debug("Disco-items response: %s" % (resp.serialize(),)) 195 self.stream.send(resp)
196
197 - def _session_started(self):
198 """Called when session is started. 199 200 Activates objects from `self.interface_provides` by installing 201 their disco features.""" 202 Client._session_started(self) 203 for ob in self.interface_providers: 204 if IFeaturesProvider.providedBy(ob): 205 for ns in ob.get_features(): 206 self.register_feature(ns)
207 208 # methods to override 209
210 - def authorized(self):
211 """Handle "authorized" event. May be overriden in derived classes. 212 By default: request an IM session and setup Disco handlers.""" 213 Client.authorized(self) 214 self.stream.set_iq_get_handler("query",DISCO_ITEMS_NS,self.__disco_items) 215 self.stream.set_iq_get_handler("query",DISCO_INFO_NS,self.__disco_info) 216 disco.register_disco_cache_fetchers(self.cache,self.stream)
217
218 - def disco_get_info(self,node,iq):
219 """Return Disco#info data for a node. 220 221 :Parameters: 222 - `node`: the node queried. 223 - `iq`: the request stanza received. 224 :Types: 225 - `node`: `unicode` 226 - `iq`: `pyxmpp.iq.Iq` 227 228 :return: self.disco_info if `node` is empty or `None` otherwise. 229 :returntype: `DiscoInfo`""" 230 to=iq.get_to() 231 if to and to!=self.jid: 232 return iq.make_error_response("recipient-unavailable") 233 if not node and self.disco_info: 234 return self.disco_info 235 return None
236
237 - def disco_get_items(self,node,iq):
238 """Return Disco#items data for a node. 239 240 :Parameters: 241 - `node`: the node queried. 242 - `iq`: the request stanza received. 243 :Types: 244 - `node`: `unicode` 245 - `iq`: `pyxmpp.iq.Iq` 246 247 :return: self.disco_info if `node` is empty or `None` otherwise. 248 :returntype: `DiscoInfo`""" 249 to=iq.get_to() 250 if to and to!=self.jid: 251 return iq.make_error_response("recipient-unavailable") 252 if not node and self.disco_items: 253 return self.disco_items 254 return None
255
256 - def process_registration_form(self, stanza, form):
257 """Fill-in the registration form provided by the server. 258 259 This default implementation fills-in "username" and "passwords" 260 fields only and instantly submits the form. 261 262 :Parameters: 263 - `stanza`: the stanza received. 264 - `form`: the registration form. 265 :Types: 266 - `stanza`: `pyxmpp.iq.Iq` 267 - `form`: `pyxmpp.jabber.dataforms.Form` 268 """ 269 _unused = stanza 270 self.__logger.debug(u"default registration callback started. auto-filling-in the form...") 271 if not 'FORM_TYPE' in form or 'jabber:iq:register' not in form['FORM_TYPE'].values: 272 raise RuntimeError, "Unknown form type: %r %r" % (form, form['FORM_TYPE']) 273 for field in form: 274 if field.name == u"username": 275 self.__logger.debug(u"Setting username to %r" % (self.jid.node,)) 276 field.value = self.jid.node 277 elif field.name == u"password": 278 self.__logger.debug(u"Setting password to %r" % (self.password,)) 279 field.value = self.password 280 elif field.required: 281 self.__logger.debug(u"Unknown required field: %r" % (field.name,)) 282 raise RuntimeError, "Unsupported required registration form field %r" % (field.name,) 283 else: 284 self.__logger.debug(u"Unknown field: %r" % (field.name,)) 285 self.submit_registration_form(form)
286 287 # vi: sts=4 et sw=4 288