From 56d60f3fd57e80859b7bc823e6e75077858992d9 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sat, 7 May 2005 10:57:40 +0000 Subject: [PATCH] xmpppy changes: - Sync with latest CVS version + gajim patches. - streamErrorHandler disabled. If you want enable it - unrem it at line 66 of dispatcher.py - TLS re-enabled. Should work fine now - disconnection TLS plugout re-enabled. Didn't successed in reproducing problem though so maybe not fixed. - My temporary fixes replaced with CVS ones. gajim connection.py changes (in chunks order): - getTags is incorrect. Replaced with getChildren - browseAgents is absent from xmpppy. Fixed with manual node send. Made use of buildReply method and con argument - formatting fix - crude fixed register problem. I do not know how to do it properly. It is client stuff. Honest! I have no idea how to make it non-blocking and yet make library to fallback to older protocols. getInstructions() moved to xmpppy Thanks Alexey ! --- src/common/connection.py | 24 ++++------ src/common/xmpp/auth.py | 9 ++-- src/common/xmpp/browser.py | 20 +++++---- src/common/xmpp/client.py | 34 +++++++-------- src/common/xmpp/commands.py | 14 ++++-- src/common/xmpp/dispatcher.py | 28 +++++++++--- src/common/xmpp/features.py | 9 ++-- src/common/xmpp/protocol.py | 82 +++++++++++++++++++++++++++++------ src/common/xmpp/roster.py | 10 ++--- src/common/xmpp/simplexml.py | 6 +-- src/common/xmpp/transports.py | 46 ++++++++++++++++---- 11 files changed, 194 insertions(+), 88 deletions(-) diff --git a/src/common/connection.py b/src/common/connection.py index d51982ee9..536ac238c 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -328,7 +328,7 @@ class Connection: for key in q.getAttrs().keys(): attr[key.encode('utf8')] = q.getAttr(key).encode('utf8') identities = [attr] - for node in q.getTags(): + for node in q.getChildren(): if node.getName() == 'ns': features.append(node.getData()) else: @@ -387,24 +387,22 @@ class Connection: features.append(i.getAttr('var')) jid = str(iq_obj.getFrom()) if not identities: - self.connection.browseAgents(jid, node) + self.connection.send(common.xmpp.Iq(typ = 'get', queryNS = \ + common.xmpp.NS_AGENTS)) else: self.dispatch('AGENT_INFO_INFO', (jid, node, identities, features)) self.discoverItems(jid, node) def _VersionCB(self, con, iq_obj): gajim.log.debug('VersionCB') - f = iq_obj.getFrom() - iq_obj.setFrom(iq_obj.getTo()) - iq_obj.setTo(f) - iq_obj.setType('result') + iq_obj = iq_obj.buildReply('result') qp = iq_obj.getTag('query') qp.setTagData('name', 'Gajim') qp.setTagData('version', gajim.version) send_os = gajim.config.get('send_os_info') if send_os: qp.setTagData('os', get_os_info()) - self.connection.send(iq_obj) + con.send(iq_obj) def _VersionResultCB(self, con, iq_obj): gajim.log.debug('VersionResultCB') @@ -638,8 +636,7 @@ class Connection: prio = str(gajim.config.get_per('accounts', self.name, 'priority')) p = common.xmpp.Presence(typ = ptype, priority = prio, show = status, status = msg) - if signed: p.setTag(common.xmpp.NS_SIGNED + ' x').setData( - signed) + if signed: p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed) self.connection.send(p) self.dispatch('STATUS', status) @@ -712,17 +709,14 @@ class Connection: def request_agents(self, jid, node): if self.connection: + self.connection.send(common.xmpp.Iq(to = jid, typ = 'get', + queryNS = common.xmpp.NS_BROWSE)) self.discoverInfo(jid, node) def ask_register_agent_info(self, agent): if not self.connection: return None - data = common.xmpp.features.getRegInfo(self.connection, agent) # FIXME: blocking - info = data.asDict() - instructions = data.getInstructions() - if instructions: - info['instructions'] = instructions - return info + return common.xmpp.features.getRegInfo(self.connection, agent).asDict() # FIXME: blocking def register_agent(self, agent, info): if not self.connection: diff --git a/src/common/xmpp/auth.py b/src/common/xmpp/auth.py index 5803d0d29..898a9c654 100644 --- a/src/common/xmpp/auth.py +++ b/src/common/xmpp/auth.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: auth.py,v 1.25 2005/03/08 19:36:29 snakeru Exp $ +# $Id: auth.py,v 1.27 2005/04/30 10:17:20 snakeru Exp $ """ Provides library with all Non-SASL and SASL authentication mechanisms. @@ -97,7 +97,8 @@ class NonSASL(PlugIn): class SASL(PlugIn): """ Implements SASL authentication. """ def plugin(self,owner): - self.startsasl=None + if not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): self.startsasl='not-supported' + else: self.startsasl=None def auth(self,username,password): """ Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be @@ -120,7 +121,7 @@ class SASL(PlugIn): def FeaturesHandler(self,conn,feats): """ Used to determine if server supports SASL auth. Used internally. """ if not feats.getTag('mechanisms',namespace=NS_SASL): - self.startsasl='failure' + self.startsasl='not-supported' self.DEBUG('SASL not supported by server','error') return mecs=[] @@ -213,7 +214,7 @@ class Bind(PlugIn): except NodeProcessed: pass else: self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) - def plugout(self,owner): + def plugout(self): """ Remove Bind handler from owner's dispatcher. Used internally. """ self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) diff --git a/src/common/xmpp/browser.py b/src/common/xmpp/browser.py index 729b8b176..39c488104 100644 --- a/src/common/xmpp/browser.py +++ b/src/common/xmpp/browser.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: browser.py,v 1.8 2004/12/24 19:56:35 snakeru Exp $ +# $Id: browser.py,v 1.9 2005/04/30 07:13:33 snakeru Exp $ """Browser module provides DISCO server framework for your application. This functionality can be used for very different purposes - from publishing @@ -27,7 +27,9 @@ from dispatcher import * from client import PlugIn class Browser(PlugIn): - """ Standart xmpppy class that is ancestor of PlugIn and can be attached + """ WARNING! This class is for components only. It will not work in client mode! + + Standart xmpppy class that is ancestor of PlugIn and can be attached to your application. All processing will be performed in the handlers registered in the browser instance. You can register any number of handlers ensuring that for each @@ -85,14 +87,14 @@ class Browser(PlugIn): def plugin(self, owner): """ Registers it's own iq handlers in your application dispatcher instance. Used internally.""" - owner.RegisterHandler('iq',self._DiscoveryHandler,ns=NS_DISCO_INFO) - owner.RegisterHandler('iq',self._DiscoveryHandler,ns=NS_DISCO_ITEMS) + owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO) + owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS) def plugout(self): """ Unregisters browser's iq handlers from your application dispatcher instance. Used internally.""" - self._owner.UnregisterHandler('iq',self._DiscoveryHandler,ns=NS_DISCO_INFO) - self._owner.UnregisterHandler('iq',self._DiscoveryHandler,ns=NS_DISCO_ITEMS) + self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO) + self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS) def _traversePath(self,node,jid,set=0): """ Returns dictionary and key or None,None @@ -136,7 +138,7 @@ class Browser(PlugIn): {'jid':'jid2','action':'action2','node':'node2','name':'name2'}, {'jid':'jid3','node':'node3','name':'name3'}, {'jid':'jid4','node':'node4'} - ] + ], 'info' :{ 'ids':[ {'category':'category1','type':'type1','name':'name1'}, @@ -182,7 +184,9 @@ class Browser(PlugIn): to handle the request. Used internally. """ handler=self.getDiscoHandler(request.getQuerynode(),request.getTo()) - if not handler: return conn.send(Error(request,ERR_ITEM_NOT_FOUND)) + if not handler: + conn.send(Error(request,ERR_ITEM_NOT_FOUND)) + raise NodeProcessed rep=request.buildReply('result') q=rep.getTag('query') if request.getQueryNS()==NS_DISCO_ITEMS: diff --git a/src/common/xmpp/client.py b/src/common/xmpp/client.py index 62b5faaa7..85bedd777 100644 --- a/src/common/xmpp/client.py +++ b/src/common/xmpp/client.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: client.py,v 1.33 2005/04/10 08:09:23 snakeru Exp $ +# $Id: client.py,v 1.35 2005/04/30 10:17:19 snakeru Exp $ """ Provides PlugIn class functionality to develop extentions for xmpppy. @@ -105,7 +105,7 @@ class CommonClient: self.debug_flags.append(self.DBG) self._owner=self self._registered_name=None -# self.RegisterDisconnectHandler(self.DisconnectHandler) + self.RegisterDisconnectHandler(self.DisconnectHandler) self.connected='' def RegisterDisconnectHandler(self,handler): @@ -123,7 +123,7 @@ class CommonClient: self.disconnect_handlers.reverse() for i in self.disconnect_handlers: i() self.disconnect_handlers.reverse() -# if self.__dict__.has_key('TLS'): self.TLS.PlugOut() + if self.__dict__.has_key('TLS'): self.TLS.PlugOut() def DisconnectHandler(self): """ Default disconnect handler. Just raises an IOError. @@ -156,9 +156,9 @@ class CommonClient: if not connected: return self._Server,self._Proxy=server,proxy self.connected='tcp' -# if self.Connection.getPort()==5223: -# transports.TLS().PlugIn(self,now=1) -# self.connected='tls' + if self.Connection.getPort()==5223: + transports.TLS().PlugIn(self,now=1) + self.connected='tls' dispatcher.Dispatcher().PlugIn(self) while self.Dispatcher.Stream._document_attrs is None: self.Process(1) if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0': @@ -173,32 +173,32 @@ class Client(CommonClient): specify it's address and credentials (if needed) in the second argument. Example: connect(('192.168.5.5':5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})""" if not CommonClient.connect(self,server,proxy): return self.connected -# transports.TLS().PlugIn(self) + transports.TLS().PlugIn(self) if not self.Dispatcher.Stream._document_attrs.has_key('version') or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented -# if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected # TLS not supported by server -# while not self.TLS.starttls and self.Process(): pass -# if self.TLS.starttls<>'success': self.event('tls_failed'); return self.connected -# self.connected='tls' + if not self.Dispatcher.Stream.features.getTag('starttls'): return self.connected # TLS not supported by server + while not self.TLS.starttls and self.Process(): pass + if self.TLS.starttls<>'success': self.event('tls_failed'); return self.connected + self.connected='tls' return self.connected def auth(self,user,password,resource=''): """ Authenticate connnection and bind resource. If resource is not provided random one or library name used. """ self._User,self._Password,self._Resource=user,password,resource - auth.SASL().PlugIn(self) - self.SASL.auth(user,password) while not self.Dispatcher.Stream._document_attrs and self.Process(): pass if self.Dispatcher.Stream._document_attrs.has_key('version') and self.Dispatcher.Stream._document_attrs['version']=='1.0': while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented - while self.SASL.startsasl=='in-process' and self.Process(): pass - else: self.SASL.startsasl='failure' - if self.SASL.startsasl=='failure': + auth.SASL().PlugIn(self) + if self.SASL.startsasl=='not-supported': if not resource: resource='xmpppy' if auth.NonSASL(user,password,resource).PlugIn(self): self.connected+='+old_auth' return 'old_auth' - else: + return + self.SASL.auth(user,password) + while self.SASL.startsasl=='in-process' and self.Process(): pass + if self.SASL.startsasl=='success': auth.Bind().PlugIn(self) while self.Bind.bound is None: self.Process() if self.Bind.Bind(resource): diff --git a/src/common/xmpp/commands.py b/src/common/xmpp/commands.py index 6bbead07e..1c3ee6e46 100644 --- a/src/common/xmpp/commands.py +++ b/src/common/xmpp/commands.py @@ -1,4 +1,4 @@ -## $Id: commands.py,v 1.3 2005/03/08 19:50:43 snakeru Exp $ +## $Id: commands.py,v 1.4 2005/04/30 07:33:11 snakeru Exp $ ## Ad-Hoc Command manager ## Mike Albon (c) 5th January 2005 @@ -34,8 +34,6 @@ What it supplies: from xmpp.protocol import * from xmpp.client import PlugIn -NS_COMMANDS='http://jabber.org/protocol/commands' - class Commands(PlugIn): """Commands is an ancestor of Plugin and can be attached to any session. @@ -167,7 +165,10 @@ class Commands(PlugIn): return self._handlers[jid][name] class Command_Handler_Prototype(PlugIn): - """This is a prototype command handler, as each command uses a disco method and execute method you can implement it any way you like, however this is my first attempt at making a generic handler that you can hang process stages on too. There is an example command below. + """This is a prototype command handler, as each command uses a disco method + and execute method you can implement it any way you like, however this is + my first attempt at making a generic handler that you can hang process + stages on too. There is an example command below. The parameters are as follows: name : the name of the command within the jabber environment @@ -241,14 +242,19 @@ class Command_Handler_Prototype(PlugIn): return self.discoinfo class TestCommand(Command_Handler_Prototype): + """ Example class. You should read source if you wish to understate how it works. + Generally, it presents a "master" that giudes user through to calculate something. + """ name = 'testcommand' description = 'a noddy example command' def __init__(self): + """ Init internal constants. """ Command_Handler_Prototype.__init__(self) self.pi = 3.14 self.initial = {'execute':self.cmdFirstStage} def cmdFirstStage(self,conn,request): + """ Determine """ # This is the only place this should be repeated as all other stages should have SessionIDs try: session = request.getTagAttr('command','sessionid') diff --git a/src/common/xmpp/dispatcher.py b/src/common/xmpp/dispatcher.py index 2a0c4b4eb..4e8b911df 100644 --- a/src/common/xmpp/dispatcher.py +++ b/src/common/xmpp/dispatcher.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: dispatcher.py,v 1.31 2005/03/08 19:36:29 snakeru Exp $ +# $Id: dispatcher.py,v 1.34 2005/05/02 08:36:41 snakeru Exp $ """ Main xmpppy mechanism. Provides library with methods to assign different handlers @@ -55,6 +55,7 @@ class Dispatcher(PlugIn): self.handlers=handlers def _init(self): + """ Registers default namespaces/protocols/handlers. Used internally. """ self.RegisterNamespace('unknown') self.RegisterNamespace(NS_STREAMS) self.RegisterNamespace(self._owner.defaultNamespace) @@ -62,6 +63,7 @@ class Dispatcher(PlugIn): self.RegisterProtocol('presence',Presence) self.RegisterProtocol('message',Message) self.RegisterDefaultHandler(self.returnStanzaHandler) +# self.RegisterHandler('error',self.streamErrorHandler,xmlns=NS_STREAMS) def plugin(self, owner): """ Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally.""" @@ -74,6 +76,7 @@ class Dispatcher(PlugIn): self.StreamInit() def plugout(self): + """ Prepares instance to be destructed. """ self.Stream.dispatch=None self.Stream.DEBUG=None self.Stream.features=None @@ -99,7 +102,10 @@ class Dispatcher(PlugIn): Returns: 1) length of processed data if some data were processed; 2) '0' string if no data were processed but link is alive; - 3) 0 (zero) if underlying connection is closed.""" + 3) 0 (zero) if underlying connection is closed. + Take note that in case of disconnection detect during Process() call + disconnect handlers are called automatically. + """ if time.time() > self._lastIncome + 60: #1 min iq = Iq('get', NS_LAST, to=self._owner.Server) self.send(iq) @@ -107,10 +113,12 @@ class Dispatcher(PlugIn): self.disconnected() for handler in self._cycleHandlers: handler(self) if self._owner.Connection.pending_data(timeout): - data=self._owner.Connection.receive() + try: data=self._owner.Connection.receive() + except IOError: return self.Stream.Parse(data) - self._lastIncome = time.time() - return len(data) + if data: + self._lastIncome = time.time() + return len(data) return '0' # It means that nothing is received but link is alive. def RegisterNamespace(self,xmlns,order='info'): @@ -190,6 +198,16 @@ class Dispatcher(PlugIn): if stanza.getType() in ['get','set']: conn.send(Error(stanza,ERR_FEATURE_NOT_IMPLEMENTED)) + def streamErrorHandler(self,conn,error): + name,text='error',error.getData() + for tag in error.getChildren(): + if tag.getNamespace()==NS_XMPP_STREAMS: + if tag.getName()=='text': text=tag.getData() + else: name=tag.getName() + if name in stream_exceptions.keys(): exc=stream_exceptions[name] + else: exc=StreamError + raise exc((name,text)) + def RegisterCycleHandler(self,handler): """ Register handler that will be called on every Dispatcher.Process() call. """ if handler not in self._cycleHandlers: self._cycleHandlers.append(handler) diff --git a/src/common/xmpp/features.py b/src/common/xmpp/features.py index 985373340..9660077db 100644 --- a/src/common/xmpp/features.py +++ b/src/common/xmpp/features.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: features.py,v 1.19 2004/12/25 20:06:59 snakeru Exp $ +# $Id: features.py,v 1.20 2005/04/30 07:43:01 snakeru Exp $ """ This module contains variable stuff that is not worth splitting into separate modules. @@ -88,10 +88,9 @@ def getRegInfo(disp,host,info={}): if df: return DataForm(node=df) df=DataForm(typ='form') for i in resp.getQueryPayload(): - try: #FIXME: temporary patch by Alexey to make it work :| - if i.getName()=='instructions': df.addInstructions(i.getData()) - else: df.setField(i.getName()).setValue(i.getData()) - except: pass + if type(i)<>type(iq): pass + elif i.getName()=='instructions': df.addInstructions(i.getData()) + else: df.setField(i.getName()).setValue(i.getData()) return df def register(disp,host,info): diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index afc6f4033..9fafb0d0e 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: protocol.py,v 1.39 2005/03/08 19:36:29 snakeru Exp $ +# $Id: protocol.py,v 1.41 2005/05/02 08:36:41 snakeru Exp $ """ Protocol module contains tools that is needed for processing of @@ -28,13 +28,13 @@ NS_AUTH ='jabber:iq:auth' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' NS_BROWSE ='jabber:iq:browse' NS_CLIENT ='jabber:client' +NS_COMMANDS ='http://jabber.org/protocol/commands' NS_COMPONENT_ACCEPT='jabber:component:accept' NS_DATA ='jabber:x:data' # JEP-0004 NS_DELAY ='jabber:x:delay' NS_DIALBACK ='jabber:server:dialback' NS_DISCO_INFO ='http://jabber.org/protocol/disco#info' NS_DISCO_ITEMS ='http://jabber.org/protocol/disco#items' -NS_ENCRYPTED ='jabber:x:encrypted' # JEP-0027 NS_GROUPCHAT ='gc-1.0' NS_IBB ='http://jabber.org/protocol/ibb' NS_INVISIBLE ='presence-invisible' # jabberd2 @@ -42,8 +42,9 @@ NS_IQ ='iq' # jabberd2 NS_LAST ='jabber:iq:last' NS_MESSAGE ='message' # jabberd2 NS_MUC ='http://jabber.org/protocol/muc' -NS_MUC_OWNER ='http://jabber.org/protocol/muc#owner' +NS_MUC_USER ='http://jabber.org/protocol/muc#user' NS_MUC_ADMIN ='http://jabber.org/protocol/muc#admin' +NS_MUC_OWNER ='http://jabber.org/protocol/muc#owner' NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # JEP-0013 NS_PRESENCE ='presence' # jabberd2 NS_PRIVACY ='jabber:iq:privacy' @@ -55,7 +56,6 @@ NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' NS_SEARCH ='jabber:iq:search' NS_SERVER ='jabber:server' NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' -NS_SIGNED ='jabber:x:signed' # JEP-0027 NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' NS_STREAMS ='http://etherx.jabber.org/streams' NS_TIME ='jabber:iq:time' @@ -63,7 +63,9 @@ NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' NS_VACATION ='http://jabber.org/protocol/vacation' NS_VCARD ='vcard-temp' NS_VERSION ='jabber:iq:version' +NS_ENCRYPTED ='jabber:x:encrypted' # JEP-0027 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' +NS_SIGNED ='jabber:x:signed' # JEP-0027 xmpp_stream_error_conditions=""" bad-format -- -- -- The entity has sent XML that cannot be processed. @@ -140,8 +142,60 @@ def isResultNode(node): def isErrorNode(node): """ Returns true if the node is a negative reply. """ return node and node.getType()=='error' + class NodeProcessed(Exception): """ Exception that should be raised by handler when the handling should be stopped. """ +class StreamError(Exception): + """ Base exception class for stream errors.""" +class BadFormat(StreamError): pass +class BadNamespacePrefix(StreamError): pass +class Conflict(StreamError): pass +class ConnectionTimeout(StreamError): pass +class HostGone(StreamError): pass +class HostUnknown(StreamError): pass +class ImproperAddressing(StreamError): pass +class InternalServerError(StreamError): pass +class InvalidFrom(StreamError): pass +class InvalidID(StreamError): pass +class InvalidNamespace(StreamError): pass +class InvalidXML(StreamError): pass +class NotAuthorized(StreamError): pass +class PolicyViolation(StreamError): pass +class RemoteConnectionFailed(StreamError): pass +class ResourceConstraint(StreamError): pass +class RestrictedXML(StreamError): pass +class SeeOtherHost(StreamError): pass +class SystemShutdown(StreamError): pass +class UndefinedCondition(StreamError): pass +class UnsupportedEncoding(StreamError): pass +class UnsupportedStanzaType(StreamError): pass +class UnsupportedVersion(StreamError): pass +class XMLNotWellFormed(StreamError): pass + +stream_exceptions = {'bad-format': BadFormat, + 'bad-namespace-prefix': BadNamespacePrefix, + 'conflict': Conflict, + 'connection-timeout': ConnectionTimeout, + 'host-gone': HostGone, + 'host-unknown': HostUnknown, + 'improper-addressing': ImproperAddressing, + 'internal-server-error': InternalServerError, + 'invalid-from': InvalidFrom, + 'invalid-id': InvalidID, + 'invalid-namespace': InvalidNamespace, + 'invalid-xml': InvalidXML, + 'not-authorized': NotAuthorized, + 'policy-violation': PolicyViolation, + 'remote-connection-failed': RemoteConnectionFailed, + 'resource-constraint': ResourceConstraint, + 'restricted-xml': RestrictedXML, + 'see-other-host': SeeOtherHost, + 'system-shutdown': SystemShutdown, + 'undefined-condition': UndefinedCondition, + 'unsupported-encoding': UnsupportedEncoding, + 'unsupported-stanza-type': UnsupportedStanzaType, + 'unsupported-version': UnsupportedVersion, + 'xml-not-well-formed': XMLNotWellFormed} class JID: """ JID object. JID can be built from string, modified, compared, serialised into string. """ @@ -345,6 +399,16 @@ class Presence(Protocol): def getStatus(self): """ Returns the status string of the message. """ return self.getTagData('status') + def setPriority(self,val): + """ Sets the priority of the message. """ + self.setTagData('priority',val) + def setShow(self,val): + """ Sets the show value of the message. """ + self.setTagData('show',val) + def setStatus(self,val): + """ Sets the status string of the message. """ + self.setTagData('status',val) + def _muc_getItemAttr(self,tag,attr): for xtag in self.getTags('x'): for child in xtag.getTags(tag): @@ -373,15 +437,6 @@ class Presence(Protocol): def getStatusCode(self): """Returns the status code of the presence (for groupchat)""" return self._muc_getItemAttr('status','code') - def setPriority(self,val): - """ Sets the priority of the message. """ - self.setTagData('priority',val) - def setShow(self,val): - """ Sets the show value of the message. """ - self.setTagData('show',val) - def setStatus(self,val): - """ Sets the status string of the message. """ - self.setTagData('status',val) class Iq(Protocol): """ XMPP Iq object - get/set dialog mechanism. """ @@ -613,6 +668,7 @@ class DataForm(Node): for i in field.getTags('value'): val.append(i.getData()) else: val=field.getTagData('value') ret[name]=val + if self.getTag('instructions'): ret['instructions']=self.getInstructions() return ret def __getitem__(self,name): """ Simple dictionary interface for getting datafields values by their names.""" diff --git a/src/common/xmpp/roster.py b/src/common/xmpp/roster.py index 6d6cba4da..0ceef00a1 100644 --- a/src/common/xmpp/roster.py +++ b/src/common/xmpp/roster.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: roster.py,v 1.16 2005/02/25 05:49:03 snakeru Exp $ +# $Id: roster.py,v 1.17 2005/05/02 08:38:49 snakeru Exp $ """ Simple roster implementation. Can be used though for different tasks like @@ -98,7 +98,7 @@ class Roster(PlugIn): if not pres.getTimestamp(): pres.setTimestamp() res['timestamp']=pres.getTimestamp() elif typ=='unavailable' and item['resources'].has_key(jid.getResource()): del item['resources'][jid.getResource()] -# Need to handle type='error' also + # Need to handle type='error' also def _getItemData(self,jid,dataname): """ Return specific jid's representation in internal format. Used internally. """ @@ -159,9 +159,6 @@ class Roster(PlugIn): def getItems(self): """ Return list of all [bare] JIDs that the roster is currently tracks.""" return self._data.keys() - def getRaw(self): - """ Returns internal representation of roster.""" - return self._data def keys(self): """ Same as getItems. Provided for the sake of dictionary interface.""" return self._data.keys() @@ -184,3 +181,6 @@ class Roster(PlugIn): """ Unauthorise JID 'jid'. Use for declining authorisation request or for removing existing authorization. """ self._owner.send(Presence(jid,'unsubscribed')) + def getRaw(self): + """Returns the internal data representation of the roster.""" + return self._data diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py index 7d2da078c..3d360ab58 100644 --- a/src/common/xmpp/simplexml.py +++ b/src/common/xmpp/simplexml.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: simplexml.py,v 1.26 2005/04/10 08:21:35 snakeru Exp $ +# $Id: simplexml.py,v 1.27 2005/04/30 07:20:27 snakeru Exp $ """Simplexml module provides xmpppy library with all needed tools to handle XML nodes and XML streams. I'm personally using it in many other separate projects. It is designed to be as standalone as possible.""" @@ -77,9 +77,9 @@ class Node: else: self.data.append(ustr(i)) def __str__(self,fancy=0): - s = (fancy-1) * 2 * ' ' + "<" + self.name """ Method used to dump node into textual representation. if "fancy" argument is set to True produces indented output for readability.""" + s = (fancy-1) * 2 * ' ' + "<" + self.name if self.namespace: if not self.parent or self.parent.namespace!=self.namespace: s = s + ' xmlns="%s"'%self.namespace @@ -249,7 +249,7 @@ class Node: raise AttributeError class T: - """ Auxiliary class used to quick acces to node's child nodes. """ + """ Auxiliary class used to quick access to node's child nodes. """ def __init__(self,node): self.__dict__['node']=node def __getattr__(self,attr): return self.node.setTag(attr) def __setattr__(self,attr,val): diff --git a/src/common/xmpp/transports.py b/src/common/xmpp/transports.py index 7cf9c9a84..7af9dcfa8 100644 --- a/src/common/xmpp/transports.py +++ b/src/common/xmpp/transports.py @@ -12,7 +12,7 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -# $Id: transports.py,v 1.15 2004/12/25 20:06:59 snakeru Exp $ +# $Id: transports.py,v 1.18 2005/04/30 08:56:36 snakeru Exp $ """ This module contains the low-level implementations of xmpppy connect methods or @@ -42,6 +42,7 @@ class error: """Serialise exception into pre-cached descriptive string.""" return self._comment +BUFLEN=1024 class TCPsocket(PlugIn): """ This class defines direct TCP connection method. """ def __init__(self, server=None): @@ -89,21 +90,27 @@ class TCPsocket(PlugIn): del self._owner.Connection def receive(self): - """ Reads all pending incoming data. Calls owner's disconnected() method if appropriate.""" - try: received = self._recv(1024) + """ Reads all pending incoming data. + In case of disconnection calls owner's disconnected() method and then raises IOError exception.""" + try: received = self._recv(BUFLEN) + except socket.sslerror: + self._seen_data=0 + return '' except: received = '' - while select.select([self._sock],[],[],0)[0]: - try: add = self._recv(1024) + while self.pending_data(0): + try: add = self._recv(BUFLEN) except: add='' received +=add if not add: break if len(received): # length of 0 means disconnect + self._seen_data=1 self.DEBUG(received,'got') else: self.DEBUG('Socket error while receiving data','error') self._owner.disconnected() + raise IOError("Disconnected from server") return received def send(self,raw_data): @@ -167,18 +174,28 @@ class HTTPPROXYsocket(TCPsocket): connector.append('Proxy-Authorization: Basic '+credentials) connector.append('\r\n') self.send('\r\n'.join(connector)) - reply = self.receive().replace('\r','') + try: reply = self.receive().replace('\r','') + except IOError: + self.DEBUG('Proxy suddenly disconnected','error') + self._owner.disconnected() + return try: proto,code,desc=reply.split('\n')[0].split(' ',2) except: raise error('Invalid proxy reply') if code<>'200': self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error') self._owner.disconnected() return - while reply.find('\n\n') == -1: reply += self.receive().replace('\r','') + while reply.find('\n\n') == -1: + try: reply += self.receive().replace('\r','') + except IOError: + self.DEBUG('Proxy suddenly disconnected','error') + self._owner.disconnected() + return self.DEBUG("Authentification successfull. Jabber server contacted.",'ok') return 'ok' def DEBUG(self,text,severity): + """Overwrites DEBUG tag to allow debug output be presented as "CONNECTproxy".""" return self._owner.DEBUG(DBG_CONNECT_PROXY,text,severity) class TLS(PlugIn): @@ -202,8 +219,8 @@ class TLS(PlugIn): """ Unregisters TLS handler's from owner's dispatcher. Take note that encription can not be stopped once started. You can only break the connection and start over.""" self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS) -# self._owner.UnregisterHandler('proceed',self.StartTLSHandler,xmlns=NS_TLS) -# self._owner.UnregisterHandler('failure',self.StartTLSHandler,xmlns=NS_TLS) + self._owner.UnregisterHandlerOnce('proceed',self.StartTLSHandler,xmlns=NS_TLS) + self._owner.UnregisterHandlerOnce('failure',self.StartTLSHandler,xmlns=NS_TLS) def FeaturesHandler(self, conn, feats): """ Used to analyse server tag for TLS support. @@ -217,14 +234,25 @@ class TLS(PlugIn): self._owner.Connection.send(''%NS_TLS) raise NodeProcessed + def pending_data(self,timeout=0): + """ Returns true if there possible is a data ready to be read. """ + return self._tcpsock._seen_data or select.select([self._tcpsock._sock],[],[],timeout)[0] + def _startSSL(self): """ Immidiatedly switch socket to TLS mode. Used internally.""" + """ Here we should switch pending_data to hint mode.""" tcpsock=self._owner.Connection tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) tcpsock._sslIssuer = tcpsock._sslObj.issuer() tcpsock._sslServer = tcpsock._sslObj.server() tcpsock._recv = tcpsock._sslObj.read tcpsock._send = tcpsock._sslObj.write + + tcpsock._seen_data=1 + self._tcpsock=tcpsock + tcpsock.pending_data=self.pending_data + tcpsock._sock.setblocking(0) + self.starttls='success' def StartTLSHandler(self, conn, starttls):