persistent HTTP connections in BOSH roughly implemented, added hack for openfire incapability of after-SASL-stream-restart-response in BOSH, changed doubles quotes to single

This commit is contained in:
tomk 2008-07-18 00:34:49 +00:00
parent cecce21c53
commit a58618c843
15 changed files with 471 additions and 499 deletions

View file

@ -550,6 +550,7 @@ class Connection(ConnectionHandlers):
self._proxy['bosh_hold'] = '1' self._proxy['bosh_hold'] = '1'
self._proxy['bosh_wait'] = '60' self._proxy['bosh_wait'] = '60'
self._proxy['bosh_content'] = 'text/xml; charset=utf-8' self._proxy['bosh_content'] = 'text/xml; charset=utf-8'
self._proxy['wait_for_restart_response'] = False
log.info('Connecting to %s: [%s:%d]', self.name, log.info('Connecting to %s: [%s:%d]', self.name,

View file

@ -143,10 +143,10 @@ class SASL(PlugIn):
if "DIGEST-MD5" in mecs: if "DIGEST-MD5" in mecs:
node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'}) node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
elif "PLAIN" in mecs: elif "PLAIN" in mecs:
sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server, sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server,
self.username, self.password) self.username, self.password)
node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'}, node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},
payload=[base64.encodestring(sasl_data).replace('\n','')]) payload=[base64.encodestring(sasl_data).replace('\n','')])
else: else:
self.startsasl='failure' self.startsasl='failure'
log.error('I can only use DIGEST-MD5 and PLAIN mecanisms.') log.error('I can only use DIGEST-MD5 and PLAIN mecanisms.')
@ -173,12 +173,19 @@ class SASL(PlugIn):
self.startsasl='success' self.startsasl='success'
log.info('Successfully authenticated with remote server.') log.info('Successfully authenticated with remote server.')
handlers=self._owner.Dispatcher.dumpHandlers() handlers=self._owner.Dispatcher.dumpHandlers()
# save old features. They will be used in case we won't get response on
# stream restart after SASL auth (happens with XMPP over BOSH with Openfire)
old_features = self._owner.Dispatcher.Stream.features
self._owner.Dispatcher.PlugOut() self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True) dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True,
old_features=old_features)
self._owner.Dispatcher.restoreHandlers(handlers) self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User = self.username self._owner.User = self.username
if self.on_sasl : if self.on_sasl :
self.on_sasl () self.on_sasl()
raise NodeProcessed raise NodeProcessed
########################################3333 ########################################3333
incoming_data = challenge.getData() incoming_data = challenge.getData()
@ -232,7 +239,7 @@ class SASL(PlugIn):
class NonBlockingNonSASL(PlugIn): class NonBlockingNonSASL(PlugIn):
''' Implements old Non-SASL (JEP-0078) authentication used ''' Implements old Non-SASL (JEP-0078) authentication used
in jabberd1.4 and transport authentication. in jabberd1.4 and transport authentication.
''' '''
def __init__(self, user, password, resource, on_auth): def __init__(self, user, password, resource, on_auth):
''' Caches username, password and resource for auth. ''' ''' Caches username, password and resource for auth. '''
@ -331,16 +338,6 @@ class NonBlockingBind(PlugIn):
PlugIn.__init__(self) PlugIn.__init__(self)
self.bound=None self.bound=None
def FeaturesHandler(self,conn,feats):
""" Determine if server supports resource binding and set some internal attributes accordingly. """
if not feats.getTag('bind',namespace=NS_BIND):
self.bound='failure'
log.error('Server does not requested binding.')
return
if feats.getTag('session',namespace=NS_SESSION): self.session=1
else: self.session=-1
self.bound=[]
def plugin(self, owner): def plugin(self, owner):
''' Start resource binding, if allowed at this time. Used internally. ''' ''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features: if self._owner.Dispatcher.Stream.features:
@ -348,7 +345,23 @@ class NonBlockingBind(PlugIn):
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features) self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
except NodeProcessed: except NodeProcessed:
pass pass
else: self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) else:
self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
def FeaturesHandler(self,conn,feats):
''' Determine if server supports resource binding and set some internal attributes accordingly. '''
if not feats.getTag('bind',namespace=NS_BIND):
log.error('Server does not requested binding.')
# we try to bind resource anyway
#self.bound='failure'
self.bound=[]
return
if feats.getTag('session',namespace=NS_SESSION):
self.session=1
else:
self.session=-1
self.bound=[]
def plugout(self): def plugout(self):
''' Remove Bind handler from owner's dispatcher. Used internally. ''' ''' Remove Bind handler from owner's dispatcher. Used internally. '''
@ -404,77 +417,3 @@ class NonBlockingBind(PlugIn):
self.session = 0 self.session = 0
self.on_bound(None) self.on_bound(None)
class NBComponentBind(PlugIn):
''' ComponentBind some JID to the current connection to allow
router know of our location.
'''
def __init__(self):
PlugIn.__init__(self)
self.bound=None
self.needsUnregister=None
def plugin(self,owner):
''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else:
self._owner.RegisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
self.needsUnregister = 1
def plugout(self):
''' Remove ComponentBind handler from owner's dispatcher. Used internally. '''
if self.needsUnregister:
self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS)
def Bind(self, domain = None, on_bind = None):
''' Perform binding. Use provided domain name (if not provided). '''
self._owner.onreceive(self._on_bound)
self.on_bind = on_bind
def _on_bound(self, resp):
if data:
self.Dispatcher.ProcessNonBlocking(data)
if self.bound is None:
return
self._owner.onreceive(None)
self._owner.SendAndWaitForResponse(
Protocol('bind', attrs={'name':domain}, xmlns=NS_COMPONENT_1),
func=self._on_bind_reponse)
def _on_bind_reponse(self, res):
if resp and resp.getAttr('error'):
log.error('Binding failed: %s.' % resp.getAttr('error'))
elif resp:
log.info('Successfully bound.')
if self.on_bind:
self.on_bind('ok')
else:
log.error('Binding failed: timeout expired.')
if self.on_bind:
self.on_bind(None)
def FeaturesHandler(self,conn,feats):
""" Determine if server supports resource binding and set some internal attributes accordingly. """
if not feats.getTag('bind',namespace=NS_BIND):
self.bound='failure'
log.error('Server does not requested binding.')
return
if feats.getTag('session',namespace=NS_SESSION): self.session=1
else: self.session=-1
self.bound=[]
def Bind(self,domain=None):
""" Perform binding. Use provided domain name (if not provided). """
while self.bound is None and self._owner.Process(1): pass
resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
if resp and resp.getAttr('error'):
log.error('Binding failed: %s.'%resp.getAttr('error'))
elif resp:
log.info('Successfully bound.')
return 'ok'
else:
log.error('Binding failed: timeout expired.')
return ''

View file

@ -24,9 +24,6 @@ class NonBlockingBOSH(NonBlockingTransport):
# (see http://www.xmpp.org/extensions/xep-0124.html#rids) # (see http://www.xmpp.org/extensions/xep-0124.html#rids)
r = random.Random() r = random.Random()
r.seed() r.seed()
global FAKE_DESCRIPTOR
FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
self.fake_fd = FAKE_DESCRIPTOR
self.bosh_rid = r.getrandbits(50) self.bosh_rid = r.getrandbits(50)
self.bosh_sid = None self.bosh_sid = None
if locale.getdefaultlocale()[0]: if locale.getdefaultlocale()[0]:
@ -35,7 +32,7 @@ class NonBlockingBOSH(NonBlockingTransport):
self.bosh_xml_lang = 'en' self.bosh_xml_lang = 'en'
self.http_version = 'HTTP/1.1' self.http_version = 'HTTP/1.1'
self.http_persistent = False self.http_persistent = True
self.http_pipelining = False self.http_pipelining = False
self.bosh_to = domain self.bosh_to = domain
@ -57,33 +54,38 @@ class NonBlockingBOSH(NonBlockingTransport):
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
global FAKE_DESCRIPTOR
FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
self.fd = FAKE_DESCRIPTOR
self.http_persistent = True
self.http_socks.append(self.get_http_socket()) self.http_socks.append(self.get_http_socket())
self.tcp_connection_started() self.tcp_connection_started()
# this connect() is not needed because sockets can be connected on send but # this connect() is not needed because sockets can be connected on send but
# we need to know if host is reachable in order to invoke callback for # we need to know if host is reachable in order to invoke callback for
# failure when connecting (it's different than callback for errors # connecting failurei eventually (it's different than callback for errors
# occurring after connection is etabilished) # occurring after connection is etabilished)
self.http_socks[0].connect( self.http_socks[0].connect(
conn_5tuple = conn_5tuple, conn_5tuple = conn_5tuple,
on_connect = lambda: self._on_connect(self.http_socks[0]), on_connect = lambda: self._on_connect(self.http_socks[0]),
on_connect_failure = self._on_connect_failure) on_connect_failure = self._on_connect_failure)
def set_timeout(self, timeout):
if self.state in [CONNECTING, CONNECTED] and self.fd != -1:
def get_fd(self): NonBlockingTransport.set_timeout(self, timeout)
return self.fake_fd else:
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd))
def on_http_request_possible(self): def on_http_request_possible(self):
''' '''
Called after HTTP response is received - another request is possible. Called after HTTP response is received - another request is possible.
There should be always one pending request on BOSH CM. There should be always one pending request on BOSH CM.
''' '''
log.info('on_http_req possible, stanzas in list: %s, state:\n%s' % log.info('on_http_req possible state:\n%s' % self.get_current_state())
(self.stanzas_to_send, self.get_current_state())) # if one of sockets is connecting, sth is about to be sent
# if one of sockets is connecting, sth is about to be sent - we don't have to # if there is a pending request, we shouldn't send another one
# send request from here
for s in self.http_socks: for s in self.http_socks:
if s.state==CONNECTING or s.pending_requests>0: return if s.state==CONNECTING or s.pending_requests>0: return
self.flush_stanzas() self.flush_stanzas()
@ -96,19 +98,17 @@ class NonBlockingBOSH(NonBlockingTransport):
tmp = self.prio_bosh_stanza tmp = self.prio_bosh_stanza
self.prio_bosh_stanza = None self.prio_bosh_stanza = None
else: else:
tmp = self.stanzas_to_send if self.stanzas_to_send:
self.stanzas_to_send = [] tmp = self.stanzas_to_send.pop(0)
else:
tmp = []
self.send_http(tmp) self.send_http(tmp)
def send(self, stanza, now=False): def send(self, stanza, now=False):
# body tags should be send only via send_http() # body tags should be send only via send_http()
assert(not isinstance(stanza, BOSHBody)) assert(not isinstance(stanza, BOSHBody))
now = True self.send_http([stanza])
if now:
self.send_http([stanza])
else:
self.stanzas_to_send.append(stanza)
def send_http(self, payload): def send_http(self, payload):
@ -130,12 +130,16 @@ class NonBlockingBOSH(NonBlockingTransport):
else: else:
# no socket was picked but one is about to connect - save the stanza and # no socket was picked but one is about to connect - save the stanza and
# return # return
log.info('send_http: no free socket:\n%s' % self.get_current_state())
if self.prio_bosh_stanza: if self.prio_bosh_stanza:
payload = self.merge_stanzas(payload, self.prio_bosh_stanza) payload = self.merge_stanzas(payload, self.prio_bosh_stanza)
if payload is None: if payload is None:
log.error('Error in BOSH socket handling - unable to send %s because %s\ # if we cant merge the stanzas (both are BOSH <body>), add the current to
is already about to be sent' % (payload, self.prio_bosh_stanza)) # queue to be sent later
self.disconnect() self.stanzas_to_send.append(bosh_stanza)
log.warn('in BOSH send_http - unable to send %s because %s\
is already about to be sent' % (str(payload), str(self.prio_bosh_stanza)))
return
self.prio_bosh_stanza = payload self.prio_bosh_stanza = payload
def merge_stanzas(self, s1, s2): def merge_stanzas(self, s1, s2):
@ -156,9 +160,11 @@ class NonBlockingBOSH(NonBlockingTransport):
def get_current_state(self): def get_current_state(self):
t = '\t\tSOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
for s in self.http_socks: for s in self.http_socks:
t = '%s\t\t%s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests) t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests)
t = '%s------ prio stanza to send: %s, queued stanzas: %s' \
% (t, self.prio_bosh_stanza, self.stanzas_to_send)
return t return t
@ -181,9 +187,10 @@ class NonBlockingBOSH(NonBlockingTransport):
return return
# being here means there are only CONNECTED scokets with pending requests. # being here means there are only CONNECTED scokets with pending requests.
# Lets create and connect another one # Lets create and connect another one
s = self.get_http_socket() if len(self.http_socks) < 2:
self.http_socks.append(s) s = self.get_http_socket()
self.connect_and_flush(s) self.http_socks.append(s)
self.connect_and_flush(s)
return return
@ -219,7 +226,6 @@ class NonBlockingBOSH(NonBlockingTransport):
attrs={ 'to': self.bosh_to, attrs={ 'to': self.bosh_to,
'sid': self.bosh_sid, 'sid': self.bosh_sid,
'xml:lang': self.bosh_xml_lang, 'xml:lang': self.bosh_xml_lang,
'xmpp:version': '1.0',
'xmpp:restart': 'true', 'xmpp:restart': 'true',
'xmlns:xmpp': 'urn:xmpp:xbosh'}) 'xmlns:xmpp': 'urn:xmpp:xbosh'})
@ -239,7 +245,8 @@ class NonBlockingBOSH(NonBlockingTransport):
on_http_request_possible = self.on_http_request_possible, on_http_request_possible = self.on_http_request_possible,
http_uri = self.bosh_host, http_uri = self.bosh_host,
http_port = self.bosh_port, http_port = self.bosh_port,
http_version = self.http_version) http_version = self.http_version,
http_persistent = self.http_persistent)
if self.current_recv_handler: if self.current_recv_handler:
s.onreceive(self.current_recv_handler) s.onreceive(self.current_recv_handler)
return s return s
@ -251,9 +258,15 @@ class NonBlockingBOSH(NonBlockingTransport):
for s in self.http_socks: for s in self.http_socks:
s.onreceive(recv_handler) s.onreceive(recv_handler)
def http_socket_disconnect(self, socket):
if self.http_persistent:
self.disconnect()
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
if self.state == DISCONNECTED: return if self.state == DISCONNECTED: return
self.fd = -1
for s in self.http_socks: for s in self.http_socks:
s.disconnect(do_callback=False) s.disconnect(do_callback=False)
NonBlockingTransport.disconnect(self, do_callback) NonBlockingTransport.disconnect(self, do_callback)

View file

@ -14,23 +14,23 @@
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $ # $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
""" '''
Provides PlugIn class functionality to develop extentions for xmpppy. Provides PlugIn class functionality to develop extentions for xmpppy.
Also provides Client and Component classes implementations as the Also provides Client and Component classes implementations as the
examples of xmpppy structures usage. examples of xmpppy structures usage.
These classes can be used for simple applications "AS IS" though. These classes can be used for simple applications "AS IS" though.
""" '''
import logging import logging
log = logging.getLogger('gajim.c.x.plugin') log = logging.getLogger('gajim.c.x.plugin')
class PlugIn: class PlugIn:
""" Common xmpppy plugins infrastructure: plugging in/out, debugging. """ ''' Common xmpppy plugins infrastructure: plugging in/out, debugging. '''
def __init__(self): def __init__(self):
self._exported_methods=[] self._exported_methods=[]
def PlugIn(self,owner): def PlugIn(self,owner):
""" Attach to main instance and register ourself and all our staff in it. """ ''' Attach to main instance and register ourself and all our staff in it. '''
self._owner=owner self._owner=owner
log.info('Plugging %s __INTO__ %s' % (self,self._owner)) log.info('Plugging %s __INTO__ %s' % (self,self._owner))
if owner.__dict__.has_key(self.__class__.__name__): if owner.__dict__.has_key(self.__class__.__name__):
@ -53,7 +53,7 @@ class PlugIn:
if hasattr(self,'plugin'): return self.plugin(owner) if hasattr(self,'plugin'): return self.plugin(owner)
def PlugOut(self): def PlugOut(self):
""" Unregister all our staff from main instance and detach from it. """ ''' Unregister all our staff from main instance and detach from it. '''
log.info('Plugging %s __OUT__ of %s.' % (self,self._owner)) log.info('Plugging %s __OUT__ of %s.' % (self,self._owner))
for method in self._exported_methods: del self._owner.__dict__[method.__name__] for method in self._exported_methods: del self._owner.__dict__[method.__name__]
for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method for method in self._old_owners_methods: self._owner.__dict__[method.__name__]=method

View file

@ -138,9 +138,9 @@ class NBCommonClient:
def _try_next_ip(self, err_message=None): def _try_next_ip(self, err_message=None):
'''iterates over IP addresses from getaddinfo''' '''iterates over IP addresses from getaddinfo'''
if err_message: if err_message:
log.debug('While looping over DNS A records: %s' % connect) log.debug('While looping over DNS A records: %s' % err_message)
if self.ip_addresses == []: if self.ip_addresses == []:
self._on_tcp_failure(err_message='Run out of hosts for name %s:%s' % self._on_tcp_failure('Run out of hosts for name %s:%s' %
(self.Server, self.Port)) (self.Server, self.Port))
else: else:
self.current_ip = self.ip_addresses.pop(0) self.current_ip = self.ip_addresses.pop(0)
@ -237,7 +237,7 @@ class NBCommonClient:
self.on_connect(self, self.connected) self.on_connect(self, self.connected)
def raise_event(self, event_type, data): def raise_event(self, event_type, data):
log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data)) log.info('raising event from transport: >>>>>%s<<<<<\n_____________\n%s\n_____________\n' % (event_type,data))
if hasattr(self, 'Dispatcher'): if hasattr(self, 'Dispatcher'):
self.Dispatcher.Event('', event_type, data) self.Dispatcher.Event('', event_type, data)
@ -293,7 +293,7 @@ class NBCommonClient:
self._Resource = 'xmpppy' self._Resource = 'xmpppy'
auth_nb.NonBlockingNonSASL(self._User, self._Password, self._Resource, self._on_old_auth).PlugIn(self) auth_nb.NonBlockingNonSASL(self._User, self._Password, self._Resource, self._on_old_auth).PlugIn(self)
return return
self.onreceive(self._on_start_sasl) #self.onreceive(self._on_start_sasl)
self.SASL.auth() self.SASL.auth()
return True return True
@ -313,8 +313,18 @@ class NBCommonClient:
self.SASL.PlugOut() self.SASL.PlugOut()
elif self.SASL.startsasl == 'success': elif self.SASL.startsasl == 'success':
auth_nb.NonBlockingBind().PlugIn(self) auth_nb.NonBlockingBind().PlugIn(self)
self.onreceive(self._on_auth_bind) if self.protocol_type == 'BOSH':
return True if self.wait_for_restart_response:
self.onreceive(self._on_auth_bind)
else:
self._on_auth_bind(None)
return
elif self.protocol_type == 'XMPP':
auth_nb.NonBlockingBind().PlugIn(self)
self.onreceive(self._on_auth_bind)
return
return
def _on_auth_bind(self, data): def _on_auth_bind(self, data):
if data: if data:
@ -390,6 +400,7 @@ class NonBlockingClient(NBCommonClient):
domain = self.Server, domain = self.Server,
bosh_dict = proxy) bosh_dict = proxy)
self.protocol_type = 'BOSH' self.protocol_type = 'BOSH'
self.wait_for_restart_response = proxy['wait_for_restart_response']
else: else:
if proxy['type'] == 'socks5': if proxy['type'] == 'socks5':

View file

@ -14,7 +14,7 @@
_version_ = '1.4.0' _version_ = '1.4.0'
"""\ '''\
Generic debug class Generic debug class
@ -35,7 +35,7 @@ by the individual classes.
For samples of usage, see samples subdir in distro source, and selftest For samples of usage, see samples subdir in distro source, and selftest
in this code in this code
""" '''
@ -70,7 +70,7 @@ color_bright_cyan = chr(27) + "[36;1m"
color_white = chr(27) + "[37;1m" color_white = chr(27) + "[37;1m"
""" '''
Define your flags in yor modules like this: Define your flags in yor modules like this:
from debug import * from debug import *
@ -99,7 +99,7 @@ DBG_MULTI = [ DBG_INIT, DBG_CONNECTION ]
------- -------
To speed code up, typically for product releases or such To speed code up, typically for product releases or such
use this class instead if you globaly want to disable debugging use this class instead if you globaly want to disable debugging
""" '''
class NoDebug: class NoDebug:
@ -214,7 +214,7 @@ class Debug:
def show( self, msg, flag = None, prefix = None, sufix = None, def show( self, msg, flag = None, prefix = None, sufix = None,
lf = 0 ): lf = 0 ):
""" '''
flag can be of folowing types: flag can be of folowing types:
None - this msg will always be shown if any debugging is on None - this msg will always be shown if any debugging is on
flag - will be shown if flag is active flag - will be shown if flag is active
@ -225,7 +225,7 @@ class Debug:
lf = -1 means strip linefeed if pressent lf = -1 means strip linefeed if pressent
lf = 1 means add linefeed if not pressent lf = 1 means add linefeed if not pressent
""" '''
if self.validate_flags: if self.validate_flags:
self._validate_flag( flag ) self._validate_flag( flag )
@ -343,10 +343,10 @@ class Debug:
def _as_one_list( self, items ): def _as_one_list( self, items ):
""" init param might contain nested lists, typically from group flags. ''' init param might contain nested lists, typically from group flags.
This code organises lst and remves dupes This code organises lst and remves dupes
""" '''
if type( items ) <> type( [] ) and type( items ) <> type( () ): if type( items ) <> type( [] ) and type( items ) <> type( () ):
return [ items ] return [ items ]
r = [] r = []
@ -363,7 +363,7 @@ class Debug:
def _append_unique_str( self, lst, item ): def _append_unique_str( self, lst, item ):
"""filter out any dupes.""" '''filter out any dupes.'''
if type(item) <> type(''): if type(item) <> type(''):
msg2 = '%s' % item msg2 = '%s' % item
raise 'Invalid item type (should be string)',msg2 raise 'Invalid item type (should be string)',msg2
@ -381,10 +381,10 @@ class Debug:
raise 'Invalid debugflag given', msg2 raise 'Invalid debugflag given', msg2
def _remove_dupe_flags( self ): def _remove_dupe_flags( self ):
""" '''
if multiple instances of Debug is used in same app, if multiple instances of Debug is used in same app,
some flags might be created multiple time, filter out dupes some flags might be created multiple time, filter out dupes
""" '''
unique_flags = [] unique_flags = []
for f in self.debug_flags: for f in self.debug_flags:
if f not in unique_flags: if f not in unique_flags:

View file

@ -38,9 +38,6 @@ ID = 0
STREAM_TERMINATOR = '</stream:stream>' STREAM_TERMINATOR = '</stream:stream>'
XML_DECLARATION = '<?xml version=\'1.0\'?>' XML_DECLARATION = '<?xml version=\'1.0\'?>'
# FIXME: ugly # FIXME: ugly
class Dispatcher(): class Dispatcher():
# Why is this here - I needed to redefine Dispatcher for BOSH and easiest way # Why is this here - I needed to redefine Dispatcher for BOSH and easiest way
@ -50,11 +47,11 @@ class Dispatcher():
# I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp/ # I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp/
# If having two kinds of dispatcher will go well, I will rewrite the # If having two kinds of dispatcher will go well, I will rewrite the
def PlugIn(self, client_obj, after_SASL=False): def PlugIn(self, client_obj, after_SASL=False, old_features=None):
if client_obj.protocol_type == 'XMPP': if client_obj.protocol_type == 'XMPP':
XMPPDispatcher().PlugIn(client_obj) XMPPDispatcher().PlugIn(client_obj)
elif client_obj.protocol_type == 'BOSH': elif client_obj.protocol_type == 'BOSH':
BOSHDispatcher().PlugIn(client_obj, after_SASL) BOSHDispatcher().PlugIn(client_obj, after_SASL, old_features)
@ -160,12 +157,11 @@ class XMPPDispatcher(PlugIn):
self.Stream.Parse(data) self.Stream.Parse(data)
# end stream:stream tag received # end stream:stream tag received
if self.Stream and self.Stream.has_received_endtag(): if self.Stream and self.Stream.has_received_endtag():
# FIXME call client method self._owner.disconnect()
self._owner.Connection.disconnect()
return 0 return 0
except ExpatError: except ExpatError:
log.error('Invalid XML received from server. Forcing disconnect.') log.error('Invalid XML received from server. Forcing disconnect.')
self._owner.Connection.disconnect() self._owner.disconnect()
return 0 return 0
if len(self._pendingExceptions) > 0: if len(self._pendingExceptions) > 0:
_pendingException = self._pendingExceptions.pop() _pendingException = self._pendingExceptions.pop()
@ -380,7 +376,6 @@ class XMPPDispatcher(PlugIn):
if not res: if not res:
return return
self._owner.remove_timeout() self._owner.remove_timeout()
print self._expected
if self._expected[self._witid] is None: if self._expected[self._witid] is None:
return return
if self.on_responses.has_key(self._witid): if self.on_responses.has_key(self._witid):
@ -427,7 +422,8 @@ class XMPPDispatcher(PlugIn):
class BOSHDispatcher(XMPPDispatcher): class BOSHDispatcher(XMPPDispatcher):
def PlugIn(self, owner, after_SASL=False): def PlugIn(self, owner, after_SASL=False, old_features=None):
self.old_features = old_features
self.after_SASL = after_SASL self.after_SASL = after_SASL
XMPPDispatcher.PlugIn(self, owner) XMPPDispatcher.PlugIn(self, owner)
@ -437,7 +433,7 @@ class BOSHDispatcher(XMPPDispatcher):
self.Stream.dispatch = self.dispatch self.Stream.dispatch = self.dispatch
self.Stream._dispatch_depth = 2 self.Stream._dispatch_depth = 2
self.Stream.stream_header_received = self._check_stream_start self.Stream.stream_header_received = self._check_stream_start
self.Stream.features = None self.Stream.features = self.old_features
self._metastream = Node('stream:stream') self._metastream = Node('stream:stream')
self._metastream.setNamespace(self._owner.Namespace) self._metastream.setNamespace(self._owner.Namespace)
@ -487,8 +483,7 @@ class BOSHDispatcher(XMPPDispatcher):
self._owner.Connection.bosh_sid = stanza_attrs['sid'] self._owner.Connection.bosh_sid = stanza_attrs['sid']
if stanza_attrs.has_key('terminate'): if stanza_attrs.has_key('terminate'):
# staznas under body still should be passed to XMPP dispatcher self._owner.disconnect()
self._owner.on_disconnect()
if stanza_attrs.has_key('error'): if stanza_attrs.has_key('error'):
# recoverable error # recoverable error
@ -498,6 +493,9 @@ class BOSHDispatcher(XMPPDispatcher):
if children: if children:
for child in children: for child in children:
# if child doesn't have any ns specified, simplexml (or expat) thinks it's
# of parent's (BOSH body) namespace, so we have to rewrite it to
# jabber:client
if child.getNamespace() == NS_HTTP_BIND: if child.getNamespace() == NS_HTTP_BIND:
child.setNamespace(self._owner.defaultNamespace) child.setNamespace(self._owner.defaultNamespace)
XMPPDispatcher.dispatch(self, child, session, direct) XMPPDispatcher.dispatch(self, child, session, direct)

View file

@ -32,10 +32,10 @@ def _on_default_response(disp, iq, cb):
disp.SendAndCallForResponse(iq, _on_response) disp.SendAndCallForResponse(iq, _on_response)
def _discover(disp, ns, jid, node = None, fb2b=0, fb2a=1, cb=None): def _discover(disp, ns, jid, node = None, fb2b=0, fb2a=1, cb=None):
""" Try to obtain info from the remote object. ''' Try to obtain info from the remote object.
If remote object doesn't support disco fall back to browse (if fb2b is true) If remote object doesn't support disco fall back to browse (if fb2b is true)
and if it doesnt support browse (or fb2b is not true) fall back to agents protocol and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
(if gb2a is true). Returns obtained info. Used internally. """ (if gb2a is true). Returns obtained info. Used internally. '''
iq=Iq(to=jid, typ='get', queryNS=ns) iq=Iq(to=jid, typ='get', queryNS=ns)
if node: if node:
iq.setQuerynode(node) iq.setQuerynode(node)
@ -61,11 +61,11 @@ def _discover(disp, ns, jid, node = None, fb2b=0, fb2a=1, cb=None):
# this function is not used in gajim ??? # this function is not used in gajim ???
def discoverItems(disp,jid,node=None, cb=None): def discoverItems(disp,jid,node=None, cb=None):
""" Query remote object about any items that it contains. Return items list. """ ''' Query remote object about any items that it contains. Return items list. '''
""" According to JEP-0030: ''' According to JEP-0030:
query MAY have node attribute query MAY have node attribute
item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes. item: MUST HAVE jid attribute and MAY HAVE name, node, action attributes.
action attribute of item can be either of remove or update value.""" action attribute of item can be either of remove or update value.'''
def _on_response(result_array): def _on_response(result_array):
ret=[] ret=[]
for result in result_array: for result in result_array:
@ -78,11 +78,11 @@ def discoverItems(disp,jid,node=None, cb=None):
# this one is # this one is
def discoverInfo(disp,jid,node=None, cb=None): def discoverInfo(disp,jid,node=None, cb=None):
""" Query remote object about info that it publishes. Returns identities and features lists.""" ''' Query remote object about info that it publishes. Returns identities and features lists.'''
""" According to JEP-0030: ''' According to JEP-0030:
query MAY have node attribute query MAY have node attribute
identity: MUST HAVE category and name attributes and MAY HAVE type attribute. identity: MUST HAVE category and name attributes and MAY HAVE type attribute.
feature: MUST HAVE var attribute""" feature: MUST HAVE var attribute'''
def _on_response(result): def _on_response(result):
identities , features = [] , [] identities , features = [] , []
for i in result: for i in result:
@ -108,11 +108,11 @@ def discoverInfo(disp,jid,node=None, cb=None):
### Registration ### jabber:iq:register ### JEP-0077 ########################### ### Registration ### jabber:iq:register ### JEP-0077 ###########################
def getRegInfo(disp, host, info={}, sync=True): def getRegInfo(disp, host, info={}, sync=True):
""" Gets registration form from remote host. ''' Gets registration form from remote host.
You can pre-fill the info dictionary. You can pre-fill the info dictionary.
F.e. if you are requesting info on registering user joey than specify F.e. if you are requesting info on registering user joey than specify
info as {'username':'joey'}. See JEP-0077 for details. info as {'username':'joey'}. See JEP-0077 for details.
'disp' must be connected dispatcher instance.""" 'disp' must be connected dispatcher instance.'''
iq=Iq('get',NS_REGISTER,to=host) iq=Iq('get',NS_REGISTER,to=host)
for i in info.keys(): for i in info.keys():
iq.setTagData(i,info[i]) iq.setTagData(i,info[i])
@ -144,11 +144,11 @@ def _ReceivedRegInfo(con, resp, agent):
con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,'')) con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
def register(disp, host, info, cb): def register(disp, host, info, cb):
""" Perform registration on remote server with provided info. ''' Perform registration on remote server with provided info.
disp must be connected dispatcher instance. disp must be connected dispatcher instance.
If registration fails you can get additional info from the dispatcher's owner If registration fails you can get additional info from the dispatcher's owner
attributes lastErrNode, lastErr and lastErrCode. attributes lastErrNode, lastErr and lastErrCode.
""" '''
iq=Iq('set', NS_REGISTER, to=host) iq=Iq('set', NS_REGISTER, to=host)
if not isinstance(info, dict): if not isinstance(info, dict):
info=info.asDict() info=info.asDict()
@ -157,16 +157,16 @@ def register(disp, host, info, cb):
disp.SendAndCallForResponse(iq, cb) disp.SendAndCallForResponse(iq, cb)
def unregister(disp, host, cb): def unregister(disp, host, cb):
""" Unregisters with host (permanently removes account). ''' Unregisters with host (permanently removes account).
disp must be connected and authorized dispatcher instance. disp must be connected and authorized dispatcher instance.
Returns true on success.""" Returns true on success.'''
iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
_on_default_response(disp, iq, cb) _on_default_response(disp, iq, cb)
def changePasswordTo(disp, newpassword, host=None, cb = None): def changePasswordTo(disp, newpassword, host=None, cb = None):
""" Changes password on specified or current (if not specified) server. ''' Changes password on specified or current (if not specified) server.
disp must be connected and authorized dispatcher instance. disp must be connected and authorized dispatcher instance.
Returns true on success.""" Returns true on success.'''
if not host: host=disp._owner.Server if not host: host=disp._owner.Server
iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username', iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
payload=[disp._owner.Server]),Node('password',payload=[newpassword])]) payload=[disp._owner.Server]),Node('password',payload=[newpassword])])
@ -177,8 +177,8 @@ def changePasswordTo(disp, newpassword, host=None, cb = None):
#action=[allow|deny] #action=[allow|deny]
def getPrivacyLists(disp): def getPrivacyLists(disp):
""" Requests privacy lists from connected server. ''' Requests privacy lists from connected server.
Returns dictionary of existing lists on success.""" Returns dictionary of existing lists on success.'''
iq = Iq('get', NS_PRIVACY) iq = Iq('get', NS_PRIVACY)
def _on_response(resp): def _on_response(resp):
dict = {'lists': []} dict = {'lists': []}
@ -209,8 +209,8 @@ def getActiveAndDefaultPrivacyLists(disp):
disp.SendAndCallForResponse(iq, _on_response) disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname): def getPrivacyList(disp, listname):
""" Requests specific privacy list listname. Returns list of XML nodes (rules) ''' Requests specific privacy list listname. Returns list of XML nodes (rules)
taken from the server responce.""" taken from the server responce.'''
def _on_response(resp): def _on_response(resp):
if not isResultNode(resp): if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
@ -220,8 +220,8 @@ def getPrivacyList(disp, listname):
disp.SendAndCallForResponse(iq, _on_response) disp.SendAndCallForResponse(iq, _on_response)
def setActivePrivacyList(disp, listname=None, typ='active', cb=None): def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
""" Switches privacy list 'listname' to specified type. ''' Switches privacy list 'listname' to specified type.
By default the type is 'active'. Returns true on success.""" By default the type is 'active'. Returns true on success.'''
if listname: if listname:
attrs={'name':listname} attrs={'name':listname}
else: else:
@ -230,13 +230,13 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
_on_default_response(disp, iq, cb) _on_default_response(disp, iq, cb)
def setDefaultPrivacyList(disp, listname=None): def setDefaultPrivacyList(disp, listname=None):
""" Sets the default privacy list as 'listname'. Returns true on success.""" ''' Sets the default privacy list as 'listname'. Returns true on success.'''
return setActivePrivacyList(disp, listname,'default') return setActivePrivacyList(disp, listname,'default')
def setPrivacyList(disp, listname, tags): def setPrivacyList(disp, listname, tags):
""" Set the ruleset. 'list' should be the simpleXML node formatted ''' Set the ruleset. 'list' should be the simpleXML node formatted
according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) ) according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
Returns true on success.""" Returns true on success.'''
iq = Iq('set', NS_PRIVACY, xmlns = '') iq = Iq('set', NS_PRIVACY, xmlns = '')
list_query = iq.getTag('query').setTag('list', {'name': listname}) list_query = iq.getTag('query').setTag('list', {'name': listname})
for item in tags: for item in tags:
@ -252,6 +252,6 @@ def setPrivacyList(disp, listname, tags):
_on_default_response(disp, iq, None) _on_default_response(disp, iq, None)
def delPrivacyList(disp,listname,cb=None): def delPrivacyList(disp,listname,cb=None):
""" Deletes privacy list 'listname'. Returns true on success.""" ''' Deletes privacy list 'listname'. Returns true on success.'''
iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})]) iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
_on_default_response(disp, iq, cb) _on_default_response(disp, iq, cb)

View file

@ -56,8 +56,7 @@ class IdleQueue:
self.selector = select.poll() self.selector = select.poll()
def remove_timeout(self, fd): def remove_timeout(self, fd):
#log.debug('read timeout removed for fd %s' % fd) log.info('read timeout removed for fd %s' % fd)
print 'read timeout removed for fd %s' % fd
if self.read_timeouts.has_key(fd): if self.read_timeouts.has_key(fd):
del(self.read_timeouts[fd]) del(self.read_timeouts[fd])
@ -73,13 +72,12 @@ class IdleQueue:
def set_read_timeout(self, fd, seconds): def set_read_timeout(self, fd, seconds):
''' set a new timeout, if it is not removed after 'seconds', ''' set a new timeout, if it is not removed after 'seconds',
then obj.read_timeout() will be called ''' then obj.read_timeout() will be called '''
#log.debug('read timeout set for fd %s on %s seconds' % (fd, seconds)) log.info('read timeout set for fd %s on %s seconds' % (fd, seconds))
print 'read timeout set for fd %s on %s seconds' % (fd, seconds)
timeout = self.current_time() + seconds timeout = self.current_time() + seconds
self.read_timeouts[fd] = timeout self.read_timeouts[fd] = timeout
def check_time_events(self): def check_time_events(self):
print 'check time evs' log.info('check time evs')
current_time = self.current_time() current_time = self.current_time()
for fd, timeout in self.read_timeouts.items(): for fd, timeout in self.read_timeouts.items():
if timeout > current_time: if timeout > current_time:

View file

@ -14,10 +14,10 @@
# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $ # $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $
""" '''
Protocol module contains tools that is needed for processing of Protocol module contains tools that is needed for processing of
xmpp-related data structures. xmpp-related data structures.
""" '''
from simplexml import Node,NodeBuilder,ustr from simplexml import Node,NodeBuilder,ustr
import time import time
@ -112,7 +112,7 @@ NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-01
NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
xmpp_stream_error_conditions=""" xmpp_stream_error_conditions='''
bad-format -- -- -- The entity has sent XML that cannot be processed. bad-format -- -- -- The entity has sent XML that cannot be processed.
bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix. bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix.
conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
@ -136,8 +136,8 @@ undefined-condition -- -- -- The error condition is not one of those defined b
unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server. unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server.
unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server. unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server. unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server.
xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.""" xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.'''
xmpp_stanza_error_conditions=""" xmpp_stanza_error_conditions='''
bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
@ -159,15 +159,15 @@ resource-constraint -- 500 -- wait -- The server or recipient lacks the system r
service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service. service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.
subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
undefined-condition -- 500 -- -- undefined-condition -- 500 -- --
unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).""" unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).'''
sasl_error_conditions=""" sasl_error_conditions='''
aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element. aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data. incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data.
invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data. invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data.
invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element. invalid-mechanism -- -- -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element.
mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data. mechanism-too-weak -- -- -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data.
not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data. not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data.
temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.""" temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.'''
ERRORS,_errorcodes={},{} ERRORS,_errorcodes={},{}
for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions), for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
@ -182,16 +182,16 @@ for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_condition
del ns,errname,errpool,err,cond,code,typ,text del ns,errname,errpool,err,cond,code,typ,text
def isResultNode(node): def isResultNode(node):
""" Returns true if the node is a positive reply. """ ''' Returns true if the node is a positive reply. '''
return node and node.getType()=='result' return node and node.getType()=='result'
def isErrorNode(node): def isErrorNode(node):
""" Returns true if the node is a negative reply. """ ''' Returns true if the node is a negative reply. '''
return node and node.getType()=='error' return node and node.getType()=='error'
class NodeProcessed(Exception): class NodeProcessed(Exception):
""" Exception that should be raised by handler when the handling should be stopped. """ ''' Exception that should be raised by handler when the handling should be stopped. '''
class StreamError(Exception): class StreamError(Exception):
""" Base exception class for stream errors.""" ''' Base exception class for stream errors.'''
class BadFormat(StreamError): pass class BadFormat(StreamError): pass
class BadNamespacePrefix(StreamError): pass class BadNamespacePrefix(StreamError): pass
class Conflict(StreamError): pass class Conflict(StreamError): pass
@ -243,13 +243,13 @@ stream_exceptions = {'bad-format': BadFormat,
'xml-not-well-formed': XMLNotWellFormed} 'xml-not-well-formed': XMLNotWellFormed}
class JID: class JID:
""" JID object. JID can be built from string, modified, compared, serialised into string. """ ''' JID object. JID can be built from string, modified, compared, serialised into string. '''
def __init__(self, jid=None, node='', domain='', resource=''): def __init__(self, jid=None, node='', domain='', resource=''):
""" Constructor. JID can be specified as string (jid argument) or as separate parts. ''' Constructor. JID can be specified as string (jid argument) or as separate parts.
Examples: Examples:
JID('node@domain/resource') JID('node@domain/resource')
JID(node='node',domain='domain.org') JID(node='node',domain='domain.org')
""" '''
if not jid and not domain: raise ValueError('JID must contain at least domain name') if not jid and not domain: raise ValueError('JID must contain at least domain name')
elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
elif domain: self.node,self.domain,self.resource=node,domain,resource elif domain: self.node,self.domain,self.resource=node,domain,resource
@ -259,45 +259,45 @@ class JID:
if jid.find('/')+1: self.domain,self.resource=jid.split('/',1) if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
else: self.domain,self.resource=jid,'' else: self.domain,self.resource=jid,''
def getNode(self): def getNode(self):
""" Return the node part of the JID """ ''' Return the node part of the JID '''
return self.node return self.node
def setNode(self,node): def setNode(self,node):
""" Set the node part of the JID to new value. Specify None to remove the node part.""" ''' Set the node part of the JID to new value. Specify None to remove the node part.'''
self.node=node.lower() self.node=node.lower()
def getDomain(self): def getDomain(self):
""" Return the domain part of the JID """ ''' Return the domain part of the JID '''
return self.domain return self.domain
def setDomain(self,domain): def setDomain(self,domain):
""" Set the domain part of the JID to new value.""" ''' Set the domain part of the JID to new value.'''
self.domain=domain.lower() self.domain=domain.lower()
def getResource(self): def getResource(self):
""" Return the resource part of the JID """ ''' Return the resource part of the JID '''
return self.resource return self.resource
def setResource(self,resource): def setResource(self,resource):
""" Set the resource part of the JID to new value. Specify None to remove the resource part.""" ''' Set the resource part of the JID to new value. Specify None to remove the resource part.'''
self.resource=resource self.resource=resource
def getStripped(self): def getStripped(self):
""" Return the bare representation of JID. I.e. string value w/o resource. """ ''' Return the bare representation of JID. I.e. string value w/o resource. '''
return self.__str__(0) return self.__str__(0)
def __eq__(self, other): def __eq__(self, other):
""" Compare the JID to another instance or to string for equality. """ ''' Compare the JID to another instance or to string for equality. '''
try: other=JID(other) try: other=JID(other)
except ValueError: return 0 except ValueError: return 0
return self.resource==other.resource and self.__str__(0) == other.__str__(0) return self.resource==other.resource and self.__str__(0) == other.__str__(0)
def __ne__(self, other): def __ne__(self, other):
""" Compare the JID to another instance or to string for non-equality. """ ''' Compare the JID to another instance or to string for non-equality. '''
return not self.__eq__(other) return not self.__eq__(other)
def bareMatch(self, other): def bareMatch(self, other):
""" Compare the node and domain parts of the JID's for equality. """ ''' Compare the node and domain parts of the JID's for equality. '''
return self.__str__(0) == JID(other).__str__(0) return self.__str__(0) == JID(other).__str__(0)
def __str__(self,wresource=1): def __str__(self,wresource=1):
""" Serialise JID into string. """ ''' Serialise JID into string. '''
if self.node: jid=self.node+'@'+self.domain if self.node: jid=self.node+'@'+self.domain
else: jid=self.domain else: jid=self.domain
if wresource and self.resource: return jid+'/'+self.resource if wresource and self.resource: return jid+'/'+self.resource
return jid return jid
def __hash__(self): def __hash__(self):
""" Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """ ''' Produce hash of the JID, Allows to use JID objects as keys of the dictionary. '''
return hash(self.__str__()) return hash(self.__str__())
class BOSHBody(Node): class BOSHBody(Node):
@ -310,16 +310,16 @@ class BOSHBody(Node):
class Protocol(Node): class Protocol(Node):
""" A "stanza" object class. Contains methods that are common for presences, iqs and messages. """ ''' A "stanza" object class. Contains methods that are common for presences, iqs and messages. '''
def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None): def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
""" Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'. ''' Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
to is the value of 'to' attribure, 'typ' - 'type' attribute to is the value of 'to' attribure, 'typ' - 'type' attribute
frn - from attribure, attrs - other attributes mapping, frn - from attribure, attrs - other attributes mapping,
payload - same meaning as for simplexml payload definition payload - same meaning as for simplexml payload definition
timestamp - the time value that needs to be stamped over stanza timestamp - the time value that needs to be stamped over stanza
xmlns - namespace of top stanza node xmlns - namespace of top stanza node
node - parsed or unparsed stana to be taken as prototype. node - parsed or unparsed stana to be taken as prototype.
""" '''
if not attrs: attrs={} if not attrs: attrs={}
if to: attrs['to']=to if to: attrs['to']=to
if frm: attrs['from']=frm if frm: attrs['from']=frm
@ -336,54 +336,54 @@ class Protocol(Node):
except: pass except: pass
if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
def getTo(self): def getTo(self):
""" Return value of the 'to' attribute. """ ''' Return value of the 'to' attribute. '''
try: return self['to'] try: return self['to']
except: return None except: return None
def getFrom(self): def getFrom(self):
""" Return value of the 'from' attribute. """ ''' Return value of the 'from' attribute. '''
try: return self['from'] try: return self['from']
except: return None except: return None
def getTimestamp(self): def getTimestamp(self):
""" Return the timestamp in the 'yyyymmddThhmmss' format. """ ''' Return the timestamp in the 'yyyymmddThhmmss' format. '''
if self.timestamp: return self.timestamp if self.timestamp: return self.timestamp
return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
def getID(self): def getID(self):
""" Return the value of the 'id' attribute. """ ''' Return the value of the 'id' attribute. '''
return self.getAttr('id') return self.getAttr('id')
def setTo(self,val): def setTo(self,val):
""" Set the value of the 'to' attribute. """ ''' Set the value of the 'to' attribute. '''
self.setAttr('to', JID(val)) self.setAttr('to', JID(val))
def getType(self): def getType(self):
""" Return the value of the 'type' attribute. """ ''' Return the value of the 'type' attribute. '''
return self.getAttr('type') return self.getAttr('type')
def setFrom(self,val): def setFrom(self,val):
""" Set the value of the 'from' attribute. """ ''' Set the value of the 'from' attribute. '''
self.setAttr('from', JID(val)) self.setAttr('from', JID(val))
def setType(self,val): def setType(self,val):
""" Set the value of the 'type' attribute. """ ''' Set the value of the 'type' attribute. '''
self.setAttr('type', val) self.setAttr('type', val)
def setID(self,val): def setID(self,val):
""" Set the value of the 'id' attribute. """ ''' Set the value of the 'id' attribute. '''
self.setAttr('id', val) self.setAttr('id', val)
def getError(self): def getError(self):
""" Return the error-condition (if present) or the textual description of the error (otherwise). """ ''' Return the error-condition (if present) or the textual description of the error (otherwise). '''
errtag=self.getTag('error') errtag=self.getTag('error')
if errtag: if errtag:
for tag in errtag.getChildren(): for tag in errtag.getChildren():
if tag.getName()<>'text': return tag.getName() if tag.getName()<>'text': return tag.getName()
return errtag.getData() return errtag.getData()
def getErrorMsg(self): def getErrorMsg(self):
""" Return the textual description of the error (if present) or the error condition """ ''' Return the textual description of the error (if present) or the error condition '''
errtag=self.getTag('error') errtag=self.getTag('error')
if errtag: if errtag:
for tag in errtag.getChildren(): for tag in errtag.getChildren():
if tag.getName()=='text': return tag.getData() if tag.getName()=='text': return tag.getData()
return self.getError() return self.getError()
def getErrorCode(self): def getErrorCode(self):
""" Return the error code. Obsolete. """ ''' Return the error code. Obsolete. '''
return self.getTagAttr('error','code') return self.getTagAttr('error','code')
def setError(self,error,code=None): def setError(self,error,code=None):
""" Set the error code. Obsolete. Use error-conditions instead. """ ''' Set the error code. Obsolete. Use error-conditions instead. '''
if code: if code:
if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error) if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error) else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
@ -391,40 +391,40 @@ class Protocol(Node):
self.setType('error') self.setType('error')
self.addChild(node=error) self.addChild(node=error)
def setTimestamp(self,val=None): def setTimestamp(self,val=None):
"""Set the timestamp. timestamp should be the yyyymmddThhmmss string.""" '''Set the timestamp. timestamp should be the yyyymmddThhmmss string.'''
if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
self.timestamp=val self.timestamp=val
self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY) self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
def getProperties(self): def getProperties(self):
""" Return the list of namespaces to which belongs the direct childs of element""" ''' Return the list of namespaces to which belongs the direct childs of element'''
props=[] props=[]
for child in self.getChildren(): for child in self.getChildren():
prop=child.getNamespace() prop=child.getNamespace()
if prop not in props: props.append(prop) if prop not in props: props.append(prop)
return props return props
def __setitem__(self,item,val): def __setitem__(self,item,val):
""" Set the item 'item' to the value 'val'.""" ''' Set the item 'item' to the value 'val'.'''
if item in ['to','from']: val=JID(val) if item in ['to','from']: val=JID(val)
return self.setAttr(item,val) return self.setAttr(item,val)
class Message(Protocol): class Message(Protocol):
""" XMPP Message stanza - "push" mechanism.""" ''' XMPP Message stanza - "push" mechanism.'''
def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
""" Create message object. You can specify recipient, text of message, type of message ''' Create message object. You can specify recipient, text of message, type of message
any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. '''
Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
if body: self.setBody(body) if body: self.setBody(body)
if xhtml: self.setXHTML(xhtml) if xhtml: self.setXHTML(xhtml)
if subject is not None: self.setSubject(subject) if subject is not None: self.setSubject(subject)
def getBody(self): def getBody(self):
""" Returns text of the message. """ ''' Returns text of the message. '''
return self.getTagData('body') return self.getTagData('body')
def getXHTML(self, xmllang=None): def getXHTML(self, xmllang=None):
""" Returns serialized xhtml-im element text of the message. ''' Returns serialized xhtml-im element text of the message.
TODO: Returning a DOM could make rendering faster.""" TODO: Returning a DOM could make rendering faster.'''
xhtml = self.getTag('html') xhtml = self.getTag('html')
if xhtml: if xhtml:
if xmllang: if xmllang:
@ -434,18 +434,18 @@ class Message(Protocol):
return str(body) return str(body)
return None return None
def getSubject(self): def getSubject(self):
""" Returns subject of the message. """ ''' Returns subject of the message. '''
return self.getTagData('subject') return self.getTagData('subject')
def getThread(self): def getThread(self):
""" Returns thread of the message. """ ''' Returns thread of the message. '''
return self.getTagData('thread') return self.getTagData('thread')
def setBody(self,val): def setBody(self,val):
""" Sets the text of the message. """ ''' Sets the text of the message. '''
self.setTagData('body',val) self.setTagData('body',val)
def setXHTML(self,val,xmllang=None): def setXHTML(self,val,xmllang=None):
""" Sets the xhtml text of the message (XEP-0071). ''' Sets the xhtml text of the message (XEP-0071).
The parameter is the "inner html" to the body.""" The parameter is the "inner html" to the body.'''
try: try:
if xmllang: if xmllang:
dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom() dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
@ -459,21 +459,21 @@ class Message(Protocol):
print "Error", e print "Error", e
pass #FIXME: log. we could not set xhtml (parse error, whatever) pass #FIXME: log. we could not set xhtml (parse error, whatever)
def setSubject(self,val): def setSubject(self,val):
""" Sets the subject of the message. """ ''' Sets the subject of the message. '''
self.setTagData('subject',val) self.setTagData('subject',val)
def setThread(self,val): def setThread(self,val):
""" Sets the thread of the message. """ ''' Sets the thread of the message. '''
self.setTagData('thread',val) self.setTagData('thread',val)
def buildReply(self,text=None): def buildReply(self,text=None):
""" Builds and returns another message object with specified text. ''' Builds and returns another message object with specified text.
The to, from and thread properties of new message are pre-set as reply to this message. """ The to, from and thread properties of new message are pre-set as reply to this message. '''
m=Message(to=self.getFrom(),frm=self.getTo(),body=text,node=self) m=Message(to=self.getFrom(),frm=self.getTo(),body=text,node=self)
th=self.getThread() th=self.getThread()
if th: m.setThread(th) if th: m.setThread(th)
return m return m
def getStatusCode(self): def getStatusCode(self):
"""Returns the status code of the message (for groupchat config '''Returns the status code of the message (for groupchat config
change)""" change)'''
attrs = [] attrs = []
for xtag in self.getTags('x'): for xtag in self.getTags('x'):
for child in xtag.getTags('status'): for child in xtag.getTags('status'):
@ -481,32 +481,32 @@ class Message(Protocol):
return attrs return attrs
class Presence(Protocol): class Presence(Protocol):
""" XMPP Presence object.""" ''' XMPP Presence object.'''
def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None): def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
""" Create presence object. You can specify recipient, type of message, priority, show and status values ''' Create presence object. You can specify recipient, type of message, priority, show and status values
any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go. any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. '''
Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
if priority: self.setPriority(priority) if priority: self.setPriority(priority)
if show: self.setShow(show) if show: self.setShow(show)
if status: self.setStatus(status) if status: self.setStatus(status)
def getPriority(self): def getPriority(self):
""" Returns the priority of the message. """ ''' Returns the priority of the message. '''
return self.getTagData('priority') return self.getTagData('priority')
def getShow(self): def getShow(self):
""" Returns the show value of the message. """ ''' Returns the show value of the message. '''
return self.getTagData('show') return self.getTagData('show')
def getStatus(self): def getStatus(self):
""" Returns the status string of the message. """ ''' Returns the status string of the message. '''
return self.getTagData('status') return self.getTagData('status')
def setPriority(self,val): def setPriority(self,val):
""" Sets the priority of the message. """ ''' Sets the priority of the message. '''
self.setTagData('priority',val) self.setTagData('priority',val)
def setShow(self,val): def setShow(self,val):
""" Sets the show value of the message. """ ''' Sets the show value of the message. '''
self.setTagData('show',val) self.setTagData('show',val)
def setStatus(self,val): def setStatus(self,val):
""" Sets the status string of the message. """ ''' Sets the status string of the message. '''
self.setTagData('status',val) self.setTagData('status',val)
def _muc_getItemAttr(self,tag,attr): def _muc_getItemAttr(self,tag,attr):
@ -520,25 +520,25 @@ class Presence(Protocol):
return cchild.getData(),cchild.getAttr(attr) return cchild.getData(),cchild.getAttr(attr)
return None,None return None,None
def getRole(self): def getRole(self):
"""Returns the presence role (for groupchat)""" '''Returns the presence role (for groupchat)'''
return self._muc_getItemAttr('item','role') return self._muc_getItemAttr('item','role')
def getAffiliation(self): def getAffiliation(self):
"""Returns the presence affiliation (for groupchat)""" '''Returns the presence affiliation (for groupchat)'''
return self._muc_getItemAttr('item','affiliation') return self._muc_getItemAttr('item','affiliation')
def getNewNick(self): def getNewNick(self):
"""Returns the status code of the presence (for groupchat)""" '''Returns the status code of the presence (for groupchat)'''
return self._muc_getItemAttr('item','nick') return self._muc_getItemAttr('item','nick')
def getJid(self): def getJid(self):
"""Returns the presence jid (for groupchat)""" '''Returns the presence jid (for groupchat)'''
return self._muc_getItemAttr('item','jid') return self._muc_getItemAttr('item','jid')
def getReason(self): def getReason(self):
"""Returns the reason of the presence (for groupchat)""" '''Returns the reason of the presence (for groupchat)'''
return self._muc_getSubTagDataAttr('reason','')[0] return self._muc_getSubTagDataAttr('reason','')[0]
def getActor(self): def getActor(self):
"""Returns the reason of the presence (for groupchat)""" '''Returns the reason of the presence (for groupchat)'''
return self._muc_getSubTagDataAttr('actor','jid')[1] return self._muc_getSubTagDataAttr('actor','jid')[1]
def getStatusCode(self): def getStatusCode(self):
"""Returns the status code of the presence (for groupchat)""" '''Returns the status code of the presence (for groupchat)'''
attrs = [] attrs = []
for xtag in self.getTags('x'): for xtag in self.getTags('x'):
for child in xtag.getTags('status'): for child in xtag.getTags('status'):
@ -546,53 +546,53 @@ class Presence(Protocol):
return attrs return attrs
class Iq(Protocol): class Iq(Protocol):
""" XMPP Iq object - get/set dialog mechanism. """ ''' XMPP Iq object - get/set dialog mechanism. '''
def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None): def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
""" Create Iq object. You can specify type, query namespace ''' Create Iq object. You can specify type, query namespace
any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go. any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """ Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. '''
Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
if payload: self.setQueryPayload(payload) if payload: self.setQueryPayload(payload)
if queryNS: self.setQueryNS(queryNS) if queryNS: self.setQueryNS(queryNS)
def getQueryNS(self): def getQueryNS(self):
""" Return the namespace of the 'query' child element.""" ''' Return the namespace of the 'query' child element.'''
tag=self.getTag('query') tag=self.getTag('query')
if tag: return tag.getNamespace() if tag: return tag.getNamespace()
def getQuerynode(self): def getQuerynode(self):
""" Return the 'node' attribute value of the 'query' child element.""" ''' Return the 'node' attribute value of the 'query' child element.'''
return self.getTagAttr('query','node') return self.getTagAttr('query','node')
def getQueryPayload(self): def getQueryPayload(self):
""" Return the 'query' child element payload.""" ''' Return the 'query' child element payload.'''
tag=self.getTag('query') tag=self.getTag('query')
if tag: return tag.getPayload() if tag: return tag.getPayload()
def getQueryChildren(self): def getQueryChildren(self):
""" Return the 'query' child element child nodes.""" ''' Return the 'query' child element child nodes.'''
tag=self.getTag('query') tag=self.getTag('query')
if tag: return tag.getChildren() if tag: return tag.getChildren()
def setQueryNS(self,namespace): def setQueryNS(self,namespace):
""" Set the namespace of the 'query' child element.""" ''' Set the namespace of the 'query' child element.'''
self.setTag('query').setNamespace(namespace) self.setTag('query').setNamespace(namespace)
def setQueryPayload(self,payload): def setQueryPayload(self,payload):
""" Set the 'query' child element payload.""" ''' Set the 'query' child element payload.'''
self.setTag('query').setPayload(payload) self.setTag('query').setPayload(payload)
def setQuerynode(self,node): def setQuerynode(self,node):
""" Set the 'node' attribute value of the 'query' child element.""" ''' Set the 'node' attribute value of the 'query' child element.'''
self.setTagAttr('query','node',node) self.setTagAttr('query','node',node)
def buildReply(self,typ): def buildReply(self,typ):
""" Builds and returns another Iq object of specified type. ''' Builds and returns another Iq object of specified type.
The to, from and query child node of new Iq are pre-set as reply to this Iq. """ The to, from and query child node of new Iq are pre-set as reply to this Iq. '''
iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()}) iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
if self.getTag('query'): iq.setQueryNS(self.getQueryNS()) if self.getTag('query'): iq.setQueryNS(self.getQueryNS())
return iq return iq
class ErrorNode(Node): class ErrorNode(Node):
""" XMPP-style error element. ''' XMPP-style error element.
In the case of stanza error should be attached to XMPP stanza. In the case of stanza error should be attached to XMPP stanza.
In the case of stream-level errors should be used separately. """ In the case of stream-level errors should be used separately. '''
def __init__(self,name,code=None,typ=None,text=None): def __init__(self,name,code=None,typ=None,text=None):
""" Create new error node object. ''' Create new error node object.
Mandatory parameter: name - name of error condition. Mandatory parameter: name - name of error condition.
Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.""" Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.'''
if ERRORS.has_key(name): if ERRORS.has_key(name):
cod,type,txt=ERRORS[name] cod,type,txt=ERRORS[name]
ns=name.split()[0] ns=name.split()[0]
@ -607,30 +607,30 @@ class ErrorNode(Node):
if cod: self.setAttr('code',cod) if cod: self.setAttr('code',cod)
class Error(Protocol): class Error(Protocol):
""" Used to quickly transform received stanza into error reply.""" ''' Used to quickly transform received stanza into error reply.'''
def __init__(self,node,error,reply=1): def __init__(self,node,error,reply=1):
""" Create error reply basing on the received 'node' stanza and the 'error' error condition. ''' Create error reply basing on the received 'node' stanza and the 'error' error condition.
If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping) If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
specify the 'reply' argument as false.""" specify the 'reply' argument as false.'''
if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node) if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
else: Protocol.__init__(self,node=node) else: Protocol.__init__(self,node=node)
self.setError(error) self.setError(error)
if node.getType()=='error': self.__str__=self.__dupstr__ if node.getType()=='error': self.__str__=self.__dupstr__
def __dupstr__(self,dup1=None,dup2=None): def __dupstr__(self,dup1=None,dup2=None):
""" Dummy function used as preventor of creating error node in reply to error node. ''' Dummy function used as preventor of creating error node in reply to error node.
I.e. you will not be able to serialise "double" error into string. I.e. you will not be able to serialise "double" error into string.
""" '''
return '' return ''
class DataField(Node): class DataField(Node):
""" This class is used in the DataForm class to describe the single data item. ''' This class is used in the DataForm class to describe the single data item.
If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
then you will need to work with instances of this class. """ then you will need to work with instances of this class. '''
def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None): def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None):
""" Create new data field of specified name,value and type. ''' Create new data field of specified name,value and type.
Also 'required','desc' and 'options' fields can be set. Also 'required','desc' and 'options' fields can be set.
Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled. Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
""" '''
Node.__init__(self,'field',node=node) Node.__init__(self,'field',node=node)
if name: self.setVar(name) if name: self.setVar(name)
if type(value) in [list,tuple]: self.setValues(value) if type(value) in [list,tuple]: self.setValues(value)
@ -641,70 +641,70 @@ class DataField(Node):
if desc: self.setDesc(desc) if desc: self.setDesc(desc)
if options: self.setOptions(options) if options: self.setOptions(options)
def setRequired(self,req=1): def setRequired(self,req=1):
""" Change the state of the 'required' flag. """ ''' Change the state of the 'required' flag. '''
if req: self.setTag('required') if req: self.setTag('required')
else: else:
try: self.delChild('required') try: self.delChild('required')
except ValueError: return except ValueError: return
def isRequired(self): def isRequired(self):
""" Returns in this field a required one. """ ''' Returns in this field a required one. '''
return self.getTag('required') return self.getTag('required')
def setDesc(self,desc): def setDesc(self,desc):
""" Set the description of this field. """ ''' Set the description of this field. '''
self.setTagData('desc',desc) self.setTagData('desc',desc)
def getDesc(self): def getDesc(self):
""" Return the description of this field. """ ''' Return the description of this field. '''
return self.getTagData('desc') return self.getTagData('desc')
def setValue(self,val): def setValue(self,val):
""" Set the value of this field. """ ''' Set the value of this field. '''
self.setTagData('value',val) self.setTagData('value',val)
def getValue(self): def getValue(self):
return self.getTagData('value') return self.getTagData('value')
def setValues(self,lst): def setValues(self,lst):
""" Set the values of this field as values-list. ''' Set the values of this field as values-list.
Replaces all previous filed values! If you need to just add a value - use addValue method.""" Replaces all previous filed values! If you need to just add a value - use addValue method.'''
while self.getTag('value'): self.delChild('value') while self.getTag('value'): self.delChild('value')
for val in lst: self.addValue(val) for val in lst: self.addValue(val)
def addValue(self,val): def addValue(self,val):
""" Add one more value to this field. Used in 'get' iq's or such.""" ''' Add one more value to this field. Used in 'get' iq's or such.'''
self.addChild('value',{},[val]) self.addChild('value',{},[val])
def getValues(self): def getValues(self):
""" Return the list of values associated with this field.""" ''' Return the list of values associated with this field.'''
ret=[] ret=[]
for tag in self.getTags('value'): ret.append(tag.getData()) for tag in self.getTags('value'): ret.append(tag.getData())
return ret return ret
def getOptions(self): def getOptions(self):
""" Return label-option pairs list associated with this field.""" ''' Return label-option pairs list associated with this field.'''
ret=[] ret=[]
for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')]) for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
return ret return ret
def setOptions(self,lst): def setOptions(self,lst):
""" Set label-option pairs list associated with this field.""" ''' Set label-option pairs list associated with this field.'''
while self.getTag('option'): self.delChild('option') while self.getTag('option'): self.delChild('option')
for opt in lst: self.addOption(opt) for opt in lst: self.addOption(opt)
def addOption(self,opt): def addOption(self,opt):
""" Add one more label-option pair to this field.""" ''' Add one more label-option pair to this field.'''
if type(opt) in [str,unicode]: self.addChild('option').setTagData('value',opt) if type(opt) in [str,unicode]: self.addChild('option').setTagData('value',opt)
else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1]) else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
def getType(self): def getType(self):
""" Get type of this field. """ ''' Get type of this field. '''
return self.getAttr('type') return self.getAttr('type')
def setType(self,val): def setType(self,val):
""" Set type of this field. """ ''' Set type of this field. '''
return self.setAttr('type',val) return self.setAttr('type',val)
def getVar(self): def getVar(self):
""" Get 'var' attribute value of this field. """ ''' Get 'var' attribute value of this field. '''
return self.getAttr('var') return self.getAttr('var')
def setVar(self,val): def setVar(self,val):
""" Set 'var' attribute value of this field. """ ''' Set 'var' attribute value of this field. '''
return self.setAttr('var',val) return self.setAttr('var',val)
class DataForm(Node): class DataForm(Node):
""" DataForm class. Used for manipulating dataforms in XMPP. ''' DataForm class. Used for manipulating dataforms in XMPP.
Relevant XEPs: 0004, 0068, 0122. Relevant XEPs: 0004, 0068, 0122.
Can be used in disco, pub-sub and many other applications.""" Can be used in disco, pub-sub and many other applications.'''
def __init__(self, typ=None, data=[], title=None, node=None): def __init__(self, typ=None, data=[], title=None, node=None):
""" '''
Create new dataform of type 'typ'. 'data' is the list of DataField Create new dataform of type 'typ'. 'data' is the list of DataField
instances that this dataform contains, 'title' - the title string. instances that this dataform contains, 'title' - the title string.
You can specify the 'node' argument as the other node to be used as You can specify the 'node' argument as the other node to be used as
@ -716,7 +716,7 @@ class DataForm(Node):
'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
'cancel' form can not contain any fields. All other forms contains AT LEAST one field. 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
'title' MAY be included in forms of type "form" and "result" 'title' MAY be included in forms of type "form" and "result"
""" '''
Node.__init__(self,'x',node=node) Node.__init__(self,'x',node=node)
if node: if node:
newkids=[] newkids=[]
@ -736,36 +736,36 @@ class DataForm(Node):
elif child.__class__.__name__=='DataField': self.kids.append(child) elif child.__class__.__name__=='DataField': self.kids.append(child)
else: self.kids.append(DataField(node=child)) else: self.kids.append(DataField(node=child))
def getType(self): def getType(self):
""" Return the type of dataform. """ ''' Return the type of dataform. '''
return self.getAttr('type') return self.getAttr('type')
def setType(self,typ): def setType(self,typ):
""" Set the type of dataform. """ ''' Set the type of dataform. '''
self.setAttr('type',typ) self.setAttr('type',typ)
def getTitle(self): def getTitle(self):
""" Return the title of dataform. """ ''' Return the title of dataform. '''
return self.getTagData('title') return self.getTagData('title')
def setTitle(self,text): def setTitle(self,text):
""" Set the title of dataform. """ ''' Set the title of dataform. '''
self.setTagData('title',text) self.setTagData('title',text)
def getInstructions(self): def getInstructions(self):
""" Return the instructions of dataform. """ ''' Return the instructions of dataform. '''
return self.getTagData('instructions') return self.getTagData('instructions')
def setInstructions(self,text): def setInstructions(self,text):
""" Set the instructions of dataform. """ ''' Set the instructions of dataform. '''
self.setTagData('instructions',text) self.setTagData('instructions',text)
def addInstructions(self,text): def addInstructions(self,text):
""" Add one more instruction to the dataform. """ ''' Add one more instruction to the dataform. '''
self.addChild('instructions',{},[text]) self.addChild('instructions',{},[text])
def getField(self,name): def getField(self,name):
""" Return the datafield object with name 'name' (if exists). """ ''' Return the datafield object with name 'name' (if exists). '''
return self.getTag('field',attrs={'var':name}) return self.getTag('field',attrs={'var':name})
def setField(self,name): def setField(self,name):
""" Create if nessessary or get the existing datafield object with name 'name' and return it. """ ''' Create if nessessary or get the existing datafield object with name 'name' and return it. '''
f=self.getField(name) f=self.getField(name)
if f: return f if f: return f
return self.addChild(node=DataField(name)) return self.addChild(node=DataField(name))
def asDict(self): def asDict(self):
""" Represent dataform as simple dictionary mapping of datafield names to their values.""" ''' Represent dataform as simple dictionary mapping of datafield names to their values.'''
ret={} ret={}
for field in self.getTags('field'): for field in self.getTags('field'):
name=field.getAttr('var') name=field.getAttr('var')
@ -778,10 +778,10 @@ class DataForm(Node):
if self.getTag('instructions'): ret['instructions']=self.getInstructions() if self.getTag('instructions'): ret['instructions']=self.getInstructions()
return ret return ret
def __getitem__(self,name): def __getitem__(self,name):
""" Simple dictionary interface for getting datafields values by their names.""" ''' Simple dictionary interface for getting datafields values by their names.'''
item=self.getField(name) item=self.getField(name)
if item: return item.getValue() if item: return item.getValue()
raise IndexError('No such field') raise IndexError('No such field')
def __setitem__(self,name,val): def __setitem__(self,name,val):
""" Simple dictionary interface for setting datafields values by their names.""" ''' Simple dictionary interface for setting datafields values by their names.'''
return self.setField(name).setValue(val) return self.setField(name).setValue(val)

View file

@ -29,31 +29,31 @@ log = logging.getLogger('gajim.c.x.roster_nb')
class NonBlockingRoster(PlugIn): class NonBlockingRoster(PlugIn):
""" Defines a plenty of methods that will allow you to manage roster. ''' Defines a plenty of methods that will allow you to manage roster.
Also automatically track presences from remote JIDs taking into Also automatically track presences from remote JIDs taking into
account that every JID can have multiple resources connected. Does not account that every JID can have multiple resources connected. Does not
currently support 'error' presences. currently support 'error' presences.
You can also use mapping interface for access to the internal representation of You can also use mapping interface for access to the internal representation of
contacts in roster. contacts in roster.
""" '''
def __init__(self): def __init__(self):
""" Init internal variables. """ ''' Init internal variables. '''
PlugIn.__init__(self) PlugIn.__init__(self)
self._data = {} self._data = {}
self.set=None self.set=None
self._exported_methods=[self.getRoster] self._exported_methods=[self.getRoster]
def Request(self,force=0): def Request(self,force=0):
""" Request roster from server if it were not yet requested ''' Request roster from server if it were not yet requested
(or if the 'force' argument is set). """ (or if the 'force' argument is set). '''
if self.set is None: self.set=0 if self.set is None: self.set=0
elif not force: return elif not force: return
self._owner.send(Iq('get',NS_ROSTER)) self._owner.send(Iq('get',NS_ROSTER))
log.info('Roster requested from server') log.info('Roster requested from server')
def RosterIqHandler(self,dis,stanza): def RosterIqHandler(self,dis,stanza):
""" Subscription tracker. Used internally for setting items state in ''' Subscription tracker. Used internally for setting items state in
internal roster representation. """ internal roster representation. '''
sender = stanza.getAttr('from') sender = stanza.getAttr('from')
if not sender is None and not sender.bareMatch( if not sender is None and not sender.bareMatch(
self._owner.User + '@' + self._owner.Server): self._owner.User + '@' + self._owner.Server):
@ -75,8 +75,8 @@ class NonBlockingRoster(PlugIn):
self.set=1 self.set=1
def PresenceHandler(self,dis,pres): def PresenceHandler(self,dis,pres):
""" Presence tracker. Used internally for setting items' resources state in ''' Presence tracker. Used internally for setting items' resources state in
internal roster representation. """ internal roster representation. '''
jid=pres.getFrom() jid=pres.getFrom()
if not jid: if not jid:
# If no from attribue, it's from server # If no from attribue, it's from server
@ -100,11 +100,11 @@ class NonBlockingRoster(PlugIn):
# Need to handle type='error' also # Need to handle type='error' also
def _getItemData(self,jid,dataname): def _getItemData(self,jid,dataname):
""" Return specific jid's representation in internal format. Used internally. """ ''' Return specific jid's representation in internal format. Used internally. '''
jid=jid[:(jid+'/').find('/')] jid=jid[:(jid+'/').find('/')]
return self._data[jid][dataname] return self._data[jid][dataname]
def _getResourceData(self,jid,dataname): def _getResourceData(self,jid,dataname):
""" Return specific jid's resource representation in internal format. Used internally. """ ''' Return specific jid's resource representation in internal format. Used internally. '''
if jid.find('/')+1: if jid.find('/')+1:
jid,resource=jid.split('/',1) jid,resource=jid.split('/',1)
if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname] if self._data[jid]['resources'].has_key(resource): return self._data[jid]['resources'][resource][dataname]
@ -114,40 +114,40 @@ class NonBlockingRoster(PlugIn):
if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority']) if int(self._data[jid]['resources'][r]['priority'])>lastpri: resource,lastpri=r,int(self._data[jid]['resources'][r]['priority'])
return self._data[jid]['resources'][resource][dataname] return self._data[jid]['resources'][resource][dataname]
def delItem(self,jid): def delItem(self,jid):
""" Delete contact 'jid' from roster.""" ''' Delete contact 'jid' from roster.'''
self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})])) self._owner.send(Iq('set',NS_ROSTER,payload=[Node('item',{'jid':jid,'subscription':'remove'})]))
def getAsk(self,jid): def getAsk(self,jid):
""" Returns 'ask' value of contact 'jid'.""" ''' Returns 'ask' value of contact 'jid'.'''
return self._getItemData(jid,'ask') return self._getItemData(jid,'ask')
def getGroups(self,jid): def getGroups(self,jid):
""" Returns groups list that contact 'jid' belongs to.""" ''' Returns groups list that contact 'jid' belongs to.'''
return self._getItemData(jid,'groups') return self._getItemData(jid,'groups')
def getName(self,jid): def getName(self,jid):
""" Returns name of contact 'jid'.""" ''' Returns name of contact 'jid'.'''
return self._getItemData(jid,'name') return self._getItemData(jid,'name')
def getPriority(self,jid): def getPriority(self,jid):
""" Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.""" ''' Returns priority of contact 'jid'. 'jid' should be a full (not bare) JID.'''
return self._getResourceData(jid,'priority') return self._getResourceData(jid,'priority')
def getRawRoster(self): def getRawRoster(self):
""" Returns roster representation in internal format. """ ''' Returns roster representation in internal format. '''
return self._data return self._data
def getRawItem(self,jid): def getRawItem(self,jid):
""" Returns roster item 'jid' representation in internal format. """ ''' Returns roster item 'jid' representation in internal format. '''
return self._data[jid[:(jid+'/').find('/')]] return self._data[jid[:(jid+'/').find('/')]]
def getShow(self, jid): def getShow(self, jid):
""" Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.""" ''' Returns 'show' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
return self._getResourceData(jid,'show') return self._getResourceData(jid,'show')
def getStatus(self, jid): def getStatus(self, jid):
""" Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.""" ''' Returns 'status' value of contact 'jid'. 'jid' should be a full (not bare) JID.'''
return self._getResourceData(jid,'status') return self._getResourceData(jid,'status')
def getSubscription(self,jid): def getSubscription(self,jid):
""" Returns 'subscription' value of contact 'jid'.""" ''' Returns 'subscription' value of contact 'jid'.'''
return self._getItemData(jid,'subscription') return self._getItemData(jid,'subscription')
def getResources(self,jid): def getResources(self,jid):
""" Returns list of connected resources of contact 'jid'.""" ''' Returns list of connected resources of contact 'jid'.'''
return self._data[jid[:(jid+'/').find('/')]]['resources'].keys() return self._data[jid[:(jid+'/').find('/')]]['resources'].keys()
def setItem(self,jid,name=None,groups=[]): def setItem(self,jid,name=None,groups=[]):
""" Renames contact 'jid' and sets the groups list that it now belongs to.""" ''' Renames contact 'jid' and sets the groups list that it now belongs to.'''
iq=Iq('set',NS_ROSTER) iq=Iq('set',NS_ROSTER)
query=iq.getTag('query') query=iq.getTag('query')
attrs={'jid':jid} attrs={'jid':jid}
@ -156,32 +156,32 @@ class NonBlockingRoster(PlugIn):
for group in groups: item.addChild(node=Node('group',payload=[group])) for group in groups: item.addChild(node=Node('group',payload=[group]))
self._owner.send(iq) self._owner.send(iq)
def getItems(self): def getItems(self):
""" Return list of all [bare] JIDs that the roster is currently tracks.""" ''' Return list of all [bare] JIDs that the roster is currently tracks.'''
return self._data.keys() return self._data.keys()
def keys(self): def keys(self):
""" Same as getItems. Provided for the sake of dictionary interface.""" ''' Same as getItems. Provided for the sake of dictionary interface.'''
return self._data.keys() return self._data.keys()
def __getitem__(self,item): def __getitem__(self,item):
""" Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.""" ''' Get the contact in the internal format. Raises KeyError if JID 'item' is not in roster.'''
return self._data[item] return self._data[item]
def getItem(self,item): def getItem(self,item):
""" Get the contact in the internal format (or None if JID 'item' is not in roster).""" ''' Get the contact in the internal format (or None if JID 'item' is not in roster).'''
if self._data.has_key(item): return self._data[item] if self._data.has_key(item): return self._data[item]
def Subscribe(self,jid): def Subscribe(self,jid):
""" Send subscription request to JID 'jid'.""" ''' Send subscription request to JID 'jid'.'''
self._owner.send(Presence(jid,'subscribe')) self._owner.send(Presence(jid,'subscribe'))
def Unsubscribe(self,jid): def Unsubscribe(self,jid):
""" Ask for removing our subscription for JID 'jid'.""" ''' Ask for removing our subscription for JID 'jid'.'''
self._owner.send(Presence(jid,'unsubscribe')) self._owner.send(Presence(jid,'unsubscribe'))
def Authorize(self,jid): def Authorize(self,jid):
""" Authorise JID 'jid'. Works only if these JID requested auth previously. """ ''' Authorise JID 'jid'. Works only if these JID requested auth previously. '''
self._owner.send(Presence(jid,'subscribed')) self._owner.send(Presence(jid,'subscribed'))
def Unauthorize(self,jid): def Unauthorize(self,jid):
""" Unauthorise JID 'jid'. Use for declining authorisation request ''' Unauthorise JID 'jid'. Use for declining authorisation request
or for removing existing authorization. """ or for removing existing authorization. '''
self._owner.send(Presence(jid,'unsubscribed')) self._owner.send(Presence(jid,'unsubscribed'))
def getRaw(self): def getRaw(self):
"""Returns the internal data representation of the roster.""" '''Returns the internal data representation of the roster.'''
return self._data return self._data
# copypasted methods for roster.py from constructor to here # copypasted methods for roster.py from constructor to here

View file

@ -14,8 +14,8 @@
# $Id: simplexml.py,v 1.27 2005/04/30 07:20:27 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. '''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.""" I'm personally using it in many other separate projects. It is designed to be as standalone as possible.'''
import xml.parsers.expat import xml.parsers.expat
import logging import logging
@ -23,13 +23,13 @@ log = logging.getLogger('gajim.c.x.simplexml')
#log.setLevel(logging.DEBUG) #log.setLevel(logging.DEBUG)
def XMLescape(txt): def XMLescape(txt):
"""Returns provided string with symbols & < > " replaced by their respective XML entities.""" '''Returns provided string with symbols & < > " replaced by their respective XML entities.'''
# replace also FORM FEED and ESC, because they are not valid XML chars # replace also FORM FEED and ESC, because they are not valid XML chars
return txt.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace(u'\x0C', "").replace(u'\x1B', "") return txt.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;").replace(u'\x0C', "").replace(u'\x1B', "")
ENCODING='utf-8' ENCODING='utf-8'
def ustr(what): def ustr(what):
"""Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.""" '''Converts object "what" to unicode string using it's own __str__ method if accessible or unicode method otherwise.'''
if type(what) == type(u''): return what if type(what) == type(u''): return what
try: r=what.__str__() try: r=what.__str__()
except AttributeError: r=str(what) except AttributeError: r=str(what)
@ -37,7 +37,7 @@ def ustr(what):
return r return r
class Node(object): class Node(object):
""" Node class describes syntax of separate XML Node. It have a constructor that permits node creation ''' Node class describes syntax of separate XML Node. It have a constructor that permits node creation
from set of "namespace name", attributes and payload of text strings and other nodes. from set of "namespace name", attributes and payload of text strings and other nodes.
It does not natively support building node from text string and uses NodeBuilder class for that purpose. It does not natively support building node from text string and uses NodeBuilder class for that purpose.
After creation node can be mangled in many ways so it can be completely changed. After creation node can be mangled in many ways so it can be completely changed.
@ -50,16 +50,16 @@ class Node(object):
info with the "original" node that is changing the one node may influence the other. Though it is info with the "original" node that is changing the one node may influence the other. Though it is
rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after rarely needed (in xmpppy it is never needed at all since I'm usually never using original node after
replication (and using replication only to move upwards on the classes tree). replication (and using replication only to move upwards on the classes tree).
""" '''
FORCE_NODE_RECREATION=0 FORCE_NODE_RECREATION=0
def __init__(self, tag=None, attrs={}, payload=[], parent=None, node=None): def __init__(self, tag=None, attrs={}, payload=[], parent=None, node=None):
""" Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it ''' Takes "tag" argument as the name of node (prepended by namespace, if needed and separated from it
by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings by a space), attrs dictionary as the set of arguments, payload list as the set of textual strings
and child nodes that this node carries within itself and "parent" argument that is another node and child nodes that this node carries within itself and "parent" argument that is another node
that this one will be the child of. Also the __init__ can be provided with "node" argument that is that this one will be the child of. Also the __init__ can be provided with "node" argument that is
either a text string containing exactly one node or another Node instance to begin with. If both either a text string containing exactly one node or another Node instance to begin with. If both
"node" and other arguments is provided then the node initially created as replica of "node" "node" and other arguments is provided then the node initially created as replica of "node"
provided and then modified to be compliant with other arguments.""" provided and then modified to be compliant with other arguments.'''
if node: if node:
if self.FORCE_NODE_RECREATION and isinstance(node, Node): if self.FORCE_NODE_RECREATION and isinstance(node, Node):
node=str(node) node=str(node)
@ -83,8 +83,8 @@ class Node(object):
else: self.data.append(ustr(i)) else: self.data.append(ustr(i))
def __str__(self,fancy=0): def __str__(self,fancy=0):
""" Method used to dump node into textual representation. ''' Method used to dump node into textual representation.
if "fancy" argument is set to True produces indented output for readability.""" if "fancy" argument is set to True produces indented output for readability.'''
s = (fancy-1) * 2 * ' ' + "<" + self.name s = (fancy-1) * 2 * ' ' + "<" + self.name
if self.namespace: if self.namespace:
if not self.parent or self.parent.namespace!=self.namespace: if not self.parent or self.parent.namespace!=self.namespace:
@ -115,8 +115,8 @@ class Node(object):
if fancy: s = s + "\n" if fancy: s = s + "\n"
return s return s
def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None): def addChild(self, name=None, attrs={}, payload=[], namespace=None, node=None):
""" If "node" argument is provided, adds it as child node. Else creates new node from ''' If "node" argument is provided, adds it as child node. Else creates new node from
the other arguments' values and adds it as well.""" the other arguments' values and adds it as well.'''
if namespace: name=namespace+' '+name if namespace: name=namespace+' '+name
if node: if node:
newnode=node newnode=node
@ -125,46 +125,46 @@ class Node(object):
self.kids.append(newnode) self.kids.append(newnode)
return newnode return newnode
def addData(self, data): def addData(self, data):
""" Adds some CDATA to node. """ ''' Adds some CDATA to node. '''
self.data.append(ustr(data)) self.data.append(ustr(data))
def clearData(self): def clearData(self):
""" Removes all CDATA from the node. """ ''' Removes all CDATA from the node. '''
self.data=[] self.data=[]
def delAttr(self, key): def delAttr(self, key):
""" Deletes an attribute "key" """ ''' Deletes an attribute "key" '''
del self.attrs[key] del self.attrs[key]
def delChild(self, node, attrs={}): def delChild(self, node, attrs={}):
""" Deletes the "node" from the node's childs list, if "node" is an instance. ''' Deletes the "node" from the node's childs list, if "node" is an instance.
Else deletes the first node that have specified name and (optionally) attributes. """ Else deletes the first node that have specified name and (optionally) attributes. '''
if not isinstance(node, Node): node=self.getTag(node,attrs) if not isinstance(node, Node): node=self.getTag(node,attrs)
self.kids.remove(node) self.kids.remove(node)
return node return node
def getAttrs(self): def getAttrs(self):
""" Returns all node's attributes as dictionary. """ ''' Returns all node's attributes as dictionary. '''
return self.attrs return self.attrs
def getAttr(self, key): def getAttr(self, key):
""" Returns value of specified attribute. """ ''' Returns value of specified attribute. '''
try: return self.attrs[key] try: return self.attrs[key]
except: return None except: return None
def getChildren(self): def getChildren(self):
""" Returns all node's child nodes as list. """ ''' Returns all node's child nodes as list. '''
return self.kids return self.kids
def getData(self): def getData(self):
""" Returns all node CDATA as string (concatenated). """ ''' Returns all node CDATA as string (concatenated). '''
return ''.join(self.data) return ''.join(self.data)
def getName(self): def getName(self):
""" Returns the name of node """ ''' Returns the name of node '''
return self.name return self.name
def getNamespace(self): def getNamespace(self):
""" Returns the namespace of node """ ''' Returns the namespace of node '''
return self.namespace return self.namespace
def getParent(self): def getParent(self):
""" Returns the parent of node (if present). """ ''' Returns the parent of node (if present). '''
return self.parent return self.parent
def getPayload(self): def getPayload(self):
""" Return the payload of node i.e. list of child nodes and CDATA entries. ''' Return the payload of node i.e. list of child nodes and CDATA entries.
F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned list: F.e. for "<node>text1<nodea/><nodeb/> text2</node>" will be returned list:
['text1', <nodea instance>, <nodeb instance>, ' text2']. """ ['text1', <nodea instance>, <nodeb instance>, ' text2']. '''
ret=[] ret=[]
for i in range(len(self.kids)+len(self.data)+1): for i in range(len(self.kids)+len(self.data)+1):
try: try:
@ -174,20 +174,20 @@ class Node(object):
except IndexError: pass except IndexError: pass
return ret return ret
def getTag(self, name, attrs={}, namespace=None): def getTag(self, name, attrs={}, namespace=None):
""" Filters all child nodes using specified arguments as filter. ''' Filters all child nodes using specified arguments as filter.
Returns the first found or None if not found. """ Returns the first found or None if not found. '''
return self.getTags(name, attrs, namespace, one=1) return self.getTags(name, attrs, namespace, one=1)
def getTagAttr(self,tag,attr): def getTagAttr(self,tag,attr):
""" Returns attribute value of the child with specified name (or None if no such attribute).""" ''' Returns attribute value of the child with specified name (or None if no such attribute).'''
try: return self.getTag(tag).attrs[attr] try: return self.getTag(tag).attrs[attr]
except: return None except: return None
def getTagData(self,tag): def getTagData(self,tag):
""" Returns cocatenated CDATA of the child with specified name.""" ''' Returns cocatenated CDATA of the child with specified name.'''
try: return self.getTag(tag).getData() try: return self.getTag(tag).getData()
except: return None except: return None
def getTags(self, name, attrs={}, namespace=None, one=0): def getTags(self, name, attrs={}, namespace=None, one=0):
""" Filters all child nodes using specified arguments as filter. ''' Filters all child nodes using specified arguments as filter.
Returns the list of nodes found. """ Returns the list of nodes found. '''
nodes=[] nodes=[]
for node in self.kids: for node in self.kids:
if namespace and namespace<>node.getNamespace(): continue if namespace and namespace<>node.getNamespace(): continue
@ -199,7 +199,7 @@ class Node(object):
if not one: return nodes if not one: return nodes
def iterTags(self, name, attrs={}, namespace=None): def iterTags(self, name, attrs={}, namespace=None):
""" Iterate over all children using specified arguments as filter. """ ''' Iterate over all children using specified arguments as filter. '''
for node in self.kids: for node in self.kids:
if namespace is not None and namespace!=node.getNamespace(): continue if namespace is not None and namespace!=node.getNamespace(): continue
if node.getName() == name: if node.getName() == name:
@ -210,57 +210,57 @@ class Node(object):
yield node yield node
def setAttr(self, key, val): def setAttr(self, key, val):
""" Sets attribute "key" with the value "val". """ ''' Sets attribute "key" with the value "val". '''
self.attrs[key]=val self.attrs[key]=val
def setData(self, data): def setData(self, data):
""" Sets node's CDATA to provided string. Resets all previous CDATA!""" ''' Sets node's CDATA to provided string. Resets all previous CDATA!'''
self.data=[ustr(data)] self.data=[ustr(data)]
def setName(self,val): def setName(self,val):
""" Changes the node name. """ ''' Changes the node name. '''
self.name = val self.name = val
def setNamespace(self, namespace): def setNamespace(self, namespace):
""" Changes the node namespace. """ ''' Changes the node namespace. '''
self.namespace=namespace self.namespace=namespace
def setParent(self, node): def setParent(self, node):
""" Sets node's parent to "node". WARNING: do not checks if the parent already present ''' Sets node's parent to "node". WARNING: do not checks if the parent already present
and not removes the node from the list of childs of previous parent. """ and not removes the node from the list of childs of previous parent. '''
self.parent = node self.parent = node
def setPayload(self,payload,add=0): def setPayload(self,payload,add=0):
""" Sets node payload according to the list specified. WARNING: completely replaces all node's ''' Sets node payload according to the list specified. WARNING: completely replaces all node's
previous content. If you wish just to add child or CDATA - use addData or addChild methods. """ previous content. If you wish just to add child or CDATA - use addData or addChild methods. '''
if type(payload) in (type(''),type(u'')): payload=[payload] if type(payload) in (type(''),type(u'')): payload=[payload]
if add: self.kids+=payload if add: self.kids+=payload
else: self.kids=payload else: self.kids=payload
def setTag(self, name, attrs={}, namespace=None): def setTag(self, name, attrs={}, namespace=None):
""" Same as getTag but if the node with specified namespace/attributes not found, creates such ''' Same as getTag but if the node with specified namespace/attributes not found, creates such
node and returns it. """ node and returns it. '''
node=self.getTags(name, attrs, namespace=namespace, one=1) node=self.getTags(name, attrs, namespace=namespace, one=1)
if node: return node if node: return node
else: return self.addChild(name, attrs, namespace=namespace) else: return self.addChild(name, attrs, namespace=namespace)
def setTagAttr(self,tag,attr,val): def setTagAttr(self,tag,attr,val):
""" Creates new node (if not already present) with name "tag" ''' Creates new node (if not already present) with name "tag"
and sets it's attribute "attr" to value "val". """ and sets it's attribute "attr" to value "val". '''
try: self.getTag(tag).attrs[attr]=val try: self.getTag(tag).attrs[attr]=val
except: self.addChild(tag,attrs={attr:val}) except: self.addChild(tag,attrs={attr:val})
def setTagData(self,tag,val,attrs={}): def setTagData(self,tag,val,attrs={}):
""" Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs" ''' Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"
and sets it's CDATA to string "val". """ and sets it's CDATA to string "val". '''
try: self.getTag(tag,attrs).setData(ustr(val)) try: self.getTag(tag,attrs).setData(ustr(val))
except: self.addChild(tag,attrs,payload=[ustr(val)]) except: self.addChild(tag,attrs,payload=[ustr(val)])
def has_attr(self,key): def has_attr(self,key):
""" Checks if node have attribute "key".""" ''' Checks if node have attribute "key".'''
return self.attrs.has_key(key) return self.attrs.has_key(key)
def __getitem__(self,item): def __getitem__(self,item):
""" Returns node's attribute "item" value. """ ''' Returns node's attribute "item" value. '''
return self.getAttr(item) return self.getAttr(item)
def __setitem__(self,item,val): def __setitem__(self,item,val):
""" Sets node's attribute "item" value. """ ''' Sets node's attribute "item" value. '''
return self.setAttr(item,val) return self.setAttr(item,val)
def __delitem__(self,item): def __delitem__(self,item):
""" Deletes node's attribute "item". """ ''' Deletes node's attribute "item". '''
return self.delAttr(item) return self.delAttr(item)
def __getattr__(self,attr): def __getattr__(self,attr):
""" Reduce memory usage caused by T/NT classes - use memory only when needed. """ ''' Reduce memory usage caused by T/NT classes - use memory only when needed. '''
if attr=='T': if attr=='T':
self.T=T(self) self.T=T(self)
return self.T return self.T
@ -270,7 +270,7 @@ class Node(object):
raise AttributeError raise AttributeError
class T: class T:
""" Auxiliary class used to quick access 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 __init__(self,node): self.__dict__['node']=node
def __getattr__(self,attr): return self.node.setTag(attr) def __getattr__(self,attr): return self.node.setTag(attr)
def __setattr__(self,attr,val): def __setattr__(self,attr,val):
@ -279,25 +279,25 @@ class T:
def __delattr__(self,attr): return self.node.delChild(attr) def __delattr__(self,attr): return self.node.delChild(attr)
class NT(T): class NT(T):
""" Auxiliary class used to quick create node's child nodes. """ ''' Auxiliary class used to quick create node's child nodes. '''
def __getattr__(self,attr): return self.node.addChild(attr) def __getattr__(self,attr): return self.node.addChild(attr)
def __setattr__(self,attr,val): def __setattr__(self,attr,val):
if isinstance(val,Node): self.node.addChild(attr,node=val) if isinstance(val,Node): self.node.addChild(attr,node=val)
else: return self.node.addChild(attr,payload=[val]) else: return self.node.addChild(attr,payload=[val])
class NodeBuilder: class NodeBuilder:
""" Builds a Node class minidom from data parsed to it. This class used for two purposes: ''' Builds a Node class minidom from data parsed to it. This class used for two purposes:
1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method. 1. Creation an XML Node from a textual representation. F.e. reading a config file. See an XML2Node method.
2. Handling an incoming XML stream. This is done by mangling 2. Handling an incoming XML stream. This is done by mangling
the __dispatch_depth parameter and redefining the dispatch method. the __dispatch_depth parameter and redefining the dispatch method.
You do not need to use this class directly if you do not designing your own XML handler.""" You do not need to use this class directly if you do not designing your own XML handler.'''
def __init__(self,data=None,initial_node=None): def __init__(self,data=None,initial_node=None):
""" Takes two optional parameters: "data" and "initial_node". ''' Takes two optional parameters: "data" and "initial_node".
By default class initialised with empty Node class instance. By default class initialised with empty Node class instance.
Though, if "initial_node" is provided it used as "starting point". Though, if "initial_node" is provided it used as "starting point".
You can think about it as of "node upgrade". You can think about it as of "node upgrade".
"data" (if provided) feeded to parser immidiatedly after instance init. "data" (if provided) feeded to parser immidiatedly after instance init.
""" '''
log.debug("Preparing to handle incoming XML stream.") log.debug("Preparing to handle incoming XML stream.")
self._parser = xml.parsers.expat.ParserCreate(namespace_separator=' ') self._parser = xml.parsers.expat.ParserCreate(namespace_separator=' ')
self._parser.StartElementHandler = self.starttag self._parser.StartElementHandler = self.starttag
@ -328,7 +328,7 @@ class NodeBuilder:
self.data_buffer = None self.data_buffer = None
def destroy(self): def destroy(self):
""" Method used to allow class instance to be garbage-collected. """ ''' Method used to allow class instance to be garbage-collected. '''
self.check_data_buffer() self.check_data_buffer()
self._parser.StartElementHandler = None self._parser.StartElementHandler = None
self._parser.EndElementHandler = None self._parser.EndElementHandler = None
@ -336,7 +336,7 @@ class NodeBuilder:
self._parser.StartNamespaceDeclHandler = None self._parser.StartNamespaceDeclHandler = None
def starttag(self, tag, attrs): def starttag(self, tag, attrs):
"""XML Parser callback. Used internally""" '''XML Parser callback. Used internally'''
self.check_data_buffer() self.check_data_buffer()
attlist=attrs.keys() # attlist=attrs.keys() #
for attr in attlist: # FIXME: Crude hack. And it also slows down the whole library considerably. for attr in attlist: # FIXME: Crude hack. And it also slows down the whole library considerably.
@ -364,7 +364,7 @@ class NodeBuilder:
self._ptr.parent.data.append('') self._ptr.parent.data.append('')
self.last_is_data = 0 self.last_is_data = 0
def endtag(self, tag ): def endtag(self, tag ):
"""XML Parser callback. Used internally""" '''XML Parser callback. Used internally'''
log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag)) log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
self.check_data_buffer() self.check_data_buffer()
if self.__depth == self._dispatch_depth: if self.__depth == self._dispatch_depth:
@ -386,27 +386,27 @@ class NodeBuilder:
self.last_is_data = 1 self.last_is_data = 1
def handle_namespace_start(self, prefix, uri): def handle_namespace_start(self, prefix, uri):
"""XML Parser callback. Used internally""" '''XML Parser callback. Used internally'''
self.check_data_buffer() self.check_data_buffer()
if prefix: self.namespaces[uri]=prefix+':' if prefix: self.namespaces[uri]=prefix+':'
else: self.xmlns=uri else: self.xmlns=uri
def getDom(self): def getDom(self):
""" Returns just built Node. """ ''' Returns just built Node. '''
self.check_data_buffer() self.check_data_buffer()
return self._mini_dom return self._mini_dom
def dispatch(self,stanza): def dispatch(self,stanza):
""" Gets called when the NodeBuilder reaches some level of depth on it's way up with the built ''' Gets called when the NodeBuilder reaches some level of depth on it's way up with the built
node as argument. Can be redefined to convert incoming XML stanzas to program events. """ node as argument. Can be redefined to convert incoming XML stanzas to program events. '''
def stream_header_received(self,ns,tag,attrs): def stream_header_received(self,ns,tag,attrs):
""" Method called when stream just opened. """ ''' Method called when stream just opened. '''
self.check_data_buffer() self.check_data_buffer()
def stream_footer_received(self): def stream_footer_received(self):
""" Method called when stream just closed. """ ''' Method called when stream just closed. '''
self.check_data_buffer() self.check_data_buffer()
def has_received_endtag(self, level=0): def has_received_endtag(self, level=0):
""" Return True if at least one end tag was seen (at level) """ ''' Return True if at least one end tag was seen (at level) '''
return self.__depth <= level and self.__max_depth > level return self.__depth <= level and self.__max_depth > level
def _inc_depth(self): def _inc_depth(self):
@ -419,12 +419,12 @@ class NodeBuilder:
self.__depth -= 1 self.__depth -= 1
def XML2Node(xml): def XML2Node(xml):
""" Converts supplied textual string into XML node. Handy f.e. for reading configuration file. ''' Converts supplied textual string into XML node. Handy f.e. for reading configuration file.
Raises xml.parser.expat.parsererror if provided string is not well-formed XML. """ Raises xml.parser.expat.parsererror if provided string is not well-formed XML. '''
return NodeBuilder(xml).getDom() return NodeBuilder(xml).getDom()
def BadXML2Node(xml): def BadXML2Node(xml):
""" Converts supplied textual string into XML node. Survives if xml data is cutted half way round. ''' Converts supplied textual string into XML node. Survives if xml data is cutted half way round.
I.e. "<html>some text <br>some more text". Will raise xml.parser.expat.parsererror on misplaced I.e. "<html>some text <br>some more text". Will raise xml.parser.expat.parsererror on misplaced
tags though. F.e. "<b>some text <br>some more text</b>" will not work.""" tags though. F.e. "<b>some text <br>some more text</b>" will not work.'''
return NodeBuilder(xml).getDom() return NodeBuilder(xml).getDom()

View file

@ -129,13 +129,13 @@ class SSLWrapper:
log.debug("%s.__init__ called with %s", self.__class__, sslobj) log.debug("%s.__init__ called with %s", self.__class__, sslobj)
def recv(self, data, flags=None): def recv(self, data, flags=None):
""" Receive wrapper for SSL object ''' Receive wrapper for SSL object
We can return None out of this function to signal that no data is We can return None out of this function to signal that no data is
available right now. Better than an exception, which differs available right now. Better than an exception, which differs
depending on which SSL lib we're using. Unfortunately returning '' depending on which SSL lib we're using. Unfortunately returning ''
can indicate that the socket has been closed, so to be sure, we avoid can indicate that the socket has been closed, so to be sure, we avoid
this by returning None. """ this by returning None. '''
raise NotImplementedException() raise NotImplementedException()

View file

@ -61,14 +61,10 @@ def get_proxy_data_from_dict(proxy):
# user and pass for socks5/http_connect proxy. In case of BOSH, it's user and # user and pass for socks5/http_connect proxy. In case of BOSH, it's user and
# pass for http proxy - If there's no proxy_host they won't be used # pass for http proxy - If there's no proxy_host they won't be used
if proxy.has_key('user'): if proxy.has_key('user'): proxy_user = proxy['user']
proxy_user = proxy['user'] else: proxy_user = None
else: if proxy.has_key('pass'): proxy_pass = proxy['pass']
proxy_user = None else: proxy_pass = None
if proxy.has_key('pass'):
proxy_pass = proxy['pass']
else:
proxy_pass = None
return tcp_host, tcp_port, proxy_user, proxy_pass return tcp_host, tcp_port, proxy_user, proxy_pass
@ -89,6 +85,7 @@ DATA_SENT='DATA SENT'
DISCONNECTED ='DISCONNECTED' DISCONNECTED ='DISCONNECTED'
DISCONNECTING ='DISCONNECTING'
CONNECTING ='CONNECTING' CONNECTING ='CONNECTING'
CONNECTED ='CONNECTED' CONNECTED ='CONNECTED'
@ -132,11 +129,10 @@ class NonBlockingTransport(PlugIn):
self.on_connect_failure = on_connect_failure self.on_connect_failure = on_connect_failure
(self.server, self.port) = conn_5tuple[4][:2] (self.server, self.port) = conn_5tuple[4][:2]
self.conn_5tuple = conn_5tuple self.conn_5tuple = conn_5tuple
log.info('NonBlocking Connect :: About to connect to %s:%s' % (self.server, self.port))
def set_state(self, newstate): def set_state(self, newstate):
assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED]) assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING])
self.state = newstate self.state = newstate
def _on_connect(self, data): def _on_connect(self, data):
@ -156,9 +152,8 @@ class NonBlockingTransport(PlugIn):
self.on_connect_failure(err_message=err_message) self.on_connect_failure(err_message=err_message)
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
if self.state not in [CONNECTED]: if self.state != CONNECTED:
# FIXME better handling needed log.error('Trying to send %s when state is %s.' %
log.error('Trying to send %s when transport is %s.' %
(raw_data, self.state)) (raw_data, self.state))
return return
@ -195,13 +190,13 @@ class NonBlockingTransport(PlugIn):
self.remove_timeout() self.remove_timeout()
def set_timeout(self, timeout): def set_timeout(self, timeout):
self.idlequeue.set_read_timeout(self.get_fd(), timeout) self.idlequeue.set_read_timeout(self.fd, timeout)
def get_fd(self): def get_fd(self):
pass pass
def remove_timeout(self): def remove_timeout(self):
self.idlequeue.remove_timeout(self.get_fd()) self.idlequeue.remove_timeout(self.fd)
def set_send_timeout(self, timeout, on_timeout): def set_send_timeout(self, timeout, on_timeout):
self.sendtimeout = timeout self.sendtimeout = timeout
@ -220,26 +215,15 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
Class constructor. Class constructor.
''' '''
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue)
# writable, readable - keep state of the last pluged flags
# This prevents replug of same object with the same flags
self.writable = True
self.readable = False
# queue with messages to be send # queue with messages to be send
self.sendqueue = [] self.sendqueue = []
# bytes remained from the last send message # bytes remained from the last send message
self.sendbuff = '' self.sendbuff = ''
def get_fd(self):
try:
tmp = self._sock.fileno()
return tmp
except socket.error, (errnum, errstr):
log.error('Trying to get file descriptor of not-connected socket: %s' % errstr )
return 0
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
''' '''
Creates and connects socket to server and port defined in conn_5tupe which Creates and connects socket to server and port defined in conn_5tupe which
@ -250,17 +234,21 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
connection connection
''' '''
NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
log.info('NonBlockingTCP Connect :: About to connect to %s:%s' % (self.server, self.port))
try: try:
self._sock = socket.socket(*conn_5tuple[:3]) self._sock = socket.socket(*conn_5tuple[:3])
except socket.error, (errnum, errstr): except socket.error, (errnum, errstr):
self._on_connect_failure('NonBlockingTCP: Error while creating socket: %s %s' % (errnum, errstr)) self._on_connect_failure('NonBlockingTCP Connect: Error while creating socket:\
%s %s' % (errnum, errstr))
return return
self._send = self._sock.send self._send = self._sock.send
self._recv = self._sock.recv self._recv = self._sock.recv
self.fd = self._sock.fileno() self.fd = self._sock.fileno()
self.idlequeue.plug_idle(self, True, False)
# we want to be notified when send is possible to connected socket
self._plug_idle(writable=True, readable=False)
self.peerhost = None self.peerhost = None
errnum = 0 errnum = 0
@ -268,7 +256,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# set timeout for TCP connecting - if nonblocking connect() fails, pollend # set timeout for TCP connecting - if nonblocking connect() fails, pollend
# is called. If if succeeds pollout is called. # is called. If if succeeds pollout is called.
self.idlequeue.set_read_timeout(self.get_fd(), CONNECT_TIMEOUT_SECONDS) self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT_SECONDS)
try: try:
self._sock.setblocking(False) self._sock.setblocking(False)
@ -296,7 +284,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def _on_connect(self, data): def _on_connect(self, data):
''' with TCP socket, we have to remove send-timeout ''' ''' with TCP socket, we have to remove send-timeout '''
self.idlequeue.remove_timeout(self.get_fd()) self.idlequeue.remove_timeout(self.fd)
NonBlockingTransport._on_connect(self, data) NonBlockingTransport._on_connect(self, data)
@ -328,12 +316,14 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
if self.state == DISCONNECTED: if self.state == DISCONNECTED:
return return
self.idlequeue.unplug_idle(self.get_fd()) self.set_state(DISCONNECTING)
self.idlequeue.unplug_idle(self.fd)
try: try:
self._sock.shutdown(socket.SHUT_RDWR) self._sock.shutdown(socket.SHUT_RDWR)
self._sock.close() self._sock.close()
except socket.error, (errnum, errstr): except socket.error, (errnum, errstr):
log.error('Error disconnecting a socket: %s %s' % (errnum,errstr)) log.error('Error while disconnecting a socket: %s %s' % (errnum,errstr))
self.fd = -1
NonBlockingTransport.disconnect(self, do_callback) NonBlockingTransport.disconnect(self, do_callback)
def read_timeout(self): def read_timeout(self):
@ -352,12 +342,16 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def set_timeout(self, timeout): def set_timeout(self, timeout):
if self.state in [CONNECTING, CONNECTED] and self.get_fd() > 0: if self.state in [CONNECTING, CONNECTED] and self.fd != -1:
NonBlockingTransport.set_timeout(self, timeout) NonBlockingTransport.set_timeout(self, timeout)
else:
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd))
def remove_timeout(self): def remove_timeout(self):
if self.get_fd(): if self.fd:
NonBlockingTransport.remove_timeout(self) NonBlockingTransport.remove_timeout(self)
else:
log.warn('remove_timeout: no self.fd state is %s' % self.state)
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
'''Append raw_data to the queue of messages to be send. '''Append raw_data to the queue of messages to be send.
@ -374,39 +368,42 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
self._do_send() self._do_send()
else: else:
self.sendqueue.append(r) self.sendqueue.append(r)
self._plug_idle() self._plug_idle(writable=True, readable=True)
def _plug_idle(self): def _plug_idle(self, writable, readable):
# readable if socket is connected or disconnecting '''
readable = self.state != DISCONNECTED Plugs file descriptor of socket to Idlequeue. Plugged socket
fd = self.get_fd() will be watched for "send possible" or/and "recv possible" events. pollin()
# writeable if sth to send callback is invoked on "recv possible", pollout() on "send_possible".
if self.sendqueue or self.sendbuff: Plugged socket will always be watched for "error" event - in that case,
writable = True pollend() is called.
else: '''
writable = False # if we are connecting, we shouln't touch the socket until it's connected
log.debug('About to plug fd %d, W:%s, R:%s' % (fd, writable, readable)) assert(self.state!=CONNECTING)
if self.writable != writable or self.readable != readable: self.idlequeue.plug_idle(self, writable, readable)
log.debug('Really plugging fd %d, W:%s, R:%s' % (fd, writable, readable))
self.idlequeue.plug_idle(self, writable, readable) log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
else: self.idlequeue.plug_idle(self, writable, readable)
log.debug('Not plugging fd %s because it\'s already plugged' % fd)
def _do_send(self): def _do_send(self):
if not self.sendbuff: if not self.sendbuff:
if not self.sendqueue: if not self.sendqueue:
return None # nothing to send log.warn('calling send on empty buffer and queue')
return None
self.sendbuff = self.sendqueue.pop(0) self.sendbuff = self.sendqueue.pop(0)
try: try:
send_count = self._send(self.sendbuff) send_count = self._send(self.sendbuff)
if send_count: if send_count:
sent_data = self.sendbuff[:send_count] sent_data = self.sendbuff[:send_count]
self.sendbuff = self.sendbuff[send_count:] self.sendbuff = self.sendbuff[send_count:]
self._plug_idle() self._plug_idle(
writable=self.sendqueue or self.sendbuff,
readable=True)
self.raise_event(DATA_SENT, sent_data) self.raise_event(DATA_SENT, sent_data)
except socket.error, e: except socket.error, e:
@ -439,7 +436,10 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# ESHUTDOWN - shutdown(2) has been called on a socket to close down the # ESHUTDOWN - shutdown(2) has been called on a socket to close down the
# sending end of the transmision, and then data was attempted to be sent # sending end of the transmision, and then data was attempted to be sent
log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr)) log.error("Connection to %s lost: %s %s" % ( self.server, errnum, errstr))
self.disconnect() if self.on_remote_disconnect:
self.on_remote_disconnect()
else:
self.disconnect()
return return
if received is None: if received is None:
@ -454,15 +454,13 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# we have received some bytes, stop the timeout! # we have received some bytes, stop the timeout!
self.renew_send_timeout() self.renew_send_timeout()
# pass received data to owner # pass received data to owner
#self.
if self.on_receive: if self.on_receive:
self.raise_event(DATA_RECEIVED, received) self.raise_event(DATA_RECEIVED, received)
self._on_receive(received) self._on_receive(received)
else: else:
# This should never happen, so we need the debug. (If there is no handler # This should never happen, so we need the debug. (If there is no handler
# on receive spacified, data are passed to Dispatcher.ProcessNonBlocking) # on receive specified, data are passed to Dispatcher.ProcessNonBlocking)
log.error('SOCKET %s Unhandled data received: %s' % (id(self), received)) log.error('SOCKET %s Unhandled data received: %s' % (id(self), received))
import traceback
traceback.print_stack() traceback.print_stack()
self.disconnect() self.disconnect()
@ -474,12 +472,14 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
class NonBlockingHTTP(NonBlockingTCP): class NonBlockingHTTP(NonBlockingTCP):
''' '''
Socket wrapper that cretes HTTP message out of sent data and peels-off Socket wrapper that creates HTTP message out of sent data and peels-off
HTTP headers from incoming messages HTTP headers from incoming messages
''' '''
def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible, def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible,
http_uri, http_port, http_version='HTTP/1.1'): http_uri, http_port, http_version='HTTP/1.1', http_persistent=False):
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue)
self.http_protocol, self.http_host, self.http_path = urisplit(http_uri) self.http_protocol, self.http_host, self.http_path = urisplit(http_uri)
if self.http_protocol is None: if self.http_protocol is None:
@ -488,12 +488,12 @@ class NonBlockingHTTP(NonBlockingTCP):
http_path = '/' http_path = '/'
self.http_port = http_port self.http_port = http_port
self.http_version = http_version self.http_version = http_version
self.http_persistent = http_persistent
# buffer for partial responses # buffer for partial responses
self.recvbuff = '' self.recvbuff = ''
self.expected_length = 0 self.expected_length = 0
self.pending_requests = 0 self.pending_requests = 0
self.on_http_request_possible = on_http_request_possible self.on_http_request_possible = on_http_request_possible
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue)
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
NonBlockingTCP.send( NonBlockingTCP.send(
@ -503,6 +503,19 @@ class NonBlockingHTTP(NonBlockingTCP):
self.pending_requests += 1 self.pending_requests += 1
def on_remote_disconnect(self):
if self.http_persistent:
self.http_persistent = False
self.disconnect(do_callback=False)
self.connect(
conn_5tuple = self.conn_5tuple,
on_connect = lambda: self._plug_idle(writable=True, readable=True),
on_connect_failure = self.disconnect)
else:
self.disconnect()
return
def _on_receive(self,data): def _on_receive(self,data):
'''Preceeds passing received data to owner class. Gets rid of HTTP headers '''Preceeds passing received data to owner class. Gets rid of HTTP headers
and checks them.''' and checks them.'''
@ -524,9 +537,6 @@ class NonBlockingHTTP(NonBlockingTCP):
log.info('not enough bytes - %d expected, %d got' % (self.expected_length, len(self.recvbuff))) log.info('not enough bytes - %d expected, %d got' % (self.expected_length, len(self.recvbuff)))
return return
# FIXME the reassembling doesn't work - Connection Manager on jabbim.cz
# closes TCP connection before sending <Content-Length> announced bytes.. WTF
# all was received, now call the on_receive callback # all was received, now call the on_receive callback
httpbody = self.recvbuff httpbody = self.recvbuff
@ -534,17 +544,18 @@ class NonBlockingHTTP(NonBlockingTCP):
self.expected_length=0 self.expected_length=0
self.pending_requests -= 1 self.pending_requests -= 1
assert(self.pending_requests >= 0) assert(self.pending_requests >= 0)
# not-persistent connections if not self.http_persistent:
self.disconnect(do_callback = False) # not-persistent connections disconnect after response
self.disconnect(do_callback = False)
self.on_receive(httpbody) self.on_receive(httpbody)
self.on_http_request_possible() self.on_http_request_possible()
def build_http_message(self, httpbody, method='POST'): def build_http_message(self, httpbody, method='POST'):
''' '''
Builds http message with given body. Builds http message with given body.
Values for headers and status line fields are taken from class variables. Values for headers and status line fields are taken from class variables.
)
''' '''
absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host, absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host,
self.http_port, self.http_path) self.http_port, self.http_path)

View file

@ -50,7 +50,8 @@ import logging
consoleloghandler = logging.StreamHandler() consoleloghandler = logging.StreamHandler()
consoleloghandler.setLevel(1) consoleloghandler.setLevel(1)
consoleloghandler.setFormatter( consoleloghandler.setFormatter(
logging.Formatter('%(name)s: %(levelname)s: %(message)s')) logging.Formatter('%(name)s: %(levelname)s: %(message)s')
)
log = logging.getLogger('gajim') log = logging.getLogger('gajim')
log.setLevel(logging.WARNING) log.setLevel(logging.WARNING)
log.addHandler(consoleloghandler) log.addHandler(consoleloghandler)