Issue 26. Only set from address in reply() for components

This commit is contained in:
Joe Hildebrand 2010-07-20 13:55:48 -07:00
parent 66e92c6c9f
commit d70a6e6f32
3 changed files with 635 additions and 628 deletions

View file

@ -30,231 +30,232 @@ from . import plugins
#from . import stanza #from . import stanza
srvsupport = True srvsupport = True
try: try:
import dns.resolver import dns.resolver
except ImportError: except ImportError:
srvsupport = False srvsupport = False
#class PresenceStanzaType(object): #class PresenceStanzaType(object):
# #
# def fromXML(self, xml): # def fromXML(self, xml):
# self.ptype = xml.get('type') # self.ptype = xml.get('type')
class ClientXMPP(basexmpp, XMLStream): class ClientXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil.""" """SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
global srvsupport global srvsupport
XMLStream.__init__(self) XMLStream.__init__(self)
self.default_ns = 'jabber:client' self.default_ns = 'jabber:client'
basexmpp.__init__(self) basexmpp.__init__(self)
self.plugin_config = plugin_config self.plugin_config = plugin_config
self.escape_quotes = escape_quotes self.escape_quotes = escape_quotes
self.set_jid(jid) self.set_jid(jid)
self.plugin_whitelist = plugin_whitelist self.plugin_whitelist = plugin_whitelist
self.auto_reconnect = True self.auto_reconnect = True
self.srvsupport = srvsupport self.srvsupport = srvsupport
self.password = password self.password = password
self.registered_features = [] self.registered_features = []
self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.server,self.default_ns) self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.server,self.default_ns)
self.stream_footer = "</stream:stream>" self.stream_footer = "</stream:stream>"
#self.map_namespace('http://etherx.jabber.org/streams', 'stream') #self.map_namespace('http://etherx.jabber.org/streams', 'stream')
#self.map_namespace('jabber:client', '') #self.map_namespace('jabber:client', '')
self.features = [] self.features = []
#TODO: Use stream state here #TODO: Use stream state here
self.authenticated = False self.authenticated = False
self.sessionstarted = False self.sessionstarted = False
self.bound = False self.bound = False
self.bindfail = False self.bindfail = False
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) self.is_component = False
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True)) self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True) #self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True) self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource) self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session) self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType) #self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins() #self.register_plugins()
def __getitem__(self, key): def __getitem__(self, key):
if key in self.plugin: if key in self.plugin:
return self.plugin[key] return self.plugin[key]
else: else:
logging.warning("""Plugin "%s" is not loaded.""" % key) logging.warning("""Plugin "%s" is not loaded.""" % key)
return False return False
def get(self, key, default): def get(self, key, default):
return self.plugin.get(key, default) return self.plugin.get(key, default)
def connect(self, address=tuple()): def connect(self, address=tuple()):
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses """Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
the JID server.""" the JID server."""
if not address or len(address) < 2: if not address or len(address) < 2:
if not self.srvsupport: if not self.srvsupport:
logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.") logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.")
else: else:
logging.debug("Since no address is supplied, attempting SRV lookup.") logging.debug("Since no address is supplied, attempting SRV lookup.")
try: try:
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV) answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV)
except dns.resolver.NXDOMAIN: except dns.resolver.NXDOMAIN:
logging.debug("No appropriate SRV record found. Using JID server name.") logging.debug("No appropriate SRV record found. Using JID server name.")
else: else:
# pick a random answer, weighted by priority # pick a random answer, weighted by priority
# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway # there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway
# suggestions are welcome # suggestions are welcome
addresses = {} addresses = {}
intmax = 0 intmax = 0
priorities = [] priorities = []
for answer in answers: for answer in answers:
intmax += answer.priority intmax += answer.priority
addresses[intmax] = (answer.target.to_text()[:-1], answer.port) addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
picked = random.randint(0, intmax) picked = random.randint(0, intmax)
for priority in priorities: for priority in priorities:
if picked <= priority: if picked <= priority:
address = addresses[priority] address = addresses[priority]
break break
if not address: if not address:
# if all else fails take server from JID. # if all else fails take server from JID.
address = (self.server, 5222) address = (self.server, 5222)
result = XMLStream.connect(self, address[0], address[1], use_tls=True) result = XMLStream.connect(self, address[0], address[1], use_tls=True)
if result: if result:
self.event("connected") self.event("connected")
else: else:
logging.warning("Failed to connect") logging.warning("Failed to connect")
self.event("disconnected") self.event("disconnected")
return result return result
# overriding reconnect and disconnect so that we can get some events # overriding reconnect and disconnect so that we can get some events
# should events be part of or required by xmlstream? Maybe that would be cleaner # should events be part of or required by xmlstream? Maybe that would be cleaner
def reconnect(self): def reconnect(self):
logging.info("Reconnecting") logging.info("Reconnecting")
self.event("disconnected") self.event("disconnected")
XMLStream.reconnect(self) XMLStream.reconnect(self)
def disconnect(self, init=True, close=False, reconnect=False): def disconnect(self, init=True, close=False, reconnect=False):
self.event("disconnected") self.event("disconnected")
XMLStream.disconnect(self, reconnect) XMLStream.disconnect(self, reconnect)
def registerFeature(self, mask, pointer, breaker = False): def registerFeature(self, mask, pointer, breaker = False):
"""Register a stream feature.""" """Register a stream feature."""
self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
def updateRoster(self, jid, name=None, subscription=None, groups=[]): def updateRoster(self, jid, name=None, subscription=None, groups=[]):
"""Add or change a roster item.""" """Add or change a roster item."""
iq = self.Iq().setStanzaValues({'type': 'set'}) iq = self.Iq().setStanzaValues({'type': 'set'})
iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
#self.send(iq, self.Iq().setValues({'id': iq['id']})) #self.send(iq, self.Iq().setValues({'id': iq['id']}))
r = iq.send() r = iq.send()
return r['type'] == 'result' return r['type'] == 'result'
def delRosterItem(self, jid): def delRosterItem(self, jid):
iq = self.Iq() iq = self.Iq()
iq['type'] = 'set' iq['type'] = 'set'
iq['roster']['items'] = {jid: {'subscription': 'remove'}} iq['roster']['items'] = {jid: {'subscription': 'remove'}}
return iq.send()['type'] == 'result' return iq.send()['type'] == 'result'
def getRoster(self): def getRoster(self):
"""Request the roster be sent.""" """Request the roster be sent."""
iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send() iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send()
self._handleRoster(iq, request=True) self._handleRoster(iq, request=True)
def _handleStreamFeatures(self, features): def _handleStreamFeatures(self, features):
self.features = [] self.features = []
for sub in features.xml: for sub in features.xml:
self.features.append(sub.tag) self.features.append(sub.tag)
for subelement in features.xml: for subelement in features.xml:
for feature in self.registered_features: for feature in self.registered_features:
if feature[0].match(subelement): if feature[0].match(subelement):
#if self.maskcmp(subelement, feature[0], True): #if self.maskcmp(subelement, feature[0], True):
if feature[1](subelement) and feature[2]: #if breaker, don't continue if feature[1](subelement) and feature[2]: #if breaker, don't continue
return True return True
def handler_starttls(self, xml): def handler_starttls(self, xml):
if not self.authenticated and self.ssl_support: if not self.authenticated and self.ssl_support:
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True) self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True)
self.sendXML(xml) self.sendXML(xml)
return True return True
else: else:
logging.warning("The module tlslite is required in to some servers, and has not been found.") logging.warning("The module tlslite is required in to some servers, and has not been found.")
return False return False
def handler_tls_start(self, xml): def handler_tls_start(self, xml):
logging.debug("Starting TLS") logging.debug("Starting TLS")
if self.startTLS(): if self.startTLS():
raise RestartStream() raise RestartStream()
def handler_sasl_auth(self, xml): def handler_sasl_auth(self, xml):
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
return False return False
logging.debug("Starting SASL Auth") logging.debug("Starting SASL Auth")
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, name='SASL Sucess', instream=True) self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, name='SASL Sucess', instream=True)
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, name='SASL Failure', instream=True) self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, name='SASL Failure', instream=True)
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
if len(sasl_mechs): if len(sasl_mechs):
for sasl_mech in sasl_mechs: for sasl_mech in sasl_mechs:
self.features.append("sasl:%s" % sasl_mech.text) self.features.append("sasl:%s" % sasl_mech.text)
if 'sasl:PLAIN' in self.features: if 'sasl:PLAIN' in self.features:
if sys.version_info < (3,0): if sys.version_info < (3,0):
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8'))
else: else:
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8'))
else: else:
logging.error("No appropriate login method.") logging.error("No appropriate login method.")
self.disconnect() self.disconnect()
#if 'sasl:DIGEST-MD5' in self.features: #if 'sasl:DIGEST-MD5' in self.features:
# self._auth_digestmd5() # self._auth_digestmd5()
return True return True
def handler_auth_success(self, xml): def handler_auth_success(self, xml):
self.authenticated = True self.authenticated = True
self.features = [] self.features = []
raise RestartStream() raise RestartStream()
def handler_auth_fail(self, xml): def handler_auth_fail(self, xml):
logging.info("Authentication failed.") logging.info("Authentication failed.")
self.disconnect() self.disconnect()
self.event("failed_auth") self.event("failed_auth")
def handler_bind_resource(self, xml): def handler_bind_resource(self, xml):
logging.debug("Requesting resource: %s" % self.resource) logging.debug("Requesting resource: %s" % self.resource)
xml.clear() xml.clear()
iq = self.Iq(stype='set') iq = self.Iq(stype='set')
if self.resource: if self.resource:
res = ET.Element('resource') res = ET.Element('resource')
res.text = self.resource res.text = self.resource
xml.append(res) xml.append(res)
iq.append(xml) iq.append(xml)
response = iq.send() response = iq.send()
#response = self.send(iq, self.Iq(sid=iq['id'])) #response = self.send(iq, self.Iq(sid=iq['id']))
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
self.bound = True self.bound = True
logging.info("Node set to: %s" % self.fulljid) logging.info("Node set to: %s" % self.fulljid)
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail: if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
logging.debug("Established Session") logging.debug("Established Session")
self.sessionstarted = True self.sessionstarted = True
self.event("session_start") self.event("session_start")
def handler_start_session(self, xml): def handler_start_session(self, xml):
if self.authenticated and self.bound: if self.authenticated and self.bound:
iq = self.makeIqSet(xml) iq = self.makeIqSet(xml)
response = iq.send() response = iq.send()
logging.debug("Established Session") logging.debug("Established Session")
self.sessionstarted = True self.sessionstarted = True
self.event("session_start") self.event("session_start")
else: else:
#bind probably hasn't happened yet #bind probably hasn't happened yet
self.bindfail = True self.bindfail = True
def _handleRoster(self, iq, request=False): def _handleRoster(self, iq, request=False):
if iq['type'] == 'set' or (iq['type'] == 'result' and request): if iq['type'] == 'set' or (iq['type'] == 'result' and request):
for jid in iq['roster']['items']: for jid in iq['roster']['items']:
if not jid in self.roster: if not jid in self.roster:
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
self.roster[jid].update(iq['roster']['items'][jid]) self.roster[jid].update(iq['roster']['items'][jid])
if iq['type'] == 'set': if iq['type'] == 'set':
self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster')) self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster'))
self.event("roster_update", iq) self.event("roster_update", iq)

View file

@ -30,59 +30,60 @@ from . import stanza
import hashlib import hashlib
srvsupport = True srvsupport = True
try: try:
import dns.resolver import dns.resolver
except ImportError: except ImportError:
srvsupport = False srvsupport = False
class ComponentXMPP(basexmpp, XMLStream): class ComponentXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil.""" """SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False): def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
XMLStream.__init__(self) XMLStream.__init__(self)
if use_jc_ns: if use_jc_ns:
self.default_ns = 'jabber:client' self.default_ns = 'jabber:client'
else: else:
self.default_ns = 'jabber:component:accept' self.default_ns = 'jabber:component:accept'
basexmpp.__init__(self) basexmpp.__init__(self)
self.auto_authorize = None self.auto_authorize = None
self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
self.stream_footer = "</stream:stream>" self.stream_footer = "</stream:stream>"
self.server_host = host self.server_host = host
self.server_port = port self.server_port = port
self.set_jid(jid) self.set_jid(jid)
self.secret = secret self.secret = secret
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) self.is_component = True
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
def __getitem__(self, key): def __getitem__(self, key):
if key in self.plugin: if key in self.plugin:
return self.plugin[key] return self.plugin[key]
else: else:
logging.warning("""Plugin "%s" is not loaded.""" % key) logging.warning("""Plugin "%s" is not loaded.""" % key)
return False return False
def get(self, key, default): def get(self, key, default):
return self.plugin.get(key, default) return self.plugin.get(key, default)
def incoming_filter(self, xmlobj): def incoming_filter(self, xmlobj):
if xmlobj.tag.startswith('{jabber:client}'): if xmlobj.tag.startswith('{jabber:client}'):
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
for sub in xmlobj: for sub in xmlobj:
self.incoming_filter(sub) self.incoming_filter(sub)
return xmlobj return xmlobj
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
sid = xml.get('id', '') sid = xml.get('id', '')
handshake = ET.Element('{jabber:component:accept}handshake') handshake = ET.Element('{jabber:component:accept}handshake')
if sys.version_info < (3,0): if sys.version_info < (3,0):
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
else: else:
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower() handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
self.sendXML(handshake) self.sendXML(handshake)
def _handleHandshake(self, xml): def _handleHandshake(self, xml):
self.event("session_start") self.event("session_start")
def connect(self): def connect(self):
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port) return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)

View file

@ -13,393 +13,398 @@ import weakref
import copy import copy
if sys.version_info < (3,0): if sys.version_info < (3,0):
from . import tostring26 as tostring from . import tostring26 as tostring
else: else:
from . import tostring from . import tostring
xmltester = type(ET.Element('xml')) xmltester = type(ET.Element('xml'))
def registerStanzaPlugin(stanza, plugin): def registerStanzaPlugin(stanza, plugin):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
""" """
tag = "{%s}%s" % (plugin.namespace, plugin.name) tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin stanza.plugin_tag_map[tag] = plugin
class JID(object): class JID(object):
def __init__(self, jid): def __init__(self, jid):
self.jid = jid self.jid = jid
def __getattr__(self, name): def __getattr__(self, name):
if name == 'resource': if name == 'resource':
return self.jid.split('/', 1)[-1] return self.jid.split('/', 1)[-1]
elif name == 'user': elif name == 'user':
if '@' in self.jid: if '@' in self.jid:
return self.jid.split('@', 1)[0] return self.jid.split('@', 1)[0]
else: else:
return '' return ''
elif name == 'server': elif name == 'server':
return self.jid.split('@', 1)[-1].split('/', 1)[0] return self.jid.split('@', 1)[-1].split('/', 1)[0]
elif name == 'full': elif name == 'full':
return self.jid return self.jid
elif name == 'bare': elif name == 'bare':
return self.jid.split('/', 1)[0] return self.jid.split('/', 1)[0]
def __str__(self): def __str__(self):
return self.jid return self.jid
class ElementBase(tostring.ToString): class ElementBase(tostring.ToString):
name = 'stanza' name = 'stanza'
plugin_attrib = 'plugin' plugin_attrib = 'plugin'
namespace = 'jabber:client' namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
subitem = None subitem = None
def __init__(self, xml=None, parent=None): def __init__(self, xml=None, parent=None):
if parent is None: if parent is None:
self.parent = None self.parent = None
else: else:
self.parent = weakref.ref(parent) self.parent = weakref.ref(parent)
self.xml = xml self.xml = xml
self.plugins = {} self.plugins = {}
self.iterables = [] self.iterables = []
self.idx = 0 self.idx = 0
if not self.setup(xml): if not self.setup(xml):
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag in self.plugin_tag_map: if child.tag in self.plugin_tag_map:
self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self) self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self)
if self.subitem is not None: if self.subitem is not None:
for sub in self.subitem: for sub in self.subitem:
if child.tag == "{%s}%s" % (sub.namespace, sub.name): if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(xml=child, parent=self)) self.iterables.append(sub(xml=child, parent=self))
break break
@property @property
def attrib(self): #backwards compatibility def attrib(self): #backwards compatibility
return self return self
def __iter__(self): def __iter__(self):
self.idx = 0 self.idx = 0
return self return self
def __bool__(self): def __bool__(self):
return True return True
def __next__(self): def __next__(self):
self.idx += 1 self.idx += 1
if self.idx > len(self.iterables): if self.idx > len(self.iterables):
self.idx = 0 self.idx = 0
raise StopIteration raise StopIteration
return self.iterables[self.idx - 1] return self.iterables[self.idx - 1]
def next(self): def next(self):
return self.__next__() return self.__next__()
def __len__(self): def __len__(self):
return len(self.iterables) return len(self.iterables)
def append(self, item): def append(self, item):
if not isinstance(item, ElementBase): if not isinstance(item, ElementBase):
if type(item) == xmltester: if type(item) == xmltester:
return self.appendxml(item) return self.appendxml(item)
else: else:
raise TypeError raise TypeError
self.xml.append(item.xml) self.xml.append(item.xml)
self.iterables.append(item) self.iterables.append(item)
return self return self
def pop(self, idx=0): def pop(self, idx=0):
aff = self.iterables.pop(idx) aff = self.iterables.pop(idx)
self.xml.remove(aff.xml) self.xml.remove(aff.xml)
return aff return aff
def get(self, key, defaultvalue=None): def get(self, key, defaultvalue=None):
value = self[key] value = self[key]
if value is None or value == '': if value is None or value == '':
return defaultvalue return defaultvalue
return value return value
def keys(self): def keys(self):
out = [] out = []
out += [x for x in self.interfaces] out += [x for x in self.interfaces]
out += [x for x in self.plugins] out += [x for x in self.plugins]
if self.iterables: if self.iterables:
out.append('substanzas') out.append('substanzas')
return tuple(out) return tuple(out)
def match(self, matchstring): def match(self, matchstring):
if isinstance(matchstring, str): if isinstance(matchstring, str):
nodes = matchstring.split('/') nodes = matchstring.split('/')
else: else:
nodes = matchstring nodes = matchstring
tagargs = nodes[0].split('@') tagargs = nodes[0].split('@')
if tagargs[0] not in (self.plugins, self.plugin_attrib): return False if tagargs[0] not in (self.plugins, self.plugin_attrib): return False
founditerable = False founditerable = False
for iterable in self.iterables: for iterable in self.iterables:
if nodes[1:] == []: if nodes[1:] == []:
break break
founditerable = iterable.match(nodes[1:]) founditerable = iterable.match(nodes[1:])
if founditerable: break; if founditerable: break;
for evals in tagargs[1:]: for evals in tagargs[1:]:
x,y = evals.split('=') x,y = evals.split('=')
if self[x] != y: return False if self[x] != y: return False
if not founditerable and len(nodes) > 1: if not founditerable and len(nodes) > 1:
next = nodes[1].split('@')[0] next = nodes[1].split('@')[0]
if next in self.plugins: if next in self.plugins:
return self.plugins[next].match(nodes[1:]) return self.plugins[next].match(nodes[1:])
else: else:
return False return False
return True return True
def find(self, xpath): # for backwards compatiblity, expose elementtree interface def find(self, xpath): # for backwards compatiblity, expose elementtree interface
return self.xml.find(xpath) return self.xml.find(xpath)
def findall(self, xpath): def findall(self, xpath):
return self.xml.findall(xpath) return self.xml.findall(xpath)
def setup(self, xml=None): def setup(self, xml=None):
if self.xml is None: if self.xml is None:
self.xml = xml self.xml = xml
if self.xml is None: if self.xml is None:
for ename in self.name.split('/'): for ename in self.name.split('/'):
new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
if self.xml is None: if self.xml is None:
self.xml = new self.xml = new
else: else:
self.xml.append(new) self.xml.append(new)
if self.parent is not None: if self.parent is not None:
self.parent().xml.append(self.xml) self.parent().xml.append(self.xml)
return True #had to generate XML return True #had to generate XML
else: else:
return False return False
def enable(self, attrib): def enable(self, attrib):
self.initPlugin(attrib) self.initPlugin(attrib)
return self return self
def initPlugin(self, attrib): def initPlugin(self, attrib):
if attrib not in self.plugins: if attrib not in self.plugins:
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self) self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self)
def __getitem__(self, attrib): def __getitem__(self, attrib):
if attrib == 'substanzas': if attrib == 'substanzas':
return self.iterables return self.iterables
elif attrib in self.interfaces: elif attrib in self.interfaces:
if hasattr(self, "get%s" % attrib.title()): if hasattr(self, "get%s" % attrib.title()):
return getattr(self, "get%s" % attrib.title())() return getattr(self, "get%s" % attrib.title())()
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._getSubText(attrib) return self._getSubText(attrib)
else: else:
return self._getAttr(attrib) return self._getAttr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: self.initPlugin(attrib) if attrib not in self.plugins: self.initPlugin(attrib)
return self.plugins[attrib] return self.plugins[attrib]
else: else:
return '' return ''
def __setitem__(self, attrib, value): def __setitem__(self, attrib, value):
if attrib in self.interfaces: if attrib in self.interfaces:
if value is not None: if value is not None:
if hasattr(self, "set%s" % attrib.title()): if hasattr(self, "set%s" % attrib.title()):
getattr(self, "set%s" % attrib.title())(value,) getattr(self, "set%s" % attrib.title())(value,)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._setSubText(attrib, text=value) return self._setSubText(attrib, text=value)
else: else:
self._setAttr(attrib, value) self._setAttr(attrib, value)
else: else:
self.__delitem__(attrib) self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: self.initPlugin(attrib) if attrib not in self.plugins: self.initPlugin(attrib)
self.initPlugin(attrib) self.initPlugin(attrib)
self.plugins[attrib][attrib] = value self.plugins[attrib][attrib] = value
return self return self
def __delitem__(self, attrib): def __delitem__(self, attrib):
if attrib.lower() in self.interfaces: if attrib.lower() in self.interfaces:
if hasattr(self, "del%s" % attrib.title()): if hasattr(self, "del%s" % attrib.title()):
getattr(self, "del%s" % attrib.title())() getattr(self, "del%s" % attrib.title())()
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._delSub(attrib) return self._delSub(attrib)
else: else:
self._delAttr(attrib) self._delAttr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib in self.plugins: if attrib in self.plugins:
del self.plugins[attrib] del self.plugins[attrib]
return self return self
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, ElementBase): if not isinstance(other, ElementBase):
return False return False
values = self.getStanzaValues() values = self.getStanzaValues()
for key in other: for key in other:
if key not in values or values[key] != other[key]: if key not in values or values[key] != other[key]:
return False return False
return True return True
def _setAttr(self, name, value): def _setAttr(self, name, value):
if value is None or value == '': if value is None or value == '':
self.__delitem__(name) self.__delitem__(name)
else: else:
self.xml.attrib[name] = value self.xml.attrib[name] = value
def _delAttr(self, name): def _delAttr(self, name):
if name in self.xml.attrib: if name in self.xml.attrib:
del self.xml.attrib[name] del self.xml.attrib[name]
def _getAttr(self, name): def _getAttr(self, name):
return self.xml.attrib.get(name, '') return self.xml.attrib.get(name, '')
def _getSubText(self, name): def _getSubText(self, name):
stanza = self.xml.find("{%s}%s" % (self.namespace, name)) stanza = self.xml.find("{%s}%s" % (self.namespace, name))
if stanza is None or stanza.text is None: if stanza is None or stanza.text is None:
return '' return ''
else: else:
return stanza.text return stanza.text
def _setSubText(self, name, attrib={}, text=None): def _setSubText(self, name, attrib={}, text=None):
if text is None or text == '': if text is None or text == '':
return self.__delitem__(name) return self.__delitem__(name)
stanza = self.xml.find("{%s}%s" % (self.namespace, name)) stanza = self.xml.find("{%s}%s" % (self.namespace, name))
if stanza is None: if stanza is None:
#self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) #self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib))
stanza = ET.Element("{%s}%s" % (self.namespace, name)) stanza = ET.Element("{%s}%s" % (self.namespace, name))
self.xml.append(stanza) self.xml.append(stanza)
stanza.text = text stanza.text = text
return stanza return stanza
def _delSub(self, name): def _delSub(self, name):
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag == "{%s}%s" % (self.namespace, name): if child.tag == "{%s}%s" % (self.namespace, name):
self.xml.remove(child) self.xml.remove(child)
def getStanzaValues(self): def getStanzaValues(self):
out = {} out = {}
for interface in self.interfaces: for interface in self.interfaces:
out[interface] = self[interface] out[interface] = self[interface]
for pluginkey in self.plugins: for pluginkey in self.plugins:
out[pluginkey] = self.plugins[pluginkey].getStanzaValues() out[pluginkey] = self.plugins[pluginkey].getStanzaValues()
if self.iterables: if self.iterables:
iterables = [] iterables = []
for stanza in self.iterables: for stanza in self.iterables:
iterables.append(stanza.getStanzaValues()) iterables.append(stanza.getStanzaValues())
iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)})
out['substanzas'] = iterables out['substanzas'] = iterables
return out return out
def setStanzaValues(self, attrib): def setStanzaValues(self, attrib):
for interface in attrib: for interface in attrib:
if interface == 'substanzas': if interface == 'substanzas':
for subdict in attrib['substanzas']: for subdict in attrib['substanzas']:
if '__childtag__' in subdict: if '__childtag__' in subdict:
for subclass in self.subitem: for subclass in self.subitem:
if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
sub = subclass(parent=self) sub = subclass(parent=self)
sub.setStanzaValues(subdict) sub.setStanzaValues(subdict)
self.iterables.append(sub) self.iterables.append(sub)
break break
elif interface in self.interfaces: elif interface in self.interfaces:
self[interface] = attrib[interface] self[interface] = attrib[interface]
elif interface in self.plugin_attrib_map and interface not in self.plugins: elif interface in self.plugin_attrib_map and interface not in self.plugins:
self.initPlugin(interface) self.initPlugin(interface)
if interface in self.plugins: if interface in self.plugins:
self.plugins[interface].setStanzaValues(attrib[interface]) self.plugins[interface].setStanzaValues(attrib[interface])
return self return self
def appendxml(self, xml): def appendxml(self, xml):
self.xml.append(xml) self.xml.append(xml)
return self return self
def __copy__(self): def __copy__(self):
return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
#def __del__(self): #prevents garbage collection of reference cycle #def __del__(self): #prevents garbage collection of reference cycle
# if self.parent is not None: # if self.parent is not None:
# self.parent.xml.remove(self.xml) # self.parent.xml.remove(self.xml)
class StanzaBase(ElementBase): class StanzaBase(ElementBase):
name = 'stanza' name = 'stanza'
namespace = 'jabber:client' namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None): def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
self.stream = stream self.stream = stream
if stream is not None: if stream is not None:
self.namespace = stream.default_ns self.namespace = stream.default_ns
ElementBase.__init__(self, xml) ElementBase.__init__(self, xml)
if stype is not None: if stype is not None:
self['type'] = stype self['type'] = stype
if sto is not None: if sto is not None:
self['to'] = sto self['to'] = sto
if sfrom is not None: if sfrom is not None:
self['from'] = sfrom self['from'] = sfrom
self.tag = "{%s}%s" % (self.namespace, self.name) self.tag = "{%s}%s" % (self.namespace, self.name)
def setType(self, value): def setType(self, value):
if value in self.types: if value in self.types:
self.xml.attrib['type'] = value self.xml.attrib['type'] = value
return self return self
def getPayload(self): def getPayload(self):
return self.xml.getchildren() return self.xml.getchildren()
def setPayload(self, value): def setPayload(self, value):
self.xml.append(value) self.xml.append(value)
return self return self
def delPayload(self): def delPayload(self):
self.clear() self.clear()
return self return self
def clear(self): def clear(self):
for child in self.xml.getchildren(): for child in self.xml.getchildren():
self.xml.remove(child) self.xml.remove(child)
for plugin in list(self.plugins.keys()): for plugin in list(self.plugins.keys()):
del self.plugins[plugin] del self.plugins[plugin]
return self return self
def reply(self): def reply(self):
self['from'], self['to'] = self['to'], self['from'] # if it's a component, use from
self.clear() if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component:
return self self['from'], self['to'] = self['to'], self['from']
else:
self['to'] = self['from']
del self['from']
self.clear()
return self
def error(self): def error(self):
self['type'] = 'error' self['type'] = 'error'
return self return self
def getTo(self): def getTo(self):
return JID(self._getAttr('to')) return JID(self._getAttr('to'))
def setTo(self, value): def setTo(self, value):
return self._setAttr('to', str(value)) return self._setAttr('to', str(value))
def getFrom(self): def getFrom(self):
return JID(self._getAttr('from')) return JID(self._getAttr('from'))
def setFrom(self, value): def setFrom(self, value):
return self._setAttr('from', str(value)) return self._setAttr('from', str(value))
def unhandled(self): def unhandled(self):
pass pass
def exception(self, e): def exception(self, e):
logging.error(traceback.format_tb(e)) logging.error(traceback.format_tb(e))
def send(self): def send(self):
self.stream.sendRaw(self.__str__()) self.stream.sendRaw(self.__str__())
def __copy__(self): def __copy__(self):
return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream)