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:
parent
cecce21c53
commit
a58618c843
|
@ -550,6 +550,7 @@ class Connection(ConnectionHandlers):
|
|||
self._proxy['bosh_hold'] = '1'
|
||||
self._proxy['bosh_wait'] = '60'
|
||||
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,
|
||||
|
|
|
@ -143,10 +143,10 @@ class SASL(PlugIn):
|
|||
if "DIGEST-MD5" in mecs:
|
||||
node=Node('auth',attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
|
||||
elif "PLAIN" in mecs:
|
||||
sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server,
|
||||
self.username, self.password)
|
||||
sasl_data='%s\x00%s\x00%s' % (self.username+'@' + self._owner.Server,
|
||||
self.username, self.password)
|
||||
node=Node('auth', attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},
|
||||
payload=[base64.encodestring(sasl_data).replace('\n','')])
|
||||
payload=[base64.encodestring(sasl_data).replace('\n','')])
|
||||
else:
|
||||
self.startsasl='failure'
|
||||
log.error('I can only use DIGEST-MD5 and PLAIN mecanisms.')
|
||||
|
@ -173,12 +173,19 @@ class SASL(PlugIn):
|
|||
self.startsasl='success'
|
||||
log.info('Successfully authenticated with remote server.')
|
||||
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()
|
||||
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.User = self.username
|
||||
if self.on_sasl :
|
||||
self.on_sasl ()
|
||||
self.on_sasl()
|
||||
raise NodeProcessed
|
||||
########################################3333
|
||||
incoming_data = challenge.getData()
|
||||
|
@ -232,7 +239,7 @@ class SASL(PlugIn):
|
|||
|
||||
class NonBlockingNonSASL(PlugIn):
|
||||
''' 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):
|
||||
''' Caches username, password and resource for auth. '''
|
||||
|
@ -331,16 +338,6 @@ class NonBlockingBind(PlugIn):
|
|||
PlugIn.__init__(self)
|
||||
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):
|
||||
''' Start resource binding, if allowed at this time. Used internally. '''
|
||||
if self._owner.Dispatcher.Stream.features:
|
||||
|
@ -348,7 +345,23 @@ class NonBlockingBind(PlugIn):
|
|||
self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
|
||||
except NodeProcessed:
|
||||
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):
|
||||
''' Remove Bind handler from owner's dispatcher. Used internally. '''
|
||||
|
@ -404,77 +417,3 @@ class NonBlockingBind(PlugIn):
|
|||
self.session = 0
|
||||
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 ''
|
||||
|
|
|
@ -24,9 +24,6 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
# (see http://www.xmpp.org/extensions/xep-0124.html#rids)
|
||||
r = random.Random()
|
||||
r.seed()
|
||||
global FAKE_DESCRIPTOR
|
||||
FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
|
||||
self.fake_fd = FAKE_DESCRIPTOR
|
||||
self.bosh_rid = r.getrandbits(50)
|
||||
self.bosh_sid = None
|
||||
if locale.getdefaultlocale()[0]:
|
||||
|
@ -35,7 +32,7 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
self.bosh_xml_lang = 'en'
|
||||
|
||||
self.http_version = 'HTTP/1.1'
|
||||
self.http_persistent = False
|
||||
self.http_persistent = True
|
||||
self.http_pipelining = False
|
||||
self.bosh_to = domain
|
||||
|
||||
|
@ -57,33 +54,38 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
|
||||
def 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.tcp_connection_started()
|
||||
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
self.http_socks[0].connect(
|
||||
conn_5tuple = conn_5tuple,
|
||||
on_connect = lambda: self._on_connect(self.http_socks[0]),
|
||||
on_connect_failure = self._on_connect_failure)
|
||||
|
||||
|
||||
|
||||
def get_fd(self):
|
||||
return self.fake_fd
|
||||
def set_timeout(self, timeout):
|
||||
if self.state in [CONNECTING, CONNECTED] and self.fd != -1:
|
||||
NonBlockingTransport.set_timeout(self, timeout)
|
||||
else:
|
||||
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd))
|
||||
|
||||
def on_http_request_possible(self):
|
||||
'''
|
||||
Called after HTTP response is received - another request is possible.
|
||||
There should be always one pending request on BOSH CM.
|
||||
'''
|
||||
log.info('on_http_req possible, stanzas in list: %s, state:\n%s' %
|
||||
(self.stanzas_to_send, self.get_current_state()))
|
||||
# if one of sockets is connecting, sth is about to be sent - we don't have to
|
||||
# send request from here
|
||||
log.info('on_http_req possible state:\n%s' % self.get_current_state())
|
||||
# if one of sockets is connecting, sth is about to be sent
|
||||
# if there is a pending request, we shouldn't send another one
|
||||
for s in self.http_socks:
|
||||
if s.state==CONNECTING or s.pending_requests>0: return
|
||||
self.flush_stanzas()
|
||||
|
@ -96,19 +98,17 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
tmp = self.prio_bosh_stanza
|
||||
self.prio_bosh_stanza = None
|
||||
else:
|
||||
tmp = self.stanzas_to_send
|
||||
self.stanzas_to_send = []
|
||||
if self.stanzas_to_send:
|
||||
tmp = self.stanzas_to_send.pop(0)
|
||||
else:
|
||||
tmp = []
|
||||
self.send_http(tmp)
|
||||
|
||||
|
||||
def send(self, stanza, now=False):
|
||||
# body tags should be send only via send_http()
|
||||
assert(not isinstance(stanza, BOSHBody))
|
||||
now = True
|
||||
if now:
|
||||
self.send_http([stanza])
|
||||
else:
|
||||
self.stanzas_to_send.append(stanza)
|
||||
self.send_http([stanza])
|
||||
|
||||
|
||||
def send_http(self, payload):
|
||||
|
@ -130,12 +130,16 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
else:
|
||||
# no socket was picked but one is about to connect - save the stanza and
|
||||
# return
|
||||
log.info('send_http: no free socket:\n%s' % self.get_current_state())
|
||||
if self.prio_bosh_stanza:
|
||||
payload = self.merge_stanzas(payload, self.prio_bosh_stanza)
|
||||
if payload is None:
|
||||
log.error('Error in BOSH socket handling - unable to send %s because %s\
|
||||
is already about to be sent' % (payload, self.prio_bosh_stanza))
|
||||
self.disconnect()
|
||||
# if we cant merge the stanzas (both are BOSH <body>), add the current to
|
||||
# queue to be sent later
|
||||
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
|
||||
|
||||
def merge_stanzas(self, s1, s2):
|
||||
|
@ -156,9 +160,11 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
|
@ -181,9 +187,10 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
return
|
||||
# being here means there are only CONNECTED scokets with pending requests.
|
||||
# Lets create and connect another one
|
||||
s = self.get_http_socket()
|
||||
self.http_socks.append(s)
|
||||
self.connect_and_flush(s)
|
||||
if len(self.http_socks) < 2:
|
||||
s = self.get_http_socket()
|
||||
self.http_socks.append(s)
|
||||
self.connect_and_flush(s)
|
||||
return
|
||||
|
||||
|
||||
|
@ -219,7 +226,6 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
attrs={ 'to': self.bosh_to,
|
||||
'sid': self.bosh_sid,
|
||||
'xml:lang': self.bosh_xml_lang,
|
||||
'xmpp:version': '1.0',
|
||||
'xmpp:restart': 'true',
|
||||
'xmlns:xmpp': 'urn:xmpp:xbosh'})
|
||||
|
||||
|
@ -239,7 +245,8 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
on_http_request_possible = self.on_http_request_possible,
|
||||
http_uri = self.bosh_host,
|
||||
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:
|
||||
s.onreceive(self.current_recv_handler)
|
||||
return s
|
||||
|
@ -251,9 +258,15 @@ class NonBlockingBOSH(NonBlockingTransport):
|
|||
for s in self.http_socks:
|
||||
s.onreceive(recv_handler)
|
||||
|
||||
def http_socket_disconnect(self, socket):
|
||||
if self.http_persistent:
|
||||
self.disconnect()
|
||||
|
||||
|
||||
|
||||
def disconnect(self, do_callback=True):
|
||||
if self.state == DISCONNECTED: return
|
||||
|
||||
self.fd = -1
|
||||
for s in self.http_socks:
|
||||
s.disconnect(do_callback=False)
|
||||
NonBlockingTransport.disconnect(self, do_callback)
|
||||
|
|
|
@ -14,23 +14,23 @@
|
|||
|
||||
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
|
||||
|
||||
"""
|
||||
'''
|
||||
Provides PlugIn class functionality to develop extentions for xmpppy.
|
||||
Also provides Client and Component classes implementations as the
|
||||
examples of xmpppy structures usage.
|
||||
These classes can be used for simple applications "AS IS" though.
|
||||
"""
|
||||
'''
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.x.plugin')
|
||||
|
||||
class PlugIn:
|
||||
""" Common xmpppy plugins infrastructure: plugging in/out, debugging. """
|
||||
''' Common xmpppy plugins infrastructure: plugging in/out, debugging. '''
|
||||
def __init__(self):
|
||||
self._exported_methods=[]
|
||||
|
||||
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
|
||||
log.info('Plugging %s __INTO__ %s' % (self,self._owner))
|
||||
if owner.__dict__.has_key(self.__class__.__name__):
|
||||
|
@ -53,7 +53,7 @@ class PlugIn:
|
|||
if hasattr(self,'plugin'): return self.plugin(owner)
|
||||
|
||||
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))
|
||||
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
|
||||
|
|
|
@ -138,9 +138,9 @@ class NBCommonClient:
|
|||
def _try_next_ip(self, err_message=None):
|
||||
'''iterates over IP addresses from getaddinfo'''
|
||||
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 == []:
|
||||
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))
|
||||
else:
|
||||
self.current_ip = self.ip_addresses.pop(0)
|
||||
|
@ -237,7 +237,7 @@ class NBCommonClient:
|
|||
self.on_connect(self, self.connected)
|
||||
|
||||
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'):
|
||||
self.Dispatcher.Event('', event_type, data)
|
||||
|
||||
|
@ -293,7 +293,7 @@ class NBCommonClient:
|
|||
self._Resource = 'xmpppy'
|
||||
auth_nb.NonBlockingNonSASL(self._User, self._Password, self._Resource, self._on_old_auth).PlugIn(self)
|
||||
return
|
||||
self.onreceive(self._on_start_sasl)
|
||||
#self.onreceive(self._on_start_sasl)
|
||||
self.SASL.auth()
|
||||
return True
|
||||
|
||||
|
@ -313,8 +313,18 @@ class NBCommonClient:
|
|||
self.SASL.PlugOut()
|
||||
elif self.SASL.startsasl == 'success':
|
||||
auth_nb.NonBlockingBind().PlugIn(self)
|
||||
self.onreceive(self._on_auth_bind)
|
||||
return True
|
||||
if self.protocol_type == 'BOSH':
|
||||
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):
|
||||
if data:
|
||||
|
@ -390,6 +400,7 @@ class NonBlockingClient(NBCommonClient):
|
|||
domain = self.Server,
|
||||
bosh_dict = proxy)
|
||||
self.protocol_type = 'BOSH'
|
||||
self.wait_for_restart_response = proxy['wait_for_restart_response']
|
||||
|
||||
else:
|
||||
if proxy['type'] == 'socks5':
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
_version_ = '1.4.0'
|
||||
|
||||
"""\
|
||||
'''\
|
||||
|
||||
Generic debug class
|
||||
|
||||
|
@ -35,7 +35,7 @@ by the individual classes.
|
|||
For samples of usage, see samples subdir in distro source, and selftest
|
||||
in this code
|
||||
|
||||
"""
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
@ -70,7 +70,7 @@ color_bright_cyan = chr(27) + "[36;1m"
|
|||
color_white = chr(27) + "[37;1m"
|
||||
|
||||
|
||||
"""
|
||||
'''
|
||||
Define your flags in yor modules like this:
|
||||
|
||||
from debug import *
|
||||
|
@ -99,7 +99,7 @@ DBG_MULTI = [ DBG_INIT, DBG_CONNECTION ]
|
|||
-------
|
||||
To speed code up, typically for product releases or such
|
||||
use this class instead if you globaly want to disable debugging
|
||||
"""
|
||||
'''
|
||||
|
||||
|
||||
class NoDebug:
|
||||
|
@ -214,7 +214,7 @@ class Debug:
|
|||
|
||||
def show( self, msg, flag = None, prefix = None, sufix = None,
|
||||
lf = 0 ):
|
||||
"""
|
||||
'''
|
||||
flag can be of folowing types:
|
||||
None - this msg will always be shown if any debugging is on
|
||||
flag - will be shown if flag is active
|
||||
|
@ -225,7 +225,7 @@ class Debug:
|
|||
|
||||
lf = -1 means strip linefeed if pressent
|
||||
lf = 1 means add linefeed if not pressent
|
||||
"""
|
||||
'''
|
||||
|
||||
if self.validate_flags:
|
||||
self._validate_flag( flag )
|
||||
|
@ -343,10 +343,10 @@ class Debug:
|
|||
|
||||
|
||||
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
|
||||
"""
|
||||
'''
|
||||
if type( items ) <> type( [] ) and type( items ) <> type( () ):
|
||||
return [ items ]
|
||||
r = []
|
||||
|
@ -363,7 +363,7 @@ class Debug:
|
|||
|
||||
|
||||
def _append_unique_str( self, lst, item ):
|
||||
"""filter out any dupes."""
|
||||
'''filter out any dupes.'''
|
||||
if type(item) <> type(''):
|
||||
msg2 = '%s' % item
|
||||
raise 'Invalid item type (should be string)',msg2
|
||||
|
@ -381,10 +381,10 @@ class Debug:
|
|||
raise 'Invalid debugflag given', msg2
|
||||
|
||||
def _remove_dupe_flags( self ):
|
||||
"""
|
||||
'''
|
||||
if multiple instances of Debug is used in same app,
|
||||
some flags might be created multiple time, filter out dupes
|
||||
"""
|
||||
'''
|
||||
unique_flags = []
|
||||
for f in self.debug_flags:
|
||||
if f not in unique_flags:
|
||||
|
|
|
@ -38,9 +38,6 @@ ID = 0
|
|||
STREAM_TERMINATOR = '</stream:stream>'
|
||||
XML_DECLARATION = '<?xml version=\'1.0\'?>'
|
||||
|
||||
|
||||
|
||||
|
||||
# FIXME: ugly
|
||||
class Dispatcher():
|
||||
# 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/
|
||||
|
||||
# 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':
|
||||
XMPPDispatcher().PlugIn(client_obj)
|
||||
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)
|
||||
# end stream:stream tag received
|
||||
if self.Stream and self.Stream.has_received_endtag():
|
||||
# FIXME call client method
|
||||
self._owner.Connection.disconnect()
|
||||
self._owner.disconnect()
|
||||
return 0
|
||||
except ExpatError:
|
||||
log.error('Invalid XML received from server. Forcing disconnect.')
|
||||
self._owner.Connection.disconnect()
|
||||
self._owner.disconnect()
|
||||
return 0
|
||||
if len(self._pendingExceptions) > 0:
|
||||
_pendingException = self._pendingExceptions.pop()
|
||||
|
@ -380,7 +376,6 @@ class XMPPDispatcher(PlugIn):
|
|||
if not res:
|
||||
return
|
||||
self._owner.remove_timeout()
|
||||
print self._expected
|
||||
if self._expected[self._witid] is None:
|
||||
return
|
||||
if self.on_responses.has_key(self._witid):
|
||||
|
@ -427,7 +422,8 @@ class XMPPDispatcher(PlugIn):
|
|||
|
||||
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
|
||||
XMPPDispatcher.PlugIn(self, owner)
|
||||
|
||||
|
@ -437,7 +433,7 @@ class BOSHDispatcher(XMPPDispatcher):
|
|||
self.Stream.dispatch = self.dispatch
|
||||
self.Stream._dispatch_depth = 2
|
||||
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.setNamespace(self._owner.Namespace)
|
||||
|
@ -487,8 +483,7 @@ class BOSHDispatcher(XMPPDispatcher):
|
|||
self._owner.Connection.bosh_sid = stanza_attrs['sid']
|
||||
|
||||
if stanza_attrs.has_key('terminate'):
|
||||
# staznas under body still should be passed to XMPP dispatcher
|
||||
self._owner.on_disconnect()
|
||||
self._owner.disconnect()
|
||||
|
||||
if stanza_attrs.has_key('error'):
|
||||
# recoverable error
|
||||
|
@ -498,6 +493,9 @@ class BOSHDispatcher(XMPPDispatcher):
|
|||
|
||||
if 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:
|
||||
child.setNamespace(self._owner.defaultNamespace)
|
||||
XMPPDispatcher.dispatch(self, child, session, direct)
|
||||
|
|
|
@ -32,10 +32,10 @@ def _on_default_response(disp, iq, cb):
|
|||
disp.SendAndCallForResponse(iq, _on_response)
|
||||
|
||||
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)
|
||||
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)
|
||||
if 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 ???
|
||||
def discoverItems(disp,jid,node=None, cb=None):
|
||||
""" Query remote object about any items that it contains. Return items list. """
|
||||
""" According to JEP-0030:
|
||||
''' Query remote object about any items that it contains. Return items list. '''
|
||||
''' According to JEP-0030:
|
||||
query MAY have node attribute
|
||||
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):
|
||||
ret=[]
|
||||
for result in result_array:
|
||||
|
@ -78,11 +78,11 @@ def discoverItems(disp,jid,node=None, cb=None):
|
|||
|
||||
# this one is
|
||||
def discoverInfo(disp,jid,node=None, cb=None):
|
||||
""" Query remote object about info that it publishes. Returns identities and features lists."""
|
||||
""" According to JEP-0030:
|
||||
''' Query remote object about info that it publishes. Returns identities and features lists.'''
|
||||
''' According to JEP-0030:
|
||||
query MAY have node 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):
|
||||
identities , features = [] , []
|
||||
for i in result:
|
||||
|
@ -108,11 +108,11 @@ def discoverInfo(disp,jid,node=None, cb=None):
|
|||
|
||||
### Registration ### jabber:iq:register ### JEP-0077 ###########################
|
||||
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.
|
||||
F.e. if you are requesting info on registering user joey than specify
|
||||
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)
|
||||
for i in info.keys():
|
||||
iq.setTagData(i,info[i])
|
||||
|
@ -144,11 +144,11 @@ def _ReceivedRegInfo(con, resp, agent):
|
|||
con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,''))
|
||||
|
||||
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.
|
||||
If registration fails you can get additional info from the dispatcher's owner
|
||||
attributes lastErrNode, lastErr and lastErrCode.
|
||||
"""
|
||||
'''
|
||||
iq=Iq('set', NS_REGISTER, to=host)
|
||||
if not isinstance(info, dict):
|
||||
info=info.asDict()
|
||||
|
@ -157,16 +157,16 @@ def register(disp, host, info, cb):
|
|||
disp.SendAndCallForResponse(iq, 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.
|
||||
Returns true on success."""
|
||||
Returns true on success.'''
|
||||
iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')])
|
||||
_on_default_response(disp, iq, cb)
|
||||
|
||||
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.
|
||||
Returns true on success."""
|
||||
Returns true on success.'''
|
||||
if not host: host=disp._owner.Server
|
||||
iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username',
|
||||
payload=[disp._owner.Server]),Node('password',payload=[newpassword])])
|
||||
|
@ -177,8 +177,8 @@ def changePasswordTo(disp, newpassword, host=None, cb = None):
|
|||
#action=[allow|deny]
|
||||
|
||||
def getPrivacyLists(disp):
|
||||
""" Requests privacy lists from connected server.
|
||||
Returns dictionary of existing lists on success."""
|
||||
''' Requests privacy lists from connected server.
|
||||
Returns dictionary of existing lists on success.'''
|
||||
iq = Iq('get', NS_PRIVACY)
|
||||
def _on_response(resp):
|
||||
dict = {'lists': []}
|
||||
|
@ -209,8 +209,8 @@ def getActiveAndDefaultPrivacyLists(disp):
|
|||
disp.SendAndCallForResponse(iq, _on_response)
|
||||
|
||||
def getPrivacyList(disp, listname):
|
||||
""" Requests specific privacy list listname. Returns list of XML nodes (rules)
|
||||
taken from the server responce."""
|
||||
''' Requests specific privacy list listname. Returns list of XML nodes (rules)
|
||||
taken from the server responce.'''
|
||||
def _on_response(resp):
|
||||
if not isResultNode(resp):
|
||||
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
|
||||
|
@ -220,8 +220,8 @@ def getPrivacyList(disp, listname):
|
|||
disp.SendAndCallForResponse(iq, _on_response)
|
||||
|
||||
def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
|
||||
""" Switches privacy list 'listname' to specified type.
|
||||
By default the type is 'active'. Returns true on success."""
|
||||
''' Switches privacy list 'listname' to specified type.
|
||||
By default the type is 'active'. Returns true on success.'''
|
||||
if listname:
|
||||
attrs={'name':listname}
|
||||
else:
|
||||
|
@ -230,13 +230,13 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None):
|
|||
_on_default_response(disp, iq, cb)
|
||||
|
||||
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')
|
||||
|
||||
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=[...]) )
|
||||
Returns true on success."""
|
||||
Returns true on success.'''
|
||||
iq = Iq('set', NS_PRIVACY, xmlns = '')
|
||||
list_query = iq.getTag('query').setTag('list', {'name': listname})
|
||||
for item in tags:
|
||||
|
@ -252,6 +252,6 @@ def setPrivacyList(disp, listname, tags):
|
|||
_on_default_response(disp, iq, 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})])
|
||||
_on_default_response(disp, iq, cb)
|
||||
|
|
|
@ -56,8 +56,7 @@ class IdleQueue:
|
|||
self.selector = select.poll()
|
||||
|
||||
def remove_timeout(self, fd):
|
||||
#log.debug('read timeout removed for fd %s' % fd)
|
||||
print 'read timeout removed for fd %s' % fd
|
||||
log.info('read timeout removed for fd %s' % fd)
|
||||
if self.read_timeouts.has_key(fd):
|
||||
del(self.read_timeouts[fd])
|
||||
|
||||
|
@ -73,13 +72,12 @@ class IdleQueue:
|
|||
def set_read_timeout(self, fd, seconds):
|
||||
''' set a new timeout, if it is not removed after 'seconds',
|
||||
then obj.read_timeout() will be called '''
|
||||
#log.debug('read timeout set for fd %s on %s seconds' % (fd, seconds))
|
||||
print 'read timeout set for fd %s on %s seconds' % (fd, seconds)
|
||||
log.info('read timeout set for fd %s on %s seconds' % (fd, seconds))
|
||||
timeout = self.current_time() + seconds
|
||||
self.read_timeouts[fd] = timeout
|
||||
|
||||
def check_time_events(self):
|
||||
print 'check time evs'
|
||||
log.info('check time evs')
|
||||
current_time = self.current_time()
|
||||
for fd, timeout in self.read_timeouts.items():
|
||||
if timeout > current_time:
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $
|
||||
|
||||
"""
|
||||
'''
|
||||
Protocol module contains tools that is needed for processing of
|
||||
xmpp-related data structures.
|
||||
"""
|
||||
'''
|
||||
|
||||
from simplexml import Node,NodeBuilder,ustr
|
||||
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_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-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.
|
||||
|
@ -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-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.
|
||||
xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed."""
|
||||
xmpp_stanza_error_conditions="""
|
||||
xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.'''
|
||||
xmpp_stanza_error_conditions='''
|
||||
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.
|
||||
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.
|
||||
subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
|
||||
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)."""
|
||||
sasl_error_conditions="""
|
||||
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='''
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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={},{}
|
||||
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
|
||||
|
||||
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'
|
||||
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'
|
||||
|
||||
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):
|
||||
""" Base exception class for stream errors."""
|
||||
''' Base exception class for stream errors.'''
|
||||
class BadFormat(StreamError): pass
|
||||
class BadNamespacePrefix(StreamError): pass
|
||||
class Conflict(StreamError): pass
|
||||
|
@ -243,13 +243,13 @@ stream_exceptions = {'bad-format': BadFormat,
|
|||
'xml-not-well-formed': XMLNotWellFormed}
|
||||
|
||||
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=''):
|
||||
""" 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:
|
||||
JID('node@domain/resource')
|
||||
JID(node='node',domain='domain.org')
|
||||
"""
|
||||
'''
|
||||
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 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)
|
||||
else: self.domain,self.resource=jid,''
|
||||
def getNode(self):
|
||||
""" Return the node part of the JID """
|
||||
''' Return the node part of the JID '''
|
||||
return 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()
|
||||
def getDomain(self):
|
||||
""" Return the domain part of the JID """
|
||||
''' Return the domain part of the JID '''
|
||||
return 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()
|
||||
def getResource(self):
|
||||
""" Return the resource part of the JID """
|
||||
''' Return the resource part of the JID '''
|
||||
return 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
|
||||
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)
|
||||
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)
|
||||
except ValueError: return 0
|
||||
return self.resource==other.resource and self.__str__(0) == other.__str__(0)
|
||||
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)
|
||||
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)
|
||||
def __str__(self,wresource=1):
|
||||
""" Serialise JID into string. """
|
||||
''' Serialise JID into string. '''
|
||||
if self.node: jid=self.node+'@'+self.domain
|
||||
else: jid=self.domain
|
||||
if wresource and self.resource: return jid+'/'+self.resource
|
||||
return jid
|
||||
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__())
|
||||
|
||||
class BOSHBody(Node):
|
||||
|
@ -310,16 +310,16 @@ class BOSHBody(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):
|
||||
""" 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
|
||||
frn - from attribure, attrs - other attributes mapping,
|
||||
payload - same meaning as for simplexml payload definition
|
||||
timestamp - the time value that needs to be stamped over stanza
|
||||
xmlns - namespace of top stanza node
|
||||
node - parsed or unparsed stana to be taken as prototype.
|
||||
"""
|
||||
'''
|
||||
if not attrs: attrs={}
|
||||
if to: attrs['to']=to
|
||||
if frm: attrs['from']=frm
|
||||
|
@ -336,54 +336,54 @@ class Protocol(Node):
|
|||
except: pass
|
||||
if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
|
||||
def getTo(self):
|
||||
""" Return value of the 'to' attribute. """
|
||||
''' Return value of the 'to' attribute. '''
|
||||
try: return self['to']
|
||||
except: return None
|
||||
def getFrom(self):
|
||||
""" Return value of the 'from' attribute. """
|
||||
''' Return value of the 'from' attribute. '''
|
||||
try: return self['from']
|
||||
except: return None
|
||||
def getTimestamp(self):
|
||||
""" Return the timestamp in the 'yyyymmddThhmmss' format. """
|
||||
''' Return the timestamp in the 'yyyymmddThhmmss' format. '''
|
||||
if self.timestamp: return self.timestamp
|
||||
return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
|
||||
def getID(self):
|
||||
""" Return the value of the 'id' attribute. """
|
||||
''' Return the value of the 'id' attribute. '''
|
||||
return self.getAttr('id')
|
||||
def setTo(self,val):
|
||||
""" Set the value of the 'to' attribute. """
|
||||
''' Set the value of the 'to' attribute. '''
|
||||
self.setAttr('to', JID(val))
|
||||
def getType(self):
|
||||
""" Return the value of the 'type' attribute. """
|
||||
''' Return the value of the 'type' attribute. '''
|
||||
return self.getAttr('type')
|
||||
def setFrom(self,val):
|
||||
""" Set the value of the 'from' attribute. """
|
||||
''' Set the value of the 'from' attribute. '''
|
||||
self.setAttr('from', JID(val))
|
||||
def setType(self,val):
|
||||
""" Set the value of the 'type' attribute. """
|
||||
''' Set the value of the 'type' attribute. '''
|
||||
self.setAttr('type', val)
|
||||
def setID(self,val):
|
||||
""" Set the value of the 'id' attribute. """
|
||||
''' Set the value of the 'id' attribute. '''
|
||||
self.setAttr('id', val)
|
||||
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')
|
||||
if errtag:
|
||||
for tag in errtag.getChildren():
|
||||
if tag.getName()<>'text': return tag.getName()
|
||||
return errtag.getData()
|
||||
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')
|
||||
if errtag:
|
||||
for tag in errtag.getChildren():
|
||||
if tag.getName()=='text': return tag.getData()
|
||||
return self.getError()
|
||||
def getErrorCode(self):
|
||||
""" Return the error code. Obsolete. """
|
||||
''' Return the error code. Obsolete. '''
|
||||
return self.getTagAttr('error','code')
|
||||
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 str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],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.addChild(node=error)
|
||||
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())
|
||||
self.timestamp=val
|
||||
self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
|
||||
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=[]
|
||||
for child in self.getChildren():
|
||||
prop=child.getNamespace()
|
||||
if prop not in props: props.append(prop)
|
||||
return props
|
||||
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)
|
||||
return self.setAttr(item,val)
|
||||
|
||||
|
||||
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):
|
||||
""" 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.
|
||||
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)
|
||||
if body: self.setBody(body)
|
||||
if xhtml: self.setXHTML(xhtml)
|
||||
if subject is not None: self.setSubject(subject)
|
||||
def getBody(self):
|
||||
""" Returns text of the message. """
|
||||
''' Returns text of the message. '''
|
||||
return self.getTagData('body')
|
||||
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')
|
||||
if xhtml:
|
||||
if xmllang:
|
||||
|
@ -434,18 +434,18 @@ class Message(Protocol):
|
|||
return str(body)
|
||||
return None
|
||||
def getSubject(self):
|
||||
""" Returns subject of the message. """
|
||||
''' Returns subject of the message. '''
|
||||
return self.getTagData('subject')
|
||||
def getThread(self):
|
||||
""" Returns thread of the message. """
|
||||
''' Returns thread of the message. '''
|
||||
return self.getTagData('thread')
|
||||
def setBody(self,val):
|
||||
""" Sets the text of the message. """
|
||||
''' Sets the text of the message. '''
|
||||
self.setTagData('body',val)
|
||||
|
||||
def setXHTML(self,val,xmllang=None):
|
||||
""" Sets the xhtml text of the message (XEP-0071).
|
||||
The parameter is the "inner html" to the body."""
|
||||
''' Sets the xhtml text of the message (XEP-0071).
|
||||
The parameter is the "inner html" to the body.'''
|
||||
try:
|
||||
if xmllang:
|
||||
dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
|
||||
|
@ -459,21 +459,21 @@ class Message(Protocol):
|
|||
print "Error", e
|
||||
pass #FIXME: log. we could not set xhtml (parse error, whatever)
|
||||
def setSubject(self,val):
|
||||
""" Sets the subject of the message. """
|
||||
''' Sets the subject of the message. '''
|
||||
self.setTagData('subject',val)
|
||||
def setThread(self,val):
|
||||
""" Sets the thread of the message. """
|
||||
''' Sets the thread of the message. '''
|
||||
self.setTagData('thread',val)
|
||||
def buildReply(self,text=None):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
m=Message(to=self.getFrom(),frm=self.getTo(),body=text,node=self)
|
||||
th=self.getThread()
|
||||
if th: m.setThread(th)
|
||||
return m
|
||||
def getStatusCode(self):
|
||||
"""Returns the status code of the message (for groupchat config
|
||||
change)"""
|
||||
'''Returns the status code of the message (for groupchat config
|
||||
change)'''
|
||||
attrs = []
|
||||
for xtag in self.getTags('x'):
|
||||
for child in xtag.getTags('status'):
|
||||
|
@ -481,32 +481,32 @@ class Message(Protocol):
|
|||
return attrs
|
||||
|
||||
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):
|
||||
""" 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.
|
||||
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)
|
||||
if priority: self.setPriority(priority)
|
||||
if show: self.setShow(show)
|
||||
if status: self.setStatus(status)
|
||||
def getPriority(self):
|
||||
""" Returns the priority of the message. """
|
||||
''' Returns the priority of the message. '''
|
||||
return self.getTagData('priority')
|
||||
def getShow(self):
|
||||
""" Returns the show value of the message. """
|
||||
''' Returns the show value of the message. '''
|
||||
return self.getTagData('show')
|
||||
def getStatus(self):
|
||||
""" Returns the status string of the message. """
|
||||
''' Returns the status string of the message. '''
|
||||
return self.getTagData('status')
|
||||
def setPriority(self,val):
|
||||
""" Sets the priority of the message. """
|
||||
''' Sets the priority of the message. '''
|
||||
self.setTagData('priority',val)
|
||||
def setShow(self,val):
|
||||
""" Sets the show value of the message. """
|
||||
''' Sets the show value of the message. '''
|
||||
self.setTagData('show',val)
|
||||
def setStatus(self,val):
|
||||
""" Sets the status string of the message. """
|
||||
''' Sets the status string of the message. '''
|
||||
self.setTagData('status',val)
|
||||
|
||||
def _muc_getItemAttr(self,tag,attr):
|
||||
|
@ -520,25 +520,25 @@ class Presence(Protocol):
|
|||
return cchild.getData(),cchild.getAttr(attr)
|
||||
return None,None
|
||||
def getRole(self):
|
||||
"""Returns the presence role (for groupchat)"""
|
||||
'''Returns the presence role (for groupchat)'''
|
||||
return self._muc_getItemAttr('item','role')
|
||||
def getAffiliation(self):
|
||||
"""Returns the presence affiliation (for groupchat)"""
|
||||
'''Returns the presence affiliation (for groupchat)'''
|
||||
return self._muc_getItemAttr('item','affiliation')
|
||||
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')
|
||||
def getJid(self):
|
||||
"""Returns the presence jid (for groupchat)"""
|
||||
'''Returns the presence jid (for groupchat)'''
|
||||
return self._muc_getItemAttr('item','jid')
|
||||
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]
|
||||
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]
|
||||
def getStatusCode(self):
|
||||
"""Returns the status code of the presence (for groupchat)"""
|
||||
'''Returns the status code of the presence (for groupchat)'''
|
||||
attrs = []
|
||||
for xtag in self.getTags('x'):
|
||||
for child in xtag.getTags('status'):
|
||||
|
@ -546,53 +546,53 @@ class Presence(Protocol):
|
|||
return attrs
|
||||
|
||||
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):
|
||||
""" 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.
|
||||
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)
|
||||
if payload: self.setQueryPayload(payload)
|
||||
if queryNS: self.setQueryNS(queryNS)
|
||||
def getQueryNS(self):
|
||||
""" Return the namespace of the 'query' child element."""
|
||||
''' Return the namespace of the 'query' child element.'''
|
||||
tag=self.getTag('query')
|
||||
if tag: return tag.getNamespace()
|
||||
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')
|
||||
def getQueryPayload(self):
|
||||
""" Return the 'query' child element payload."""
|
||||
''' Return the 'query' child element payload.'''
|
||||
tag=self.getTag('query')
|
||||
if tag: return tag.getPayload()
|
||||
def getQueryChildren(self):
|
||||
""" Return the 'query' child element child nodes."""
|
||||
''' Return the 'query' child element child nodes.'''
|
||||
tag=self.getTag('query')
|
||||
if tag: return tag.getChildren()
|
||||
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)
|
||||
def setQueryPayload(self,payload):
|
||||
""" Set the 'query' child element payload."""
|
||||
''' Set the 'query' child element payload.'''
|
||||
self.setTag('query').setPayload(payload)
|
||||
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)
|
||||
def buildReply(self,typ):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
|
||||
if self.getTag('query'): iq.setQueryNS(self.getQueryNS())
|
||||
return iq
|
||||
|
||||
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 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):
|
||||
""" Create new error node object.
|
||||
''' Create new error node object.
|
||||
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):
|
||||
cod,type,txt=ERRORS[name]
|
||||
ns=name.split()[0]
|
||||
|
@ -607,30 +607,30 @@ class ErrorNode(Node):
|
|||
if cod: self.setAttr('code',cod)
|
||||
|
||||
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):
|
||||
""" 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)
|
||||
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)
|
||||
else: Protocol.__init__(self,node=node)
|
||||
self.setError(error)
|
||||
if node.getType()=='error': self.__str__=self.__dupstr__
|
||||
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.
|
||||
"""
|
||||
'''
|
||||
return ''
|
||||
|
||||
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)
|
||||
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):
|
||||
""" 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.
|
||||
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)
|
||||
if name: self.setVar(name)
|
||||
if type(value) in [list,tuple]: self.setValues(value)
|
||||
|
@ -641,70 +641,70 @@ class DataField(Node):
|
|||
if desc: self.setDesc(desc)
|
||||
if options: self.setOptions(options)
|
||||
def setRequired(self,req=1):
|
||||
""" Change the state of the 'required' flag. """
|
||||
''' Change the state of the 'required' flag. '''
|
||||
if req: self.setTag('required')
|
||||
else:
|
||||
try: self.delChild('required')
|
||||
except ValueError: return
|
||||
def isRequired(self):
|
||||
""" Returns in this field a required one. """
|
||||
''' Returns in this field a required one. '''
|
||||
return self.getTag('required')
|
||||
def setDesc(self,desc):
|
||||
""" Set the description of this field. """
|
||||
''' Set the description of this field. '''
|
||||
self.setTagData('desc',desc)
|
||||
def getDesc(self):
|
||||
""" Return the description of this field. """
|
||||
''' Return the description of this field. '''
|
||||
return self.getTagData('desc')
|
||||
def setValue(self,val):
|
||||
""" Set the value of this field. """
|
||||
''' Set the value of this field. '''
|
||||
self.setTagData('value',val)
|
||||
def getValue(self):
|
||||
return self.getTagData('value')
|
||||
def setValues(self,lst):
|
||||
""" 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."""
|
||||
''' 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.'''
|
||||
while self.getTag('value'): self.delChild('value')
|
||||
for val in lst: self.addValue(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])
|
||||
def getValues(self):
|
||||
""" Return the list of values associated with this field."""
|
||||
''' Return the list of values associated with this field.'''
|
||||
ret=[]
|
||||
for tag in self.getTags('value'): ret.append(tag.getData())
|
||||
return ret
|
||||
def getOptions(self):
|
||||
""" Return label-option pairs list associated with this field."""
|
||||
''' Return label-option pairs list associated with this field.'''
|
||||
ret=[]
|
||||
for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
|
||||
return ret
|
||||
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')
|
||||
for opt in lst: self.addOption(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)
|
||||
else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
|
||||
def getType(self):
|
||||
""" Get type of this field. """
|
||||
''' Get type of this field. '''
|
||||
return self.getAttr('type')
|
||||
def setType(self,val):
|
||||
""" Set type of this field. """
|
||||
''' Set type of this field. '''
|
||||
return self.setAttr('type',val)
|
||||
def getVar(self):
|
||||
""" Get 'var' attribute value of this field. """
|
||||
''' Get 'var' attribute value of this field. '''
|
||||
return self.getAttr('var')
|
||||
def setVar(self,val):
|
||||
""" Set 'var' attribute value of this field. """
|
||||
''' Set 'var' attribute value of this field. '''
|
||||
return self.setAttr('var',val)
|
||||
|
||||
class DataForm(Node):
|
||||
""" DataForm class. Used for manipulating dataforms in XMPP.
|
||||
''' DataForm class. Used for manipulating dataforms in XMPP.
|
||||
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):
|
||||
"""
|
||||
'''
|
||||
Create new dataform of type 'typ'. 'data' is the list of DataField
|
||||
instances that this dataform contains, 'title' - the title string.
|
||||
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.
|
||||
'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"
|
||||
"""
|
||||
'''
|
||||
Node.__init__(self,'x',node=node)
|
||||
if node:
|
||||
newkids=[]
|
||||
|
@ -736,36 +736,36 @@ class DataForm(Node):
|
|||
elif child.__class__.__name__=='DataField': self.kids.append(child)
|
||||
else: self.kids.append(DataField(node=child))
|
||||
def getType(self):
|
||||
""" Return the type of dataform. """
|
||||
''' Return the type of dataform. '''
|
||||
return self.getAttr('type')
|
||||
def setType(self,typ):
|
||||
""" Set the type of dataform. """
|
||||
''' Set the type of dataform. '''
|
||||
self.setAttr('type',typ)
|
||||
def getTitle(self):
|
||||
""" Return the title of dataform. """
|
||||
''' Return the title of dataform. '''
|
||||
return self.getTagData('title')
|
||||
def setTitle(self,text):
|
||||
""" Set the title of dataform. """
|
||||
''' Set the title of dataform. '''
|
||||
self.setTagData('title',text)
|
||||
def getInstructions(self):
|
||||
""" Return the instructions of dataform. """
|
||||
''' Return the instructions of dataform. '''
|
||||
return self.getTagData('instructions')
|
||||
def setInstructions(self,text):
|
||||
""" Set the instructions of dataform. """
|
||||
''' Set the instructions of dataform. '''
|
||||
self.setTagData('instructions',text)
|
||||
def addInstructions(self,text):
|
||||
""" Add one more instruction to the dataform. """
|
||||
''' Add one more instruction to the dataform. '''
|
||||
self.addChild('instructions',{},[text])
|
||||
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})
|
||||
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)
|
||||
if f: return f
|
||||
return self.addChild(node=DataField(name))
|
||||
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={}
|
||||
for field in self.getTags('field'):
|
||||
name=field.getAttr('var')
|
||||
|
@ -778,10 +778,10 @@ class DataForm(Node):
|
|||
if self.getTag('instructions'): ret['instructions']=self.getInstructions()
|
||||
return ret
|
||||
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)
|
||||
if item: return item.getValue()
|
||||
raise IndexError('No such field')
|
||||
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)
|
||||
|
|
|
@ -29,31 +29,31 @@ log = logging.getLogger('gajim.c.x.roster_nb')
|
|||
|
||||
|
||||
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
|
||||
account that every JID can have multiple resources connected. Does not
|
||||
currently support 'error' presences.
|
||||
You can also use mapping interface for access to the internal representation of
|
||||
contacts in roster.
|
||||
"""
|
||||
'''
|
||||
def __init__(self):
|
||||
""" Init internal variables. """
|
||||
''' Init internal variables. '''
|
||||
PlugIn.__init__(self)
|
||||
self._data = {}
|
||||
self.set=None
|
||||
self._exported_methods=[self.getRoster]
|
||||
|
||||
def Request(self,force=0):
|
||||
""" Request roster from server if it were not yet requested
|
||||
(or if the 'force' argument is set). """
|
||||
''' Request roster from server if it were not yet requested
|
||||
(or if the 'force' argument is set). '''
|
||||
if self.set is None: self.set=0
|
||||
elif not force: return
|
||||
self._owner.send(Iq('get',NS_ROSTER))
|
||||
log.info('Roster requested from server')
|
||||
|
||||
def RosterIqHandler(self,dis,stanza):
|
||||
""" Subscription tracker. Used internally for setting items state in
|
||||
internal roster representation. """
|
||||
''' Subscription tracker. Used internally for setting items state in
|
||||
internal roster representation. '''
|
||||
sender = stanza.getAttr('from')
|
||||
if not sender is None and not sender.bareMatch(
|
||||
self._owner.User + '@' + self._owner.Server):
|
||||
|
@ -75,8 +75,8 @@ class NonBlockingRoster(PlugIn):
|
|||
self.set=1
|
||||
|
||||
def PresenceHandler(self,dis,pres):
|
||||
""" Presence tracker. Used internally for setting items' resources state in
|
||||
internal roster representation. """
|
||||
''' Presence tracker. Used internally for setting items' resources state in
|
||||
internal roster representation. '''
|
||||
jid=pres.getFrom()
|
||||
if not jid:
|
||||
# If no from attribue, it's from server
|
||||
|
@ -100,11 +100,11 @@ class NonBlockingRoster(PlugIn):
|
|||
# Need to handle type='error' also
|
||||
|
||||
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('/')]
|
||||
return self._data[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:
|
||||
jid,resource=jid.split('/',1)
|
||||
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'])
|
||||
return self._data[jid]['resources'][resource][dataname]
|
||||
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'})]))
|
||||
def getAsk(self,jid):
|
||||
""" Returns 'ask' value of contact 'jid'."""
|
||||
''' Returns 'ask' value of contact 'jid'.'''
|
||||
return self._getItemData(jid,'ask')
|
||||
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')
|
||||
def getName(self,jid):
|
||||
""" Returns name of contact 'jid'."""
|
||||
''' Returns name of contact 'jid'.'''
|
||||
return self._getItemData(jid,'name')
|
||||
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')
|
||||
def getRawRoster(self):
|
||||
""" Returns roster representation in internal format. """
|
||||
''' Returns roster representation in internal format. '''
|
||||
return self._data
|
||||
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('/')]]
|
||||
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')
|
||||
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')
|
||||
def getSubscription(self,jid):
|
||||
""" Returns 'subscription' value of contact 'jid'."""
|
||||
''' Returns 'subscription' value of contact 'jid'.'''
|
||||
return self._getItemData(jid,'subscription')
|
||||
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()
|
||||
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)
|
||||
query=iq.getTag('query')
|
||||
attrs={'jid':jid}
|
||||
|
@ -156,32 +156,32 @@ class NonBlockingRoster(PlugIn):
|
|||
for group in groups: item.addChild(node=Node('group',payload=[group]))
|
||||
self._owner.send(iq)
|
||||
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()
|
||||
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()
|
||||
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]
|
||||
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]
|
||||
def Subscribe(self,jid):
|
||||
""" Send subscription request to JID 'jid'."""
|
||||
''' Send subscription request to JID 'jid'.'''
|
||||
self._owner.send(Presence(jid,'subscribe'))
|
||||
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'))
|
||||
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'))
|
||||
def Unauthorize(self,jid):
|
||||
""" Unauthorise JID 'jid'. Use for declining authorisation request
|
||||
or for removing existing authorization. """
|
||||
''' Unauthorise JID 'jid'. Use for declining authorisation request
|
||||
or for removing existing authorization. '''
|
||||
self._owner.send(Presence(jid,'unsubscribed'))
|
||||
def getRaw(self):
|
||||
"""Returns the internal data representation of the roster."""
|
||||
'''Returns the internal data representation of the roster.'''
|
||||
return self._data
|
||||
# copypasted methods for roster.py from constructor to here
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
# $Id: simplexml.py,v 1.27 2005/04/30 07:20:27 snakeru Exp $
|
||||
|
||||
"""Simplexml module provides xmpppy library with all needed tools to handle XML nodes and XML streams.
|
||||
I'm personally using it in many other separate projects. It is designed to be as standalone as possible."""
|
||||
'''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.'''
|
||||
|
||||
import xml.parsers.expat
|
||||
import logging
|
||||
|
@ -23,13 +23,13 @@ log = logging.getLogger('gajim.c.x.simplexml')
|
|||
#log.setLevel(logging.DEBUG)
|
||||
|
||||
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
|
||||
return txt.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """).replace(u'\x0C', "").replace(u'\x1B', "")
|
||||
|
||||
ENCODING='utf-8'
|
||||
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
|
||||
try: r=what.__str__()
|
||||
except AttributeError: r=str(what)
|
||||
|
@ -37,7 +37,7 @@ def ustr(what):
|
|||
return r
|
||||
|
||||
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.
|
||||
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.
|
||||
|
@ -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
|
||||
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).
|
||||
"""
|
||||
'''
|
||||
FORCE_NODE_RECREATION=0
|
||||
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
|
||||
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
|
||||
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"
|
||||
provided and then modified to be compliant with other arguments."""
|
||||
provided and then modified to be compliant with other arguments.'''
|
||||
if node:
|
||||
if self.FORCE_NODE_RECREATION and isinstance(node, Node):
|
||||
node=str(node)
|
||||
|
@ -83,8 +83,8 @@ class Node(object):
|
|||
else: self.data.append(ustr(i))
|
||||
|
||||
def __str__(self,fancy=0):
|
||||
""" Method used to dump node into textual representation.
|
||||
if "fancy" argument is set to True produces indented output for readability."""
|
||||
''' Method used to dump node into textual representation.
|
||||
if "fancy" argument is set to True produces indented output for readability.'''
|
||||
s = (fancy-1) * 2 * ' ' + "<" + self.name
|
||||
if self.namespace:
|
||||
if not self.parent or self.parent.namespace!=self.namespace:
|
||||
|
@ -115,8 +115,8 @@ class Node(object):
|
|||
if fancy: s = s + "\n"
|
||||
return s
|
||||
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
|
||||
the other arguments' values and adds it as well."""
|
||||
''' If "node" argument is provided, adds it as child node. Else creates new node from
|
||||
the other arguments' values and adds it as well.'''
|
||||
if namespace: name=namespace+' '+name
|
||||
if node:
|
||||
newnode=node
|
||||
|
@ -125,46 +125,46 @@ class Node(object):
|
|||
self.kids.append(newnode)
|
||||
return newnode
|
||||
def addData(self, data):
|
||||
""" Adds some CDATA to node. """
|
||||
''' Adds some CDATA to node. '''
|
||||
self.data.append(ustr(data))
|
||||
def clearData(self):
|
||||
""" Removes all CDATA from the node. """
|
||||
''' Removes all CDATA from the node. '''
|
||||
self.data=[]
|
||||
def delAttr(self, key):
|
||||
""" Deletes an attribute "key" """
|
||||
''' Deletes an attribute "key" '''
|
||||
del self.attrs[key]
|
||||
def delChild(self, node, attrs={}):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
if not isinstance(node, Node): node=self.getTag(node,attrs)
|
||||
self.kids.remove(node)
|
||||
return node
|
||||
def getAttrs(self):
|
||||
""" Returns all node's attributes as dictionary. """
|
||||
''' Returns all node's attributes as dictionary. '''
|
||||
return self.attrs
|
||||
def getAttr(self, key):
|
||||
""" Returns value of specified attribute. """
|
||||
''' Returns value of specified attribute. '''
|
||||
try: return self.attrs[key]
|
||||
except: return None
|
||||
def getChildren(self):
|
||||
""" Returns all node's child nodes as list. """
|
||||
''' Returns all node's child nodes as list. '''
|
||||
return self.kids
|
||||
def getData(self):
|
||||
""" Returns all node CDATA as string (concatenated). """
|
||||
''' Returns all node CDATA as string (concatenated). '''
|
||||
return ''.join(self.data)
|
||||
def getName(self):
|
||||
""" Returns the name of node """
|
||||
''' Returns the name of node '''
|
||||
return self.name
|
||||
def getNamespace(self):
|
||||
""" Returns the namespace of node """
|
||||
''' Returns the namespace of node '''
|
||||
return self.namespace
|
||||
def getParent(self):
|
||||
""" Returns the parent of node (if present). """
|
||||
''' Returns the parent of node (if present). '''
|
||||
return self.parent
|
||||
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:
|
||||
['text1', <nodea instance>, <nodeb instance>, ' text2']. """
|
||||
['text1', <nodea instance>, <nodeb instance>, ' text2']. '''
|
||||
ret=[]
|
||||
for i in range(len(self.kids)+len(self.data)+1):
|
||||
try:
|
||||
|
@ -174,20 +174,20 @@ class Node(object):
|
|||
except IndexError: pass
|
||||
return ret
|
||||
def getTag(self, name, attrs={}, namespace=None):
|
||||
""" Filters all child nodes using specified arguments as filter.
|
||||
Returns the first found or None if not found. """
|
||||
''' Filters all child nodes using specified arguments as filter.
|
||||
Returns the first found or None if not found. '''
|
||||
return self.getTags(name, attrs, namespace, one=1)
|
||||
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]
|
||||
except: return None
|
||||
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()
|
||||
except: return None
|
||||
def getTags(self, name, attrs={}, namespace=None, one=0):
|
||||
""" Filters all child nodes using specified arguments as filter.
|
||||
Returns the list of nodes found. """
|
||||
''' Filters all child nodes using specified arguments as filter.
|
||||
Returns the list of nodes found. '''
|
||||
nodes=[]
|
||||
for node in self.kids:
|
||||
if namespace and namespace<>node.getNamespace(): continue
|
||||
|
@ -199,7 +199,7 @@ class Node(object):
|
|||
if not one: return nodes
|
||||
|
||||
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:
|
||||
if namespace is not None and namespace!=node.getNamespace(): continue
|
||||
if node.getName() == name:
|
||||
|
@ -210,57 +210,57 @@ class Node(object):
|
|||
yield node
|
||||
|
||||
def setAttr(self, key, val):
|
||||
""" Sets attribute "key" with the value "val". """
|
||||
''' Sets attribute "key" with the value "val". '''
|
||||
self.attrs[key]=val
|
||||
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)]
|
||||
def setName(self,val):
|
||||
""" Changes the node name. """
|
||||
''' Changes the node name. '''
|
||||
self.name = val
|
||||
def setNamespace(self, namespace):
|
||||
""" Changes the node namespace. """
|
||||
''' Changes the node namespace. '''
|
||||
self.namespace=namespace
|
||||
def setParent(self, node):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
self.parent = node
|
||||
def setPayload(self,payload,add=0):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
if type(payload) in (type(''),type(u'')): payload=[payload]
|
||||
if add: self.kids+=payload
|
||||
else: self.kids=payload
|
||||
def setTag(self, name, attrs={}, namespace=None):
|
||||
""" Same as getTag but if the node with specified namespace/attributes not found, creates such
|
||||
node and returns it. """
|
||||
''' Same as getTag but if the node with specified namespace/attributes not found, creates such
|
||||
node and returns it. '''
|
||||
node=self.getTags(name, attrs, namespace=namespace, one=1)
|
||||
if node: return node
|
||||
else: return self.addChild(name, attrs, namespace=namespace)
|
||||
def setTagAttr(self,tag,attr,val):
|
||||
""" Creates new node (if not already present) with name "tag"
|
||||
and sets it's attribute "attr" to value "val". """
|
||||
''' Creates new node (if not already present) with name "tag"
|
||||
and sets it's attribute "attr" to value "val". '''
|
||||
try: self.getTag(tag).attrs[attr]=val
|
||||
except: self.addChild(tag,attrs={attr:val})
|
||||
def setTagData(self,tag,val,attrs={}):
|
||||
""" Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"
|
||||
and sets it's CDATA to string "val". """
|
||||
''' Creates new node (if not already present) with name "tag" and (optionally) attributes "attrs"
|
||||
and sets it's CDATA to string "val". '''
|
||||
try: self.getTag(tag,attrs).setData(ustr(val))
|
||||
except: self.addChild(tag,attrs,payload=[ustr(val)])
|
||||
def has_attr(self,key):
|
||||
""" Checks if node have attribute "key"."""
|
||||
''' Checks if node have attribute "key".'''
|
||||
return self.attrs.has_key(key)
|
||||
def __getitem__(self,item):
|
||||
""" Returns node's attribute "item" value. """
|
||||
''' Returns node's attribute "item" value. '''
|
||||
return self.getAttr(item)
|
||||
def __setitem__(self,item,val):
|
||||
""" Sets node's attribute "item" value. """
|
||||
''' Sets node's attribute "item" value. '''
|
||||
return self.setAttr(item,val)
|
||||
def __delitem__(self,item):
|
||||
""" Deletes node's attribute "item". """
|
||||
''' Deletes node's attribute "item". '''
|
||||
return self.delAttr(item)
|
||||
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':
|
||||
self.T=T(self)
|
||||
return self.T
|
||||
|
@ -270,7 +270,7 @@ class Node(object):
|
|||
raise AttributeError
|
||||
|
||||
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 __getattr__(self,attr): return self.node.setTag(attr)
|
||||
def __setattr__(self,attr,val):
|
||||
|
@ -279,25 +279,25 @@ class T:
|
|||
def __delattr__(self,attr): return self.node.delChild(attr)
|
||||
|
||||
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 __setattr__(self,attr,val):
|
||||
if isinstance(val,Node): self.node.addChild(attr,node=val)
|
||||
else: return self.node.addChild(attr,payload=[val])
|
||||
|
||||
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.
|
||||
2. Handling an incoming XML stream. This is done by mangling
|
||||
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):
|
||||
""" 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.
|
||||
Though, if "initial_node" is provided it used as "starting point".
|
||||
You can think about it as of "node upgrade".
|
||||
"data" (if provided) feeded to parser immidiatedly after instance init.
|
||||
"""
|
||||
'''
|
||||
log.debug("Preparing to handle incoming XML stream.")
|
||||
self._parser = xml.parsers.expat.ParserCreate(namespace_separator=' ')
|
||||
self._parser.StartElementHandler = self.starttag
|
||||
|
@ -328,7 +328,7 @@ class NodeBuilder:
|
|||
self.data_buffer = None
|
||||
|
||||
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._parser.StartElementHandler = None
|
||||
self._parser.EndElementHandler = None
|
||||
|
@ -336,7 +336,7 @@ class NodeBuilder:
|
|||
self._parser.StartNamespaceDeclHandler = None
|
||||
|
||||
def starttag(self, tag, attrs):
|
||||
"""XML Parser callback. Used internally"""
|
||||
'''XML Parser callback. Used internally'''
|
||||
self.check_data_buffer()
|
||||
attlist=attrs.keys() #
|
||||
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.last_is_data = 0
|
||||
def endtag(self, tag ):
|
||||
"""XML Parser callback. Used internally"""
|
||||
'''XML Parser callback. Used internally'''
|
||||
log.info("DEPTH -> %i , tag -> %s" % (self.__depth, tag))
|
||||
self.check_data_buffer()
|
||||
if self.__depth == self._dispatch_depth:
|
||||
|
@ -386,27 +386,27 @@ class NodeBuilder:
|
|||
self.last_is_data = 1
|
||||
|
||||
def handle_namespace_start(self, prefix, uri):
|
||||
"""XML Parser callback. Used internally"""
|
||||
'''XML Parser callback. Used internally'''
|
||||
self.check_data_buffer()
|
||||
if prefix: self.namespaces[uri]=prefix+':'
|
||||
else: self.xmlns=uri
|
||||
|
||||
def getDom(self):
|
||||
""" Returns just built Node. """
|
||||
''' Returns just built Node. '''
|
||||
self.check_data_buffer()
|
||||
return self._mini_dom
|
||||
def dispatch(self,stanza):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
def stream_header_received(self,ns,tag,attrs):
|
||||
""" Method called when stream just opened. """
|
||||
''' Method called when stream just opened. '''
|
||||
self.check_data_buffer()
|
||||
def stream_footer_received(self):
|
||||
""" Method called when stream just closed. """
|
||||
''' Method called when stream just closed. '''
|
||||
self.check_data_buffer()
|
||||
|
||||
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
|
||||
|
||||
def _inc_depth(self):
|
||||
|
@ -419,12 +419,12 @@ class NodeBuilder:
|
|||
self.__depth -= 1
|
||||
|
||||
def XML2Node(xml):
|
||||
""" 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. """
|
||||
''' 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. '''
|
||||
return NodeBuilder(xml).getDom()
|
||||
|
||||
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
|
||||
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()
|
||||
|
|
|
@ -129,13 +129,13 @@ class SSLWrapper:
|
|||
log.debug("%s.__init__ called with %s", self.__class__, sslobj)
|
||||
|
||||
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
|
||||
available right now. Better than an exception, which differs
|
||||
depending on which SSL lib we're using. Unfortunately returning ''
|
||||
can indicate that the socket has been closed, so to be sure, we avoid
|
||||
this by returning None. """
|
||||
this by returning None. '''
|
||||
|
||||
raise NotImplementedException()
|
||||
|
||||
|
|
|
@ -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
|
||||
# pass for http proxy - If there's no proxy_host they won't be used
|
||||
if proxy.has_key('user'):
|
||||
proxy_user = proxy['user']
|
||||
else:
|
||||
proxy_user = None
|
||||
if proxy.has_key('pass'):
|
||||
proxy_pass = proxy['pass']
|
||||
else:
|
||||
proxy_pass = None
|
||||
if proxy.has_key('user'): proxy_user = proxy['user']
|
||||
else: proxy_user = None
|
||||
if proxy.has_key('pass'): proxy_pass = proxy['pass']
|
||||
else: proxy_pass = None
|
||||
return tcp_host, tcp_port, proxy_user, proxy_pass
|
||||
|
||||
|
||||
|
@ -89,6 +85,7 @@ DATA_SENT='DATA SENT'
|
|||
|
||||
|
||||
DISCONNECTED ='DISCONNECTED'
|
||||
DISCONNECTING ='DISCONNECTING'
|
||||
CONNECTING ='CONNECTING'
|
||||
CONNECTED ='CONNECTED'
|
||||
|
||||
|
@ -132,11 +129,10 @@ class NonBlockingTransport(PlugIn):
|
|||
self.on_connect_failure = on_connect_failure
|
||||
(self.server, self.port) = conn_5tuple[4][:2]
|
||||
self.conn_5tuple = conn_5tuple
|
||||
log.info('NonBlocking Connect :: About to connect to %s:%s' % (self.server, self.port))
|
||||
|
||||
|
||||
def set_state(self, newstate):
|
||||
assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED])
|
||||
assert(newstate in [DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING])
|
||||
self.state = newstate
|
||||
|
||||
def _on_connect(self, data):
|
||||
|
@ -156,9 +152,8 @@ class NonBlockingTransport(PlugIn):
|
|||
self.on_connect_failure(err_message=err_message)
|
||||
|
||||
def send(self, raw_data, now=False):
|
||||
if self.state not in [CONNECTED]:
|
||||
# FIXME better handling needed
|
||||
log.error('Trying to send %s when transport is %s.' %
|
||||
if self.state != CONNECTED:
|
||||
log.error('Trying to send %s when state is %s.' %
|
||||
(raw_data, self.state))
|
||||
return
|
||||
|
||||
|
@ -195,13 +190,13 @@ class NonBlockingTransport(PlugIn):
|
|||
self.remove_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):
|
||||
pass
|
||||
|
||||
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):
|
||||
self.sendtimeout = timeout
|
||||
|
@ -220,26 +215,15 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
Class constructor.
|
||||
'''
|
||||
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
|
||||
self.sendqueue = []
|
||||
|
||||
# bytes remained from the last send message
|
||||
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):
|
||||
'''
|
||||
Creates and connects socket to server and port defined in conn_5tupe which
|
||||
|
@ -250,17 +234,21 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
connection
|
||||
'''
|
||||
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:
|
||||
self._sock = socket.socket(*conn_5tuple[:3])
|
||||
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
|
||||
|
||||
self._send = self._sock.send
|
||||
self._recv = self._sock.recv
|
||||
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
|
||||
|
||||
errnum = 0
|
||||
|
@ -268,7 +256,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
# set timeout for TCP connecting - if nonblocking connect() fails, pollend
|
||||
# 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:
|
||||
self._sock.setblocking(False)
|
||||
|
@ -296,7 +284,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
def _on_connect(self, data):
|
||||
''' 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)
|
||||
|
||||
|
@ -328,12 +316,14 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
def disconnect(self, do_callback=True):
|
||||
if self.state == DISCONNECTED:
|
||||
return
|
||||
self.idlequeue.unplug_idle(self.get_fd())
|
||||
self.set_state(DISCONNECTING)
|
||||
self.idlequeue.unplug_idle(self.fd)
|
||||
try:
|
||||
self._sock.shutdown(socket.SHUT_RDWR)
|
||||
self._sock.close()
|
||||
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)
|
||||
|
||||
def read_timeout(self):
|
||||
|
@ -352,12 +342,16 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
|
||||
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)
|
||||
else:
|
||||
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd))
|
||||
|
||||
def remove_timeout(self):
|
||||
if self.get_fd():
|
||||
if self.fd:
|
||||
NonBlockingTransport.remove_timeout(self)
|
||||
else:
|
||||
log.warn('remove_timeout: no self.fd state is %s' % self.state)
|
||||
|
||||
def send(self, raw_data, now=False):
|
||||
'''Append raw_data to the queue of messages to be send.
|
||||
|
@ -374,39 +368,42 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
self._do_send()
|
||||
else:
|
||||
self.sendqueue.append(r)
|
||||
self._plug_idle()
|
||||
self._plug_idle(writable=True, readable=True)
|
||||
|
||||
|
||||
|
||||
def _plug_idle(self):
|
||||
# readable if socket is connected or disconnecting
|
||||
readable = self.state != DISCONNECTED
|
||||
fd = self.get_fd()
|
||||
# writeable if sth to send
|
||||
if self.sendqueue or self.sendbuff:
|
||||
writable = True
|
||||
else:
|
||||
writable = False
|
||||
log.debug('About to plug fd %d, W:%s, R:%s' % (fd, writable, readable))
|
||||
if self.writable != writable or self.readable != readable:
|
||||
log.debug('Really plugging fd %d, W:%s, R:%s' % (fd, writable, readable))
|
||||
self.idlequeue.plug_idle(self, writable, readable)
|
||||
else:
|
||||
log.debug('Not plugging fd %s because it\'s already plugged' % fd)
|
||||
def _plug_idle(self, writable, readable):
|
||||
'''
|
||||
Plugs file descriptor of socket to Idlequeue. Plugged socket
|
||||
will be watched for "send possible" or/and "recv possible" events. pollin()
|
||||
callback is invoked on "recv possible", pollout() on "send_possible".
|
||||
Plugged socket will always be watched for "error" event - in that case,
|
||||
pollend() is called.
|
||||
'''
|
||||
# if we are connecting, we shouln't touch the socket until it's connected
|
||||
assert(self.state!=CONNECTING)
|
||||
self.idlequeue.plug_idle(self, writable, readable)
|
||||
|
||||
log.info('Plugging fd %d, W:%s, R:%s' % (self.fd, writable, readable))
|
||||
self.idlequeue.plug_idle(self, writable, readable)
|
||||
|
||||
|
||||
|
||||
|
||||
def _do_send(self):
|
||||
if not self.sendbuff:
|
||||
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)
|
||||
try:
|
||||
send_count = self._send(self.sendbuff)
|
||||
if send_count:
|
||||
sent_data = 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)
|
||||
|
||||
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
|
||||
# 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))
|
||||
self.disconnect()
|
||||
if self.on_remote_disconnect:
|
||||
self.on_remote_disconnect()
|
||||
else:
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
if received is None:
|
||||
|
@ -454,15 +454,13 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
# we have received some bytes, stop the timeout!
|
||||
self.renew_send_timeout()
|
||||
# pass received data to owner
|
||||
#self.
|
||||
if self.on_receive:
|
||||
self.raise_event(DATA_RECEIVED, received)
|
||||
self._on_receive(received)
|
||||
else:
|
||||
# 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))
|
||||
import traceback
|
||||
traceback.print_stack()
|
||||
self.disconnect()
|
||||
|
||||
|
@ -474,12 +472,14 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
|
|||
|
||||
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
|
||||
'''
|
||||
|
||||
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)
|
||||
if self.http_protocol is None:
|
||||
|
@ -488,12 +488,12 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
http_path = '/'
|
||||
self.http_port = http_port
|
||||
self.http_version = http_version
|
||||
self.http_persistent = http_persistent
|
||||
# buffer for partial responses
|
||||
self.recvbuff = ''
|
||||
self.expected_length = 0
|
||||
self.pending_requests = 0
|
||||
self.on_http_request_possible = on_http_request_possible
|
||||
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue)
|
||||
|
||||
def send(self, raw_data, now=False):
|
||||
NonBlockingTCP.send(
|
||||
|
@ -503,6 +503,19 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
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):
|
||||
'''Preceeds passing received data to owner class. Gets rid of HTTP headers
|
||||
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)))
|
||||
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
|
||||
httpbody = self.recvbuff
|
||||
|
||||
|
@ -534,17 +544,18 @@ class NonBlockingHTTP(NonBlockingTCP):
|
|||
self.expected_length=0
|
||||
self.pending_requests -= 1
|
||||
assert(self.pending_requests >= 0)
|
||||
# not-persistent connections
|
||||
self.disconnect(do_callback = False)
|
||||
if not self.http_persistent:
|
||||
# not-persistent connections disconnect after response
|
||||
self.disconnect(do_callback = False)
|
||||
self.on_receive(httpbody)
|
||||
self.on_http_request_possible()
|
||||
|
||||
|
||||
|
||||
def build_http_message(self, httpbody, method='POST'):
|
||||
'''
|
||||
Builds http message with given body.
|
||||
Values for headers and status line fields are taken from class variables.
|
||||
)
|
||||
'''
|
||||
absolute_uri = '%s://%s:%s%s' % (self.http_protocol, self.http_host,
|
||||
self.http_port, self.http_path)
|
||||
|
|
|
@ -50,7 +50,8 @@ import logging
|
|||
consoleloghandler = logging.StreamHandler()
|
||||
consoleloghandler.setLevel(1)
|
||||
consoleloghandler.setFormatter(
|
||||
logging.Formatter('%(name)s: %(levelname)s: %(message)s'))
|
||||
logging.Formatter('%(name)s: %(levelname)s: %(message)s')
|
||||
)
|
||||
log = logging.getLogger('gajim')
|
||||
log.setLevel(logging.WARNING)
|
||||
log.addHandler(consoleloghandler)
|
||||
|
|
Loading…
Reference in New Issue