diff options
| author | Thibaut Horel <thibaut.horel@gmail.com> | 2010-12-31 19:19:25 +0100 |
|---|---|---|
| committer | Thibaut Horel <thibaut.horel@gmail.com> | 2010-12-31 19:19:25 +0100 |
| commit | d90aec17e2201f256783a531c548dcc9857c889d (patch) | |
| tree | 56b6d0580ee1993c73e67c63d4a452a81bbaaf1e /sleekxmpp | |
| parent | af76bcdf7a947702eaa19d39f5b9ecfcd7ec6fd2 (diff) | |
| download | alias-d90aec17e2201f256783a531c548dcc9857c889d.tar.gz | |
Cleanup of repository. Bases of webclient.
* remove sleekxmpp (install guideline in server/README)
* move server code to server directory
* webclient directory with basic strophejs example
Diffstat (limited to 'sleekxmpp')
67 files changed, 0 insertions, 10883 deletions
diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py deleted file mode 100644 index 20f4367..0000000 --- a/sleekxmpp/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.clientxmpp import ClientXMPP -from sleekxmpp.componentxmpp import ComponentXMPP -from sleekxmpp.stanza import Message, Presence, Iq -from sleekxmpp.xmlstream.handler import * -from sleekxmpp.xmlstream import XMLStream, RestartStream -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py deleted file mode 100644 index cd7d251..0000000 --- a/sleekxmpp/basexmpp.py +++ /dev/null @@ -1,634 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import with_statement, unicode_literals - -import sys -import copy -import logging - -import sleekxmpp -from sleekxmpp import plugins - -from sleekxmpp.stanza import Message, Presence, Iq, Error -from sleekxmpp.stanza.roster import Roster -from sleekxmpp.stanza.nick import Nick -from sleekxmpp.stanza.htmlim import HTMLIM - -from sleekxmpp.xmlstream import XMLStream, JID, tostring -from sleekxmpp.xmlstream import ET, register_stanza_plugin -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.xmlstream.handler import * - - -log = logging.getLogger(__name__) - -# In order to make sure that Unicode is handled properly -# in Python 2.x, reset the default encoding. -if sys.version_info < (3, 0): - reload(sys) - sys.setdefaultencoding('utf8') - - -class BaseXMPP(XMLStream): - - """ - The BaseXMPP class adapts the generic XMLStream class for use - with XMPP. It also provides a plugin mechanism to easily extend - and add support for new XMPP features. - - Attributes: - auto_authorize -- Manage automatically accepting roster - subscriptions. - auto_subscribe -- Manage automatically requesting mutual - subscriptions. - is_component -- Indicates if this stream is for an XMPP component. - jid -- The XMPP JID for this stream. - plugin -- A dictionary of loaded plugins. - plugin_config -- A dictionary of plugin configurations. - plugin_whitelist -- A list of approved plugins. - sentpresence -- Indicates if an initial presence has been sent. - roster -- A dictionary containing subscribed JIDs and - their presence statuses. - - Methods: - Iq -- Factory for creating an Iq stanzas. - Message -- Factory for creating Message stanzas. - Presence -- Factory for creating Presence stanzas. - get -- Return a plugin given its name. - make_iq -- Create and initialize an Iq stanza. - make_iq_error -- Create an Iq stanza of type 'error'. - make_iq_get -- Create an Iq stanza of type 'get'. - make_iq_query -- Create an Iq stanza with a given query. - make_iq_result -- Create an Iq stanza of type 'result'. - make_iq_set -- Create an Iq stanza of type 'set'. - make_message -- Create and initialize a Message stanza. - make_presence -- Create and initialize a Presence stanza. - make_query_roster -- Create a roster query. - process -- Overrides XMLStream.process. - register_plugin -- Load and configure a plugin. - register_plugins -- Load and configure multiple plugins. - send_message -- Create and send a Message stanza. - send_presence -- Create and send a Presence stanza. - send_presence_subscribe -- Send a subscription request. - """ - - def __init__(self, default_ns='jabber:client'): - """ - Adapt an XML stream for use with XMPP. - - Arguments: - default_ns -- Ensure that the correct default XML namespace - is used during initialization. - """ - XMLStream.__init__(self) - - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.registerPlugin = self.register_plugin - self.makeIq = self.make_iq - self.makeIqGet = self.make_iq_get - self.makeIqResult = self.make_iq_result - self.makeIqSet = self.make_iq_set - self.makeIqError = self.make_iq_error - self.makeIqQuery = self.make_iq_query - self.makeQueryRoster = self.make_query_roster - self.makeMessage = self.make_message - self.makePresence = self.make_presence - self.sendMessage = self.send_message - self.sendPresence = self.send_presence - self.sendPresenceSubscription = self.send_presence_subscription - - self.default_ns = default_ns - self.stream_ns = 'http://etherx.jabber.org/streams' - - self.boundjid = JID("") - - self.plugin = {} - self.roster = {} - self.is_component = False - self.auto_authorize = True - self.auto_subscribe = True - - self.sentpresence = False - - self.register_handler( - Callback('IM', - MatchXPath('{%s}message/{%s}body' % (self.default_ns, - self.default_ns)), - self._handle_message)) - self.register_handler( - Callback('Presence', - MatchXPath("{%s}presence" % self.default_ns), - self._handle_presence)) - - self.add_event_handler('presence_subscribe', - self._handle_subscribe) - self.add_event_handler('disconnected', - self._handle_disconnected) - - # Set up the XML stream with XMPP's root stanzas. - self.registerStanza(Message) - self.registerStanza(Iq) - self.registerStanza(Presence) - - # Initialize a few default stanza plugins. - register_stanza_plugin(Iq, Roster) - register_stanza_plugin(Message, Nick) - register_stanza_plugin(Message, HTMLIM) - - def process(self, *args, **kwargs): - """ - Ensure that plugin inter-dependencies are handled before starting - event processing. - - Overrides XMLStream.process. - """ - for name in self.plugin: - if not self.plugin[name].post_inited: - self.plugin[name].post_init() - return XMLStream.process(self, *args, **kwargs) - - def register_plugin(self, plugin, pconfig={}, module=None): - """ - Register and configure a plugin for use in this stream. - - Arguments: - plugin -- The name of the plugin class. Plugin names must - be unique. - pconfig -- A dictionary of configuration data for the plugin. - Defaults to an empty dictionary. - module -- Optional refence to the module containing the plugin - class if using custom plugins. - """ - try: - # Import the given module that contains the plugin. - if not module: - module = sleekxmpp.plugins - module = __import__("%s.%s" % (module.__name__, plugin), - globals(), locals(), [plugin]) - if isinstance(module, str): - # We probably want to load a module from outside - # the sleekxmpp package, so leave out the globals(). - module = __import__(module, fromlist=[plugin]) - - # Load the plugin class from the module. - self.plugin[plugin] = getattr(module, plugin)(self, pconfig) - - # Let XEP implementing plugins have some extra logging info. - xep = '' - if hasattr(self.plugin[plugin], 'xep'): - xep = "(XEP-%s) " % self.plugin[plugin].xep - - desc = (xep, self.plugin[plugin].description) - log.debug("Loaded Plugin %s%s" % desc) - except: - log.exception("Unable to load plugin: %s", plugin) - - def register_plugins(self): - """ - Register and initialize all built-in plugins. - - Optionally, the list of plugins loaded may be limited to those - contained in self.plugin_whitelist. - - Plugin configurations stored in self.plugin_config will be used. - """ - if self.plugin_whitelist: - plugin_list = self.plugin_whitelist - else: - plugin_list = plugins.__all__ - - for plugin in plugin_list: - if plugin in plugins.__all__: - self.register_plugin(plugin, - self.plugin_config.get(plugin, {})) - else: - raise NameError("Plugin %s not in plugins.__all__." % plugin) - - # Resolve plugin inter-dependencies. - for plugin in self.plugin: - self.plugin[plugin].post_init() - - def __getitem__(self, key): - """ - Return a plugin given its name, if it has been registered. - """ - if key in self.plugin: - return self.plugin[key] - else: - log.warning("""Plugin "%s" is not loaded.""" % key) - return False - - def get(self, key, default): - """ - Return a plugin given its name, if it has been registered. - """ - return self.plugin.get(key, default) - - def Message(self, *args, **kwargs): - """Create a Message stanza associated with this stream.""" - return Message(self, *args, **kwargs) - - def Iq(self, *args, **kwargs): - """Create an Iq stanza associated with this stream.""" - return Iq(self, *args, **kwargs) - - def Presence(self, *args, **kwargs): - """Create a Presence stanza associated with this stream.""" - return Presence(self, *args, **kwargs) - - def make_iq(self, id=0, ifrom=None): - """ - Create a new Iq stanza with a given Id and from JID. - - Arguments: - id -- An ideally unique ID value for this stanza thread. - Defaults to 0. - ifrom -- The from JID to use for this stanza. - """ - return self.Iq()._set_stanza_values({'id': str(id), - 'from': ifrom}) - - def make_iq_get(self, queryxmlns=None): - """ - Create an Iq stanza of type 'get'. - - Optionally, a query element may be added. - - Arguments: - queryxmlns -- The namespace of the query to use. - """ - return self.Iq()._set_stanza_values({'type': 'get', - 'query': queryxmlns}) - - def make_iq_result(self, id): - """ - Create an Iq stanza of type 'result' with the given ID value. - - Arguments: - id -- An ideally unique ID value. May use self.new_id(). - """ - return self.Iq()._set_stanza_values({'id': id, - 'type': 'result'}) - - def make_iq_set(self, sub=None): - """ - Create an Iq stanza of type 'set'. - - Optionally, a substanza may be given to use as the - stanza's payload. - - Arguments: - sub -- A stanza or XML object to use as the Iq's payload. - """ - iq = self.Iq()._set_stanza_values({'type': 'set'}) - if sub != None: - iq.append(sub) - return iq - - def make_iq_error(self, id, type='cancel', - condition='feature-not-implemented', text=None): - """ - Create an Iq stanza of type 'error'. - - Arguments: - id -- An ideally unique ID value. May use self.new_id(). - type -- The type of the error, such as 'cancel' or 'modify'. - Defaults to 'cancel'. - condition -- The error condition. - Defaults to 'feature-not-implemented'. - text -- A message describing the cause of the error. - """ - iq = self.Iq()._set_stanza_values({'id': id}) - iq['error']._set_stanza_values({'type': type, - 'condition': condition, - 'text': text}) - return iq - - def make_iq_query(self, iq=None, xmlns=''): - """ - Create or modify an Iq stanza to use the given - query namespace. - - Arguments: - iq -- Optional Iq stanza to modify. A new - stanza is created otherwise. - xmlns -- The query's namespace. - """ - if not iq: - iq = self.Iq() - iq['query'] = xmlns - return iq - - def make_query_roster(self, iq=None): - """ - Create a roster query element. - - Arguments: - iq -- Optional Iq stanza to modify. A new stanza - is created otherwise. - """ - if iq: - iq['query'] = 'jabber:iq:roster' - return ET.Element("{jabber:iq:roster}query") - - def make_message(self, mto, mbody=None, msubject=None, mtype=None, - mhtml=None, mfrom=None, mnick=None): - """ - Create and initialize a new Message stanza. - - Arguments: - mto -- The recipient of the message. - mbody -- The main contents of the message. - msubject -- Optional subject for the message. - mtype -- The message's type, such as 'chat' or 'groupchat'. - mhtml -- Optional HTML body content. - mfrom -- The sender of the message. If sending from a client, - be aware that some servers require that the full JID - of the sender be used. - mnick -- Optional nickname of the sender. - """ - message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) - message['body'] = mbody - message['subject'] = msubject - if mnick is not None: - message['nick'] = mnick - if mhtml is not None: - message['html']['body'] = mhtml - return message - - def make_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, ptype=None, pfrom=None): - """ - Create and initialize a new Presence stanza. - - Arguments: - pshow -- The presence's show value. - pstatus -- The presence's status message. - ppriority -- This connections' priority. - pto -- The recipient of a directed presence. - ptype -- The type of presence, such as 'subscribe'. - pfrom -- The sender of the presence. - """ - presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) - if pshow is not None: - presence['type'] = pshow - if pfrom is None: - presence['from'] = self.boundjid.full - presence['priority'] = ppriority - presence['status'] = pstatus - return presence - - def send_message(self, mto, mbody, msubject=None, mtype=None, - mhtml=None, mfrom=None, mnick=None): - """ - Create, initialize, and send a Message stanza. - - - """ - self.makeMessage(mto, mbody, msubject, mtype, - mhtml, mfrom, mnick).send() - - def send_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, pfrom=None, ptype=None): - """ - Create, initialize, and send a Presence stanza. - - Arguments: - pshow -- The presence's show value. - pstatus -- The presence's status message. - ppriority -- This connections' priority. - pto -- The recipient of a directed presence. - ptype -- The type of presence, such as 'subscribe'. - pfrom -- The sender of the presence. - """ - self.makePresence(pshow, pstatus, ppriority, pto, - ptype=ptype, pfrom=pfrom).send() - # Unexpected errors may occur if - if not self.sentpresence: - self.event('sent_presence') - self.sentpresence = True - - def send_presence_subscription(self, pto, pfrom=None, - ptype='subscribe', pnick=None): - """ - Create, initialize, and send a Presence stanza of type 'subscribe'. - - Arguments: - pto -- The recipient of a directed presence. - pfrom -- The sender of the presence. - ptype -- The type of presence. Defaults to 'subscribe'. - pnick -- Nickname of the presence's sender. - """ - presence = self.makePresence(ptype=ptype, - pfrom=pfrom, - pto=self.getjidbare(pto)) - if pnick: - nick = ET.Element('{http://jabber.org/protocol/nick}nick') - nick.text = pnick - presence.append(nick) - presence.send() - - @property - def jid(self): - """ - Attribute accessor for bare jid - """ - log.warning("jid property deprecated. Use boundjid.bare") - return self.boundjid.bare - - @jid.setter - def jid(self, value): - log.warning("jid property deprecated. Use boundjid.bare") - self.boundjid.bare = value - - @property - def fulljid(self): - """ - Attribute accessor for full jid - """ - log.warning("fulljid property deprecated. Use boundjid.full") - return self.boundjid.full - - @fulljid.setter - def fulljid(self, value): - log.warning("fulljid property deprecated. Use boundjid.full") - self.boundjid.full = value - - @property - def resource(self): - """ - Attribute accessor for jid resource - """ - log.warning("resource property deprecated. Use boundjid.resource") - return self.boundjid.resource - - @resource.setter - def resource(self, value): - log.warning("fulljid property deprecated. Use boundjid.full") - self.boundjid.resource = value - - @property - def username(self): - """ - Attribute accessor for jid usernode - """ - log.warning("username property deprecated. Use boundjid.user") - return self.boundjid.user - - @username.setter - def username(self, value): - log.warning("username property deprecated. Use boundjid.user") - self.boundjid.user = value - - @property - def server(self): - """ - Attribute accessor for jid host - """ - log.warning("server property deprecated. Use boundjid.host") - return self.boundjid.server - - @server.setter - def server(self, value): - log.warning("server property deprecated. Use boundjid.host") - self.boundjid.server = value - - def set_jid(self, jid): - """Rip a JID apart and claim it as our own.""" - log.debug("setting jid to %s" % jid) - self.boundjid.full = jid - - def getjidresource(self, fulljid): - if '/' in fulljid: - return fulljid.split('/', 1)[-1] - else: - return '' - - def getjidbare(self, fulljid): - return fulljid.split('/', 1)[0] - - def _handle_disconnected(self, event): - """When disconnected, reset the roster""" - self.roster = {} - - def _handle_message(self, msg): - """Process incoming message stanzas.""" - self.event('message', msg) - - def _handle_presence(self, presence): - """ - Process incoming presence stanzas. - - Update the roster with presence information. - """ - self.event("presence_%s" % presence['type'], presence) - - # Check for changes in subscription state. - if presence['type'] in ('subscribe', 'subscribed', - 'unsubscribe', 'unsubscribed'): - self.event('changed_subscription', presence) - return - elif not presence['type'] in ('available', 'unavailable') and \ - not presence['type'] in presence.showtypes: - return - - # Strip the information from the stanza. - jid = presence['from'].bare - resource = presence['from'].resource - show = presence['type'] - status = presence['status'] - priority = presence['priority'] - - was_offline = False - got_online = False - old_roster = self.roster.get(jid, {}).get(resource, {}) - - # Create a new roster entry if needed. - if not jid in self.roster: - self.roster[jid] = {'groups': [], - 'name': '', - 'subscription': 'none', - 'presence': {}, - 'in_roster': False} - - # Alias to simplify some references. - connections = self.roster[jid]['presence'] - - # Determine if the user has just come online. - if not resource in connections: - if show == 'available' or show in presence.showtypes: - got_online = True - was_offline = True - connections[resource] = {} - - if connections[resource].get('show', 'unavailable') == 'unavailable': - was_offline = True - - # Update the roster's state for this JID's resource. - connections[resource] = {'show': show, - 'status': status, - 'priority': priority} - - name = self.roster[jid].get('name', '') - - # Remove unneeded state information after a resource - # disconnects. Determine if this was the last connection - # for the JID. - if show == 'unavailable': - log.debug("%s %s got offline" % (jid, resource)) - del connections[resource] - - if not connections and not self.roster[jid]['in_roster']: - del self.roster[jid] - if not was_offline: - self.event("got_offline", presence) - else: - return False - - name = '(%s) ' % name if name else '' - - # Presence state has changed. - self.event("changed_status", presence) - if got_online: - self.event("got_online", presence) - log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, - show, status)) - - def _handle_subscribe(self, presence): - """ - Automatically managage subscription requests. - - Subscription behavior is controlled by the settings - self.auto_authorize and self.auto_subscribe. - - auto_auth auto_sub Result: - True True Create bi-directional subsriptions. - True False Create only directed subscriptions. - False * Decline all subscriptions. - None * Disable automatic handling and use - a custom handler. - """ - presence.reply() - presence['to'] = presence['to'].bare - - # We are using trinary logic, so conditions have to be - # more explicit than usual. - if self.auto_authorize == True: - presence['type'] = 'subscribed' - presence.send() - if self.auto_subscribe: - presence['type'] = 'subscribe' - presence.send() - elif self.auto_authorize == False: - presence['type'] = 'unsubscribed' - presence.send() - -# Restore the old, lowercased name for backwards compatibility. -basexmpp = BaseXMPP diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py deleted file mode 100644 index 1c60081..0000000 --- a/sleekxmpp/clientxmpp.py +++ /dev/null @@ -1,436 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import absolute_import, unicode_literals - -import logging -import base64 -import sys -import hashlib -import random -import threading - -from sleekxmpp import plugins -from sleekxmpp import stanza -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.stanza import Message, Presence, Iq -from sleekxmpp.xmlstream import XMLStream, RestartStream -from sleekxmpp.xmlstream import StanzaBase, ET -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.xmlstream.handler import * - -# Flag indicating if DNS SRV records are available for use. -SRV_SUPPORT = True -try: - import dns.resolver -except: - SRV_SUPPORT = False - - -log = logging.getLogger(__name__) - - -class ClientXMPP(BaseXMPP): - - """ - SleekXMPP's client class. - - Use only for good, not for evil. - - Attributes: - - Methods: - connect -- Overrides XMLStream.connect. - del_roster_item -- Delete a roster item. - get_roster -- Retrieve the roster from the server. - register_feature -- Register a stream feature. - update_roster -- Update a roster item. - """ - - def __init__(self, jid, password, ssl=False, plugin_config={}, - plugin_whitelist=[], escape_quotes=True): - """ - Create a new SleekXMPP client. - - Arguments: - jid -- The JID of the XMPP user account. - password -- The password for the XMPP user account. - ssl -- Deprecated. - plugin_config -- A dictionary of plugin configurations. - plugin_whitelist -- A list of approved plugins that will be loaded - when calling register_plugins. - escape_quotes -- Deprecated. - """ - BaseXMPP.__init__(self, 'jabber:client') - - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.updateRoster = self.update_roster - self.delRosterItem = self.del_roster_item - self.getRoster = self.get_roster - self.registerFeature = self.register_feature - - self.set_jid(jid) - self.password = password - self.escape_quotes = escape_quotes - self.plugin_config = plugin_config - self.plugin_whitelist = plugin_whitelist - self.srv_support = SRV_SUPPORT - - self.session_started_event = threading.Event() - self.session_started_event.clear() - - self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( - self.boundjid.host, - "xmlns:stream='%s'" % self.stream_ns, - "xmlns='%s'" % self.default_ns) - self.stream_footer = "</stream:stream>" - - self.features = [] - self.registered_features = [] - - #TODO: Use stream state here - self.authenticated = False - self.sessionstarted = False - self.bound = False - self.bindfail = False - self.add_event_handler('connected', self.handle_connected) - - self.register_handler( - Callback('Stream Features', - MatchXPath('{%s}features' % self.stream_ns), - self._handle_stream_features)) - self.register_handler( - Callback('Roster Update', - MatchXPath('{%s}iq/{%s}query' % ( - self.default_ns, - 'jabber:iq:roster')), - self._handle_roster)) - - self.register_feature( - "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", - self._handle_starttls, True) - self.register_feature( - "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", - self._handle_sasl_auth, True) - self.register_feature( - "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", - self._handle_bind_resource) - self.register_feature( - "<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", - self._handle_start_session) - - def handle_connected(self, event=None): - #TODO: Use stream state here - self.authenticated = False - self.sessionstarted = False - self.bound = False - self.bindfail = False - self.schedule("session timeout checker", 15, - self._session_timeout_check) - - def _session_timeout_check(self): - if not self.session_started_event.isSet(): - log.debug("Session start has taken more than 15 seconds") - self.disconnect(reconnect=self.auto_reconnect) - - def connect(self, address=tuple()): - """ - Connect to the XMPP server. - - When no address is given, a SRV lookup for the server will - be attempted. If that fails, the server user in the JID - will be used. - - Arguments: - address -- A tuple containing the server's host and port. - """ - self.session_started_event.clear() - if not address or len(address) < 2: - if not self.srv_support: - log.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: - log.debug("Since no address is supplied," + \ - "attempting SRV lookup.") - try: - xmpp_srv = "_xmpp-client._tcp.%s" % self.server - answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - log.debug("No appropriate SRV record found." + \ - " Using JID server name.") - else: - # Pick a random server, weighted by priority. - - addresses = {} - intmax = 0 - for answer in answers: - intmax += answer.priority - addresses[intmax] = (answer.target.to_text()[:-1], - answer.port) - #python3 returns a generator for dictionary keys - priorities = [x for x in addresses.keys()] - priorities.sort() - - picked = random.randint(0, intmax) - for priority in priorities: - if picked <= priority: - address = addresses[priority] - break - - if not address: - # If all else fails, use the server from the JID. - address = (self.boundjid.host, 5222) - - return XMLStream.connect(self, address[0], address[1], use_tls=True) - - def register_feature(self, mask, pointer, breaker=False): - """ - Register a stream feature. - - Arguments: - mask -- An XML string matching the feature's element. - pointer -- The function to execute if the feature is received. - breaker -- Indicates if feature processing should halt with - this feature. Defaults to False. - """ - self.registered_features.append((MatchXMLMask(mask), - pointer, - breaker)) - - def update_roster(self, jid, name=None, subscription=None, groups=[]): - """ - Add or change a roster item. - - Arguments: - jid -- The JID of the entry to modify. - name -- The user's nickname for this JID. - subscription -- The subscription status. May be one of - 'to', 'from', 'both', or 'none'. If set - to 'remove', the entry will be deleted. - groups -- The roster groups that contain this item. - """ - iq = self.Iq()._set_stanza_values({'type': 'set'}) - iq['roster']['items'] = {jid: {'name': name, - 'subscription': subscription, - 'groups': groups}} - response = iq.send() - return response['type'] == 'result' - - def del_roster_item(self, jid): - """ - Remove an item from the roster by setting its subscription - status to 'remove'. - - Arguments: - jid -- The JID of the item to remove. - """ - return self.update_roster(jid, subscription='remove') - - def get_roster(self): - """Request the roster from the server.""" - iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster') - response = iq.send() - self._handle_roster(response, request=True) - - def _handle_stream_features(self, features): - """ - Process the received stream features. - - Arguments: - features -- The features stanza. - """ - # Record all of the features. - self.features = [] - for sub in features.xml: - self.features.append(sub.tag) - - # Process the features. - for sub in features.xml: - for feature in self.registered_features: - mask, handler, halt = feature - if mask.match(sub): - if handler(sub) and halt: - # Don't continue if the feature was - # marked as a breaker. - return True - - def _handle_starttls(self, xml): - """ - Handle notification that the server supports TLS. - - Arguments: - xml -- The STARTLS proceed element. - """ - if not self.authenticated and self.ssl_support: - tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls' - self.add_handler("<proceed xmlns='%s' />" % tls_ns, - self._handle_tls_start, - name='TLS Proceed', - instream=True) - self.send_xml(xml) - return True - else: - log.warning("The module tlslite is required to log in" +\ - " to some servers, and has not been found.") - return False - - def _handle_tls_start(self, xml): - """ - Handle encrypting the stream using TLS. - - Restarts the stream. - """ - log.debug("Starting TLS") - if self.start_tls(): - raise RestartStream() - - def _handle_sasl_auth(self, xml): - """ - Handle authenticating using SASL. - - Arguments: - xml -- The SASL mechanisms stanza. - """ - if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: - return False - - log.debug("Starting SASL Auth") - sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl' - self.add_handler("<success xmlns='%s' />" % sasl_ns, - self._handle_auth_success, - name='SASL Sucess', - instream=True) - self.add_handler("<failure xmlns='%s' />" % sasl_ns, - self._handle_auth_fail, - name='SASL Failure', - instream=True) - - sasl_mechs = xml.findall('{%s}mechanism' % sasl_ns) - if sasl_mechs: - for sasl_mech in sasl_mechs: - self.features.append("sasl:%s" % sasl_mech.text) - if 'sasl:PLAIN' in self.features and self.boundjid.user: - if sys.version_info < (3, 0): - user = bytes(self.boundjid.user) - password = bytes(self.password) - else: - user = bytes(self.boundjid.user, 'utf-8') - password = bytes(self.password, 'utf-8') - - auth = base64.b64encode(b'\x00' + user + \ - b'\x00' + password).decode('utf-8') - - self.send("<auth xmlns='%s' mechanism='PLAIN'>%s</auth>" % ( - sasl_ns, - auth)) - elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user: - self.send("<auth xmlns='%s' mechanism='%s' />" % ( - sasl_ns, - 'ANONYMOUS')) - else: - log.error("No appropriate login method.") - self.disconnect() - return True - - def _handle_auth_success(self, xml): - """ - SASL authentication succeeded. Restart the stream. - - Arguments: - xml -- The SASL authentication success element. - """ - self.authenticated = True - self.features = [] - raise RestartStream() - - def _handle_auth_fail(self, xml): - """ - SASL authentication failed. Disconnect and shutdown. - - Arguments: - xml -- The SASL authentication failure element. - """ - log.info("Authentication failed.") - self.event("failed_auth", direct=True) - self.disconnect() - - def _handle_bind_resource(self, xml): - """ - Handle requesting a specific resource. - - Arguments: - xml -- The bind feature element. - """ - log.debug("Requesting resource: %s" % self.boundjid.resource) - xml.clear() - iq = self.Iq(stype='set') - if self.boundjid.resource: - res = ET.Element('resource') - res.text = self.boundjid.resource - xml.append(res) - iq.append(xml) - response = iq.send() - - bind_ns = 'urn:ietf:params:xml:ns:xmpp-bind' - self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns, - bind_ns)).text) - self.bound = True - log.info("Node set to: %s" % self.boundjid.fulljid) - session_ns = 'urn:ietf:params:xml:ns:xmpp-session' - if "{%s}session" % session_ns not in self.features or self.bindfail: - log.debug("Established Session") - self.sessionstarted = True - self.session_started_event.set() - self.event("session_start") - - def _handle_start_session(self, xml): - """ - Handle the start of the session. - - Arguments: - xml -- The session feature element. - """ - if self.authenticated and self.bound: - iq = self.makeIqSet(xml) - response = iq.send() - log.debug("Established Session") - self.sessionstarted = True - self.session_started_event.set() - self.event("session_start") - else: - # Bind probably hasn't happened yet. - self.bindfail = True - - def _handle_roster(self, iq, request=False): - """ - Update the roster after receiving a roster stanza. - - Arguments: - iq -- The roster stanza. - request -- Indicates if this stanza is a response - to a request for the roster. - """ - if iq['type'] == 'set' or (iq['type'] == 'result' and request): - for jid in iq['roster']['items']: - if not jid in self.roster: - self.roster[jid] = {'groups': [], - 'name': '', - 'subscription': 'none', - 'presence': {}, - 'in_roster': True} - self.roster[jid].update(iq['roster']['items'][jid]) - - self.event("roster_update", iq) - if iq['type'] == 'set': - iq.reply() - iq.enable('roster') - iq.send() diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py deleted file mode 100644 index ae58c5f..0000000 --- a/sleekxmpp/componentxmpp.py +++ /dev/null @@ -1,141 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import absolute_import - -import logging -import base64 -import sys -import hashlib - -from sleekxmpp import plugins -from sleekxmpp import stanza -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.xmlstream import XMLStream, RestartStream -from sleekxmpp.xmlstream import StanzaBase, ET -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.xmlstream.handler import * - - -log = logging.getLogger(__name__) - - -class ComponentXMPP(BaseXMPP): - - """ - SleekXMPP's basic XMPP server component. - - Use only for good, not for evil. - - Methods: - connect -- Overrides XMLStream.connect. - incoming_filter -- Overrides XMLStream.incoming_filter. - start_stream_handler -- Overrides XMLStream.start_stream_handler. - """ - - def __init__(self, jid, secret, host, port, - plugin_config={}, plugin_whitelist=[], use_jc_ns=False): - """ - Arguments: - jid -- The JID of the component. - secret -- The secret or password for the component. - host -- The server accepting the component. - port -- The port used to connect to the server. - plugin_config -- A dictionary of plugin configurations. - plugin_whitelist -- A list of desired plugins to load - when using register_plugins. - use_js_ns -- Indicates if the 'jabber:client' namespace - should be used instead of the standard - 'jabber:component:accept' namespace. - Defaults to False. - """ - if use_jc_ns: - default_ns = 'jabber:client' - else: - default_ns = 'jabber:component:accept' - BaseXMPP.__init__(self, default_ns) - - self.auto_authorize = None - self.stream_header = "<stream:stream %s %s to='%s'>" % ( - 'xmlns="jabber:component:accept"', - 'xmlns:stream="%s"' % self.stream_ns, - jid) - self.stream_footer = "</stream:stream>" - self.server_host = host - self.server_port = port - self.set_jid(jid) - self.secret = secret - self.plugin_config = plugin_config - self.plugin_whitelist = plugin_whitelist - self.is_component = True - - self.register_handler( - Callback('Handshake', - MatchXPath('{jabber:component:accept}handshake'), - self._handle_handshake)) - - def connect(self): - """ - Connect to the server. - - Overrides XMLStream.connect. - """ - log.debug("Connecting to %s:%s" % (self.server_host, - self.server_port)) - return XMLStream.connect(self, self.server_host, - self.server_port) - - def incoming_filter(self, xml): - """ - Pre-process incoming XML stanzas by converting any 'jabber:client' - namespaced elements to the component's default namespace. - - Overrides XMLStream.incoming_filter. - - Arguments: - xml -- The XML stanza to pre-process. - """ - if xml.tag.startswith('{jabber:client}'): - xml.tag = xml.tag.replace('jabber:client', self.default_ns) - - # The incoming_filter call is only made on top level stanza - # elements. So we manually continue filtering on sub-elements. - for sub in xml: - self.incoming_filter(sub) - - return xml - - def start_stream_handler(self, xml): - """ - Once the streams are established, attempt to handshake - with the server to be accepted as a component. - - Overrides XMLStream.start_stream_handler. - - Arguments: - xml -- The incoming stream's root element. - """ - # Construct a hash of the stream ID and the component secret. - sid = xml.get('id', '') - pre_hash = '%s%s' % (sid, self.secret) - if sys.version_info >= (3, 0): - # Handle Unicode byte encoding in Python 3. - pre_hash = bytes(pre_hash, 'utf-8') - - handshake = ET.Element('{jabber:component:accept}handshake') - handshake.text = hashlib.sha1(pre_hash).hexdigest().lower() - self.send_xml(handshake) - - def _handle_handshake(self, xml): - """ - The handshake has been accepted. - - Arguments: - xml -- The reply handshake stanza. - """ - self.event("session_start") diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py deleted file mode 100644 index d3988b4..0000000 --- a/sleekxmpp/exceptions.py +++ /dev/null @@ -1,49 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class XMPPError(Exception): - - """ - A generic exception that may be raised while processing an XMPP stanza - to indicate that an error response stanza should be sent. - - The exception method for stanza objects extending RootStanza will create - an error stanza and initialize any additional substanzas using the - extension information included in the exception. - - Meant for use in SleekXMPP plugins and applications using SleekXMPP. - """ - - def __init__(self, condition='undefined-condition', text=None, etype=None, - extension=None, extension_ns=None, extension_args=None): - """ - Create a new XMPPError exception. - - Extension information can be included to add additional XML elements - to the generated error stanza. - - Arguments: - condition -- The XMPP defined error condition. - text -- Human readable text describing the error. - etype -- The XMPP error type, such as cancel or modify. - extension -- Tag name of the extension's XML content. - extension_ns -- XML namespace of the extensions' XML content. - extension_args -- Content and attributes for the extension - element. Same as the additional arguments to - the ET.Element constructor. - """ - if extension_args is None: - extension_args = {} - - self.condition = condition - self.text = text - self.etype = etype - self.extension = extension - self.extension_ns = extension_ns - self.extension_args = extension_args diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py deleted file mode 100644 index 427ab04..0000000 --- a/sleekxmpp/plugins/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -__all__ = ['xep_0004', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', - 'xep_0050', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', - 'xep_0060', 'xep_0202'] diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py deleted file mode 100644 index 254397e..0000000 --- a/sleekxmpp/plugins/base.py +++ /dev/null @@ -1,26 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class base_plugin(object): - - def __init__(self, xmpp, config): - self.xep = 'base' - self.description = 'Base Plugin' - self.xmpp = xmpp - self.config = config - self.post_inited = False - self.enable = config.get('enable', True) - if self.enable: - self.plugin_init() - - def plugin_init(self): - pass - - def post_init(self): - self.post_inited = True diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py deleted file mode 100644 index 7e888b9..0000000 --- a/sleekxmpp/plugins/gmail_notify.py +++ /dev/null @@ -1,149 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.iq import Iq - - -log = logging.getLogger(__name__) - - -class GmailQuery(ElementBase): - namespace = 'google:mail:notify' - name = 'query' - plugin_attrib = 'gmail' - interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) - - def getSearch(self): - return self['q'] - - def setSearch(self, search): - self['q'] = search - - def delSearch(self): - del self['q'] - - -class MailBox(ElementBase): - namespace = 'google:mail:notify' - name = 'mailbox' - plugin_attrib = 'mailbox' - interfaces = set(('result-time', 'total-matched', 'total-estimate', - 'url', 'threads', 'matched', 'estimate')) - - def getThreads(self): - threads = [] - for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, - MailThread.name)): - threads.append(MailThread(xml=threadXML, parent=None)) - return threads - - def getMatched(self): - return self['total-matched'] - - def getEstimate(self): - return self['total-estimate'] == '1' - - -class MailThread(ElementBase): - namespace = 'google:mail:notify' - name = 'mail-thread-info' - plugin_attrib = 'thread' - interfaces = set(('tid', 'participation', 'messages', 'date', - 'senders', 'url', 'labels', 'subject', 'snippet')) - sub_interfaces = set(('labels', 'subject', 'snippet')) - - def getSenders(self): - senders = [] - sendersXML = self.xml.find('{%s}senders' % self.namespace) - if sendersXML is not None: - for senderXML in sendersXML.findall('{%s}sender' % self.namespace): - senders.append(MailSender(xml=senderXML, parent=None)) - return senders - - -class MailSender(ElementBase): - namespace = 'google:mail:notify' - name = 'sender' - plugin_attrib = 'sender' - interfaces = set(('address', 'name', 'originator', 'unread')) - - def getOriginator(self): - return self.xml.attrib.get('originator', '0') == '1' - - def getUnread(self): - return self.xml.attrib.get('unread', '0') == '1' - - -class NewMail(ElementBase): - namespace = 'google:mail:notify' - name = 'new-mail' - plugin_attrib = 'new-mail' - - -class gmail_notify(base.base_plugin): - """ - Google Talk: Gmail Notifications - """ - - def plugin_init(self): - self.description = 'Google Talk: Gmail Notifications' - - self.xmpp.registerHandler( - Callback('Gmail Result', - MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, - MailBox.namespace, - MailBox.name)), - self.handle_gmail)) - - self.xmpp.registerHandler( - Callback('Gmail New Mail', - MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, - NewMail.namespace, - NewMail.name)), - self.handle_new_mail)) - - registerStanzaPlugin(Iq, GmailQuery) - registerStanzaPlugin(Iq, MailBox) - registerStanzaPlugin(Iq, NewMail) - - self.last_result_time = None - - def handle_gmail(self, iq): - mailbox = iq['mailbox'] - approx = ' approximately' if mailbox['estimated'] else '' - log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) - self.last_result_time = mailbox['result-time'] - self.xmpp.event('gmail_messages', iq) - - def handle_new_mail(self, iq): - log.info("Gmail: New emails received!") - self.xmpp.event('gmail_notify') - self.checkEmail() - - def getEmail(self, query=None): - return self.search(query) - - def checkEmail(self): - return self.search(newer=self.last_result_time) - - def search(self, query=None, newer=None): - if query is None: - log.info("Gmail: Checking for new emails") - else: - log.info('Gmail: Searching for emails matching: "%s"' % query) - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = self.xmpp.jid - iq['gmail']['q'] = query - iq['gmail']['newer-than-time'] = newer - return iq.send() diff --git a/sleekxmpp/plugins/jobs.py b/sleekxmpp/plugins/jobs.py deleted file mode 100644 index 0b93d62..0000000 --- a/sleekxmpp/plugins/jobs.py +++ /dev/null @@ -1,50 +0,0 @@ -from . import base -import logging -from xml.etree import cElementTree as ET -import types - - -log = logging.getLogger(__name__) - - -class jobs(base.base_plugin): - def plugin_init(self): - self.xep = 'pubsubjob' - self.description = "Job distribution over Pubsub" - - def post_init(self): - pass - #TODO add event - - def createJobNode(self, host, jid, node, config=None): - pass - - def createJob(self, host, node, jobid=None, payload=None): - return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),)) - - def claimJob(self, host, node, jobid, ifrom=None): - return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed')) - - def unclaimJob(self, host, node, jobid): - return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed')) - - def finishJob(self, host, node, jobid, payload=None): - finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished') - if payload is not None: - finished.append(payload) - return self._setState(host, node, jobid, finished) - - def _setState(self, host, node, jobid, state, ifrom=None): - iq = self.xmpp.Iq() - iq['to'] = host - if ifrom: iq['from'] = ifrom - iq['type'] = 'set' - iq['psstate']['node'] = node - iq['psstate']['item'] = jobid - iq['psstate']['payload'] = state - result = iq.send() - if result is None or type(result) == types.BooleanType or result['type'] != 'result': - log.error("Unable to change %s:%s to %s" % (node, jobid, state)) - return False - return True - diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py deleted file mode 100644 index ade3d68..0000000 --- a/sleekxmpp/plugins/old_0004.py +++ /dev/null @@ -1,421 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from . import base -import log -from xml.etree import cElementTree as ET -import copy -import logging -#TODO support item groups and results - - -log = logging.getLogger(__name__) - - -class old_0004(base.base_plugin): - - def plugin_init(self): - self.xep = '0004' - self.description = '*Deprecated Data Forms' - self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form') - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') - log.warning("This implementation of XEP-0004 is deprecated.") - - def handler_message_xform(self, xml): - object = self.handle_form(xml) - self.xmpp.event("message_form", object) - - def handler_presence_xform(self, xml): - object = self.handle_form(xml) - self.xmpp.event("presence_form", object) - - def handle_form(self, xml): - xmlform = xml.find('{jabber:x:data}x') - object = self.buildForm(xmlform) - self.xmpp.event("message_xform", object) - return object - - def buildForm(self, xml): - form = Form(ftype=xml.attrib['type']) - form.fromXML(xml) - return form - - def makeForm(self, ftype='form', title='', instructions=''): - return Form(self.xmpp, ftype, title, instructions) - -class FieldContainer(object): - def __init__(self, stanza = 'form'): - self.fields = [] - self.field = {} - self.stanza = stanza - - def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): - self.field[var] = FormField(var, ftype, label, desc, required, value) - self.fields.append(self.field[var]) - return self.field[var] - - def buildField(self, xml): - self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) - self.fields.append(self.field[xml.get('var', '__unnamed__')]) - self.field[xml.get('var', '__unnamed__')].buildField(xml) - - def buildContainer(self, xml): - self.stanza = xml.tag - for field in xml.findall('{jabber:x:data}field'): - self.buildField(field) - - def getXML(self, ftype): - container = ET.Element(self.stanza) - for field in self.fields: - container.append(field.getXML(ftype)) - return container - -class Form(FieldContainer): - types = ('form', 'submit', 'cancel', 'result') - def __init__(self, xmpp=None, ftype='form', title='', instructions=''): - if not ftype in self.types: - raise ValueError("Invalid Form Type") - FieldContainer.__init__(self) - self.xmpp = xmpp - self.type = ftype - self.title = title - self.instructions = instructions - self.reported = [] - self.items = [] - - def merge(self, form2): - form1 = Form(ftype=self.type) - form1.fromXML(self.getXML(self.type)) - for field in form2.fields: - if not field.var in form1.field: - form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) - else: - form1.field[field.var].value = field.value - for option, label in field.options: - if (option, label) not in form1.field[field.var].options: - form1.fields[field.var].addOption(option, label) - return form1 - - def copy(self): - newform = Form(ftype=self.type) - newform.fromXML(self.getXML(self.type)) - return newform - - def update(self, form): - values = form.getValues() - for var in values: - if var in self.fields: - self.fields[var].setValue(self.fields[var]) - - def getValues(self): - result = {} - for field in self.fields: - value = field.value - if len(value) == 1: - value = value[0] - result[field.var] = value - return result - - def setValues(self, values={}): - for field in values: - if field in self.field: - if isinstance(values[field], list) or isinstance(values[field], tuple): - for value in values[field]: - self.field[field].setValue(value) - else: - self.field[field].setValue(values[field]) - - def fromXML(self, xml): - self.buildForm(xml) - - def addItem(self): - newitem = FieldContainer('item') - self.items.append(newitem) - return newitem - - def buildItem(self, xml): - newitem = self.addItem() - newitem.buildContainer(xml) - - def addReported(self): - reported = FieldContainer('reported') - self.reported.append(reported) - return reported - - def buildReported(self, xml): - reported = self.addReported() - reported.buildContainer(xml) - - def setTitle(self, title): - self.title = title - - def setInstructions(self, instructions): - self.instructions = instructions - - def setType(self, ftype): - self.type = ftype - - def getXMLMessage(self, to): - msg = self.xmpp.makeMessage(to) - msg.append(self.getXML()) - return msg - - def buildForm(self, xml): - self.type = xml.get('type', 'form') - if xml.find('{jabber:x:data}title') is not None: - self.setTitle(xml.find('{jabber:x:data}title').text) - if xml.find('{jabber:x:data}instructions') is not None: - self.setInstructions(xml.find('{jabber:x:data}instructions').text) - for field in xml.findall('{jabber:x:data}field'): - self.buildField(field) - for reported in xml.findall('{jabber:x:data}reported'): - self.buildReported(reported) - for item in xml.findall('{jabber:x:data}item'): - self.buildItem(item) - - #def getXML(self, tostring = False): - def getXML(self, ftype=None): - if ftype: - self.type = ftype - form = ET.Element('{jabber:x:data}x') - form.attrib['type'] = self.type - if self.title and self.type in ('form', 'result'): - title = ET.Element('{jabber:x:data}title') - title.text = self.title - form.append(title) - if self.instructions and self.type == 'form': - instructions = ET.Element('{jabber:x:data}instructions') - instructions.text = self.instructions - form.append(instructions) - for field in self.fields: - form.append(field.getXML(self.type)) - for reported in self.reported: - form.append(reported.getXML('{jabber:x:data}reported')) - for item in self.items: - form.append(item.getXML(self.type)) - #if tostring: - # form = self.xmpp.tostring(form) - return form - - def getXHTML(self): - form = ET.Element('{http://www.w3.org/1999/xhtml}form') - if self.title: - title = ET.Element('h2') - title.text = self.title - form.append(title) - if self.instructions: - instructions = ET.Element('p') - instructions.text = self.instructions - form.append(instructions) - for field in self.fields: - form.append(field.getXHTML()) - for field in self.reported: - form.append(field.getXHTML()) - for field in self.items: - form.append(field.getXHTML()) - return form - - - def makeSubmit(self): - self.setType('submit') - -class FormField(object): - types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') - listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') - lbtypes = ('fixed', 'text-multi') - def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): - if not ftype in self.types: - raise ValueError("Invalid Field Type") - self.type = ftype - self.var = var - self.label = label - self.desc = desc - self.options = [] - self.required = False - self.value = [] - if self.type in self.listtypes: - self.islist = True - else: - self.islist = False - if self.type in self.lbtypes: - self.islinebreak = True - else: - self.islinebreak = False - if value: - self.setValue(value) - - def addOption(self, value, label): - if self.islist: - self.options.append((value, label)) - else: - raise ValueError("Cannot add options to non-list type field.") - - def setTrue(self): - if self.type == 'boolean': - self.value = [True] - - def setFalse(self): - if self.type == 'boolean': - self.value = [False] - - def require(self): - self.required = True - - def setDescription(self, desc): - self.desc = desc - - def setValue(self, value): - if self.type == 'boolean': - if value in ('1', 1, True, 'true', 'True', 'yes'): - value = True - else: - value = False - if self.islinebreak and value is not None: - self.value += value.split('\n') - else: - if len(self.value) and (not self.islist or self.type == 'list-single'): - self.value = [value] - else: - self.value.append(value) - - def delValue(self, value): - if type(self.value) == type([]): - try: - idx = self.value.index(value) - if idx != -1: - self.value.pop(idx) - except ValueError: - pass - else: - self.value = '' - - def setAnswer(self, value): - self.setValue(value) - - def buildField(self, xml): - self.type = xml.get('type', 'text-single') - self.label = xml.get('label', '') - for option in xml.findall('{jabber:x:data}option'): - self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) - for value in xml.findall('{jabber:x:data}value'): - self.setValue(value.text) - if xml.find('{jabber:x:data}required') is not None: - self.require() - if xml.find('{jabber:x:data}desc') is not None: - self.setDescription(xml.find('{jabber:x:data}desc').text) - - def getXML(self, ftype): - field = ET.Element('{jabber:x:data}field') - if ftype != 'result': - field.attrib['type'] = self.type - if self.type != 'fixed': - if self.var: - field.attrib['var'] = self.var - if self.label: - field.attrib['label'] = self.label - if ftype == 'form': - for option in self.options: - optionxml = ET.Element('{jabber:x:data}option') - optionxml.attrib['label'] = option[1] - optionval = ET.Element('{jabber:x:data}value') - optionval.text = option[0] - optionxml.append(optionval) - field.append(optionxml) - if self.required: - required = ET.Element('{jabber:x:data}required') - field.append(required) - if self.desc: - desc = ET.Element('{jabber:x:data}desc') - desc.text = self.desc - field.append(desc) - for value in self.value: - valuexml = ET.Element('{jabber:x:data}value') - if value is True or value is False: - if value: - valuexml.text = '1' - else: - valuexml.text = '0' - else: - valuexml.text = value - field.append(valuexml) - return field - - def getXHTML(self): - field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) - if self.label: - label = ET.Element('p') - label.text = "%s: " % self.label - else: - label = ET.Element('p') - label.text = "%s: " % self.var - field.append(label) - if self.type == 'boolean': - formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) - if len(self.value) and self.value[0] in (True, 'true', '1'): - formf.attrib['checked'] = 'checked' - elif self.type == 'fixed': - formf = ET.Element('p') - try: - formf.text = ', '.join(self.value) - except: - pass - field.append(formf) - formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - elif self.type == 'hidden': - formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - elif self.type in ('jid-multi', 'list-multi'): - formf = ET.Element('select', {'name': self.var}) - for option in self.options: - optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) - optf.text = option[1] - if option[1] in self.value: - optf.attrib['selected'] = 'selected' - formf.append(option) - elif self.type in ('jid-single', 'text-single'): - formf = ET.Element('input', {'type': 'text', 'name': self.var}) - try: - formf.attrib['value'] = ', '.join(self.value) - except: - pass - elif self.type == 'list-single': - formf = ET.Element('select', {'name': self.var}) - for option in self.options: - optf = ET.Element('option', {'value': option[0]}) - optf.text = option[1] - if not optf.text: - optf.text = option[0] - if option[1] in self.value: - optf.attrib['selected'] = 'selected' - formf.append(optf) - elif self.type == 'text-multi': - formf = ET.Element('textarea', {'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - if not formf.text: - formf.text = ' ' - elif self.type == 'text-private': - formf = ET.Element('input', {'type': 'password', 'name': self.var}) - try: - formf.attrib['value'] = ', '.join(self.value) - except: - pass - label.append(formf) - return field - diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py deleted file mode 100644 index 2d809a3..0000000 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ /dev/null @@ -1,555 +0,0 @@ -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.iq import Iq -from .. stanza.message import Message -from .. basexmpp import basexmpp -from .. xmlstream.xmlstream import XMLStream -import logging -from . import xep_0004 - - -class PubsubState(ElementBase): - namespace = 'http://jabber.org/protocol/psstate' - name = 'state' - plugin_attrib = 'psstate' - interfaces = set(('node', 'item', 'payload')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setPayload(self, value): - self.xml.append(value) - - def getPayload(self): - childs = self.xml.getchildren() - if len(childs) > 0: - return childs[0] - - def delPayload(self): - for child in self.xml.getchildren(): - self.xml.remove(child) - -registerStanzaPlugin(Iq, PubsubState) - -class PubsubStateEvent(ElementBase): - namespace = 'http://jabber.org/protocol/psstate#event' - name = 'event' - plugin_attrib = 'psstate_event' - intefaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Message, PubsubStateEvent) -registerStanzaPlugin(PubsubStateEvent, PubsubState) - -class Pubsub(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'pubsub' - plugin_attrib = 'pubsub' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Iq, Pubsub) - -class PubsubOwner(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'pubsub' - plugin_attrib = 'pubsub_owner' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Iq, PubsubOwner) - -class Affiliation(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliation' - plugin_attrib = name - interfaces = set(('node', 'affiliation')) - plugin_attrib_map = {} - plugin_tag_map = {} - -class Affiliations(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliations' - plugin_attrib = 'affiliations' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Affiliation,) - - def append(self, affiliation): - if not isinstance(affiliation, Affiliation): - raise TypeError - self.xml.append(affiliation.xml) - return self.iterables.append(affiliation) - -registerStanzaPlugin(Pubsub, Affiliations) - - -class Subscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'node', 'subscription', 'subid')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setjid(self, value): - self._setattr('jid', str(value)) - - def getjid(self): - return jid(self._getattr('jid')) - -registerStanzaPlugin(Pubsub, Subscription) - -class Subscriptions(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscriptions' - plugin_attrib = 'subscriptions' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Subscription,) - -registerStanzaPlugin(Pubsub, Subscriptions) - -class OptionalSetting(object): - interfaces = set(('required',)) - - def setRequired(self, value): - value = bool(value) - if value and not self['required']: - self.xml.append(ET.Element("{%s}required" % self.namespace)) - elif not value and self['required']: - self.delRequired() - - def getRequired(self): - required = self.xml.find("{%s}required" % self.namespace) - if required is not None: - return True - else: - return False - - def delRequired(self): - required = self.xml.find("{%s}required" % self.namespace) - if required is not None: - self.xml.remove(required) - - -class SubscribeOptions(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe-options' - plugin_attrib = 'suboptions' - plugin_attrib_map = {} - plugin_tag_map = {} - interfaces = set(('required',)) - -registerStanzaPlugin(Subscription, SubscribeOptions) - -class Item(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'item' - plugin_attrib = name - interfaces = set(('id', 'payload')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setPayload(self, value): - self.xml.append(value) - - def getPayload(self): - childs = self.xml.getchildren() - if len(childs) > 0: - return childs[0] - - def delPayload(self): - for child in self.xml.getchildren(): - self.xml.remove(child) - -class Items(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'items' - plugin_attrib = 'items' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Item,) - -registerStanzaPlugin(Pubsub, Items) - -class Create(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'create' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Pubsub, Create) - -#class Default(ElementBase): -# namespace = 'http://jabber.org/protocol/pubsub' -# name = 'default' -# plugin_attrib = name -# interfaces = set(('node', 'type')) -# plugin_attrib_map = {} -# plugin_tag_map = {} -# -# def getType(self): -# t = self._getAttr('type') -# if not t: t == 'leaf' -# return t -# -#registerStanzaPlugin(Pubsub, Default) - -class Publish(Items): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'publish' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Item,) - -registerStanzaPlugin(Pubsub, Publish) - -class Retract(Items): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'retract' - plugin_attrib = name - interfaces = set(('node', 'notify')) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Pubsub, Retract) - -class Unsubscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'unsubscribe' - plugin_attrib = name - interfaces = set(('node', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('jid')) - -class Subscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe' - plugin_attrib = name - interfaces = set(('node', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('jid')) - -registerStanzaPlugin(Pubsub, Subscribe) - -class Configure(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'configure' - plugin_attrib = name - interfaces = set(('node', 'type')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def getType(self): - t = self._getAttr('type') - if not t: t == 'leaf' - return t - -registerStanzaPlugin(Pubsub, Configure) -registerStanzaPlugin(Configure, xep_0004.Form) - -class DefaultConfig(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'default' - plugin_attrib = 'default' - interfaces = set(('node', 'type', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) - - def getType(self): - t = self._getAttr('type') - if not t: t = 'leaf' - return t - - def getConfig(self): - return self['form'] - - def setConfig(self, value): - self['form'].setStanzaValues(value.getStanzaValues()) - return self - -registerStanzaPlugin(PubsubOwner, DefaultConfig) -registerStanzaPlugin(DefaultConfig, xep_0004.Form) - -class Options(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'options' - plugin_attrib = 'options' - interfaces = set(('jid', 'node', 'options')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) - - def getOptions(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setOptions(self, value): - self.xml.append(value.getXML()) - return self - - def delOptions(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('jid')) - -registerStanzaPlugin(Pubsub, Options) -registerStanzaPlugin(Subscribe, Options) - -class OwnerAffiliations(Affiliations): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def append(self, affiliation): - if not isinstance(affiliation, OwnerAffiliation): - raise TypeError - self.xml.append(affiliation.xml) - return self.affiliations.append(affiliation) - -registerStanzaPlugin(PubsubOwner, OwnerAffiliations) - -class OwnerAffiliation(Affiliation): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('affiliation', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} - -class OwnerConfigure(Configure): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(PubsubOwner, OwnerConfigure) - -class OwnerDefault(OwnerConfigure): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def getConfig(self): - return self['form'] - - def setConfig(self, value): - self['form'].setStanzaValues(value.getStanzaValues()) - return self - -registerStanzaPlugin(PubsubOwner, OwnerDefault) -registerStanzaPlugin(OwnerDefault, xep_0004.Form) - -class OwnerDelete(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'delete' - plugin_attrib = 'delete' - plugin_attrib_map = {} - plugin_tag_map = {} - interfaces = set(('node',)) - -registerStanzaPlugin(PubsubOwner, OwnerDelete) - -class OwnerPurge(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'purge' - plugin_attrib = name - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(PubsubOwner, OwnerPurge) - -class OwnerRedirect(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'redirect' - plugin_attrib = name - interfaces = set(('node', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('jid')) - -registerStanzaPlugin(OwnerDelete, OwnerRedirect) - -class OwnerSubscriptions(Subscriptions): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - - def append(self, subscription): - if not isinstance(subscription, OwnerSubscription): - raise TypeError - self.xml.append(subscription.xml) - return self.subscriptions.append(subscription) - -registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) - -class OwnerSubscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'subscription')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('from')) - -class Event(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'event' - plugin_attrib = 'pubsub_event' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Message, Event) - -class EventItem(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'item' - plugin_attrib = 'item' - interfaces = set(('id', 'payload')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setPayload(self, value): - self.xml.append(value) - - def getPayload(self): - childs = self.xml.getchildren() - if len(childs) > 0: - return childs[0] - - def delPayload(self): - for child in self.xml.getchildren(): - self.xml.remove(child) - - -class EventRetract(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'retract' - plugin_attrib = 'retract' - interfaces = set(('id',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -class EventItems(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'items' - plugin_attrib = 'items' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (EventItem, EventRetract) - -registerStanzaPlugin(Event, EventItems) - -class EventCollection(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'collection' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Event, EventCollection) - -class EventAssociate(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'associate' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(EventCollection, EventAssociate) - -class EventDisassociate(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'disassociate' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(EventCollection, EventDisassociate) - -class EventConfiguration(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'configuration' - plugin_attrib = name - interfaces = set(('node', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Event, EventConfiguration) -registerStanzaPlugin(EventConfiguration, xep_0004.Form) - -class EventPurge(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'purge' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - -registerStanzaPlugin(Event, EventPurge) - -class EventSubscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'subscription' - plugin_attrib = name - interfaces = set(('node','expiry', 'jid', 'subid', 'subscription')) - plugin_attrib_map = {} - plugin_tag_map = {} - - def setJid(self, value): - self._setAttr('jid', str(value)) - - def getJid(self): - return JID(self._getAttr('jid')) - -registerStanzaPlugin(Event, EventSubscription) diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py deleted file mode 100644 index b8b7ebf..0000000 --- a/sleekxmpp/plugins/xep_0004.py +++ /dev/null @@ -1,395 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import copy -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.message import Message -import types - - -log = logging.getLogger(__name__) - - -class Form(ElementBase): - namespace = 'jabber:x:data' - name = 'x' - plugin_attrib = 'form' - interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) - sub_interfaces = set(('title',)) - form_types = set(('cancel', 'form', 'result', 'submit')) - - def __init__(self, *args, **kwargs): - title = None - if 'title' in kwargs: - title = kwargs['title'] - del kwargs['title'] - ElementBase.__init__(self, *args, **kwargs) - if title is not None: - self['title'] = title - self.field = FieldAccessor(self) - - def setup(self, xml=None): - if ElementBase.setup(self, xml): #if we had to generate xml - self['type'] = 'form' - - def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): - kwtype = kwargs.get('type', None) - if kwtype is None: - kwtype = ftype - - field = FormField(parent=self) - field['var'] = var - field['type'] = kwtype - field['label'] = label - field['desc'] = desc - field['required'] = required - field['value'] = value - if options is not None: - field['options'] = options - return field - - def getXML(self, type='submit'): - log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") - return self.xml - - def fromXML(self, xml): - log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") - n = Form(xml=xml) - return n - - def addItem(self, values): - itemXML = ET.Element('{%s}item' % self.namespace) - self.xml.append(itemXML) - reported_vars = self['reported'].keys() - for var in reported_vars: - fieldXML = ET.Element('{%s}field' % FormField.namespace) - itemXML.append(fieldXML) - field = FormField(xml=fieldXML) - field['var'] = var - field['value'] = values.get(var, None) - - def addReported(self, var, ftype=None, label='', desc='', **kwargs): - kwtype = kwargs.get('type', None) - if kwtype is None: - kwtype = ftype - reported = self.xml.find('{%s}reported' % self.namespace) - if reported is None: - reported = ET.Element('{%s}reported' % self.namespace) - self.xml.append(reported) - fieldXML = ET.Element('{%s}field' % FormField.namespace) - reported.append(fieldXML) - field = FormField(xml=fieldXML) - field['var'] = var - field['type'] = kwtype - field['label'] = label - field['desc'] = desc - return field - - def cancel(self): - self['type'] = 'cancel' - - def delFields(self): - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - self.xml.remove(fieldXML) - - def delInstructions(self): - instsXML = self.xml.findall('{%s}instructions') - for instXML in instsXML: - self.xml.remove(instXML) - - def delItems(self): - itemsXML = self.xml.find('{%s}item' % self.namespace) - for itemXML in itemsXML: - self.xml.remove(itemXML) - - def delReported(self): - reportedXML = self.xml.find('{%s}reported' % self.namespace) - if reportedXML is not None: - self.xml.remove(reportedXML) - - def getFields(self, use_dict=False): - fields = {} if use_dict else [] - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - if use_dict: - fields[field['var']] = field - else: - fields.append((field['var'], field)) - return fields - - def getInstructions(self): - instructions = '' - instsXML = self.xml.findall('{%s}instructions' % self.namespace) - return "\n".join([instXML.text for instXML in instsXML]) - - def getItems(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - item = {} - fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - item[field['var']] = field['value'] - items.append(item) - return items - - def getReported(self): - fields = {} - fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, - FormField.namespace)) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - fields[field['var']] = field - return fields - - def getValues(self): - values = {} - fields = self.getFields(use_dict=True) - for var in fields: - values[var] = fields[var]['value'] - return values - - def reply(self): - if self['type'] == 'form': - self['type'] = 'submit' - elif self['type'] == 'submit': - self['type'] = 'result' - - def setFields(self, fields, default=None): - del self['fields'] - for field_data in fields: - var = field_data[0] - field = field_data[1] - field['var'] = var - - self.addField(**field) - - def setInstructions(self, instructions): - del self['instructions'] - if instructions in [None, '']: - return - instructions = instructions.split('\n') - for instruction in instructions: - inst = ET.Element('{%s}instructions' % self.namespace) - inst.text = instruction - self.xml.append(inst) - - def setItems(self, items): - for item in items: - self.addItem(item) - - def setReported(self, reported, default=None): - for var in reported: - field = reported[var] - field['var'] = var - self.addReported(var, **field) - - def setValues(self, values): - fields = self.getFields(use_dict=True) - for field in values: - fields[field]['value'] = values[field] - - def merge(self, other): - new = copy.copy(self) - if type(other) == types.DictType: - new.setValues(other) - return new - nfields = new.getFields(use_dict=True) - ofields = other.getFields(use_dict=True) - nfields.update(ofields) - new.setFields([(x, nfields[x]) for x in nfields]) - return new - -class FieldAccessor(object): - def __init__(self, form): - self.form = form - - def __getitem__(self, key): - return self.form.getFields(use_dict=True)[key] - - def __contains__(self, key): - return key in self.form.getFields(use_dict=True) - - def has_key(self, key): - return key in self.form.getFields(use_dict=True) - - -class FormField(ElementBase): - namespace = 'jabber:x:data' - name = 'field' - plugin_attrib = 'field' - interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) - sub_interfaces = set(('desc',)) - field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', - 'list-single', 'text-multi', 'text-private', 'text-single')) - multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) - multi_line_types = set(('hidden', 'text-multi')) - option_types = set(('list-multi', 'list-single')) - true_values = set((True, '1', 'true')) - - def addOption(self, label='', value=''): - if self['type'] in self.option_types: - opt = FieldOption(parent=self) - opt['label'] = label - opt['value'] = value - else: - raise ValueError("Cannot add options to a %s field." % self['type']) - - def delOptions(self): - optsXML = self.xml.findall('{%s}option' % self.namespace) - for optXML in optsXML: - self.xml.remove(optXML) - - def delRequired(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - if reqXML is not None: - self.xml.remove(reqXML) - - def delValue(self): - valsXML = self.xml.findall('{%s}value' % self.namespace) - for valXML in valsXML: - self.xml.remove(valXML) - - def getAnswer(self): - return self.getValue() - - def getOptions(self): - options = [] - optsXML = self.xml.findall('{%s}option' % self.namespace) - for optXML in optsXML: - opt = FieldOption(xml=optXML) - options.append({'label': opt['label'], 'value':opt['value']}) - return options - - def getRequired(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - return reqXML is not None - - def getValue(self): - valsXML = self.xml.findall('{%s}value' % self.namespace) - if len(valsXML) == 0: - return None - elif self['type'] == 'boolean': - return valsXML[0].text in self.true_values - elif self['type'] in self.multi_value_types: - values = [] - for valXML in valsXML: - if valXML.text is None: - valXML.text = '' - values.append(valXML.text) - if self['type'] == 'text-multi': - values = "\n".join(values) - return values - else: - return valsXML[0].text - - def setAnswer(self, answer): - self.setValue(answer) - - def setFalse(self): - self.setValue(False) - - def setOptions(self, options): - for value in options: - if isinstance(value, dict): - self.addOption(**value) - else: - self.addOption(value=value) - - def setRequired(self, required): - exists = self.getRequired() - if not exists and required: - self.xml.append(ET.Element('{%s}required' % self.namespace)) - elif exists and not required: - self.delRequired() - - def setTrue(self): - self.setValue(True) - - def setValue(self, value): - self.delValue() - valXMLName = '{%s}value' % self.namespace - - if self['type'] == 'boolean': - if value in self.true_values: - valXML = ET.Element(valXMLName) - valXML.text = '1' - self.xml.append(valXML) - else: - valXML = ET.Element(valXMLName) - valXML.text = '0' - self.xml.append(valXML) - elif self['type'] in self.multi_value_types or self['type'] in ['', None]: - if self['type'] in self.multi_line_types and isinstance(value, str): - value = value.split('\n') - if not isinstance(value, list): - value = [value] - for val in value: - if self['type'] in ['', None] and val in self.true_values: - val = '1' - valXML = ET.Element(valXMLName) - valXML.text = val - self.xml.append(valXML) - else: - if isinstance(value, list): - raise ValueError("Cannot add multiple values to a %s field." % self['type']) - valXML = ET.Element(valXMLName) - valXML.text = value - self.xml.append(valXML) - - -class FieldOption(ElementBase): - namespace = 'jabber:x:data' - name = 'option' - plugin_attrib = 'option' - interfaces = set(('label', 'value')) - sub_interfaces = set(('value',)) - - -class xep_0004(base.base_plugin): - """ - XEP-0004: Data Forms - """ - - def plugin_init(self): - self.xep = '0004' - self.description = 'Data Forms' - - self.xmpp.registerHandler( - Callback('Data Form', - MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, - Form.namespace)), - self.handle_form)) - - registerStanzaPlugin(FormField, FieldOption) - registerStanzaPlugin(Form, FormField) - registerStanzaPlugin(Message, Form) - - def makeForm(self, ftype='form', title='', instructions=''): - f = Form() - f['type'] = ftype - f['title'] = title - f['instructions'] = instructions - return f - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') - - def handle_form(self, message): - self.xmpp.event("message_xform", message) - - def buildForm(self, xml): - return Form(xml=xml) diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py deleted file mode 100644 index 625b03f..0000000 --- a/sleekxmpp/plugins/xep_0009.py +++ /dev/null @@ -1,277 +0,0 @@ -"""
-XEP-0009 XMPP Remote Procedure Calls
-"""
-from __future__ import with_statement
-from . import base
-import logging
-from xml.etree import cElementTree as ET
-import copy
-import time
-import base64
-
-def py2xml(*args):
- params = ET.Element("params")
- for x in args:
- param = ET.Element("param")
- param.append(_py2xml(x))
- params.append(param) #<params><param>...
- return params
-
-def _py2xml(*args):
- for x in args:
- val = ET.Element("value")
- if type(x) is int:
- i4 = ET.Element("i4")
- i4.text = str(x)
- val.append(i4)
- if type(x) is bool:
- boolean = ET.Element("boolean")
- boolean.text = str(int(x))
- val.append(boolean)
- elif type(x) is str:
- string = ET.Element("string")
- string.text = x
- val.append(string)
- elif type(x) is float:
- double = ET.Element("double")
- double.text = str(x)
- val.append(double)
- elif type(x) is rpcbase64:
- b64 = ET.Element("Base64")
- b64.text = x.encoded()
- val.append(b64)
- elif type(x) is rpctime:
- iso = ET.Element("dateTime.iso8601")
- iso.text = str(x)
- val.append(iso)
- elif type(x) is list:
- array = ET.Element("array")
- data = ET.Element("data")
- for y in x:
- data.append(_py2xml(y))
- array.append(data)
- val.append(array)
- elif type(x) is dict:
- struct = ET.Element("struct")
- for y in x.keys():
- member = ET.Element("member")
- name = ET.Element("name")
- name.text = y
- member.append(name)
- member.append(_py2xml(x[y]))
- struct.append(member)
- val.append(struct)
- return val
-
-def xml2py(params):
- vals = []
- for param in params.findall('param'):
- vals.append(_xml2py(param.find('value')))
- return vals
-
-def _xml2py(value):
- if value.find('i4') is not None:
- return int(value.find('i4').text)
- if value.find('int') is not None:
- return int(value.find('int').text)
- if value.find('boolean') is not None:
- return bool(value.find('boolean').text)
- if value.find('string') is not None:
- return value.find('string').text
- if value.find('double') is not None:
- return float(value.find('double').text)
- if value.find('Base64') is not None:
- return rpcbase64(value.find('Base64').text)
- if value.find('dateTime.iso8601') is not None:
- return rpctime(value.find('dateTime.iso8601'))
- if value.find('struct') is not None:
- struct = {}
- for member in value.find('struct').findall('member'):
- struct[member.find('name').text] = _xml2py(member.find('value'))
- return struct
- if value.find('array') is not None:
- array = []
- for val in value.find('array').find('data').findall('value'):
- array.append(_xml2py(val))
- return array
- raise ValueError()
-
-class rpcbase64(object):
- def __init__(self, data):
- #base 64 encoded string
- self.data = data
-
- def decode(self):
- return base64.decodestring(data)
-
- def __str__(self):
- return self.decode()
-
- def encoded(self):
- return self.data
-
-class rpctime(object):
- def __init__(self,data=None):
- #assume string data is in iso format YYYYMMDDTHH:MM:SS
- if type(data) is str:
- self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
- elif type(data) is time.struct_time:
- self.timestamp = data
- elif data is None:
- self.timestamp = time.gmtime()
- else:
- raise ValueError()
-
- def iso8601(self):
- #return a iso8601 string
- return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
-
- def __str__(self):
- return self.iso8601()
-
-class JabberRPCEntry(object):
- def __init__(self,call):
- self.call = call
- self.result = None
- self.error = None
- self.allow = {} #{'<jid>':['<resource1>',...],...}
- self.deny = {}
-
- def check_acl(self, jid, resource):
- #Check for deny
- if jid in self.deny.keys():
- if self.deny[jid] == None or resource in self.deny[jid]:
- return False
- #Check for allow
- if allow == None:
- return True
- if jid in self.allow.keys():
- if self.allow[jid] == None or resource in self.allow[jid]:
- return True
- return False
-
- def acl_allow(self, jid, resource):
- if jid == None:
- self.allow = None
- elif resource == None:
- self.allow[jid] = None
- elif jid in self.allow.keys():
- self.allow[jid].append(resource)
- else:
- self.allow[jid] = [resource]
-
- def acl_deny(self, jid, resource):
- if jid == None:
- self.deny = None
- elif resource == None:
- self.deny[jid] = None
- elif jid in self.deny.keys():
- self.deny[jid].append(resource)
- else:
- self.deny[jid] = [resource]
-
- def call_method(self, args):
- ret = self.call(*args)
-
-class xep_0009(base.base_plugin):
-
- def plugin_init(self):
- self.xep = '0009'
- self.description = 'Jabber-RPC'
- self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callMethod, name='Jabber RPC Call')
- self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callResult, name='Jabber RPC Result')
- self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
- self._callError, name='Jabber RPC Error')
- self.entries = {}
- self.activeCalls = []
-
- def post_init(self):
- base.base_plugin.post_init(self)
- self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
- self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
-
- def register_call(self, method, name=None):
- #@returns an string that can be used in acl commands.
- with self.lock:
- if name is None:
- self.entries[method.__name__] = JabberRPCEntry(method)
- return method.__name__
- else:
- self.entries[name] = JabberRPCEntry(method)
- return name
-
- def acl_allow(self, entry, jid=None, resource=None):
- #allow the method entry to be called by the given jid and resource.
- #if jid is None it will allow any jid/resource.
- #if resource is None it will allow any resource belonging to the jid.
- with self.lock:
- if self.entries[entry]:
- self.entries[entry].acl_allow(jid,resource)
- else:
- raise ValueError()
-
- def acl_deny(self, entry, jid=None, resource=None):
- #Note: by default all requests are denied unless allowed with acl_allow.
- #If you deny an entry it will not be allowed regardless of acl_allow
- with self.lock:
- if self.entries[entry]:
- self.entries[entry].acl_deny(jid,resource)
- else:
- raise ValueError()
-
- def unregister_call(self, entry):
- #removes the registered call
- with self.lock:
- if self.entries[entry]:
- del self.entries[entry]
- else:
- raise ValueError()
-
- def makeMethodCallQuery(self,pmethod,params):
- query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
- methodCall = ET.Element('methodCall')
- methodName = ET.Element('methodName')
- methodName.text = pmethod
- methodCall.append(methodName)
- methodCall.append(params)
- query.append(methodCall)
- return query
-
- def makeIqMethodCall(self,pto,pmethod,params):
- iq = self.xmpp.makeIqSet()
- iq.set('to',pto)
- iq.append(self.makeMethodCallQuery(pmethod,params))
- return iq
-
- def makeIqMethodResponse(self,pto,pid,params):
- iq = self.xmpp.makeIqResult(pid)
- iq.set('to',pto)
- query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
- methodResponse = ET.Element('methodResponse')
- methodResponse.append(params)
- query.append(methodResponse)
- return iq
-
- def makeIqMethodError(self,pto,id,pmethod,params,condition):
- iq = self.xmpp.makeIqError(id)
- iq.set('to',pto)
- iq.append(self.makeMethodCallQuery(pmethod,params))
- iq.append(self.xmpp['xep_0086'].makeError(condition))
- return iq
-
-
-
- def call_remote(self, pto, pmethod, *args):
- #calls a remote method. Returns the id of the Iq.
- pass
-
- def _callMethod(self,xml):
- pass
-
- def _callResult(self,xml):
- pass
-
- def _callError(self,xml):
- pass
diff --git a/sleekxmpp/plugins/xep_0012.py b/sleekxmpp/plugins/xep_0012.py deleted file mode 100644 index d636d4d..0000000 --- a/sleekxmpp/plugins/xep_0012.py +++ /dev/null @@ -1,118 +0,0 @@ -"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from datetime import datetime
-import logging
-
-from . import base
-from .. stanza.iq import Iq
-from .. xmlstream.handler.callback import Callback
-from .. xmlstream.matcher.xpath import MatchXPath
-from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class LastActivity(ElementBase):
- name = 'query'
- namespace = 'jabber:iq:last'
- plugin_attrib = 'last_activity'
- interfaces = set(('seconds', 'status'))
-
- def get_seconds(self):
- return int(self._get_attr('seconds'))
-
- def set_seconds(self, value):
- self._set_attr('seconds', str(value))
-
- def get_status(self):
- return self.xml.text
-
- def set_status(self, value):
- self.xml.text = str(value)
-
- def del_status(self):
- self.xml.text = ''
-
-class xep_0012(base.base_plugin):
- """
- XEP-0012 Last Activity
- """
- def plugin_init(self):
- self.description = "Last Activity"
- self.xep = "0012"
-
- self.xmpp.registerHandler(
- Callback('Last Activity',
- MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
- LastActivity.namespace)),
- self.handle_last_activity_query))
- register_stanza_plugin(Iq, LastActivity)
-
- self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity)
-
-
- def post_init(self):
- base.base_plugin.post_init(self)
- if self.xmpp.is_component:
- # We are a component, so we track the uptime
- self.xmpp.add_event_handler("session_start", self._reset_uptime)
- self._start_datetime = datetime.now()
- self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last')
-
- def _reset_uptime(self, event):
- self._start_datetime = datetime.now()
-
- def handle_last_activity_query(self, iq):
- if iq['type'] == 'get':
- log.debug("Last activity requested by %s" % iq['from'])
- self.xmpp.event('last_activity_request', iq)
- elif iq['type'] == 'result':
- log.debug("Last activity result from %s" % iq['from'])
- self.xmpp.event('last_activity', iq)
-
- def handle_last_activity(self, iq):
- jid = iq['from']
-
- if self.xmpp.is_component:
- # Send the uptime
- result = LastActivity()
- td = (datetime.now() - self._start_datetime)
- result['seconds'] = td.seconds + td.days * 24 * 3600
- reply = iq.reply().setPayload(result.xml).send()
- else:
- barejid = JID(jid).bare
- if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or
- barejid == self.xmpp.boundjid.bare ):
- # We don't know how to calculate it
- iq.reply().error().setPayload(iq['last_activity'].xml)
- iq['error']['code'] = '503'
- iq['error']['type'] = 'cancel'
- iq['error']['condition'] = 'service-unavailable'
- iq.send()
- else:
- iq.reply().error().setPayload(iq['last_activity'].xml)
- iq['error']['code'] = '403'
- iq['error']['type'] = 'auth'
- iq['error']['condition'] = 'forbidden'
- iq.send()
-
- def get_last_activity(self, jid):
- """Query the LastActivity of jid and return it in seconds"""
- iq = self.xmpp.makeIqGet()
- query = LastActivity()
- iq.append(query.xml)
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq.get('id')
- result = iq.send()
- if result and result is not None and result.get('type', 'error') != 'error':
- return result['last_activity']['seconds']
- else:
- return False
diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py deleted file mode 100644 index a3fac34..0000000 --- a/sleekxmpp/plugins/xep_0030.py +++ /dev/null @@ -1,329 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.iq import Iq - - -log = logging.getLogger(__name__) - - -class DiscoInfo(ElementBase): - namespace = 'http://jabber.org/protocol/disco#info' - name = 'query' - plugin_attrib = 'disco_info' - interfaces = set(('node', 'features', 'identities')) - - def getFeatures(self): - features = [] - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - features.append(feature.attrib['var']) - return features - - def setFeatures(self, features): - self.delFeatures() - for name in features: - self.addFeature(name) - - def delFeatures(self): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - self.xml.remove(feature) - - def addFeature(self, feature): - featureXML = ET.Element('{%s}feature' % self.namespace, - {'var': feature}) - self.xml.append(featureXML) - - def delFeature(self, feature): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for featureXML in featuresXML: - if featureXML.attrib['var'] == feature: - self.xml.remove(featureXML) - - def getIdentities(self): - ids = [] - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type'], - idXML.attrib.get('name', '')) - ids.append(idData) - return ids - - def setIdentities(self, ids): - self.delIdentities() - for idData in ids: - self.addIdentity(*idData) - - def delIdentities(self): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - self.xml.remove(idXML) - - def addIdentity(self, category, id_type, name=''): - idXML = ET.Element('{%s}identity' % self.namespace, - {'category': category, - 'type': id_type, - 'name': name}) - self.xml.append(idXML) - - def delIdentity(self, category, id_type, name=''): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type']) - delId = (category, id_type) - if idData == delId: - self.xml.remove(idXML) - - -class DiscoItems(ElementBase): - namespace = 'http://jabber.org/protocol/disco#items' - name = 'query' - plugin_attrib = 'disco_items' - interfaces = set(('node', 'items')) - - def getItems(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - itemData = (item.attrib['jid'], - item.attrib.get('node'), - item.attrib.get('name')) - items.append(itemData) - return items - - def setItems(self, items): - self.delItems() - for item in items: - self.addItem(*item) - - def delItems(self): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - self.xml.remove(item) - - def addItem(self, jid, node='', name=''): - itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) - if name: - itemXML.attrib['name'] = name - if node: - itemXML.attrib['node'] = node - self.xml.append(itemXML) - - def delItem(self, jid, node=''): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - itemData = (itemXML.attrib['jid'], - itemXML.attrib.get('node', '')) - itemDel = (jid, node) - if itemData == itemDel: - self.xml.remove(itemXML) - - -class DiscoNode(object): - """ - Collection object for grouping info and item information - into nodes. - """ - def __init__(self, name): - self.name = name - self.info = DiscoInfo() - self.items = DiscoItems() - - self.info['node'] = name - self.items['node'] = name - - # This is a bit like poor man's inheritance, but - # to simplify adding information to the node we - # map node functions to either the info or items - # stanza objects. - # - # We don't want to make DiscoNode inherit from - # DiscoInfo and DiscoItems because DiscoNode is - # not an actual stanza, and doing so would create - # confusion and potential bugs. - - self._map(self.items, 'items', ['get', 'set', 'del']) - self._map(self.items, 'item', ['add', 'del']) - self._map(self.info, 'identities', ['get', 'set', 'del']) - self._map(self.info, 'identity', ['add', 'del']) - self._map(self.info, 'features', ['get', 'set', 'del']) - self._map(self.info, 'feature', ['add', 'del']) - - def isEmpty(self): - """ - Test if the node contains any information. Useful for - determining if a node can be deleted. - """ - ids = self.getIdentities() - features = self.getFeatures() - items = self.getItems() - - if not ids and not features and not items: - return True - return False - - def _map(self, obj, interface, access): - """ - Map functions of the form obj.accessInterface - to self.accessInterface for each given access type. - """ - interface = interface.title() - for access_type in access: - method = access_type + interface - if hasattr(obj, method): - setattr(self, method, getattr(obj, method)) - - -class xep_0030(base.base_plugin): - """ - XEP-0030 Service Discovery - """ - - def plugin_init(self): - self.xep = '0030' - self.description = 'Service Discovery' - - self.xmpp.registerHandler( - Callback('Disco Items', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoItems.namespace)), - self.handle_item_query)) - - self.xmpp.registerHandler( - Callback('Disco Info', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoInfo.namespace)), - self.handle_info_query)) - - registerStanzaPlugin(Iq, DiscoInfo) - registerStanzaPlugin(Iq, DiscoItems) - - self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) - self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) - - self.nodes = {'main': DiscoNode('main')} - - def add_node(self, node): - if node not in self.nodes: - self.nodes[node] = DiscoNode(node) - - def del_node(self, node): - if node in self.nodes: - del self.nodes[node] - - def handle_item_query(self, iq): - if iq['type'] == 'get': - log.debug("Items requested by %s" % iq['from']) - self.xmpp.event('disco_items_request', iq) - elif iq['type'] == 'result': - log.debug("Items result from %s" % iq['from']) - self.xmpp.event('disco_items', iq) - - def handle_info_query(self, iq): - if iq['type'] == 'get': - log.debug("Info requested by %s" % iq['from']) - self.xmpp.event('disco_info_request', iq) - elif iq['type'] == 'result': - log.debug("Info result from %s" % iq['from']) - self.xmpp.event('disco_info', iq) - - def handle_disco_info(self, iq, forwarded=False): - """ - A default handler for disco#info requests. If another - handler is registered, this one will defer and not run. - """ - if not forwarded and self.xmpp.event_handled('disco_info_request'): - return - - node_name = iq['disco_info']['node'] - if not node_name: - node_name = 'main' - - log.debug("Using default handler for disco#info on node '%s'." % node_name) - - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply().setPayload(node.info.xml).send() - else: - log.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_info'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() - - def handle_disco_items(self, iq, forwarded=False): - """ - A default handler for disco#items requests. If another - handler is registered, this one will defer and not run. - - If this handler is called by your own custom handler with - forwarded set to True, then it will run as normal. - """ - if not forwarded and self.xmpp.event_handled('disco_items_request'): - return - - node_name = iq['disco_items']['node'] - if not node_name: - node_name = 'main' - - log.debug("Using default handler for disco#items on node '%s'." % node_name) - - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply().setPayload(node.items.xml).send() - else: - log.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_items'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() - - # Older interface methods for backwards compatibility - - def getInfo(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_info']['node'] = node - return iq.send() - - def getItems(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_items']['node'] = node - return iq.send() - - def add_feature(self, feature, node='main'): - self.add_node(node) - self.nodes[node].addFeature(feature) - - def add_identity(self, category='', itype='', name='', node='main'): - self.add_node(node) - self.nodes[node].addIdentity(category=category, - id_type=itype, - name=name) - - def add_item(self, jid=None, name='', node='main', subnode=''): - self.add_node(node) - self.add_node(subnode) - if jid is None: - jid = self.xmpp.fulljid - self.nodes[node].addItem(jid=jid, name=name, node=subnode) diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py deleted file mode 100644 index c0c4d89..0000000 --- a/sleekxmpp/plugins/xep_0033.py +++ /dev/null @@ -1,161 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.message import Message - - -class Addresses(ElementBase): - namespace = 'http://jabber.org/protocol/address' - name = 'addresses' - plugin_attrib = 'addresses' - interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) - - def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): - address = Address(parent=self) - address['type'] = atype - address['jid'] = jid - address['node'] = node - address['uri'] = uri - address['desc'] = desc - address['delivered'] = delivered - return address - - def getAddresses(self, atype=None): - addresses = [] - for addrXML in self.xml.findall('{%s}address' % Address.namespace): - # ElementTree 1.2.6 does not support [@attr='value'] in findall - if atype is None or addrXML.attrib.get('type') == atype: - addresses.append(Address(xml=addrXML, parent=None)) - return addresses - - def setAddresses(self, addresses, set_type=None): - self.delAddresses(set_type) - for addr in addresses: - addr = dict(addr) - # Remap 'type' to 'atype' to match the add method - if set_type is not None: - addr['type'] = set_type - curr_type = addr.get('type', None) - if curr_type is not None: - del addr['type'] - addr['atype'] = curr_type - self.addAddress(**addr) - - def delAddresses(self, atype=None): - if atype is None: - return - for addrXML in self.xml.findall('{%s}address' % Address.namespace): - # ElementTree 1.2.6 does not support [@attr='value'] in findall - if addrXML.attrib.get('type') == atype: - self.xml.remove(addrXML) - - # -------------------------------------------------------------- - - def delBcc(self): - self.delAddresses('bcc') - - def delCc(self): - self.delAddresses('cc') - - def delNoreply(self): - self.delAddresses('noreply') - - def delReplyroom(self): - self.delAddresses('replyroom') - - def delReplyto(self): - self.delAddresses('replyto') - - def delTo(self): - self.delAddresses('to') - - # -------------------------------------------------------------- - - def getBcc(self): - return self.getAddresses('bcc') - - def getCc(self): - return self.getAddresses('cc') - - def getNoreply(self): - return self.getAddresses('noreply') - - def getReplyroom(self): - return self.getAddresses('replyroom') - - def getReplyto(self): - return self.getAddresses('replyto') - - def getTo(self): - return self.getAddresses('to') - - # -------------------------------------------------------------- - - def setBcc(self, addresses): - self.setAddresses(addresses, 'bcc') - - def setCc(self, addresses): - self.setAddresses(addresses, 'cc') - - def setNoreply(self, addresses): - self.setAddresses(addresses, 'noreply') - - def setReplyroom(self, addresses): - self.setAddresses(addresses, 'replyroom') - - def setReplyto(self, addresses): - self.setAddresses(addresses, 'replyto') - - def setTo(self, addresses): - self.setAddresses(addresses, 'to') - - -class Address(ElementBase): - namespace = 'http://jabber.org/protocol/address' - name = 'address' - plugin_attrib = 'address' - interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) - address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) - - def getDelivered(self): - return self.xml.attrib.get('delivered', False) - - def setDelivered(self, delivered): - if delivered: - self.xml.attrib['delivered'] = "true" - else: - del self['delivered'] - - def setUri(self, uri): - if uri: - del self['jid'] - del self['node'] - self.xml.attrib['uri'] = uri - elif 'uri' in self.xml.attrib: - del self.xml.attrib['uri'] - - -class xep_0033(base.base_plugin): - """ - XEP-0033: Extended Stanza Addressing - """ - - def plugin_init(self): - self.xep = '0033' - self.description = 'Extended Stanza Addressing' - - registerStanzaPlugin(Message, Addresses) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py deleted file mode 100644 index db41cdb..0000000 --- a/sleekxmpp/plugins/xep_0045.py +++ /dev/null @@ -1,344 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from __future__ import with_statement -from . import base -import logging -from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID -from .. stanza.presence import Presence -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.matcher.xmlmask import MatchXMLMask - - -log = logging.getLogger(__name__) - - -class MUCPresence(ElementBase): - name = 'x' - namespace = 'http://jabber.org/protocol/muc#user' - plugin_attrib = 'muc' - interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room')) - affiliations = set(('', )) - roles = set(('', )) - - def getXMLItem(self): - item = self.xml.find('{http://jabber.org/protocol/muc#user}item') - if item is None: - item = ET.Element('{http://jabber.org/protocol/muc#user}item') - self.xml.append(item) - return item - - def getAffiliation(self): - #TODO if no affilation, set it to the default and return default - item = self.getXMLItem() - return item.get('affiliation', '') - - def setAffiliation(self, value): - item = self.getXMLItem() - #TODO check for valid affiliation - item.attrib['affiliation'] = value - return self - - def delAffiliation(self): - item = self.getXMLItem() - #TODO set default affiliation - if 'affiliation' in item.attrib: del item.attrib['affiliation'] - return self - - def getJid(self): - item = self.getXMLItem() - return JID(item.get('jid', '')) - - def setJid(self, value): - item = self.getXMLItem() - if not isinstance(value, str): - value = str(value) - item.attrib['jid'] = value - return self - - def delJid(self): - item = self.getXMLItem() - if 'jid' in item.attrib: del item.attrib['jid'] - return self - - def getRole(self): - item = self.getXMLItem() - #TODO get default role, set default role if none - return item.get('role', '') - - def setRole(self, value): - item = self.getXMLItem() - #TODO check for valid role - item.attrib['role'] = value - return self - - def delRole(self): - item = self.getXMLItem() - #TODO set default role - if 'role' in item.attrib: del item.attrib['role'] - return self - - def getNick(self): - return self.parent()['from'].resource - - def getRoom(self): - return self.parent()['from'].bare - - def setNick(self, value): - log.warning("Cannot set nick through mucpresence plugin.") - return self - - def setRoom(self, value): - log.warning("Cannot set room through mucpresence plugin.") - return self - - def delNick(self): - log.warning("Cannot delete nick through mucpresence plugin.") - return self - - def delRoom(self): - log.warning("Cannot delete room through mucpresence plugin.") - return self - -class xep_0045(base.base_plugin): - """ - Impliments XEP-0045 Multi User Chat - """ - - def plugin_init(self): - self.rooms = {} - self.ourNicks = {} - self.xep = '0045' - self.description = 'Multi User Chat' - # load MUC support in presence stanzas - registerStanzaPlugin(Presence, MUCPresence) - self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) - self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) - self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) - - def handle_groupchat_presence(self, pr): - """ Handle a presence in a muc. - """ - got_offline = False - got_online = False - if pr['muc']['room'] not in self.rooms.keys(): - return - entry = pr['muc'].getStanzaValues() - entry['show'] = pr['show'] - entry['status'] = pr['status'] - if pr['type'] == 'unavailable': - if entry['nick'] in self.rooms[entry['room']]: - del self.rooms[entry['room']][entry['nick']] - got_offline = True - else: - if entry['nick'] not in self.rooms[entry['room']]: - got_online = True - self.rooms[entry['room']][entry['nick']] = entry - log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) - self.xmpp.event("groupchat_presence", pr) - self.xmpp.event("muc::%s::presence" % entry['room'], pr) - if got_offline: - self.xmpp.event("muc::%s::got_offline" % entry['room'], pr) - if got_online: - self.xmpp.event("muc::%s::got_online" % entry['room'], pr) - - def handle_groupchat_message(self, msg): - """ Handle a message event in a muc. - """ - self.xmpp.event('groupchat_message', msg) - self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) - - def handle_groupchat_subject(self, msg): - """ Handle a message coming from a muc indicating - a change of subject (or announcing it when joining the room) - """ - self.xmpp.event('groupchat_subject', msg) - - def jidInRoom(self, room, jid): - for nick in self.rooms[room]: - entry = self.rooms[room][nick] - if entry is not None and entry['jid'].full == jid: - return True - return False - - def getNick(self, room, jid): - for nick in self.rooms[room]: - entry = self.rooms[room][nick] - if entry is not None and entry['jid'].full == jid: - return nick - - def getRoomForm(self, room, ifrom=None): - iq = self.xmpp.makeIqGet() - iq['to'] = room - if ifrom is not None: - iq['from'] = ifrom - query = ET.Element('{http://jabber.org/protocol/muc#owner}query') - iq.append(query) - result = iq.send() - if result['type'] == 'error': - return False - xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') - if xform is None: return False - form = self.xmpp.plugin['old_0004'].buildForm(xform) - return form - - def configureRoom(self, room, form=None, ifrom=None): - if form is None: - form = self.getRoomForm(room, ifrom=ifrom) - #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') - #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') - iq = self.xmpp.makeIqSet() - iq['to'] = room - if ifrom is not None: - iq['from'] = ifrom - query = ET.Element('{http://jabber.org/protocol/muc#owner}query') - form = form.getXML('submit') - query.append(form) - iq.append(query) - result = iq.send() - if result['type'] == 'error': - return False - return True - - def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): - """ Join the specified room, requesting 'maxhistory' lines of history. - """ - stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow) - x = ET.Element('{http://jabber.org/protocol/muc}x') - if password: - passelement = ET.Element('password') - passelement.text = password - x.append(passelement) - if maxhistory: - history = ET.Element('history') - if maxhistory == "0": - history.attrib['maxchars'] = maxhistory - else: - history.attrib['maxstanzas'] = maxhistory - x.append(history) - stanza.append(x) - if not wait: - self.xmpp.send(stanza) - else: - #wait for our own room presence back - expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)}) - self.xmpp.send(stanza, expect) - self.rooms[room] = {} - self.ourNicks[room] = nick - - def destroy(self, room, reason='', altroom = '', ifrom=None): - iq = self.xmpp.makeIqSet() - if ifrom is not None: - iq['from'] = ifrom - iq['to'] = room - query = ET.Element('{http://jabber.org/protocol/muc#owner}query') - destroy = ET.Element('destroy') - if altroom: - destroy.attrib['jid'] = altroom - xreason = ET.Element('reason') - xreason.text = reason - destroy.append(xreason) - query.append(destroy) - iq.append(query) - r = iq.send() - if r is False or r['type'] == 'error': - return False - return True - - def setAffiliation(self, room, jid=None, nick=None, affiliation='member'): - """ Change room affiliation.""" - if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): - raise TypeError - query = ET.Element('{http://jabber.org/protocol/muc#admin}query') - if nick is not None: - item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) - else: - item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) - query.append(item) - iq = self.xmpp.makeIqSet(query) - iq['to'] = room - result = iq.send() - if result is False or result['type'] != 'result': - raise ValueError - return True - - def invite(self, room, jid, reason=''): - """ Invite a jid to a room.""" - msg = self.xmpp.makeMessage(room) - msg['from'] = self.xmpp.jid - x = ET.Element('{http://jabber.org/protocol/muc#user}x') - invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) - if reason: - rxml = ET.Element('reason') - rxml.text = reason - invite.append(rxml) - x.append(invite) - msg.append(x) - self.xmpp.send(msg) - - def leaveMUC(self, room, nick, msg=''): - """ Leave the specified room. - """ - if msg: - self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg) - else: - self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) - del self.rooms[room] - - def getRoomConfig(self, room): - iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') - iq['to'] = room - iq['from'] = self.xmpp.jid - result = iq.send() - if result is None or result['type'] != 'result': - raise ValueError - form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') - if form is None: - raise ValueError - return self.xmpp.plugin['xep_0004'].buildForm(form) - - def cancelConfig(self, room): - query = ET.Element('{http://jabber.org/protocol/muc#owner}query') - x = ET.Element('{jabber:x:data}x', type='cancel') - query.append(x) - iq = self.xmpp.makeIqSet(query) - iq.send() - - def setRoomConfig(self, room, config): - query = ET.Element('{http://jabber.org/protocol/muc#owner}query') - x = config.getXML('submit') - query.append(x) - iq = self.xmpp.makeIqSet(query) - iq['to'] = room - iq['from'] = self.xmpp.jid - iq.send() - - def getJoinedRooms(self): - return self.rooms.keys() - - def getOurJidInRoom(self, roomJid): - """ Return the jid we're using in a room. - """ - return "%s/%s" % (roomJid, self.ourNicks[roomJid]) - - def getJidProperty(self, room, nick, jidProperty): - """ Get the property of a nick in a room, such as its 'jid' or 'affiliation' - If not found, return None. - """ - if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]: - return self.rooms[room][nick][jidProperty] - else: - return None - - def getRoster(self, room): - """ Get the list of nicks in a room. - """ - if room not in self.rooms.keys(): - return None - return self.rooms[room].keys() diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py deleted file mode 100644 index 5efb911..0000000 --- a/sleekxmpp/plugins/xep_0050.py +++ /dev/null @@ -1,133 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from __future__ import with_statement -from . import base -import logging -from xml.etree import cElementTree as ET -import time - -class xep_0050(base.base_plugin): - """ - XEP-0050 Ad-Hoc Commands - """ - - def plugin_init(self): - self.xep = '0050' - self.description = 'Ad-Hoc Commands' - self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') - self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute') - self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True) - self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel') - self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete') - self.commands = {} - self.sessions = {} - self.sd = self.xmpp.plugin['xep_0030'] - - def post_init(self): - base.base_plugin.post_init(self) - self.sd.add_feature('http://jabber.org/protocol/commands') - - def addCommand(self, node, name, form, pointer=None, multi=False): - self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node) - self.sd.add_identity('automation', 'command-node', name, node) - self.sd.add_feature('http://jabber.org/protocol/commands', node) - self.sd.add_feature('jabber:x:data', node) - self.commands[node] = (name, form, pointer, multi) - - def getNewSession(self): - return str(time.time()) + '-' + self.xmpp.getNewId() - - def handler_command(self, xml): - in_command = xml.find('{http://jabber.org/protocol/commands}command') - sessionid = in_command.get('sessionid', None) - node = in_command.get('node') - sessionid = self.getNewSession() - name, form, pointer, multi = self.commands[node] - self.sessions[sessionid] = {} - self.sessions[sessionid]['jid'] = xml.get('from') - self.sessions[sessionid]['to'] = xml.get('to') - self.sessions[sessionid]['past'] = [(form, None)] - self.sessions[sessionid]['next'] = pointer - npointer = pointer - if multi: - actions = ['next'] - status = 'executing' - else: - if pointer is None: - status = 'completed' - actions = [] - else: - status = 'executing' - actions = ['complete'] - self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions)) - - def handler_command_complete(self, xml): - in_command = xml.find('{http://jabber.org/protocol/commands}command') - sessionid = in_command.get('sessionid', None) - pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['old_0004'].makeForm('result') - results.fromXML(in_command.find('{jabber:x:data}x')) - pointer(results,sessionid) - self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) - del self.sessions[in_command.get('sessionid')] - - - def handler_command_next(self, xml): - in_command = xml.find('{http://jabber.org/protocol/commands}command') - sessionid = in_command.get('sessionid', None) - pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['old_0004'].makeForm('result') - results.fromXML(in_command.find('{jabber:x:data}x')) - form, npointer, next = pointer(results,sessionid) - self.sessions[sessionid]['next'] = npointer - self.sessions[sessionid]['past'].append((form, pointer)) - actions = [] - actions.append('prev') - if npointer is None: - status = 'completed' - else: - status = 'executing' - if next: - actions.append('next') - else: - actions.append('complete') - self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions)) - - def handler_command_cancel(self, xml): - command = xml.find('{http://jabber.org/protocol/commands}command') - try: - del self.sessions[command.get('sessionid')] - except: - pass - self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled')) - - def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]): - if not id: - id = self.xmpp.getNewId() - iq = self.xmpp.makeIqResult(id) - iq.attrib['from'] = self.xmpp.fulljid - iq.attrib['to'] = to - command = ET.Element('{http://jabber.org/protocol/commands}command') - command.attrib['node'] = node - command.attrib['status'] = status - xmlactions = ET.Element('actions') - for action in actions: - xmlactions.append(ET.Element(action)) - if xmlactions: - command.append(xmlactions) - if not sessionid: - sessionid = self.getNewSession() - else: - iq.attrib['from'] = self.sessions[sessionid]['to'] - command.attrib['sessionid'] = sessionid - if form is not None: - if hasattr(form,'getXML'): - form = form.getXML() - command.append(form) - iq.append(command) - return iq diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py deleted file mode 100644 index a7c6d02..0000000 --- a/sleekxmpp/plugins/xep_0060.py +++ /dev/null @@ -1,313 +0,0 @@ -from __future__ import with_statement -from . import base -import logging -#from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET -from . import stanza_pubsub -from . xep_0004 import Form - - -log = logging.getLogger(__name__) - - -class xep_0060(base.base_plugin): - """ - XEP-0060 Publish Subscribe - """ - - def plugin_init(self): - self.xep = '0060' - self.description = 'Publish-Subscribe' - - def create_node(self, jid, node, config=None, collection=False, ntype=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - create = ET.Element('create') - create.set('node', node) - pubsub.append(create) - configure = ET.Element('configure') - if collection: - ntype = 'collection' - #if config is None: - # submitform = self.xmpp.plugin['xep_0004'].makeForm('submit') - #else: - if config is not None: - submitform = config - if 'FORM_TYPE' in submitform.field: - submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') - else: - submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') - if ntype: - if 'pubsub#node_type' in submitform.field: - submitform.field['pubsub#node_type'].setValue(ntype) - else: - submitform.addField('pubsub#node_type', value=ntype) - else: - if 'pubsub#node_type' in submitform.field: - submitform.field['pubsub#node_type'].setValue('leaf') - else: - submitform.addField('pubsub#node_type', value='leaf') - submitform['type'] = 'submit' - configure.append(submitform.xml) - pubsub.append(configure) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True - - def subscribe(self, jid, node, bare=True, subscribee=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - subscribe = ET.Element('subscribe') - subscribe.attrib['node'] = node - if subscribee is None: - if bare: - subscribe.attrib['jid'] = self.xmpp.jid - else: - subscribe.attrib['jid'] = self.xmpp.fulljid - else: - subscribe.attrib['jid'] = subscribee - pubsub.append(subscribe) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True - - def unsubscribe(self, jid, node, bare=True, subscribee=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - unsubscribe = ET.Element('unsubscribe') - unsubscribe.attrib['node'] = node - if subscribee is None: - if bare: - unsubscribe.attrib['jid'] = self.xmpp.jid - else: - unsubscribe.attrib['jid'] = self.xmpp.fulljid - else: - unsubscribe.attrib['jid'] = subscribee - pubsub.append(unsubscribe) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True - - def getNodeConfig(self, jid, node=None): # if no node, then grab default - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - if node is not None: - configure = ET.Element('configure') - configure.attrib['node'] = node - else: - configure = ET.Element('default') - pubsub.append(configure) - #TODO: Add configure support. - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - #self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - if node is not None: - form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') - else: - form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') - if not form or form is None: - log.error("No form found.") - return False - return Form(xml=form) - - def getNodeSubscriptions(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - subscriptions = ET.Element('subscriptions') - subscriptions.attrib['node'] = node - pubsub.append(subscriptions) - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - else: - results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') - if results is None: - return False - subs = {} - for sub in results: - subs[sub.get('jid')] = sub.get('subscription') - return subs - - def getNodeAffiliations(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - affiliations = ET.Element('affiliations') - affiliations.attrib['node'] = node - pubsub.append(affiliations) - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - else: - results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') - if results is None: - return False - subs = {} - for sub in results: - subs[sub.get('jid')] = sub.get('affiliation') - return subs - - def deleteNode(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - iq = self.xmpp.makeIqSet() - delete = ET.Element('delete') - delete.attrib['node'] = node - pubsub.append(delete) - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - result = iq.send() - if result is not None and result is not False and result['type'] != 'error': - return True - else: - return False - - - def setNodeConfig(self, jid, node, config): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - configure = ET.Element('configure') - configure.attrib['node'] = node - config = config.getXML('submit') - configure.append(config) - pubsub.append(configure) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result['type'] == 'error': - return False - return True - - def setItem(self, jid, node, items=[]): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - publish = ET.Element('publish') - publish.attrib['node'] = node - for pub_item in items: - id, payload = pub_item - item = ET.Element('item') - if id is not None: - item.attrib['id'] = id - item.append(payload) - publish.append(item) - pubsub.append(publish) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': return False - return True - - def addItem(self, jid, node, items=[]): - return self.setItem(jid, node, items) - - def deleteItem(self, jid, node, item): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - retract = ET.Element('retract') - retract.attrib['node'] = node - itemn = ET.Element('item') - itemn.attrib['id'] = item - retract.append(itemn) - pubsub.append(retract) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': return False - return True - - def getNodes(self, jid): - response = self.xmpp.plugin['xep_0030'].getItems(jid) - items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') - nodes = {} - if items is not None and items is not False: - for item in items: - nodes[item.get('node')] = item.get('name') - return nodes - - def getItems(self, jid, node): - response = self.xmpp.plugin['xep_0030'].getItems(jid, node) - items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') - nodeitems = [] - if items is not None and items is not False: - for item in items: - nodeitems.append(item.get('node')) - return nodeitems - - def addNodeToCollection(self, jid, child, parent=''): - config = self.getNodeConfig(jid, child) - if not config or config is None: - self.lasterror = "Config Error" - return False - try: - config.field['pubsub#collection'].setValue(parent) - except KeyError: - log.warning("pubsub#collection doesn't exist in config, trying to add it") - config.addField('pubsub#collection', value=parent) - if not self.setNodeConfig(jid, child, config): - return False - return True - - def modifyAffiliation(self, ps_jid, node, user_jid, affiliation): - if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'): - raise TypeError - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - affs = ET.Element('affiliations') - affs.attrib['node'] = node - aff = ET.Element('affiliation') - aff.attrib['jid'] = user_jid - aff.attrib['affiliation'] = affiliation - affs.append(aff) - pubsub.append(affs) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = ps_jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': - return False - return True - - def addNodeToCollection(self, jid, child, parent=''): - config = self.getNodeConfig(jid, child) - if not config or config is None: - self.lasterror = "Config Error" - return False - try: - config.field['pubsub#collection'].setValue(parent) - except KeyError: - log.warning("pubsub#collection doesn't exist in config, trying to add it") - config.addField('pubsub#collection', value=parent) - if not self.setNodeConfig(jid, child, config): - return False - return True - - def removeNodeFromCollection(self, jid, child): - self.addNodeToCollection(jid, child, '') - diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py deleted file mode 100644 index d2c81b1..0000000 --- a/sleekxmpp/plugins/xep_0078.py +++ /dev/null @@ -1,72 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from __future__ import with_statement -from xml.etree import cElementTree as ET -import logging -import hashlib -from . import base - - -log = logging.getLogger(__name__) - - -class xep_0078(base.base_plugin): - """ - XEP-0078 NON-SASL Authentication - """ - def plugin_init(self): - self.description = "Non-SASL Authentication (broken)" - self.xep = "0078" - self.xmpp.add_event_handler("session_start", self.check_stream) - #disabling until I fix conflict with PLAIN - #self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth) - self.streamid = '' - - def check_stream(self, xml): - self.streamid = xml.attrib['id'] - if xml.get('version', '0') != '1.0': - self.auth() - - def auth(self, xml=None): - log.debug("Starting jabber:iq:auth Authentication") - auth_request = self.xmpp.makeIqGet() - auth_request_query = ET.Element('{jabber:iq:auth}query') - auth_request.attrib['to'] = self.xmpp.server - username = ET.Element('username') - username.text = self.xmpp.username - auth_request_query.append(username) - auth_request.append(auth_request_query) - result = auth_request.send() - rquery = result.find('{jabber:iq:auth}query') - attempt = self.xmpp.makeIqSet() - query = ET.Element('{jabber:iq:auth}query') - resource = ET.Element('resource') - resource.text = self.xmpp.resource - query.append(username) - query.append(resource) - if rquery.find('{jabber:iq:auth}digest') is None: - log.warning("Authenticating via jabber:iq:auth Plain.") - password = ET.Element('password') - password.text = self.xmpp.password - query.append(password) - else: - log.debug("Authenticating via jabber:iq:auth Digest") - digest = ET.Element('digest') - digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest() - query.append(digest) - attempt.append(query) - result = attempt.send() - if result.attrib['type'] == 'result': - with self.xmpp.lock: - self.xmpp.authenticated = True - self.xmpp.sessionstarted = True - self.xmpp.event("session_start") - else: - log.info("Authentication failed") - self.xmpp.disconnect() - self.xmpp.event("failed_auth") diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py deleted file mode 100644 index 3627e71..0000000 --- a/sleekxmpp/plugins/xep_0085.py +++ /dev/null @@ -1,104 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.message import Message - - -log = logging.getLogger(__name__) - - -class ChatState(ElementBase): - namespace = 'http://jabber.org/protocol/chatstates' - plugin_attrib = 'chat_state' - interface = set(('state',)) - states = set(('active', 'composing', 'gone', 'inactive', 'paused')) - - def active(self): - self.setState('active') - - def composing(self): - self.setState('composing') - - def gone(self): - self.setState('gone') - - def inactive(self): - self.setState('inactive') - - def paused(self): - self.setState('paused') - - def setState(self, state): - if state in self.states: - self.name = state - self.xml.tag = '{%s}%s' % (self.namespace, state) - else: - raise ValueError('Invalid chat state') - - def getState(self): - return self.name - -# In order to match the various chat state elements, -# we need one stanza object per state, even though -# they are all the same except for the initial name -# value. Do not depend on the type of the chat state -# stanza object for the actual state. - -class Active(ChatState): - name = 'active' -class Composing(ChatState): - name = 'composing' -class Gone(ChatState): - name = 'gone' -class Inactive(ChatState): - name = 'inactive' -class Paused(ChatState): - name = 'paused' - - -class xep_0085(base.base_plugin): - """ - XEP-0085 Chat State Notifications - """ - - def plugin_init(self): - self.xep = '0085' - self.description = 'Chat State Notifications' - - handlers = [('Active Chat State', 'active'), - ('Composing Chat State', 'composing'), - ('Gone Chat State', 'gone'), - ('Inactive Chat State', 'inactive'), - ('Paused Chat State', 'paused')] - for handler in handlers: - self.xmpp.registerHandler( - Callback(handler[0], - MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, - ChatState.namespace, - handler[1])), - self._handleChatState)) - - registerStanzaPlugin(Message, Active) - registerStanzaPlugin(Message, Composing) - registerStanzaPlugin(Message, Gone) - registerStanzaPlugin(Message, Inactive) - registerStanzaPlugin(Message, Paused) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates') - - def _handleChatState(self, msg): - state = msg['chat_state'].name - log.debug("Chat State: %s, %s" % (state, msg['from'].jid)) - self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0086.py b/sleekxmpp/plugins/xep_0086.py deleted file mode 100644 index e6c18c7..0000000 --- a/sleekxmpp/plugins/xep_0086.py +++ /dev/null @@ -1,49 +0,0 @@ -
-from __future__ import with_statement
-from . import base
-import logging
-from xml.etree import cElementTree as ET
-import copy
-
-class xep_0086(base.base_plugin):
- """
- XEP-0086 Error Condition Mappings
- """
-
- def plugin_init(self):
- self.xep = '0086'
- self.description = 'Error Condition Mappings'
- self.error_map = {
- 'bad-request':('modify','400'),
- 'conflict':('cancel','409'),
- 'feature-not-implemented':('cancel','501'),
- 'forbidden':('auth','403'),
- 'gone':('modify','302'),
- 'internal-server-error':('wait','500'),
- 'item-not-found':('cancel','404'),
- 'jid-malformed':('modify','400'),
- 'not-acceptable':('modify','406'),
- 'not-allowed':('cancel','405'),
- 'not-authorized':('auth','401'),
- 'payment-required':('auth','402'),
- 'recipient-unavailable':('wait','404'),
- 'redirect':('modify','302'),
- 'registration-required':('auth','407'),
- 'remote-server-not-found':('cancel','404'),
- 'remote-server-timeout':('wait','504'),
- 'resource-constraint':('wait','500'),
- 'service-unavailable':('cancel','503'),
- 'subscription-required':('auth','407'),
- 'undefined-condition':(None,'500'),
- 'unexpected-request':('wait','400')
- }
-
-
- def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None):
- conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata)
- if errorType is None:
- error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem)
- else:
- error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem)
- error.append(conditionElem)
- return error
diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py deleted file mode 100644 index ca02c4a..0000000 --- a/sleekxmpp/plugins/xep_0092.py +++ /dev/null @@ -1,56 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from xml.etree import cElementTree as ET -from . import base -from .. xmlstream.handler.xmlwaiter import XMLWaiter - -class xep_0092(base.base_plugin): - """ - XEP-0092 Software Version - """ - def plugin_init(self): - self.description = "Software Version" - self.xep = "0092" - self.name = self.config.get('name', 'SleekXMPP') - self.version = self.config.get('version', '0.1-dev') - self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version, name='Sofware Version') - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') - - def report_version(self, xml): - iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) - iq.attrib['to'] = xml.get('from', self.xmpp.server) - query = ET.Element('{jabber:iq:version}query') - name = ET.Element('name') - name.text = self.name - version = ET.Element('version') - version.text = self.version - query.append(name) - query.append(version) - iq.append(query) - self.xmpp.send(iq) - - def getVersion(self, jid): - iq = self.xmpp.makeIqGet() - query = ET.Element('{jabber:iq:version}query') - iq.append(query) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.fulljid - id = iq.get('id') - result = iq.send() - if result and result is not None and result.get('type', 'error') != 'error': - qry = result.find('{jabber:iq:version}query') - version = {} - for child in qry.getchildren(): - version[child.tag.split('}')[-1]] = child.text - return version - else: - return False - diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py deleted file mode 100644 index 824977b..0000000 --- a/sleekxmpp/plugins/xep_0128.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.iq import Iq -from . xep_0030 import DiscoInfo, DiscoItems -from . xep_0004 import Form - - -class xep_0128(base.base_plugin): - """ - XEP-0128 Service Discovery Extensions - """ - - def plugin_init(self): - self.xep = '0128' - self.description = 'Service Discovery Extensions' - - registerStanzaPlugin(DiscoInfo, Form) - registerStanzaPlugin(DiscoItems, Form) - - def extend_info(self, node, data=None): - if data is None: - data = {} - node = self.xmpp['xep_0030'].nodes.get(node, None) - if node is None: - self.xmpp['xep_0030'].add_node(node) - - info = node.info - info['form']['type'] = 'result' - info['form'].setFields(data, default=None) - - def extend_items(self, node, data=None): - if data is None: - data = {} - node = self.xmpp['xep_0030'].nodes.get(node, None) - if node is None: - self.xmpp['xep_0030'].add_node(node) - - items = node.items - items['form']['type'] = 'result' - items['form'].setFields(data, default=None) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py deleted file mode 100644 index 2e99ae7..0000000 --- a/sleekxmpp/plugins/xep_0199.py +++ /dev/null @@ -1,63 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from xml.etree import cElementTree as ET -from . import base -import time -import logging - - -log = logging.getLogger(__name__) - - -class xep_0199(base.base_plugin): - """XEP-0199 XMPP Ping""" - - def plugin_init(self): - self.description = "XMPP Ping" - self.xep = "0199" - self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='urn:xmpp:ping'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') - if self.config.get('keepalive', True): - self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') - - def handler_pingserver(self, xml): - self.xmpp.schedule("xep-0119 ping", float(self.config.get('frequency', 300)), self.scheduled_ping, repeat=True) - - def scheduled_ping(self): - log.debug("pinging...") - if self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is False: - log.debug("Did not recieve ping back in time. Requesting Reconnect.") - self.xmpp.reconnect() - - def handler_ping(self, xml): - iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) - iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) - self.xmpp.send(iq) - - def sendPing(self, jid, timeout = 30): - """ sendPing(jid, timeout) - Sends a ping to the specified jid, returning the time (in seconds) - to receive a reply, or None if no reply is received in timeout seconds. - """ - id = self.xmpp.getNewId() - iq = self.xmpp.makeIq(id) - iq.attrib['type'] = 'get' - iq.attrib['to'] = jid - ping = ET.Element('{urn:xmpp:ping}ping') - iq.append(ping) - startTime = time.clock() - #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout) - pingresult = iq.send() - endTime = time.clock() - if pingresult == False: - #self.xmpp.disconnect(reconnect=True) - return False - return endTime - startTime diff --git a/sleekxmpp/plugins/xep_0202.py b/sleekxmpp/plugins/xep_0202.py deleted file mode 100644 index fe1191e..0000000 --- a/sleekxmpp/plugins/xep_0202.py +++ /dev/null @@ -1,115 +0,0 @@ -"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from datetime import datetime, tzinfo
-import logging
-import time
-
-from . import base
-from .. stanza.iq import Iq
-from .. xmlstream.handler.callback import Callback
-from .. xmlstream.matcher.xpath import MatchXPath
-from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin
-
-
-log = logging.getLogger(__name__)
-
-
-class EntityTime(ElementBase):
- name = 'time'
- namespace = 'urn:xmpp:time'
- plugin_attrib = 'entity_time'
- interfaces = set(('tzo', 'utc'))
- sub_interfaces = set(('tzo', 'utc'))
-
- #def get_utc(self): # TODO: return a datetime.tzinfo object?
- #pass
-
- def set_tzo(self, tzo): # TODO: support datetime.tzinfo objects?
- if isinstance(tzo, tzinfo):
- td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here'
- seconds = td.seconds + td.days * 24 * 3600
- sign = ('+' if seconds >= 0 else '-')
- minutes = abs(seconds // 60)
- tzo = '{sign}{hours:02d}:{minutes:02d}'.format(sign=sign, hours=minutes//60, minutes=minutes%60)
- elif not isinstance(tzo, str):
- raise TypeError('The time should be a string or a datetime.tzinfo object.')
- self._set_sub_text('tzo', tzo)
-
- def get_utc(self):
- # Returns a datetime object instead the string. Is this a good idea?
- value = self._get_sub_text('utc')
- if '.' in value:
- return datetime.strptime(value, '%Y-%m-%d.%fT%H:%M:%SZ')
- else:
- return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
-
- def set_utc(self, tim=None):
- if isinstance(tim, datetime):
- if tim.utcoffset():
- tim = tim - tim.utcoffset()
- tim = tim.strftime('%Y-%m-%dT%H:%M:%SZ')
- elif isinstance(tim, time.struct_time):
- tim = time.strftime('%Y-%m-%dT%H:%M:%SZ', tim)
- elif not isinstance(tim, str):
- raise TypeError('The time should be a string or a datetime.datetime or time.struct_time object.')
-
- self._set_sub_text('utc', tim)
-
-
-class xep_0202(base.base_plugin):
- """
- XEP-0202 Entity Time
- """
- def plugin_init(self):
- self.description = "Entity Time"
- self.xep = "0202"
-
- self.xmpp.registerHandler(
- Callback('Time Request',
- MatchXPath('{%s}iq/{%s}time' % (self.xmpp.default_ns,
- EntityTime.namespace)),
- self.handle_entity_time_query))
- register_stanza_plugin(Iq, EntityTime)
-
- self.xmpp.add_event_handler('entity_time_request', self.handle_entity_time)
-
-
- def post_init(self):
- base.base_plugin.post_init(self)
-
- self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:time')
-
- def handle_entity_time_query(self, iq):
- if iq['type'] == 'get':
- log.debug("Entity time requested by %s" % iq['from'])
- self.xmpp.event('entity_time_request', iq)
- elif iq['type'] == 'result':
- log.debug("Entity time result from %s" % iq['from'])
- self.xmpp.event('entity_time', iq)
-
- def handle_entity_time(self, iq):
- iq = iq.reply()
- iq.enable('entity_time')
- tzo = time.strftime('%z') # %z is not on all ANSI C libraries
- tzo = tzo[:3] + ':' + tzo[3:]
- iq['entity_time']['tzo'] = tzo
- iq['entity_time']['utc'] = datetime.utcnow()
- iq.send()
-
- def get_entity_time(self, jid):
- iq = self.xmpp.makeIqGet()
- iq.enable('entity_time')
- iq.attrib['to'] = jid
- iq.attrib['from'] = self.xmpp.boundjid.full
- id = iq.get('id')
- result = iq.send()
- if result and result is not None and result.get('type', 'error') != 'error':
- return {'utc': result['entity_time']['utc'], 'tzo': result['entity_time']['tzo']}
- else:
- return False
diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py deleted file mode 100644 index 8302c43..0000000 --- a/sleekxmpp/stanza/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.stanza.error import Error -from sleekxmpp.stanza.iq import Iq -from sleekxmpp.stanza.message import Message -from sleekxmpp.stanza.presence import Presence diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py deleted file mode 100644 index 244ef31..0000000 --- a/sleekxmpp/stanza/atom.py +++ /dev/null @@ -1,26 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class AtomEntry(ElementBase): - - """ - A simple Atom feed entry. - - Stanza Interface: - title -- The title of the Atom feed entry. - summary -- The summary of the Atom feed entry. - """ - - namespace = 'http://www.w3.org/2005/Atom' - name = 'entry' - plugin_attrib = 'entry' - interfaces = set(('title', 'summary')) - sub_interfaces = set(('title', 'summary')) diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py deleted file mode 100644 index 09229bc..0000000 --- a/sleekxmpp/stanza/error.py +++ /dev/null @@ -1,141 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class Error(ElementBase): - - """ - XMPP stanzas of type 'error' should include an <error> stanza that - describes the nature of the error and how it should be handled. - - Use the 'XEP-0086: Error Condition Mappings' plugin to include error - codes used in older XMPP versions. - - Example error stanza: - <error type="cancel" code="404"> - <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> - <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> - The item was not found. - </text> - </error> - - Stanza Interface: - code -- The error code used in older XMPP versions. - condition -- The name of the condition element. - text -- Human readable description of the error. - type -- Error type indicating how the error should be handled. - - Attributes: - conditions -- The set of allowable error condition elements. - condition_ns -- The namespace for the condition element. - types -- A set of values indicating how the error - should be treated. - - Methods: - setup -- Overrides ElementBase.setup. - get_condition -- Retrieve the name of the condition element. - set_condition -- Add a condition element. - del_condition -- Remove the condition element. - get_text -- Retrieve the contents of the <text> element. - set_text -- Set the contents of the <text> element. - del_text -- Remove the <text> element. - """ - - namespace = 'jabber:client' - name = 'error' - plugin_attrib = 'error' - interfaces = set(('code', 'condition', 'text', 'type')) - sub_interfaces = set(('text',)) - conditions = set(('bad-request', 'conflict', 'feature-not-implemented', - 'forbidden', 'gone', 'internal-server-error', - 'item-not-found', 'jid-malformed', 'not-acceptable', - 'not-allowed', 'not-authorized', 'payment-required', - 'recipient-unavailable', 'redirect', - 'registration-required', 'remote-server-not-found', - 'remote-server-timeout', 'resource-constraint', - 'service-unavailable', 'subscription-required', - 'undefined-condition', 'unexpected-request')) - condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' - types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup. - - Sets a default error type and condition, and changes the - parent stanza's type to 'error'. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.getCondition = self.get_condition - self.setCondition = self.set_condition - self.delCondition = self.del_condition - self.getText = self.get_text - self.setText = self.set_text - self.delText = self.del_text - - if ElementBase.setup(self, xml): - #If we had to generate XML then set default values. - self['type'] = 'cancel' - self['condition'] = 'feature-not-implemented' - if self.parent is not None: - self.parent()['type'] = 'error' - - def get_condition(self): - """Return the condition element's name.""" - for child in self.xml.getchildren(): - if "{%s}" % self.condition_ns in child.tag: - return child.tag.split('}', 1)[-1] - return '' - - def set_condition(self, value): - """ - Set the tag name of the condition element. - - Arguments: - value -- The tag name of the condition element. - """ - if value in self.conditions: - del self['condition'] - self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value))) - return self - - def del_condition(self): - """Remove the condition element.""" - for child in self.xml.getchildren(): - if "{%s}" % self.condition_ns in child.tag: - tag = child.tag.split('}', 1)[-1] - if tag in self.conditions: - self.xml.remove(child) - return self - - def get_text(self): - """Retrieve the contents of the <text> element.""" - return self._get_sub_text('{%s}text' % self.condition_ns) - - def set_text(self, value): - """ - Set the contents of the <text> element. - - Arguments: - value -- The new contents for the <text> element. - """ - self._set_sub_text('{%s}text' % self.condition_ns, text=value) - return self - - def del_text(self): - """Remove the <text> element.""" - self._del_sub('{%s}text' % self.condition_ns) - return self diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py deleted file mode 100644 index 4586828..0000000 --- a/sleekxmpp/stanza/htmlim.py +++ /dev/null @@ -1,97 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Message -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class HTMLIM(ElementBase): - - """ - XEP-0071: XHTML-IM defines a method for embedding XHTML content - within a <message> stanza so that lightweight markup can be used - to format the message contents and to create links. - - Only a subset of XHTML is recommended for use with XHTML-IM. - See the full spec at 'http://xmpp.org/extensions/xep-0071.html' - for more information. - - Example stanza: - <message to="user@example.com"> - <body>Non-html message content.</body> - <html xmlns="http://jabber.org/protocol/xhtml-im"> - <body xmlns="http://www.w3.org/1999/xhtml"> - <p><b>HTML!</b></p> - </body> - </html> - </message> - - Stanza Interface: - body -- The contents of the HTML body tag. - - Methods: - setup -- Overrides ElementBase.setup. - get_body -- Return the HTML body contents. - set_body -- Set the HTML body contents. - del_body -- Remove the HTML body contents. - """ - - namespace = 'http://jabber.org/protocol/xhtml-im' - name = 'html' - interfaces = set(('body',)) - plugin_attrib = name - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides StanzaBase.setup. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setBody = self.set_body - self.getBody = self.get_body - self.delBody = self.del_body - - return ElementBase.setup(self, xml) - - def set_body(self, html): - """ - Set the contents of the HTML body. - - Arguments: - html -- Either a string or XML object. If the top level - element is not <body> with a namespace of - 'http://www.w3.org/1999/xhtml', it will be wrapped. - """ - if isinstance(html, str): - html = ET.XML(html) - if html.tag != '{http://www.w3.org/1999/xhtml}body': - body = ET.Element('{http://www.w3.org/1999/xhtml}body') - body.append(html) - self.xml.append(body) - else: - self.xml.append(html) - - def get_body(self): - """Return the contents of the HTML body.""" - html = self.xml.find('{http://www.w3.org/1999/xhtml}body') - if html is None: - return '' - return html - - def del_body(self): - """Remove the HTML body contents.""" - if self.parent is not None: - self.parent().xml.remove(self.xml) - - -register_stanza_plugin(Message, HTMLIM) diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py deleted file mode 100644 index 614d14f..0000000 --- a/sleekxmpp/stanza/iq.py +++ /dev/null @@ -1,183 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.stanza.rootstanza import RootStanza -from sleekxmpp.xmlstream import RESPONSE_TIMEOUT, StanzaBase, ET -from sleekxmpp.xmlstream.handler import Waiter -from sleekxmpp.xmlstream.matcher import MatcherId - - -class Iq(RootStanza): - - """ - XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of - requesting and modifying information, similar to HTTP's GET and - POST methods. - - Each <iq> stanza must have an 'id' value which associates the - stanza with the response stanza. XMPP entities must always - be given a response <iq> stanza with a type of 'result' after - sending a stanza of type 'get' or 'set'. - - Most uses cases for <iq> stanzas will involve adding a <query> - element whose namespace indicates the type of information - desired. However, some custom XMPP applications use <iq> stanzas - as a carrier stanza for an application-specific protocol instead. - - Example <iq> Stanzas: - <iq to="user@example.com" type="get" id="314"> - <query xmlns="http://jabber.org/protocol/disco#items" /> - </iq> - - <iq to="user@localhost" type="result" id="17"> - <query xmlns='jabber:iq:roster'> - <item jid='otheruser@example.net' - name='John Doe' - subscription='both'> - <group>Friends</group> - </item> - </query> - </iq> - - Stanza Interface: - query -- The namespace of the <query> element if one exists. - - Attributes: - types -- May be one of: get, set, result, or error. - - Methods: - __init__ -- Overrides StanzaBase.__init__. - unhandled -- Send error if there are no handlers. - set_payload -- Overrides StanzaBase.set_payload. - set_query -- Add or modify a <query> element. - get_query -- Return the namespace of the <query> element. - del_query -- Remove the <query> element. - reply -- Overrides StanzaBase.reply - send -- Overrides StanzaBase.send - """ - - namespace = 'jabber:client' - name = 'iq' - interfaces = set(('type', 'to', 'from', 'id', 'query')) - types = set(('get', 'result', 'set', 'error')) - plugin_attrib = name - - def __init__(self, *args, **kwargs): - """ - Initialize a new <iq> stanza with an 'id' value. - - Overrides StanzaBase.__init__. - """ - StanzaBase.__init__(self, *args, **kwargs) - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setPayload = self.set_payload - self.getQuery = self.get_query - self.setQuery = self.set_query - self.delQuery = self.del_query - - if self['id'] == '': - if self.stream is not None: - self['id'] = self.stream.getNewId() - else: - self['id'] = '0' - - def unhandled(self): - """ - Send a feature-not-implemented error if the stanza is not handled. - - Overrides StanzaBase.unhandled. - """ - if self['type'] in ('get', 'set'): - self.reply() - self['error']['condition'] = 'feature-not-implemented' - self['error']['text'] = 'No handlers registered for this request.' - self.send() - - def set_payload(self, value): - """ - Set the XML contents of the <iq> stanza. - - Arguments: - value -- An XML object to use as the <iq> stanza's contents - """ - self.clear() - StanzaBase.set_payload(self, value) - return self - - def set_query(self, value): - """ - Add or modify a <query> element. - - Query elements are differentiated by their namespace. - - Arguments: - value -- The namespace of the <query> element. - """ - query = self.xml.find("{%s}query" % value) - if query is None and value: - self.clear() - query = ET.Element("{%s}query" % value) - self.xml.append(query) - return self - - def get_query(self): - """Return the namespace of the <query> element.""" - for child in self.xml.getchildren(): - if child.tag.endswith('query'): - ns = child.tag.split('}')[0] - if '{' in ns: - ns = ns[1:] - return ns - return '' - - def del_query(self): - """Remove the <query> element.""" - for child in self.xml.getchildren(): - if child.tag.endswith('query'): - self.xml.remove(child) - return self - - def reply(self): - """ - Send a reply <iq> stanza. - - Overrides StanzaBase.reply - - Sets the 'type' to 'result' in addition to the default - StanzaBase.reply behavior. - """ - self['type'] = 'result' - StanzaBase.reply(self) - return self - - def send(self, block=True, timeout=RESPONSE_TIMEOUT): - """ - Send an <iq> stanza over the XML stream. - - The send call can optionally block until a response is received or - a timeout occurs. Be aware that using blocking in non-threaded event - handlers can drastically impact performance. - - Overrides StanzaBase.send - - Arguments: - block -- Specify if the send call will block until a response - is received, or a timeout occurs. Defaults to True. - timeout -- The length of time (in seconds) to wait for a response - before exiting the send call if blocking is used. - Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT - """ - if block and self['type'] in ('get', 'set'): - waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) - self.stream.registerHandler(waitfor) - StanzaBase.send(self) - return waitfor.wait(timeout) - else: - return StanzaBase.send(self) diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py deleted file mode 100644 index 66c74d8..0000000 --- a/sleekxmpp/stanza/message.py +++ /dev/null @@ -1,165 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.stanza.rootstanza import RootStanza -from sleekxmpp.xmlstream import StanzaBase, ET - - -class Message(RootStanza): - - """ - XMPP's <message> stanzas are a "push" mechanism to send information - to other XMPP entities without requiring a response. - - Chat clients will typically use <message> stanzas that have a type - of either "chat" or "groupchat". - - When handling a message event, be sure to check if the message is - an error response. - - Example <message> stanzas: - <message to="user1@example.com" from="user2@example.com"> - <body>Hi!</body> - </message> - - <message type="groupchat" to="room@conference.example.com"> - <body>Hi everyone!</body> - </message> - - Stanza Interface: - body -- The main contents of the message. - subject -- An optional description of the message's contents. - mucroom -- (Read-only) The name of the MUC room that sent the message. - mucnick -- (Read-only) The MUC nickname of message's sender. - - Attributes: - types -- May be one of: normal, chat, headline, groupchat, or error. - - Methods: - setup -- Overrides StanzaBase.setup. - chat -- Set the message type to 'chat'. - normal -- Set the message type to 'normal'. - reply -- Overrides StanzaBase.reply - get_type -- Overrides StanzaBase interface - get_mucroom -- Return the name of the MUC room of the message. - set_mucroom -- Dummy method to prevent assignment. - del_mucroom -- Dummy method to prevent deletion. - get_mucnick -- Return the MUC nickname of the message's sender. - set_mucnick -- Dummy method to prevent assignment. - del_mucnick -- Dummy method to prevent deletion. - """ - - namespace = 'jabber:client' - name = 'message' - interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', - 'mucroom', 'mucnick')) - sub_interfaces = set(('body', 'subject')) - plugin_attrib = name - types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides StanzaBase.setup. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.getType = self.get_type - self.getMucroom = self.get_mucroom - self.setMucroom = self.set_mucroom - self.delMucroom = self.del_mucroom - self.getMucnick = self.get_mucnick - self.setMucnick = self.set_mucnick - self.delMucnick = self.del_mucnick - - return StanzaBase.setup(self, xml) - - def get_type(self): - """ - Return the message type. - - Overrides default stanza interface behavior. - - Returns 'normal' if no type attribute is present. - """ - return self._get_attr('type', 'normal') - - def chat(self): - """Set the message type to 'chat'.""" - self['type'] = 'chat' - return self - - def normal(self): - """Set the message type to 'chat'.""" - self['type'] = 'normal' - return self - - def reply(self, body=None): - """ - Create a message reply. - - Overrides StanzaBase.reply. - - Sets proper 'to' attribute if the message is from a MUC, and - adds a message body if one is given. - - Arguments: - body -- Optional text content for the message. - """ - StanzaBase.reply(self) - if self['type'] == 'groupchat': - self['to'] = self['to'].bare - - del self['id'] - - if body is not None: - self['body'] = body - return self - - def get_mucroom(self): - """ - Return the name of the MUC room where the message originated. - - Read-only stanza interface. - """ - if self['type'] == 'groupchat': - return self['from'].bare - else: - return '' - - def get_mucnick(self): - """ - Return the nickname of the MUC user that sent the message. - - Read-only stanza interface. - """ - if self['type'] == 'groupchat': - return self['from'].resource - else: - return '' - - def set_mucroom(self, value): - """Dummy method to prevent modification.""" - pass - - def del_mucroom(self): - """Dummy method to prevent deletion.""" - pass - - def set_mucnick(self, value): - """Dummy method to prevent modification.""" - pass - - def del_mucnick(self): - """Dummy method to prevent deletion.""" - pass diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py deleted file mode 100644 index a9243d1..0000000 --- a/sleekxmpp/stanza/nick.py +++ /dev/null @@ -1,89 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Message, Presence -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class Nick(ElementBase): - - """ - XEP-0172: User Nickname allows the addition of a <nick> element - in several stanza types, including <message> and <presence> stanzas. - - The nickname contained in a <nick> should be the global, friendly or - informal name chosen by the owner of a bare JID. The <nick> element - may be included when establishing communications with new entities, - such as normal XMPP users or MUC services. - - The nickname contained in a <nick> element will not necessarily be - the same as the nickname used in a MUC. - - Example stanzas: - <message to="user@example.com"> - <nick xmlns="http://jabber.org/nick/nick">The User</nick> - <body>...</body> - </message> - - <presence to="otheruser@example.com" type="subscribe"> - <nick xmlns="http://jabber.org/nick/nick">The User</nick> - </presence> - - Stanza Interface: - nick -- A global, friendly or informal name chosen by a user. - - Methods: - setup -- Overrides ElementBase.setup. - get_nick -- Return the nickname in the <nick> element. - set_nick -- Add a <nick> element with the given nickname. - del_nick -- Remove the <nick> element. - """ - - namespace = 'http://jabber.org/nick/nick' - name = 'nick' - plugin_attrib = name - interfaces = set(('nick',)) - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides StanzaBase.setup. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setNick = self.set_nick - self.getNick = self.get_nick - self.delNick = self.del_nick - - return ElementBase.setup(self, xml) - - def set_nick(self, nick): - """ - Add a <nick> element with the given nickname. - - Arguments: - nick -- A human readable, informal name. - """ - self.xml.text = nick - - def get_nick(self): - """Return the nickname in the <nick> element.""" - return self.xml.text - - def del_nick(self): - """Remove the <nick> element.""" - if self.parent is not None: - self.parent().xml.remove(self.xml) - - -register_stanza_plugin(Message, Nick) -register_stanza_plugin(Presence, Nick) diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py deleted file mode 100644 index 7dcd8f9..0000000 --- a/sleekxmpp/stanza/presence.py +++ /dev/null @@ -1,186 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.stanza.rootstanza import RootStanza -from sleekxmpp.xmlstream import StanzaBase, ET - - -class Presence(RootStanza): - - """ - XMPP's <presence> stanza allows entities to know the status of other - clients and components. Since it is currently the only multi-cast - stanza in XMPP, many extensions add more information to <presence> - stanzas to broadcast to every entry in the roster, such as - capabilities, music choices, or locations (XEP-0115: Entity Capabilities - and XEP-0163: Personal Eventing Protocol). - - Since <presence> stanzas are broadcast when an XMPP entity changes - its status, the bulk of the traffic in an XMPP network will be from - <presence> stanzas. Therefore, do not include more information than - necessary in a status message or within a <presence> stanza in order - to help keep the network running smoothly. - - Example <presence> stanzas: - <presence /> - - <presence from="user@example.com"> - <show>away</show> - <status>Getting lunch.</status> - <priority>5</priority> - </presence> - - <presence type="unavailable" /> - - <presence to="user@otherhost.com" type="subscribe" /> - - Stanza Interface: - priority -- A value used by servers to determine message routing. - show -- The type of status, such as away or available for chat. - status -- Custom, human readable status message. - - Attributes: - types -- One of: available, unavailable, error, probe, - subscribe, subscribed, unsubscribe, - and unsubscribed. - showtypes -- One of: away, chat, dnd, and xa. - - Methods: - setup -- Overrides StanzaBase.setup - reply -- Overrides StanzaBase.reply - set_show -- Set the value of the <show> element. - get_type -- Get the value of the type attribute or <show> element. - set_type -- Set the value of the type attribute or <show> element. - get_priority -- Get the value of the <priority> element. - set_priority -- Set the value of the <priority> element. - """ - - namespace = 'jabber:client' - name = 'presence' - interfaces = set(('type', 'to', 'from', 'id', 'show', - 'status', 'priority')) - sub_interfaces = set(('show', 'status', 'priority')) - plugin_attrib = name - - types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', - 'subscribed', 'unsubscribe', 'unsubscribed')) - showtypes = set(('dnd', 'chat', 'xa', 'away')) - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setShow = self.set_show - self.getType = self.get_type - self.setType = self.set_type - self.delType = self.get_type - self.getPriority = self.get_priority - self.setPriority = self.set_priority - - return StanzaBase.setup(self, xml) - - def exception(self, e): - """ - Override exception passback for presence. - """ - pass - - def set_show(self, show): - """ - Set the value of the <show> element. - - Arguments: - show -- Must be one of: away, chat, dnd, or xa. - """ - if show is None: - self._del_sub('show') - elif show in self.showtypes: - self._set_sub_text('show', text=show) - return self - - def get_type(self): - """ - Return the value of the <presence> stanza's type attribute, or - the value of the <show> element. - """ - out = self._get_attr('type') - if not out: - out = self['show'] - if not out or out is None: - out = 'available' - return out - - def set_type(self, value): - """ - Set the type attribute's value, and the <show> element - if applicable. - - Arguments: - value -- Must be in either self.types or self.showtypes. - """ - if value in self.types: - self['show'] = None - if value == 'available': - value = '' - self._set_attr('type', value) - elif value in self.showtypes: - self['show'] = value - return self - - def del_type(self): - """ - Remove both the type attribute and the <show> element. - """ - self._del_attr('type') - self._del_sub('show') - - def set_priority(self, value): - """ - Set the entity's priority value. Some server use priority to - determine message routing behavior. - - Bot clients should typically use a priority of 0 if the same - JID is used elsewhere by a human-interacting client. - - Arguments: - value -- An integer value greater than or equal to 0. - """ - self._set_sub_text('priority', text=str(value)) - - def get_priority(self): - """ - Return the value of the <presence> element as an integer. - """ - p = self._get_sub_text('priority') - if not p: - p = 0 - try: - return int(p) - except ValueError: - # The priority is not a number: we consider it 0 as a default - return 0 - - def reply(self): - """ - Set the appropriate presence reply type. - - Overrides StanzaBase.reply. - """ - if self['type'] == 'unsubscribe': - self['type'] = 'unsubscribed' - elif self['type'] == 'subscribe': - self['type'] = 'subscribed' - return StanzaBase.reply(self) diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py deleted file mode 100644 index 6975c72..0000000 --- a/sleekxmpp/stanza/rootstanza.py +++ /dev/null @@ -1,69 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import traceback -import sys - -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.stanza import Error -from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin - - -log = logging.getLogger(__name__) - - -class RootStanza(StanzaBase): - - """ - A top-level XMPP stanza in an XMLStream. - - The RootStanza class provides a more XMPP specific exception - handler than provided by the generic StanzaBase class. - - Methods: - exception -- Overrides StanzaBase.exception - """ - - def exception(self, e): - """ - Create and send an error reply. - - Typically called when an event handler raises an exception. - The error's type and text content are based on the exception - object's type and content. - - Overrides StanzaBase.exception. - - Arguments: - e -- Exception object - """ - self.reply() - if isinstance(e, XMPPError): - # We raised this deliberately - self['error']['condition'] = e.condition - self['error']['text'] = e.text - if e.extension is not None: - # Extended error tag - extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), - e.extension_args) - self['error'].append(extxml) - self['error']['type'] = e.etype - else: - # We probably didn't raise this on purpose, so send a traceback - self['error']['condition'] = 'undefined-condition' - if sys.version_info < (3, 0): - self['error']['text'] = "SleekXMPP got into trouble." - else: - self['error']['text'] = traceback.format_tb(e.__traceback__) - log.exception('Error handling {%s}%s stanza' % - (self.namespace, self.name)) - self.send() - - -register_stanza_plugin(RootStanza, Error) diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py deleted file mode 100644 index 8f154a2..0000000 --- a/sleekxmpp/stanza/roster.py +++ /dev/null @@ -1,125 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Iq -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin - - -class Roster(ElementBase): - - """ - Example roster stanzas: - <iq type="set"> - <query xmlns="jabber:iq:roster"> - <item jid="user@example.com" subscription="both" name="User"> - <group>Friends</group> - </item> - </query> - </iq> - - Stanza Inteface: - items -- A dictionary of roster entries contained - in the stanza. - - Methods: - get_items -- Return a dictionary of roster entries. - set_items -- Add <item> elements. - del_items -- Remove all <item> elements. - """ - - namespace = 'jabber:iq:roster' - name = 'query' - plugin_attrib = 'roster' - interfaces = set(('items',)) - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides StanzaBase.setup. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setItems = self.set_items - self.getItems = self.get_items - self.delItems = self.del_items - - return ElementBase.setup(self, xml) - - def set_items(self, items): - """ - Set the roster entries in the <roster> stanza. - - Uses a dictionary using JIDs as keys, where each entry is itself - a dictionary that contains: - name -- An alias or nickname for the JID. - subscription -- The subscription type. Can be one of 'to', - 'from', 'both', 'none', or 'remove'. - groups -- A list of group names to which the JID - has been assigned. - - Arguments: - items -- A dictionary of roster entries. - """ - self.del_items() - for jid in items: - ijid = str(jid) - item = ET.Element('{jabber:iq:roster}item', {'jid': ijid}) - if 'subscription' in items[jid]: - item.attrib['subscription'] = items[jid]['subscription'] - if 'name' in items[jid]: - name = items[jid]['name'] - if name is not None: - item.attrib['name'] = name - if 'groups' in items[jid]: - for group in items[jid]['groups']: - groupxml = ET.Element('{jabber:iq:roster}group') - groupxml.text = group - item.append(groupxml) - self.xml.append(item) - return self - - def get_items(self): - """ - Return a dictionary of roster entries. - - Each item is keyed using its JID, and contains: - name -- An assigned alias or nickname for the JID. - subscription -- The subscription type. Can be one of 'to', - 'from', 'both', 'none', or 'remove'. - groups -- A list of group names to which the JID has - been assigned. - """ - items = {} - itemsxml = self.xml.findall('{jabber:iq:roster}item') - if itemsxml is not None: - for itemxml in itemsxml: - item = {} - item['name'] = itemxml.get('name', '') - item['subscription'] = itemxml.get('subscription', '') - item['groups'] = [] - groupsxml = itemxml.findall('{jabber:iq:roster}group') - if groupsxml is not None: - for groupxml in groupsxml: - item['groups'].append(groupxml.text) - items[itemxml.get('jid')] = item - return items - - def del_items(self): - """ - Remove all <item> elements from the roster stanza. - """ - for child in self.xml.getchildren(): - self.xml.remove(child) - - -register_stanza_plugin(Iq, Roster) diff --git a/sleekxmpp/test/__init__.py b/sleekxmpp/test/__init__.py deleted file mode 100644 index 54d4dc5..0000000 --- a/sleekxmpp/test/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.test.mocksocket import TestSocket -from sleekxmpp.test.livesocket import TestLiveSocket -from sleekxmpp.test.sleektest import * diff --git a/sleekxmpp/test/livesocket.py b/sleekxmpp/test/livesocket.py deleted file mode 100644 index 5e8c547..0000000 --- a/sleekxmpp/test/livesocket.py +++ /dev/null @@ -1,145 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import socket -try: - import queue -except ImportError: - import Queue as queue - - -class TestLiveSocket(object): - - """ - A live test socket that reads and writes to queues in - addition to an actual networking socket. - - Methods: - next_sent -- Return the next sent stanza. - next_recv -- Return the next received stanza. - recv_data -- Dummy method to have same interface as TestSocket. - recv -- Read the next stanza from the socket. - send -- Write a stanza to the socket. - makefile -- Dummy call, returns self. - read -- Read the next stanza from the socket. - """ - - def __init__(self, *args, **kwargs): - """ - Create a new, live test socket. - - Arguments: - Same as arguments for socket.socket - """ - self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.recv_buffer = [] - self.recv_queue = queue.Queue() - self.send_queue = queue.Queue() - self.is_live = True - - def __getattr__(self, name): - """ - Return attribute values of internal, live socket. - - Arguments: - name -- Name of the attribute requested. - """ - - return getattr(self.socket, name) - - # ------------------------------------------------------------------ - # Testing Interface - - def next_sent(self, timeout=None): - """ - Get the next stanza that has been sent. - - Arguments: - timeout -- Optional timeout for waiting for a new value. - """ - args = {'block': False} - if timeout is not None: - args = {'block': True, 'timeout': timeout} - try: - return self.send_queue.get(**args) - except: - return None - - def next_recv(self, timeout=None): - """ - Get the next stanza that has been received. - - Arguments: - timeout -- Optional timeout for waiting for a new value. - """ - args = {'block': False} - if timeout is not None: - args = {'block': True, 'timeout': timeout} - try: - if self.recv_buffer: - return self.recv_buffer.pop(0) - else: - return self.recv_queue.get(**args) - except: - return None - - def recv_data(self, data): - """ - Add data to a receive buffer for cases when more than a single stanza - was received. - """ - self.recv_buffer.append(data) - - # ------------------------------------------------------------------ - # Socket Interface - - def recv(self, *args, **kwargs): - """ - Read data from the socket. - - Store a copy in the receive queue. - - Arguments: - Placeholders. Same as for socket.recv. - """ - data = self.socket.recv(*args, **kwargs) - self.recv_queue.put(data) - return data - - def send(self, data): - """ - Send data on the socket. - - Store a copy in the send queue. - - Arguments: - data -- String value to write. - """ - self.send_queue.put(data) - self.socket.send(data) - - # ------------------------------------------------------------------ - # File Socket - - def makefile(self, *args, **kwargs): - """ - File socket version to use with ElementTree. - - Arguments: - Placeholders, same as socket.makefile() - """ - return self - - def read(self, *args, **kwargs): - """ - Implement the file socket read interface. - - Arguments: - Placeholders, same as socket.recv() - """ - return self.recv(*args, **kwargs) diff --git a/sleekxmpp/test/mocksocket.py b/sleekxmpp/test/mocksocket.py deleted file mode 100644 index e3ddd70..0000000 --- a/sleekxmpp/test/mocksocket.py +++ /dev/null @@ -1,140 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import socket -try: - import queue -except ImportError: - import Queue as queue - - -class TestSocket(object): - - """ - A dummy socket that reads and writes to queues instead - of an actual networking socket. - - Methods: - next_sent -- Return the next sent stanza. - recv_data -- Make a stanza available to read next. - recv -- Read the next stanza from the socket. - send -- Write a stanza to the socket. - makefile -- Dummy call, returns self. - read -- Read the next stanza from the socket. - """ - - def __init__(self, *args, **kwargs): - """ - Create a new test socket. - - Arguments: - Same as arguments for socket.socket - """ - self.socket = socket.socket(*args, **kwargs) - self.recv_queue = queue.Queue() - self.send_queue = queue.Queue() - self.is_live = False - - def __getattr__(self, name): - """ - Return attribute values of internal, dummy socket. - - Some attributes and methods are disabled to prevent the - socket from connecting to the network. - - Arguments: - name -- Name of the attribute requested. - """ - - def dummy(*args): - """Method to do nothing and prevent actual socket connections.""" - return None - - overrides = {'connect': dummy, - 'close': dummy, - 'shutdown': dummy} - - return overrides.get(name, getattr(self.socket, name)) - - # ------------------------------------------------------------------ - # Testing Interface - - def next_sent(self, timeout=None): - """ - Get the next stanza that has been 'sent'. - - Arguments: - timeout -- Optional timeout for waiting for a new value. - """ - args = {'block': False} - if timeout is not None: - args = {'block': True, 'timeout': timeout} - try: - return self.send_queue.get(**args) - except: - return None - - def recv_data(self, data): - """ - Add data to the receiving queue. - - Arguments: - data -- String data to 'write' to the socket to be received - by the XMPP client. - """ - self.recv_queue.put(data) - - # ------------------------------------------------------------------ - # Socket Interface - - def recv(self, *args, **kwargs): - """ - Read a value from the received queue. - - Arguments: - Placeholders. Same as for socket.Socket.recv. - """ - return self.read(block=True) - - def send(self, data): - """ - Send data by placing it in the send queue. - - Arguments: - data -- String value to write. - """ - self.send_queue.put(data) - - # ------------------------------------------------------------------ - # File Socket - - def makefile(self, *args, **kwargs): - """ - File socket version to use with ElementTree. - - Arguments: - Placeholders, same as socket.Socket.makefile() - """ - return self - - def read(self, block=True, timeout=None, **kwargs): - """ - Implement the file socket interface. - - Arguments: - block -- Indicate if the read should block until a - value is ready. - timeout -- Time in seconds a block should last before - returning None. - """ - if timeout is not None: - block = True - try: - return self.recv_queue.get(block, timeout) - except: - return None diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py deleted file mode 100644 index f8b4b54..0000000 --- a/sleekxmpp/test/sleektest.py +++ /dev/null @@ -1,628 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import unittest - -import sleekxmpp -from sleekxmpp import ClientXMPP, ComponentXMPP -from sleekxmpp.stanza import Message, Iq, Presence -from sleekxmpp.test import TestSocket, TestLiveSocket -from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin -from sleekxmpp.xmlstream.tostring import tostring - - -class SleekTest(unittest.TestCase): - - """ - A SleekXMPP specific TestCase class that provides - methods for comparing message, iq, and presence stanzas. - - Methods: - Message -- Create a Message stanza object. - Iq -- Create an Iq stanza object. - Presence -- Create a Presence stanza object. - check_jid -- Check a JID and its component parts. - check -- Compare a stanza against an XML string. - stream_start -- Initialize a dummy XMPP client. - stream_close -- Disconnect the XMPP client. - make_header -- Create a stream header. - send_header -- Check that the given header has been sent. - send_feature -- Send a raw XML element. - send -- Check that the XMPP client sent the given - generic stanza. - recv -- Queue data for XMPP client to receive, or - verify the data that was received from a - live connection. - recv_header -- Check that a given stream header - was received. - recv_feature -- Check that a given, raw XML element - was recveived. - fix_namespaces -- Add top-level namespace to an XML object. - compare -- Compare XML objects against each other. - """ - - def runTest(self): - pass - - def parse_xml(self, xml_string): - try: - xml = ET.fromstring(xml_string) - return xml - except SyntaxError as e: - if 'unbound' in e.msg: - known_prefixes = { - 'stream': 'http://etherx.jabber.org/streams'} - - prefix = xml_string.split('<')[1].split(':')[0] - if prefix in known_prefixes: - xml_string = '<fixns xmlns:%s="%s">%s</fixns>' % ( - prefix, - known_prefixes[prefix], - xml_string) - xml = self.parse_xml(xml_string) - xml = xml.getchildren()[0] - return xml - - # ------------------------------------------------------------------ - # Shortcut methods for creating stanza objects - - def Message(self, *args, **kwargs): - """ - Create a Message stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Message's values. - """ - return Message(None, *args, **kwargs) - - def Iq(self, *args, **kwargs): - """ - Create an Iq stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Iq's values. - """ - return Iq(None, *args, **kwargs) - - def Presence(self, *args, **kwargs): - """ - Create a Presence stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Iq's values. - """ - return Presence(None, *args, **kwargs) - - def check_jid(self, jid, user=None, domain=None, resource=None, - bare=None, full=None, string=None): - """ - Verify the components of a JID. - - Arguments: - jid -- The JID object to test. - user -- Optional. The user name portion of the JID. - domain -- Optional. The domain name portion of the JID. - resource -- Optional. The resource portion of the JID. - bare -- Optional. The bare JID. - full -- Optional. The full JID. - string -- Optional. The string version of the JID. - """ - if user is not None: - self.assertEqual(jid.user, user, - "User does not match: %s" % jid.user) - if domain is not None: - self.assertEqual(jid.domain, domain, - "Domain does not match: %s" % jid.domain) - if resource is not None: - self.assertEqual(jid.resource, resource, - "Resource does not match: %s" % jid.resource) - if bare is not None: - self.assertEqual(jid.bare, bare, - "Bare JID does not match: %s" % jid.bare) - if full is not None: - self.assertEqual(jid.full, full, - "Full JID does not match: %s" % jid.full) - if string is not None: - self.assertEqual(str(jid), string, - "String does not match: %s" % str(jid)) - - # ------------------------------------------------------------------ - # Methods for comparing stanza objects to XML strings - - def check(self, stanza, xml_string, - defaults=None, use_values=True): - """ - Create and compare several stanza objects to a correct XML string. - - If use_values is False, test using getStanzaValues() and - setStanzaValues() will not be used. - - Some stanzas provide default values for some interfaces, but - these defaults can be problematic for testing since they can easily - be forgotten when supplying the XML string. A list of interfaces that - use defaults may be provided and the generated stanzas will use the - default values for those interfaces if needed. - - However, correcting the supplied XML is not possible for interfaces - that add or remove XML elements. Only interfaces that map to XML - attributes may be set using the defaults parameter. The supplied XML - must take into account any extra elements that are included by default. - - Arguments: - stanza -- The stanza object to test. - xml_string -- A string version of the correct XML expected. - defaults -- A list of stanza interfaces that have default - values. These interfaces will be set to their - defaults for the given and generated stanzas to - prevent unexpected test failures. - use_values -- Indicates if testing using getStanzaValues() and - setStanzaValues() should be used. Defaults to - True. - """ - stanza_class = stanza.__class__ - xml = self.parse_xml(xml_string) - - # Ensure that top level namespaces are used, even if they - # were not provided. - self.fix_namespaces(stanza.xml, 'jabber:client') - self.fix_namespaces(xml, 'jabber:client') - - stanza2 = stanza_class(xml=xml) - - if use_values: - # Using getStanzaValues() and setStanzaValues() will add - # XML for any interface that has a default value. We need - # to set those defaults on the existing stanzas and XML - # so that they will compare correctly. - default_stanza = stanza_class() - if defaults is None: - known_defaults = { - Message: ['type'], - Presence: ['priority'] - } - defaults = known_defaults.get(stanza_class, []) - for interface in defaults: - stanza[interface] = stanza[interface] - stanza2[interface] = stanza2[interface] - # Can really only automatically add defaults for top - # level attribute values. Anything else must be accounted - # for in the provided XML string. - if interface not in xml.attrib: - if interface in default_stanza.xml.attrib: - value = default_stanza.xml.attrib[interface] - xml.attrib[interface] = value - - values = stanza2.getStanzaValues() - stanza3 = stanza_class() - stanza3.setStanzaValues(values) - - debug = "Three methods for creating stanzas do not match.\n" - debug += "Given XML:\n%s\n" % tostring(xml) - debug += "Given stanza:\n%s\n" % tostring(stanza.xml) - debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml) - debug += "Second generated stanza:\n%s\n" % tostring(stanza3.xml) - result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml) - else: - debug = "Two methods for creating stanzas do not match.\n" - debug += "Given XML:\n%s\n" % tostring(xml) - debug += "Given stanza:\n%s\n" % tostring(stanza.xml) - debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml) - result = self.compare(xml, stanza.xml, stanza2.xml) - - self.failUnless(result, debug) - - # ------------------------------------------------------------------ - # Methods for simulating stanza streams. - - def stream_start(self, mode='client', skip=True, header=None, - socket='mock', jid='tester@localhost', - password='test', server='localhost', - port=5222): - """ - Initialize an XMPP client or component using a dummy XML stream. - - Arguments: - mode -- Either 'client' or 'component'. Defaults to 'client'. - skip -- Indicates if the first item in the sent queue (the - stream header) should be removed. Tests that wish - to test initializing the stream should set this to - False. Otherwise, the default of True should be used. - socket -- Either 'mock' or 'live' to indicate if the socket - should be a dummy, mock socket or a live, functioning - socket. Defaults to 'mock'. - jid -- The JID to use for the connection. - Defaults to 'tester@localhost'. - password -- The password to use for the connection. - Defaults to 'test'. - server -- The name of the XMPP server. Defaults to 'localhost'. - port -- The port to use when connecting to the server. - Defaults to 5222. - """ - if mode == 'client': - self.xmpp = ClientXMPP(jid, password) - elif mode == 'component': - self.xmpp = ComponentXMPP(jid, password, - server, port) - else: - raise ValueError("Unknown XMPP connection mode.") - - if socket == 'mock': - self.xmpp.set_socket(TestSocket()) - - # Simulate connecting for mock sockets. - self.xmpp.auto_reconnect = False - self.xmpp.is_client = True - self.xmpp.state._set_state('connected') - - # Must have the stream header ready for xmpp.process() to work. - if not header: - header = self.xmpp.stream_header - self.xmpp.socket.recv_data(header) - elif socket == 'live': - self.xmpp.socket_class = TestLiveSocket - self.xmpp.connect() - else: - raise ValueError("Unknown socket type.") - - self.xmpp.register_plugins() - self.xmpp.process(threaded=True) - if skip: - # Clear startup stanzas - self.xmpp.socket.next_sent(timeout=1) - if mode == 'component': - self.xmpp.socket.next_sent(timeout=1) - - def make_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=True): - """ - Create a stream header to be received by the test XMPP agent. - - The header must be saved and passed to stream_start. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - """ - header = '<stream:stream %s>' - parts = [] - if xml_header: - header = '<?xml version="1.0"?>' + header - if sto: - parts.append('to="%s"' % sto) - if sfrom: - parts.append('from="%s"' % sfrom) - if sid: - parts.append('id="%s"' % sid) - parts.append('version="%s"' % version) - parts.append('xmlns:stream="%s"' % stream_ns) - parts.append('xmlns="%s"' % default_ns) - return header % ' '.join(parts) - - def recv(self, data, stanza_class=StanzaBase, defaults=[], - use_values=True, timeout=1): - """ - Pass data to the dummy XMPP client as if it came from an XMPP server. - - If using a live connection, verify what the server has sent. - - Arguments: - data -- String stanza XML to be received and processed by - the XMPP client or component. - stanza_class -- The stanza object class for verifying data received - by a live connection. Defaults to StanzaBase. - defaults -- A list of stanza interfaces with default values that - may interfere with comparisons. - use_values -- Indicates if stanza comparisons should test using - getStanzaValues() and setStanzaValues(). - Defaults to True. - timeout -- Time to wait in seconds for data to be received by - a live connection. - """ - if self.xmpp.socket.is_live: - # we are working with a live connection, so we should - # verify what has been received instead of simulating - # receiving data. - recv_data = self.xmpp.socket.next_recv(timeout) - if recv_data is None: - return False - stanza = stanza_class(xml=self.parse_xml(recv_data)) - return self.check(stanza_class, stanza, data, - defaults=defaults, - use_values=use_values) - else: - # place the data in the dummy socket receiving queue. - data = str(data) - self.xmpp.socket.recv_data(data) - - def recv_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=False, - timeout=1): - """ - Check that a given stream header was received. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. Set to None to ignore. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - timeout -- Length of time to wait in seconds for a - response. - """ - header = self.make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - version=version, - xml_header=xml_header) - recv_header = self.xmpp.socket.next_recv(timeout) - if recv_header is None: - raise ValueError("Socket did not return data.") - - # Apply closing elements so that we can construct - # XML objects for comparison. - header2 = header + '</stream:stream>' - recv_header2 = recv_header + '</stream:stream>' - - xml = self.parse_xml(header2) - recv_xml = self.parse_xml(recv_header2) - - if sid is None: - # Ignore the id sent by the server since - # we can't know in advance what it will be. - if 'id' in recv_xml.attrib: - del recv_xml.attrib['id'] - - # Ignore the xml:lang attribute for now. - if 'xml:lang' in recv_xml.attrib: - del recv_xml.attrib['xml:lang'] - xml_ns = 'http://www.w3.org/XML/1998/namespace' - if '{%s}lang' % xml_ns in recv_xml.attrib: - del recv_xml.attrib['{%s}lang' % xml_ns] - - if recv_xml.getchildren: - # We received more than just the header - for xml in recv_xml.getchildren(): - self.xmpp.socket.recv_data(tostring(xml)) - - attrib = recv_xml.attrib - recv_xml.clear() - recv_xml.attrib = attrib - - self.failUnless( - self.compare(xml, recv_xml), - "Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % ( - '%s %s' % (xml.tag, xml.attrib), - '%s %s' % (recv_xml.tag, recv_xml.attrib))) - - def recv_feature(self, data, use_values=True, timeout=1): - """ - """ - if self.xmpp.socket.is_live: - # we are working with a live connection, so we should - # verify what has been received instead of simulating - # receiving data. - recv_data = self.xmpp.socket.next_recv(timeout) - if recv_data is None: - return False - xml = self.parse_xml(data) - recv_xml = self.parse_xml(recv_data) - self.failUnless(self.compare(xml, recv_xml), - "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( - tostring(xml), tostring(recv_xml))) - else: - # place the data in the dummy socket receiving queue. - data = str(data) - self.xmpp.socket.recv_data(data) - - def send_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=False, - timeout=1): - """ - Check that a given stream header was sent. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - timeout -- Length of time to wait in seconds for a - response. - """ - header = self.make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - version=version, - xml_header=xml_header) - sent_header = self.xmpp.socket.next_sent(timeout) - if sent_header is None: - raise ValueError("Socket did not return data.") - - # Apply closing elements so that we can construct - # XML objects for comparison. - header2 = header + '</stream:stream>' - sent_header2 = sent_header + b'</stream:stream>' - - xml = self.parse_xml(header2) - sent_xml = self.parse_xml(sent_header2) - - self.failUnless( - self.compare(xml, sent_xml), - "Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % ( - header, sent_header)) - - def send_feature(self, data, use_values=True, timeout=1): - """ - """ - sent_data = self.xmpp.socket.next_sent(timeout) - if sent_data is None: - return False - xml = self.parse_xml(data) - sent_xml = self.parse_xml(sent_data) - self.failUnless(self.compare(xml, sent_xml), - "Features do not match.\nDesired:\n%s\nSent:\n%s" % ( - tostring(xml), tostring(sent_xml))) - - def send(self, data, defaults=None, - use_values=True, timeout=.1): - """ - Check that the XMPP client sent the given stanza XML. - - Extracts the next sent stanza and compares it with the given - XML using check. - - Arguments: - stanza_class -- The class of the sent stanza object. - data -- The XML string of the expected Message stanza, - or an equivalent stanza object. - use_values -- Modifies the type of tests used by check_message. - defaults -- A list of stanza interfaces that have defaults - values which may interfere with comparisons. - timeout -- Time in seconds to wait for a stanza before - failing the check. - """ - if isinstance(data, str): - xml = self.parse_xml(data) - self.fix_namespaces(xml, 'jabber:client') - data = self.xmpp._build_stanza(xml, 'jabber:client') - sent = self.xmpp.socket.next_sent(timeout) - self.check(data, sent, - defaults=defaults, - use_values=use_values) - - def stream_close(self): - """ - Disconnect the dummy XMPP client. - - Can be safely called even if stream_start has not been called. - - Must be placed in the tearDown method of a test class to ensure - that the XMPP client is disconnected after an error. - """ - if hasattr(self, 'xmpp') and self.xmpp is not None: - self.xmpp.socket.recv_data(self.xmpp.stream_footer) - self.xmpp.disconnect() - - # ------------------------------------------------------------------ - # XML Comparison and Cleanup - - def fix_namespaces(self, xml, ns): - """ - Assign a namespace to an element and any children that - don't have a namespace. - - Arguments: - xml -- The XML object to fix. - ns -- The namespace to add to the XML object. - """ - if xml.tag.startswith('{'): - return - xml.tag = '{%s}%s' % (ns, xml.tag) - for child in xml.getchildren(): - self.fix_namespaces(child, ns) - - def compare(self, xml, *other): - """ - Compare XML objects. - - Arguments: - xml -- The XML object to compare against. - *other -- The list of XML objects to compare. - """ - if not other: - return False - - # Compare multiple objects - if len(other) > 1: - for xml2 in other: - if not self.compare(xml, xml2): - return False - return True - - other = other[0] - - # Step 1: Check tags - if xml.tag != other.tag: - return False - - # Step 2: Check attributes - if xml.attrib != other.attrib: - return False - - # Step 3: Check text - if xml.text is None: - xml.text = "" - if other.text is None: - other.text = "" - xml.text = xml.text.strip() - other.text = other.text.strip() - - if xml.text != other.text: - return False - - # Step 4: Check children count - if len(xml.getchildren()) != len(other.getchildren()): - return False - - # Step 5: Recursively check children - for child in xml: - child2s = other.findall("%s" % child.tag) - if child2s is None: - return False - for child2 in child2s: - if self.compare(child, child2): - break - else: - return False - - # Step 6: Recursively check children the other way. - for child in other: - child2s = xml.findall("%s" % child.tag) - if child2s is None: - return False - for child2 in child2s: - if self.compare(child, child2): - break - else: - return False - - # Everything matches - return True diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/sleekxmpp/thirdparty/__init__.py +++ /dev/null diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py deleted file mode 100644 index 8a7324b..0000000 --- a/sleekxmpp/thirdparty/statemachine.py +++ /dev/null @@ -1,287 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -import threading -import time -import logging - -log = logging.getLogger(__name__) - - -class StateMachine(object): - - def __init__(self, states=[]): - self.lock = threading.Lock() - self.notifier = threading.Event() - self.__states = [] - self.addStates(states) - self.__default_state = self.__states[0] - self.__current_state = self.__default_state - - def addStates(self, states): - self.lock.acquire() - try: - for state in states: - if state in self.__states: - raise IndexError("The state '%s' is already in the StateMachine." % state) - self.__states.append(state) - finally: self.lock.release() - - - def transition(self, from_state, to_state, wait=0.0, func=None, args=[], kwargs={}): - ''' - Transition from the given `from_state` to the given `to_state`. - This method will return `True` if the state machine is now in `to_state`. It - will return `False` if a timeout occurred the transition did not occur. - If `wait` is 0 (the default,) this method returns immediately if the state machine - is not in `from_state`. - - If you want the thread to block and transition once the state machine to enters - `from_state`, set `wait` to a non-negative value. Note there is no 'block - indefinitely' flag since this leads to deadlock. If you want to wait indefinitely, - choose a reasonable value for `wait` (e.g. 20 seconds) and do so in a while loop like so: - - :: - - while not thread_should_exit and not state_machine.transition('disconnected', 'connecting', wait=20 ): - pass # timeout will occur every 20s unless transition occurs - if thread_should_exit: return - # perform actions here after successful transition - - This allows the thread to be responsive by setting `thread_should_exit=True`. - - The optional `func` argument allows the user to pass a callable operation which occurs - within the context of the state transition (e.g. while the state machine is locked.) - If `func` returns a True value, the transition will occur. If `func` returns a non- - True value or if an exception is thrown, the transition will not occur. Any thrown - exception is not caught by the state machine and is the caller's responsibility to handle. - If `func` completes normally, this method will return the value returned by `func.` If - values for `args` and `kwargs` are provided, they are expanded and passed like so: - `func( *args, **kwargs )`. - ''' - - return self.transition_any((from_state,), to_state, wait=wait, - func=func, args=args, kwargs=kwargs) - - - def transition_any(self, from_states, to_state, wait=0.0, func=None, args=[], kwargs={}): - ''' - Transition from any of the given `from_states` to the given `to_state`. - ''' - - if not (isinstance(from_states,tuple) or isinstance(from_states,list)): - raise ValueError("from_states should be a list or tuple") - - for state in from_states: - if not state in self.__states: - raise ValueError("StateMachine does not contain from_state %s." % state) - if not to_state in self.__states: - raise ValueError("StateMachine does not contain to_state %s." % to_state) - - start = time.time() - while not self.lock.acquire(False): - time.sleep(.001) - if (start + wait - time.time()) <= 0.0: - log.debug("Could not acquire lock") - return False - - while not self.__current_state in from_states: - # detect timeout: - remainder = start + wait - time.time() - if remainder > 0: - self.notifier.wait(remainder) - else: - log.debug("State was not ready") - self.lock.release() - return False - - try: # lock is acquired; all other threads will return false or wait until notify/timeout - if self.__current_state in from_states: # should always be True due to lock - - # Note that func might throw an exception, but that's OK, it aborts the transition - return_val = func(*args,**kwargs) if func is not None else True - - # some 'false' value returned from func, - # indicating that transition should not occur: - if not return_val: return return_val - - log.debug(' ==== TRANSITION %s -> %s', self.__current_state, to_state) - self._set_state(to_state) - return return_val # some 'true' value returned by func or True if func was None - else: - log.error("StateMachine bug!! The lock should ensure this doesn't happen!") - return False - finally: - self.notifier.set() # notify any waiting threads that the state has changed. - self.notifier.clear() - self.lock.release() - - - def transition_ctx(self, from_state, to_state, wait=0.0): - ''' - Use the state machine as a context manager. The transition occurs on /exit/ from - the `with` context, so long as no exception is thrown. For example: - - :: - - with state_machine.transition_ctx('one','two', wait=5) as locked: - if locked: - # the state machine is currently locked in state 'one', and will - # transition to 'two' when the 'with' statement ends, so long as - # no exception is thrown. - print 'Currently locked in state one: %s' % state_machine['one'] - - else: - # The 'wait' timed out, and no lock has been acquired - print 'Timed out before entering state "one"' - - print 'Since no exception was thrown, we are now in state "two": %s' % state_machine['two'] - - - The other main difference between this method and `transition()` is that the - state machine is locked for the duration of the `with` statement. Normally, - after a `transition()` occurs, the state machine is immediately unlocked and - available to another thread to call `transition()` again. - ''' - - if not from_state in self.__states: - raise ValueError("StateMachine does not contain from_state %s." % from_state) - if not to_state in self.__states: - raise ValueError("StateMachine does not contain to_state %s." % to_state) - - return _StateCtx(self, from_state, to_state, wait) - - - def ensure(self, state, wait=0.0, block_on_transition=False): - ''' - Ensure the state machine is currently in `state`, or wait until it enters `state`. - ''' - return self.ensure_any((state,), wait=wait, block_on_transition=block_on_transition) - - - def ensure_any(self, states, wait=0.0, block_on_transition=False): - ''' - Ensure we are currently in one of the given `states` or wait until - we enter one of those states. - - Note that due to the nature of the function, you cannot guarantee that - the entirety of some operation completes while you remain in a given - state. That would require acquiring and holding a lock, which - would mean no other threads could do the same. (You'd essentially - be serializing all of the threads that are 'ensuring' their tasks - occurred in some state. - ''' - if not (isinstance(states,tuple) or isinstance(states,list)): - raise ValueError('states arg should be a tuple or list') - - for state in states: - if not state in self.__states: - raise ValueError("StateMachine does not contain state '%s'" % state) - - # if we're in the middle of a transition, determine whether we should - # 'fall back' to the 'current' state, or wait for the new state, in order to - # avoid an operation occurring in the wrong state. - # TODO another option would be an ensure_ctx that uses a semaphore to allow - # threads to indicate they want to remain in a particular state. - - # will return immediately if no transition is in process. - if block_on_transition: - # we're not in the middle of a transition; don't hold the lock - if self.lock.acquire(False): self.lock.release() - # wait for the transition to complete - else: self.notifier.wait() - - start = time.time() - while not self.__current_state in states: - # detect timeout: - remainder = start + wait - time.time() - if remainder > 0: self.notifier.wait(remainder) - else: return False - return True - - - def reset(self): - # TODO need to lock before calling this? - self.transition(self.__current_state, self.__default_state) - - - def _set_state(self, state): #unsynchronized, only call internally after lock is acquired - self.__current_state = state - return state - - - def current_state(self): - ''' - Return the current state name. - ''' - return self.__current_state - - - def __getitem__(self, state): - ''' - Non-blocking, non-synchronized test to determine if we are in the given state. - Use `StateMachine.ensure(state)` to wait until the machine enters a certain state. - ''' - return self.__current_state == state - - def __str__(self): - return "".join(("StateMachine(", ','.join(self.__states), "): ", self.__current_state)) - - - -class _StateCtx: - - def __init__(self, state_machine, from_state, to_state, wait): - self.state_machine = state_machine - self.from_state = from_state - self.to_state = to_state - self.wait = wait - self._locked = False - - def __enter__(self): - start = time.time() - while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False): - # detect timeout: - remainder = start + self.wait - time.time() - if remainder > 0: self.state_machine.notifier.wait(remainder) - else: - log.debug('StateMachine timeout while waiting for state: %s', self.from_state) - return False - - self._locked = True # lock has been acquired at this point - self.state_machine.notifier.clear() - log.debug('StateMachine entered context in state: %s', - self.state_machine.current_state()) - return True - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_val is not None: - log.exception("StateMachine exception in context, remaining in state: %s\n%s:%s", - self.state_machine.current_state(), exc_type.__name__, exc_val) - - if self._locked: - if exc_val is None: - log.debug(' ==== TRANSITION %s -> %s', - self.state_machine.current_state(), self.to_state) - self.state_machine._set_state(self.to_state) - - self.state_machine.notifier.set() - self.state_machine.lock.release() - - return False # re-raise any exception - -if __name__ == '__main__': - - def callback(s, s2): - print((1, s.transition('on', 'off', wait=0.0, func=callback, args=[s,s2]))) - print((2, s2.transition('off', 'on', func=callback, args=[s,s2]))) - return True - - s = StateMachine(('off', 'on')) - s2 = StateMachine(('off', 'on')) - print((3, s.transition('off', 'on', wait=0.0, func=callback, args=[s,s2]),)) - print((s.current_state(), s2.current_state())) diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py deleted file mode 100644 index 67b20c5..0000000 --- a/sleekxmpp/xmlstream/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.jid import JID -from sleekxmpp.xmlstream.scheduler import Scheduler -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET -from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin -from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT -from sleekxmpp.xmlstream.xmlstream import RestartStream - -__all__ = ['JID', 'Scheduler', 'StanzaBase', 'ElementBase', - 'ET', 'StateMachine', 'tostring', 'XMLStream', - 'RESPONSE_TIMEOUT', 'RestartStream'] diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py deleted file mode 100644 index 441ff87..0000000 --- a/sleekxmpp/xmlstream/filesocket.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from socket import _fileobject -import socket - - -class FileSocket(_fileobject): - - """ - Create a file object wrapper for a socket to work around - issues present in Python 2.6 when using sockets as file objects. - - The parser for xml.etree.cElementTree requires a file, but we will - be reading from the XMPP connection socket instead. - """ - - def read(self, size=4096): - """Read data from the socket as if it were a file.""" - data = self._sock.recv(size) - if data is not None: - return data - - -class Socket26(socket._socketobject): - - """ - A custom socket implementation that uses our own FileSocket class - to work around issues in Python 2.6 when using sockets as files. - """ - - def makefile(self, mode='r', bufsize=-1): - """makefile([mode[, bufsize]]) -> file object - Return a regular file object corresponding to the socket. The mode - and bufsize arguments are as for the built-in open() function.""" - return FileSocket(self._sock, mode, bufsize) diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py deleted file mode 100644 index 7bcf0b7..0000000 --- a/sleekxmpp/xmlstream/handler/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler.callback import Callback -from sleekxmpp.xmlstream.handler.waiter import Waiter -from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback -from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter - -__all__ = ['Callback', 'Waiter', 'XMLCallback', 'XMLWaiter'] diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py deleted file mode 100644 index 9c704ec..0000000 --- a/sleekxmpp/xmlstream/handler/base.py +++ /dev/null @@ -1,89 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class BaseHandler(object): - - """ - Base class for stream handlers. Stream handlers are matched with - incoming stanzas so that the stanza may be processed in some way. - Stanzas may be matched with multiple handlers. - - Handler execution may take place in two phases. The first is during - the stream processing itself. The second is after stream processing - and during SleekXMPP's main event loop. The prerun method is used - for execution during stream processing, and the run method is used - during the main event loop. - - Attributes: - name -- The name of the handler. - stream -- The stream this handler is assigned to. - - Methods: - match -- Compare a stanza with the handler's matcher. - prerun -- Handler execution during stream processing. - run -- Handler execution during the main event loop. - check_delete -- Indicate if the handler may be removed from use. - """ - - def __init__(self, name, matcher, stream=None): - """ - Create a new stream handler. - - Arguments: - name -- The name of the handler. - matcher -- A matcher object from xmlstream.matcher that will be - used to determine if a stanza should be accepted by - this handler. - stream -- The XMLStream instance the handler should monitor. - """ - self.checkDelete = self.check_delete - - self.name = name - self.stream = stream - self._destroy = False - self._payload = None - self._matcher = matcher - if stream is not None: - stream.registerHandler(self) - - def match(self, xml): - """ - Compare a stanza or XML object with the handler's matcher. - - Arguments - xml -- An XML or stanza object. - """ - return self._matcher.match(xml) - - def prerun(self, payload): - """ - Prepare the handler for execution while the XML stream is being - processed. - - Arguments: - payload -- A stanza object. - """ - self._payload = payload - - def run(self, payload): - """ - Execute the handler after XML stream processing and during the - main event loop. - - Arguments: - payload -- A stanza object. - """ - self._payload = payload - - def check_delete(self): - """ - Check if the handler should be removed from the list of stream - handlers. - """ - return self._destroy diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py deleted file mode 100644 index f0a7285..0000000 --- a/sleekxmpp/xmlstream/handler/callback.py +++ /dev/null @@ -1,84 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler.base import BaseHandler - - -class Callback(BaseHandler): - - """ - The Callback handler will execute a callback function with - matched stanzas. - - The handler may execute the callback either during stream - processing or during the main event loop. - - Callback functions are all executed in the same thread, so be - aware if you are executing functions that will block for extended - periods of time. Typically, you should signal your own events using the - SleekXMPP object's event() method to pass the stanza off to a threaded - event handler for further processing. - - Methods: - prerun -- Overrides BaseHandler.prerun - run -- Overrides BaseHandler.run - """ - - def __init__(self, name, matcher, pointer, thread=False, - once=False, instream=False, stream=None): - """ - Create a new callback handler. - - Arguments: - name -- The name of the handler. - matcher -- A matcher object for matching stanza objects. - pointer -- The function to execute during callback. - thread -- DEPRECATED. Remains only for backwards compatibility. - once -- Indicates if the handler should be used only - once. Defaults to False. - instream -- Indicates if the callback should be executed - during stream processing instead of in the - main event loop. - stream -- The XMLStream instance this handler should monitor. - """ - BaseHandler.__init__(self, name, matcher, stream) - self._pointer = pointer - self._once = once - self._instream = instream - - def prerun(self, payload): - """ - Execute the callback during stream processing, if - the callback was created with instream=True. - - Overrides BaseHandler.prerun - - Arguments: - payload -- The matched stanza object. - """ - BaseHandler.prerun(self, payload) - if self._instream: - self.run(payload, True) - - def run(self, payload, instream=False): - """ - Execute the callback function with the matched stanza payload. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - instream -- Force the handler to execute during - stream processing. Used only by prerun. - Defaults to False. - """ - if not self._instream or instream: - BaseHandler.run(self, payload) - self._pointer(payload) - if self._once: - self._destroy = True diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py deleted file mode 100644 index a4bc354..0000000 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ /dev/null @@ -1,101 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -try: - import queue -except ImportError: - import Queue as queue - -from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT -from sleekxmpp.xmlstream.handler.base import BaseHandler - - -log = logging.getLogger(__name__) - - -class Waiter(BaseHandler): - - """ - The Waiter handler allows an event handler to block - until a particular stanza has been received. The handler - will either be given the matched stanza, or False if the - waiter has timed out. - - Methods: - check_delete -- Overrides BaseHandler.check_delete - prerun -- Overrides BaseHandler.prerun - run -- Overrides BaseHandler.run - wait -- Wait for a stanza to arrive and return it to - an event handler. - """ - - def __init__(self, name, matcher, stream=None): - """ - Create a new Waiter. - - Arguments: - name -- The name of the waiter. - matcher -- A matcher object to detect the desired stanza. - stream -- Optional XMLStream instance to monitor. - """ - BaseHandler.__init__(self, name, matcher, stream=stream) - self._payload = queue.Queue() - - def prerun(self, payload): - """ - Store the matched stanza. - - Overrides BaseHandler.prerun - - Arguments: - payload -- The matched stanza object. - """ - self._payload.put(payload) - - def run(self, payload): - """ - Do not process this handler during the main event loop. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - """ - pass - - def wait(self, timeout=RESPONSE_TIMEOUT): - """ - Block an event handler while waiting for a stanza to arrive. - - Be aware that this will impact performance if called from a - non-threaded event handler. - - Will return either the received stanza, or False if the waiter - timed out. - - Arguments: - timeout -- The number of seconds to wait for the stanza to - arrive. Defaults to the global default timeout - value sleekxmpp.xmlstream.RESPONSE_TIMEOUT. - """ - try: - stanza = self._payload.get(True, timeout) - except queue.Empty: - stanza = False - log.warning("Timed out waiting for %s" % self.name) - self.stream.removeHandler(self.name) - return stanza - - def check_delete(self): - """ - Always remove waiters after use. - - Overrides BaseHandler.check_delete - """ - return True diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py deleted file mode 100644 index 11607ff..0000000 --- a/sleekxmpp/xmlstream/handler/xmlcallback.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler import Callback - - -class XMLCallback(Callback): - - """ - The XMLCallback class is identical to the normal Callback class, - except that XML contents of matched stanzas will be processed instead - of the stanza objects themselves. - - Methods: - run -- Overrides Callback.run - """ - - def run(self, payload, instream=False): - """ - Execute the callback function with the matched stanza's - XML contents, instead of the stanza itself. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - instream -- Force the handler to execute during - stream processing. Used only by prerun. - Defaults to False. - """ - Callback.run(self, payload.xml, instream) diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py deleted file mode 100644 index 5201caf..0000000 --- a/sleekxmpp/xmlstream/handler/xmlwaiter.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler import Waiter - - -class XMLWaiter(Waiter): - - """ - The XMLWaiter class is identical to the normal Waiter class - except that it returns the XML contents of the stanza instead - of the full stanza object itself. - - Methods: - prerun -- Overrides Waiter.prerun - """ - - def prerun(self, payload): - """ - Store the XML contents of the stanza to return to the - waiting event handler. - - Overrides Waiter.prerun - - Arguments: - payload -- The matched stanza object. - """ - Waiter.prerun(self, payload.xml) diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py deleted file mode 100644 index 33d845a..0000000 --- a/sleekxmpp/xmlstream/jid.py +++ /dev/null @@ -1,123 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class JID(object): - """ - A representation of a Jabber ID, or JID. - - Each JID may have three components: a user, a domain, and an optional - resource. For example: user@domain/resource - - When a resource is not used, the JID is called a bare JID. - The JID is a full JID otherwise. - - Attributes: - jid -- Alias for 'full'. - full -- The value of the full JID. - bare -- The value of the bare JID. - user -- The username portion of the JID. - domain -- The domain name portion of the JID. - server -- Alias for 'domain'. - resource -- The resource portion of the JID. - - Methods: - reset -- Use a new JID value. - regenerate -- Recreate the JID from its components. - """ - - def __init__(self, jid): - """Initialize a new JID""" - self.reset(jid) - - def reset(self, jid): - """ - Start fresh from a new JID string. - - Arguments: - jid - The new JID value. - """ - self._full = self._jid = str(jid) - self._domain = None - self._resource = None - self._user = None - self._bare = None - - def __getattr__(self, name): - """ - Handle getting the JID values, using cache if available. - - Arguments: - name -- One of: user, server, domain, resource, - full, or bare. - """ - if name == 'resource': - if self._resource is None and '/' in self._jid: - self._resource = self._jid.split('/', 1)[-1] - return self._resource or "" - elif name == 'user': - if self._user is None: - if '@' in self._jid: - self._user = self._jid.split('@', 1)[0] - else: - self._user = self._user - return self._user or "" - elif name in ('server', 'domain', 'host'): - if self._domain is None: - self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] - return self._domain or "" - elif name == 'full': - return self._jid or "" - elif name == 'bare': - if self._bare is None: - self._bare = self._jid.split('/', 1)[0] - return self._bare or "" - - def __setattr__(self, name, value): - """ - Edit a JID by updating it's individual values, resetting the - generated JID in the end. - - Arguments: - name -- The name of the JID part. One of: user, domain, - server, resource, full, jid, or bare. - value -- The new value for the JID part. - """ - if name in ('resource', 'user', 'domain'): - object.__setattr__(self, "_%s" % name, value) - self.regenerate() - elif name in ('server', 'domain', 'host'): - self.domain = value - elif name in ('full', 'jid'): - self.reset(value) - self.regenerate() - elif name == 'bare': - if '@' in value: - u, d = value.split('@', 1) - object.__setattr__(self, "_user", u) - object.__setattr__(self, "_domain", d) - else: - object.__setattr__(self, "_user", '') - object.__setattr__(self, "_domain", value) - self.regenerate() - else: - object.__setattr__(self, name, value) - - def regenerate(self): - """Generate a new JID based on current values, useful after editing.""" - jid = "" - if self.user: - jid = "%s@" % self.user - jid += self.domain - if self.resource: - jid += "/%s" % self.resource - self.reset(jid) - - def __str__(self): - """Use the full JID as the string value.""" - return self.full diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py deleted file mode 100644 index 1038d1b..0000000 --- a/sleekxmpp/xmlstream/matcher/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.id import MatcherId -from sleekxmpp.xmlstream.matcher.many import MatchMany -from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath -from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask -from sleekxmpp.xmlstream.matcher.xpath import MatchXPath - -__all__ = ['MatcherId', 'MatchMany', 'StanzaPath', - 'MatchXMLMask', 'MatchXPath'] diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py deleted file mode 100644 index 701ab32..0000000 --- a/sleekxmpp/xmlstream/matcher/base.py +++ /dev/null @@ -1,34 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class MatcherBase(object): - - """ - Base class for stanza matchers. Stanza matchers are used to pick - stanzas out of the XML stream and pass them to the appropriate - stream handlers. - """ - - def __init__(self, criteria): - """ - Create a new stanza matcher. - - Arguments: - criteria -- Object to compare some aspect of a stanza - against. - """ - self._criteria = criteria - - def match(self, xml): - """ - Check if a stanza matches the stored criteria. - - Meant to be overridden. - """ - return False diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py deleted file mode 100644 index 0c8ce2d..0000000 --- a/sleekxmpp/xmlstream/matcher/id.py +++ /dev/null @@ -1,32 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatcherId(MatcherBase): - - """ - The ID matcher selects stanzas that have the same stanza 'id' - interface value as the desired ID. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Compare the given stanza's 'id' attribute to the stored - id value. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza to compare against. - """ - return xml['id'] == self._criteria diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py deleted file mode 100644 index f470ec9..0000000 --- a/sleekxmpp/xmlstream/matcher/many.py +++ /dev/null @@ -1,40 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatchMany(MatcherBase): - - """ - The MatchMany matcher may compare a stanza against multiple - criteria. It is essentially an OR relation combining multiple - matchers. - - Each of the criteria must implement a match() method. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Match a stanza against multiple criteria. The match is successful - if one of the criteria matches. - - Each of the criteria must implement a match() method. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza object to compare against. - """ - for m in self._criteria: - if m.match(xml): - return True - return False diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py deleted file mode 100644 index f8ff283..0000000 --- a/sleekxmpp/xmlstream/matcher/stanzapath.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class StanzaPath(MatcherBase): - - """ - The StanzaPath matcher selects stanzas that match a given "stanza path", - which is similar to a normal XPath except that it uses the interfaces and - plugins of the stanza instead of the actual, underlying XML. - - In most cases, the stanza path and XPath should be identical, but be - aware that differences may occur. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, stanza): - """ - Compare a stanza against a "stanza path". A stanza path is similar to - an XPath expression, but uses the stanza's interfaces and plugins - instead of the underlying XML. For most cases, the stanza path and - XPath should be identical, but be aware that differences may occur. - - Overrides MatcherBase.match. - - Arguments: - stanza -- The stanza object to compare against. - """ - return stanza.match(self._criteria) diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py deleted file mode 100644 index 6ebb437..0000000 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ /dev/null @@ -1,159 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from xml.parsers.expat import ExpatError - -from sleekxmpp.xmlstream.stanzabase import ET -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -# Flag indicating if the builtin XPath matcher should be used, which -# uses namespaces, or a custom matcher that ignores namespaces. -# Changing this will affect ALL XMLMask matchers. -IGNORE_NS = False - - -log = logging.getLogger(__name__) - - -class MatchXMLMask(MatcherBase): - - """ - The XMLMask matcher selects stanzas whose XML matches a given - XML pattern, or mask. For example, message stanzas with body elements - could be matched using the mask: - - <message xmlns="jabber:client"><body /></message> - - Use of XMLMask is discouraged, and XPath or StanzaPath should be used - instead. - - The use of namespaces in the mask comparison is controlled by - IGNORE_NS. Setting IGNORE_NS to True will disable namespace based matching - for ALL XMLMask matchers. - - Methods: - match -- Overrides MatcherBase.match. - setDefaultNS -- Set the default namespace for the mask. - """ - - def __init__(self, criteria): - """ - Create a new XMLMask matcher. - - Arguments: - criteria -- Either an XML object or XML string to use as a mask. - """ - MatcherBase.__init__(self, criteria) - if isinstance(criteria, str): - self._criteria = ET.fromstring(self._criteria) - self.default_ns = 'jabber:client' - - def setDefaultNS(self, ns): - """ - Set the default namespace to use during comparisons. - - Arguments: - ns -- The new namespace to use as the default. - """ - self.default_ns = ns - - def match(self, xml): - """ - Compare a stanza object or XML object against the stored XML mask. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza object or XML object to compare against. - """ - if hasattr(xml, 'xml'): - xml = xml.xml - return self._mask_cmp(xml, self._criteria, True) - - def _mask_cmp(self, source, mask, use_ns=False, default_ns='__no_ns__'): - """ - Compare an XML object against an XML mask. - - Arguments: - source -- The XML object to compare against the mask. - mask -- The XML object serving as the mask. - use_ns -- Indicates if namespaces should be respected during - the comparison. - default_ns -- The default namespace to apply to elements that - do not have a specified namespace. - Defaults to "__no_ns__". - """ - use_ns = not IGNORE_NS - - if source is None: - # If the element was not found. May happend during recursive calls. - return False - - # Convert the mask to an XML object if it is a string. - if not hasattr(mask, 'attrib'): - try: - mask = ET.fromstring(mask) - except ExpatError: - log.warning("Expat error: %s\nIn parsing: %s" % ('', mask)) - - if not use_ns: - # Compare the element without using namespaces. - source_tag = source.tag.split('}', 1)[-1] - mask_tag = mask.tag.split('}', 1)[-1] - if source_tag != mask_tag: - return False - else: - # Compare the element using namespaces - mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag) - if source.tag not in [mask.tag, mask_ns_tag]: - return False - - # If the mask includes text, compare it. - if mask.text and source.text != mask.text: - return False - - # Compare attributes. The stanza must include the attributes - # defined by the mask, but may include others. - for name, value in mask.attrib.items(): - if source.attrib.get(name, "__None__") != value: - return False - - # Recursively check subelements. - for subelement in mask: - if use_ns: - if not self._mask_cmp(source.find(subelement.tag), - subelement, use_ns): - return False - else: - if not self._mask_cmp(self._get_child(source, subelement.tag), - subelement, use_ns): - return False - - # Everything matches. - return True - - def _get_child(self, xml, tag): - """ - Return a child element given its tag, ignoring namespace values. - - Returns None if the child was not found. - - Arguments: - xml -- The XML object to search for the given child tag. - tag -- The name of the subelement to find. - """ - tag = tag.split('}')[-1] - try: - children = [c.tag.split('}')[-1] for c in xml.getchildren()] - index = children.index(tag) - except ValueError: - return None - return xml.getchildren()[index] diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py deleted file mode 100644 index 669c9f1..0000000 --- a/sleekxmpp/xmlstream/matcher/xpath.py +++ /dev/null @@ -1,79 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.stanzabase import ET -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -# Flag indicating if the builtin XPath matcher should be used, which -# uses namespaces, or a custom matcher that ignores namespaces. -# Changing this will affect ALL XPath matchers. -IGNORE_NS = False - - -class MatchXPath(MatcherBase): - - """ - The XPath matcher selects stanzas whose XML contents matches a given - XPath expression. - - Note that using this matcher may not produce expected behavior when using - attribute selectors. For Python 2.6 and 3.1, the ElementTree find method - does not support the use of attribute selectors. If you need to support - Python 2.6 or 3.1, it might be more useful to use a StanzaPath matcher. - - If the value of IGNORE_NS is set to true, then XPath expressions will - be matched without using namespaces. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Compare a stanza's XML contents to an XPath expression. - - If the value of IGNORE_NS is set to true, then XPath expressions - will be matched without using namespaces. - - Note that in Python 2.6 and 3.1 the ElementTree find method does - not support attribute selectors in the XPath expression. - - Arguments: - xml -- The stanza object to compare against. - """ - if hasattr(xml, 'xml'): - xml = xml.xml - x = ET.Element('x') - x.append(xml) - - if not IGNORE_NS: - # Use builtin, namespace respecting, XPath matcher. - if x.find(self._criteria) is not None: - return True - return False - else: - # Remove namespaces from the XPath expression. - criteria = [] - for ns_block in self._criteria.split('{'): - criteria.extend(ns_block.split('}')[-1].split('/')) - - # Walk the XPath expression. - xml = x - for tag in criteria: - if not tag: - # Skip empty tag name artifacts from the cleanup phase. - continue - - children = [c.tag.split('}')[-1] for c in xml.getchildren()] - try: - index = children.index(tag) - except ValueError: - return False - xml = xml.getchildren()[index] - return True diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py deleted file mode 100644 index 1435910..0000000 --- a/sleekxmpp/xmlstream/scheduler.py +++ /dev/null @@ -1,207 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import time -import threading -import logging -try: - import queue -except ImportError: - import Queue as queue - - -log = logging.getLogger(__name__) - - -class Task(object): - - """ - A scheduled task that will be executed by the scheduler - after a given time interval has passed. - - Attributes: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - - Methods: - run -- Either queue or execute the callback. - reset -- Reset the task's timer. - """ - - def __init__(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - """ - Create a new task. - - Arguments: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - self.name = name - self.seconds = seconds - self.callback = callback - self.args = args or tuple() - self.kwargs = kwargs or {} - self.repeat = repeat - self.next = time.time() + self.seconds - self.qpointer = qpointer - - def run(self): - """ - Execute the task's callback. - - If an event queue was supplied, place the callback in the queue; - otherwise, execute the callback immediately. - """ - if self.qpointer is not None: - self.qpointer.put(('schedule', self.callback, self.args)) - else: - self.callback(*self.args, **self.kwargs) - self.reset() - return self.repeat - - def reset(self): - """ - Reset the task's timer so that it will repeat. - """ - self.next = time.time() + self.seconds - - -class Scheduler(object): - - """ - A threaded scheduler that allows for updates mid-execution unlike the - scheduler in the standard library. - - http://docs.python.org/library/sched.html#module-sched - - Attributes: - addq -- A queue storing added tasks. - schedule -- A list of tasks in order of execution times. - thread -- If threaded, the thread processing the schedule. - run -- Indicates if the scheduler is running. - parentqueue -- A parent event queue in control of this scheduler. - - Methods: - add -- Add a new task to the schedule. - process -- Process and schedule tasks. - quit -- Stop the scheduler. - """ - - def __init__(self, parentqueue=None, parentstop=None): - """ - Create a new scheduler. - - Arguments: - parentqueue -- A separate event queue controlling this scheduler. - """ - self.addq = queue.Queue() - self.schedule = [] - self.thread = None - self.run = False - self.parentqueue = parentqueue - self.parentstop = parentstop - - def process(self, threaded=True): - """ - Begin accepting and processing scheduled tasks. - - Arguments: - threaded -- Indicates if the scheduler should execute in its own - thread. Defaults to True. - """ - if threaded: - self.thread = threading.Thread(name='sheduler_process', - target=self._process) - self.thread.start() - else: - self._process() - - def _process(self): - """Process scheduled tasks.""" - self.run = True - try: - while self.run and (self.parentstop is None or not self.parentstop.isSet()): - wait = 1 - updated = False - if self.schedule: - wait = self.schedule[0].next - time.time() - try: - if wait <= 0.0: - newtask = self.addq.get(False) - else: - if wait >= 3.0: - wait = 3.0 - newtask = self.addq.get(True, wait) - except queue.Empty: - cleanup = [] - for task in self.schedule: - if time.time() >= task.next: - updated = True - if not task.run(): - cleanup.append(task) - else: - break - for task in cleanup: - x = self.schedule.pop(self.schedule.index(task)) - else: - updated = True - self.schedule.append(newtask) - finally: - if updated: - self.schedule = sorted(self.schedule, - key=lambda task: task.next) - except KeyboardInterrupt: - self.run = False - if self.parentstop is not None: - log.debug("stopping parent") - self.parentstop.set() - except SystemExit: - self.run = False - if self.parentstop is not None: - self.parentstop.set() - log.debug("Quitting Scheduler thread") - if self.parentqueue is not None: - self.parentqueue.put(('quit', None, None)) - - def add(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - """ - Schedule a new task. - - Arguments: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - self.addq.put(Task(name, seconds, callback, args, - kwargs, repeat, qpointer)) - - def quit(self): - """Shutdown the scheduler.""" - self.run = False diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py deleted file mode 100644 index aabd386..0000000 --- a/sleekxmpp/xmlstream/stanzabase.py +++ /dev/null @@ -1,1165 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import copy -import logging -import sys -import weakref -from xml.etree import cElementTree as ET - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.tostring import tostring - - -log = logging.getLogger(__name__) - - -# Used to check if an argument is an XML object. -XML_TYPE = type(ET.Element('xml')) - - -def register_stanza_plugin(stanza, plugin): - """ - Associate a stanza object as a plugin for another stanza. - - Arguments: - stanza -- The class of the parent stanza. - plugin -- The class of the plugin stanza. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map[tag] = plugin - - -# To maintain backwards compatibility for now, preserve the camel case name. -registerStanzaPlugin = register_stanza_plugin - - -class ElementBase(object): - - """ - The core of SleekXMPP's stanza XML manipulation and handling is provided - by ElementBase. ElementBase wraps XML cElementTree objects and enables - access to the XML contents through dictionary syntax, similar in style - to the Ruby XMPP library Blather's stanza implementation. - - Stanzas are defined by their name, namespace, and interfaces. For - example, a simplistic Message stanza could be defined as: - - >>> class Message(ElementBase): - ... name = "message" - ... namespace = "jabber:client" - ... interfaces = set(('to', 'from', 'type', 'body')) - ... sub_interfaces = set(('body',)) - - The resulting Message stanza's contents may be accessed as so: - - >>> message['to'] = "user@example.com" - >>> message['body'] = "Hi!" - >>> message['body'] - "Hi!" - >>> del message['body'] - >>> message['body'] - "" - - The interface values map to either custom access methods, stanza - XML attributes, or (if the interface is also in sub_interfaces) the - text contents of a stanza's subelement. - - Custom access methods may be created by adding methods of the - form "getInterface", "setInterface", or "delInterface", where - "Interface" is the titlecase version of the interface name. - - Stanzas may be extended through the use of plugins. A plugin - is simply a stanza that has a plugin_attrib value. For example: - - >>> class MessagePlugin(ElementBase): - ... name = "custom_plugin" - ... namespace = "custom" - ... interfaces = set(('useful_thing', 'custom')) - ... plugin_attrib = "custom" - - The plugin stanza class must be associated with its intended - container stanza by using register_stanza_plugin as so: - - >>> register_stanza_plugin(Message, MessagePlugin) - - The plugin may then be accessed as if it were built-in to the parent - stanza. - - >>> message['custom']['useful_thing'] = 'foo' - - If a plugin provides an interface that is the same as the plugin's - plugin_attrib value, then the plugin's interface may be accessed - directly from the parent stanza, as so: - - >>> message['custom'] = 'bar' # Same as using message['custom']['custom'] - - Class Attributes: - name -- The name of the stanza's main element. - namespace -- The namespace of the stanza's main element. - interfaces -- A set of attribute and element names that may - be accessed using dictionary syntax. - sub_interfaces -- A subset of the set of interfaces which map - to subelements instead of attributes. - subitem -- A set of stanza classes which are allowed to - be added as substanzas. - types -- A set of generic type attribute values. - plugin_attrib -- The interface name that the stanza uses to be - accessed as a plugin from another stanza. - plugin_attrib_map -- A mapping of plugin attribute names with the - associated plugin stanza classes. - plugin_tag_map -- A mapping of plugin stanza tag names with - the associated plugin stanza classes. - - Instance Attributes: - xml -- The stanza's XML contents. - parent -- The parent stanza of this stanza. - plugins -- A map of enabled plugin names with the - initialized plugin stanza objects. - values -- A dictionary of the stanza's interfaces - and interface values, including plugins. - - Methods: - setup -- Initialize the stanza's XML contents. - enable -- Instantiate a stanza plugin. - Alias for init_plugin. - init_plugin -- Instantiate a stanza plugin. - _get_stanza_values -- Return a dictionary of stanza interfaces and - their values. - _set_stanza_values -- Set stanza interface values given a dictionary - of interfaces and values. - __getitem__ -- Return the value of a stanza interface. - __setitem__ -- Set the value of a stanza interface. - __delitem__ -- Remove the value of a stanza interface. - _set_attr -- Set an attribute value of the main - stanza element. - _del_attr -- Remove an attribute from the main - stanza element. - _get_attr -- Return an attribute's value from the main - stanza element. - _get_sub_text -- Return the text contents of a subelement. - _set_sub_ext -- Set the text contents of a subelement. - _del_sub -- Remove a subelement. - match -- Compare the stanza against an XPath expression. - find -- Return subelement matching an XPath expression. - findall -- Return subelements matching an XPath expression. - get -- Return the value of a stanza interface, with an - optional default value. - keys -- Return the set of interface names accepted by - the stanza. - append -- Add XML content or a substanza to the stanza. - appendxml -- Add XML content to the stanza. - pop -- Remove a substanza. - next -- Return the next iterable substanza. - _fix_ns -- Apply the stanza's namespace to non-namespaced - elements in an XPath expression. - """ - - name = 'stanza' - plugin_attrib = 'plugin' - namespace = 'jabber:client' - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) - sub_interfaces = tuple() - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = None - - def __init__(self, xml=None, parent=None): - """ - Create a new stanza object. - - Arguments: - xml -- Initialize the stanza with optional existing XML. - parent -- Optional stanza object that contains this stanza. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.initPlugin = self.init_plugin - self._getAttr = self._get_attr - self._setAttr = self._set_attr - self._delAttr = self._del_attr - self._getSubText = self._get_sub_text - self._setSubText = self._set_sub_text - self._delSub = self._del_sub - self.getStanzaValues = self._get_stanza_values - self.setStanzaValues = self._set_stanza_values - - self.xml = xml - self.plugins = {} - self.iterables = [] - self._index = 0 - if parent is None: - self.parent = None - else: - self.parent = weakref.ref(parent) - - ElementBase.values = property(ElementBase._get_stanza_values, - ElementBase._set_stanza_values) - - if self.setup(xml): - # If we generated our own XML, then everything is ready. - return - - # Initialize values using provided XML - for child in self.xml.getchildren(): - if child.tag in self.plugin_tag_map: - plugin = self.plugin_tag_map[child.tag] - self.plugins[plugin.plugin_attrib] = plugin(child, self) - if self.subitem is not None: - for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(child, self)) - break - - def setup(self, xml=None): - """ - Initialize the stanza's XML contents. - - Will return True if XML was generated according to the stanza's - definition. - - Arguments: - xml -- Optional XML object to use for the stanza's content - instead of generating XML. - """ - if self.xml is None: - self.xml = xml - - if self.xml is None: - # Generate XML from the stanza definition - for ename in self.name.split('/'): - new = ET.Element("{%s}%s" % (self.namespace, ename)) - if self.xml is None: - self.xml = new - else: - last_xml.append(new) - last_xml = new - if self.parent is not None: - self.parent().xml.append(self.xml) - - # We had to generate XML - return True - else: - # We did not generate XML - return False - - def enable(self, attrib): - """ - Enable and initialize a stanza plugin. - - Alias for init_plugin. - - Arguments: - attrib -- The stanza interface for the plugin. - """ - return self.init_plugin(attrib) - - def init_plugin(self, attrib): - """ - Enable and initialize a stanza plugin. - - Arguments: - attrib -- The stanza interface for the plugin. - """ - if attrib not in self.plugins: - plugin_class = self.plugin_attrib_map[attrib] - self.plugins[attrib] = plugin_class(parent=self) - return self - - def _get_stanza_values(self): - """ - Return a dictionary of the stanza's interface values. - - Stanza plugin values are included as nested dictionaries. - """ - values = {} - for interface in self.interfaces: - values[interface] = self[interface] - for plugin, stanza in self.plugins.items(): - values[plugin] = stanza._get_stanza_values() - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza._get_stanza_values()) - iterables[-1].update({ - '__childtag__': "{%s}%s" % (stanza.namespace, - stanza.name)}) - values['substanzas'] = iterables - return values - - def _set_stanza_values(self, values): - """ - Set multiple stanza interface values using a dictionary. - - Stanza plugin values may be set using nested dictionaries. - - Arguments: - values -- A dictionary mapping stanza interface with values. - Plugin interfaces may accept a nested dictionary that - will be used recursively. - """ - for interface, value in values.items(): - if interface == 'substanzas': - for subdict in value: - if '__childtag__' in subdict: - for subclass in self.subitem: - child_tag = "{%s}%s" % (subclass.namespace, - subclass.name) - if subdict['__childtag__'] == child_tag: - sub = subclass(parent=self) - sub._set_stanza_values(subdict) - self.iterables.append(sub) - break - elif interface in self.interfaces: - self[interface] = value - elif interface in self.plugin_attrib_map: - if interface not in self.plugins: - self.init_plugin(interface) - self.plugins[interface]._set_stanza_values(value) - return self - - def __getitem__(self, attrib): - """ - Return the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] - 'Message contents' - - Stanza interfaces are typically mapped directly to the underlying XML - object, but can be overridden by the presence of a get_attrib method - (or get_foo where the interface is named foo, etc). - - The search order for interface value retrieval for an interface - named 'foo' is: - 1. The list of substanzas. - 2. The result of calling get_foo. - 3. The result of calling getFoo. - 4. The contents of the foo subelement, if foo is a sub interface. - 5. The value of the foo attribute of the XML object. - 6. The plugin named 'foo' - 7. An empty string. - - Arguments: - attrib -- The name of the requested stanza interface. - """ - if attrib == 'substanzas': - return self.iterables - elif attrib in self.interfaces: - get_method = "get_%s" % attrib.lower() - get_method2 = "get%s" % attrib.title() - if hasattr(self, get_method): - return getattr(self, get_method)() - elif hasattr(self, get_method2): - return getattr(self, get_method2)() - else: - if attrib in self.sub_interfaces: - return self._get_sub_text(attrib) - else: - return self._get_attr(attrib) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: - self.init_plugin(attrib) - return self.plugins[attrib] - else: - return '' - - def __setitem__(self, attrib, value): - """ - Set the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] = "Hi!" - >>> msg['body'] - 'Hi!' - - Stanza interfaces are typically mapped directly to the underlying XML - object, but can be overridden by the presence of a set_attrib method - (or set_foo where the interface is named foo, etc). - - The effect of interface value assignment for an interface - named 'foo' will be one of: - 1. Delete the interface's contents if the value is None. - 2. Call set_foo, if it exists. - 3. Call setFoo, if it exists. - 4. Set the text of a foo element, if foo is in sub_interfaces. - 5. Set the value of a top level XML attribute name foo. - 6. Attempt to pass value to a plugin named foo using the plugin's - foo interface. - 7. Do nothing. - - Arguments: - attrib -- The name of the stanza interface to modify. - value -- The new value of the stanza interface. - """ - if attrib in self.interfaces: - if value is not None: - set_method = "set_%s" % attrib.lower() - set_method2 = "set%s" % attrib.title() - if hasattr(self, set_method): - getattr(self, set_method)(value,) - elif hasattr(self, set_method2): - getattr(self, set_method2)(value,) - else: - if attrib in self.sub_interfaces: - return self._set_sub_text(attrib, text=value) - else: - self._set_attr(attrib, value) - else: - self.__delitem__(attrib) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: - self.init_plugin(attrib) - self.plugins[attrib][attrib] = value - return self - - def __delitem__(self, attrib): - """ - Delete the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] = "Hi!" - >>> msg['body'] - 'Hi!' - >>> del msg['body'] - >>> msg['body'] - '' - - Stanza interfaces are typically mapped directly to the underlyig XML - object, but can be overridden by the presence of a del_attrib method - (or del_foo where the interface is named foo, etc). - - The effect of deleting a stanza interface value named foo will be - one of: - 1. Call del_foo, if it exists. - 2. Call delFoo, if it exists. - 3. Delete foo element, if foo is in sub_interfaces. - 4. Delete top level XML attribute named foo. - 5. Remove the foo plugin, if it was loaded. - 6. Do nothing. - - Arguments: - attrib -- The name of the affected stanza interface. - """ - if attrib in self.interfaces: - del_method = "del_%s" % attrib.lower() - del_method2 = "del%s" % attrib.title() - if hasattr(self, del_method): - getattr(self, del_method)() - elif hasattr(self, del_method2): - getattr(self, del_method2)() - else: - if attrib in self.sub_interfaces: - return self._del_sub(attrib) - else: - self._del_attr(attrib) - elif attrib in self.plugin_attrib_map: - if attrib in self.plugins: - xml = self.plugins[attrib].xml - del self.plugins[attrib] - self.xml.remove(xml) - return self - - def _set_attr(self, name, value): - """ - Set the value of a top level attribute of the underlying XML object. - - If the new value is None or an empty string, then the attribute will - be removed. - - Arguments: - name -- The name of the attribute. - value -- The new value of the attribute, or None or '' to - remove it. - """ - if value is None or value == '': - self.__delitem__(name) - else: - self.xml.attrib[name] = value - - def _del_attr(self, name): - """ - Remove a top level attribute of the underlying XML object. - - Arguments: - name -- The name of the attribute. - """ - if name in self.xml.attrib: - del self.xml.attrib[name] - - def _get_attr(self, name, default=''): - """ - Return the value of a top level attribute of the underlying - XML object. - - In case the attribute has not been set, a default value can be - returned instead. An empty string is returned if no other default - is supplied. - - Arguments: - name -- The name of the attribute. - default -- Optional value to return if the attribute has not - been set. An empty string is returned otherwise. - """ - return self.xml.attrib.get(name, default) - - def _get_sub_text(self, name, default=''): - """ - Return the text contents of a sub element. - - In case the element does not exist, or it has no textual content, - a default value can be returned instead. An empty string is returned - if no other default is supplied. - - Arguments: - name -- The name or XPath expression of the element. - default -- Optional default to return if the element does - not exists. An empty string is returned otherwise. - """ - name = self._fix_ns(name) - stanza = self.xml.find(name) - if stanza is None or stanza.text is None: - return default - else: - return stanza.text - - def _set_sub_text(self, name, text=None, keep=False): - """ - Set the text contents of a sub element. - - In case the element does not exist, a element will be created, - and its text contents will be set. - - If the text is set to an empty string, or None, then the - element will be removed, unless keep is set to True. - - Arguments: - name -- The name or XPath expression of the element. - text -- The new textual content of the element. If the text - is an empty string or None, the element will be removed - unless the parameter keep is True. - keep -- Indicates if the element should be kept if its text is - removed. Defaults to False. - """ - path = self._fix_ns(name, split=True) - element = self.xml.find(name) - - if not text and not keep: - return self._del_sub(name) - - if element is None: - # We need to add the element. If the provided name was - # an XPath expression, some of the intermediate elements - # may already exist. If so, we want to use those instead - # of generating new elements. - last_xml = self.xml - walked = [] - for ename in path: - walked.append(ename) - element = self.xml.find("/".join(walked)) - if element is None: - element = ET.Element(ename) - last_xml.append(element) - last_xml = element - element = last_xml - - element.text = text - return element - - def _del_sub(self, name, all=False): - """ - Remove sub elements that match the given name or XPath. - - If the element is in a path, then any parent elements that become - empty after deleting the element may also be deleted if requested - by setting all=True. - - Arguments: - name -- The name or XPath expression for the element(s) to remove. - all -- If True, remove all empty elements in the path to the - deleted element. Defaults to False. - """ - path = self._fix_ns(name, split=True) - original_target = path[-1] - - for level, _ in enumerate(path): - # Generate the paths to the target elements and their parent. - element_path = "/".join(path[:len(path) - level]) - parent_path = "/".join(path[:len(path) - level - 1]) - - elements = self.xml.findall(element_path) - parent = self.xml.find(parent_path) - - if elements: - if parent is None: - parent = self.xml - for element in elements: - if element.tag == original_target or \ - not element.getchildren(): - # Only delete the originally requested elements, and - # any parent elements that have become empty. - parent.remove(element) - if not all: - # If we don't want to delete elements up the tree, stop - # after deleting the first level of elements. - return - - def match(self, xpath): - """ - Compare a stanza object with an XPath expression. If the XPath matches - the contents of the stanza object, the match is successful. - - The XPath expression may include checks for stanza attributes. - For example: - presence@show=xa@priority=2/status - Would match a presence stanza whose show value is set to 'xa', has a - priority value of '2', and has a status element. - - Arguments: - xpath -- The XPath expression to check against. It may be either a - string or a list of element names with attribute checks. - """ - if isinstance(xpath, str): - xpath = self._fix_ns(xpath, split=True, propagate_ns=False) - - # Extract the tag name and attribute checks for the first XPath node. - components = xpath[0].split('@') - tag = components[0] - attributes = components[1:] - - if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \ - tag not in self.plugins and tag not in self.plugin_attrib: - # The requested tag is not in this stanza, so no match. - return False - - # Check the rest of the XPath against any substanzas. - matched_substanzas = False - for substanza in self.iterables: - if xpath[1:] == []: - break - matched_substanzas = substanza.match(xpath[1:]) - if matched_substanzas: - break - - # Check attribute values. - for attribute in attributes: - name, value = attribute.split('=') - if self[name] != value: - return False - - # Check sub interfaces. - if len(xpath) > 1: - next_tag = xpath[1] - if next_tag in self.sub_interfaces and self[next_tag]: - return True - - # Attempt to continue matching the XPath using the stanza's plugins. - if not matched_substanzas and len(xpath) > 1: - # Convert {namespace}tag@attribs to just tag - next_tag = xpath[1].split('@')[0].split('}')[-1] - if next_tag in self.plugins: - return self.plugins[next_tag].match(xpath[1:]) - else: - return False - - # Everything matched. - return True - - def find(self, xpath): - """ - Find an XML object in this stanza given an XPath expression. - - Exposes ElementTree interface for backwards compatibility. - - Note that matching on attribute values is not supported in Python 2.6 - or Python 3.1 - - Arguments: - xpath -- An XPath expression matching a single desired element. - """ - return self.xml.find(xpath) - - def findall(self, xpath): - """ - Find multiple XML objects in this stanza given an XPath expression. - - Exposes ElementTree interface for backwards compatibility. - - Note that matching on attribute values is not supported in Python 2.6 - or Python 3.1. - - Arguments: - xpath -- An XPath expression matching multiple desired elements. - """ - return self.xml.findall(xpath) - - def get(self, key, default=None): - """ - Return the value of a stanza interface. If the found value is None - or an empty string, return the supplied default value. - - Allows stanza objects to be used like dictionaries. - - Arguments: - key -- The name of the stanza interface to check. - default -- Value to return if the stanza interface has a value - of None or "". Will default to returning None. - """ - value = self[key] - if value is None or value == '': - return default - return value - - def keys(self): - """ - Return the names of all stanza interfaces provided by the - stanza object. - - Allows stanza objects to be used like dictionaries. - """ - out = [] - out += [x for x in self.interfaces] - out += [x for x in self.plugins] - if self.iterables: - out.append('substanzas') - return out - - def append(self, item): - """ - Append either an XML object or a substanza to this stanza object. - - If a substanza object is appended, it will be added to the list - of iterable stanzas. - - Allows stanza objects to be used like lists. - - Arguments: - item -- Either an XML object or a stanza object to add to - this stanza's contents. - """ - if not isinstance(item, ElementBase): - if type(item) == XML_TYPE: - return self.appendxml(item) - else: - raise TypeError - self.xml.append(item.xml) - self.iterables.append(item) - return self - - def appendxml(self, xml): - """ - Append an XML object to the stanza's XML. - - The added XML will not be included in the list of - iterable substanzas. - - Arguments: - xml -- The XML object to add to the stanza. - """ - self.xml.append(xml) - return self - - def pop(self, index=0): - """ - Remove and return the last substanza in the list of - iterable substanzas. - - Allows stanza objects to be used like lists. - - Arguments: - index -- The index of the substanza to remove. - """ - substanza = self.iterables.pop(index) - self.xml.remove(substanza.xml) - return substanza - - def next(self): - """ - Return the next iterable substanza. - """ - return self.__next__() - - @property - def attrib(self): - """ - DEPRECATED - - For backwards compatibility, stanza.attrib returns the stanza itself. - - Older implementations of stanza objects used XML objects directly, - requiring the use of .attrib to access attribute values. - - Use of the dictionary syntax with the stanza object itself for - accessing stanza interfaces is preferred. - """ - return self - - def _fix_ns(self, xpath, split=False, propagate_ns=True): - """ - Apply the stanza's namespace to elements in an XPath expression. - - Arguments: - xpath -- The XPath expression to fix with namespaces. - split -- Indicates if the fixed XPath should be left as a - list of element names with namespaces. Defaults to - False, which returns a flat string path. - propagate_ns -- Overrides propagating parent element namespaces - to child elements. Useful if you wish to simply - split an XPath that has non-specified namespaces, - and child and parent namespaces are known not to - always match. Defaults to True. - """ - fixed = [] - # Split the XPath into a series of blocks, where a block - # is started by an element with a namespace. - ns_blocks = xpath.split('{') - for ns_block in ns_blocks: - if '}' in ns_block: - # Apply the found namespace to following elements - # that do not have namespaces. - namespace = ns_block.split('}')[0] - elements = ns_block.split('}')[1].split('/') - else: - # Apply the stanza's namespace to the following - # elements since no namespace was provided. - namespace = self.namespace - elements = ns_block.split('/') - - for element in elements: - if element: - # Skip empty entry artifacts from splitting. - if propagate_ns: - tag = '{%s}%s' % (namespace, element) - else: - tag = element - fixed.append(tag) - if split: - return fixed - return '/'.join(fixed) - - def __eq__(self, other): - """ - Compare the stanza object with another to test for equality. - - Stanzas are equal if their interfaces return the same values, - and if they are both instances of ElementBase. - - Arguments: - other -- The stanza object to compare against. - """ - if not isinstance(other, ElementBase): - return False - - # Check that this stanza is a superset of the other stanza. - values = self._get_stanza_values() - for key in other.keys(): - if key not in values or values[key] != other[key]: - return False - - # Check that the other stanza is a superset of this stanza. - values = other._get_stanza_values() - for key in self.keys(): - if key not in values or values[key] != self[key]: - return False - - # Both stanzas are supersets of each other, therefore they - # must be equal. - return True - - def __ne__(self, other): - """ - Compare the stanza object with another to test for inequality. - - Stanzas are not equal if their interfaces return different values, - or if they are not both instances of ElementBase. - - Arguments: - other -- The stanza object to compare against. - """ - return not self.__eq__(other) - - def __bool__(self): - """ - Stanza objects should be treated as True in boolean contexts. - - Python 3.x version. - """ - return True - - def __nonzero__(self): - """ - Stanza objects should be treated as True in boolean contexts. - - Python 2.x version. - """ - return True - - def __len__(self): - """ - Return the number of iterable substanzas contained in this stanza. - """ - return len(self.iterables) - - def __iter__(self): - """ - Return an iterator object for iterating over the stanza's substanzas. - - The iterator is the stanza object itself. Attempting to use two - iterators on the same stanza at the same time is discouraged. - """ - self._index = 0 - return self - - def __next__(self): - """ - Return the next iterable substanza. - """ - self._index += 1 - if self._index > len(self.iterables): - self._index = 0 - raise StopIteration - return self.iterables[self._index - 1] - - def __copy__(self): - """ - Return a copy of the stanza object that does not share the same - underlying XML object. - """ - return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - - def __str__(self): - """ - Return a string serialization of the underlying XML object. - """ - return tostring(self.xml, xmlns='', stanza_ns=self.namespace) - - def __repr__(self): - """ - Use the stanza's serialized XML as its representation. - """ - return self.__str__() - - -class StanzaBase(ElementBase): - - """ - StanzaBase provides the foundation for all other stanza objects used by - SleekXMPP, and defines a basic set of interfaces common to nearly - all stanzas. These interfaces are the 'id', 'type', 'to', and 'from' - attributes. An additional interface, 'payload', is available to access - the XML contents of the stanza. Most stanza objects will provided more - specific interfaces, however. - - Stanza Interface: - from -- A JID object representing the sender's JID. - id -- An optional id value that can be used to associate stanzas - with their replies. - payload -- The XML contents of the stanza. - to -- A JID object representing the recipient's JID. - type -- The type of stanza, typically will be 'normal', 'error', - 'get', or 'set', etc. - - Attributes: - stream -- The XMLStream instance that will handle sending this stanza. - tag -- The namespaced version of the stanza's name. - - Methods: - set_type -- Set the type of the stanza. - get_to -- Return the stanza recipients JID. - set_to -- Set the stanza recipient's JID. - get_from -- Return the stanza sender's JID. - set_from -- Set the stanza sender's JID. - get_payload -- Return the stanza's XML contents. - set_payload -- Append to the stanza's XML contents. - del_payload -- Remove the stanza's XML contents. - clear -- Reset the stanza's XML contents. - reply -- Reset the stanza and modify the 'to' and 'from' - attributes to prepare for sending a reply. - error -- Set the stanza's type to 'error'. - unhandled -- Callback for when the stanza is not handled by a - stream handler. - exception -- Callback for if an exception is raised while - handling the stanza. - send -- Send the stanza using the stanza's stream. - """ - - name = 'stanza' - namespace = 'jabber:client' - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) - sub_interfaces = tuple() - - def __init__(self, stream=None, xml=None, stype=None, - sto=None, sfrom=None, sid=None): - """ - Create a new stanza. - - Arguments: - stream -- Optional XMLStream responsible for sending this stanza. - xml -- Optional XML contents to initialize stanza values. - stype -- Optional stanza type value. - sto -- Optional string or JID object of the recipient's JID. - sfrom -- Optional string or JID object of the sender's JID. - sid -- Optional ID value for the stanza. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setType = self.set_type - self.getTo = self.get_to - self.setTo = self.set_to - self.getFrom = self.get_from - self.setFrom = self.set_from - self.getPayload = self.get_payload - self.setPayload = self.set_payload - self.delPayload = self.del_payload - - self.stream = stream - if stream is not None: - self.namespace = stream.default_ns - ElementBase.__init__(self, xml) - if stype is not None: - self['type'] = stype - if sto is not None: - self['to'] = sto - if sfrom is not None: - self['from'] = sfrom - self.tag = "{%s}%s" % (self.namespace, self.name) - - def set_type(self, value): - """ - Set the stanza's 'type' attribute. - - Only type values contained in StanzaBase.types are accepted. - - Arguments: - value -- One of the values contained in StanzaBase.types - """ - if value in self.types: - self.xml.attrib['type'] = value - return self - - def get_to(self): - """Return the value of the stanza's 'to' attribute.""" - return JID(self._get_attr('to')) - - def set_to(self, value): - """ - Set the 'to' attribute of the stanza. - - Arguments: - value -- A string or JID object representing the recipient's JID. - """ - return self._set_attr('to', str(value)) - - def get_from(self): - """Return the value of the stanza's 'from' attribute.""" - return JID(self._get_attr('from')) - - def set_from(self, value): - """ - Set the 'from' attribute of the stanza. - - Arguments: - from -- A string or JID object representing the sender's JID. - """ - return self._set_attr('from', str(value)) - - def get_payload(self): - """Return a list of XML objects contained in the stanza.""" - return self.xml.getchildren() - - def set_payload(self, value): - """ - Add XML content to the stanza. - - Arguments: - value -- Either an XML or a stanza object, or a list - of XML or stanza objects. - """ - if not isinstance(value, list): - value = [value] - for val in value: - self.append(val) - return self - - def del_payload(self): - """Remove the XML contents of the stanza.""" - self.clear() - return self - - def clear(self): - """ - Remove all XML element contents and plugins. - - Any attribute values will be preserved. - """ - for child in self.xml.getchildren(): - self.xml.remove(child) - for plugin in list(self.plugins.keys()): - del self.plugins[plugin] - return self - - def reply(self): - """ - Reset the stanza and swap its 'from' and 'to' attributes to prepare - for sending a reply stanza. - - For client streams, the 'from' attribute is removed. - """ - # if it's a component, use from - if self.stream and hasattr(self.stream, "is_component") and \ - self.stream.is_component: - self['from'], self['to'] = self['to'], self['from'] - else: - self['to'] = self['from'] - del self['from'] - self.clear() - return self - - def error(self): - """Set the stanza's type to 'error'.""" - self['type'] = 'error' - return self - - def unhandled(self): - """ - Called when no handlers have been registered to process this - stanza. - - Meant to be overridden. - """ - pass - - def exception(self, e): - """ - Handle exceptions raised during stanza processing. - - Meant to be overridden. - """ - log.exception('Error handling {%s}%s stanza' % (self.namespace, - self.name)) - - def send(self): - """Queue the stanza to be sent on the XML stream.""" - self.stream.sendRaw(self.__str__()) - - def __copy__(self): - """ - Return a copy of the stanza object that does not share the - same underlying XML object, but does share the same XML stream. - """ - return self.__class__(xml=copy.deepcopy(self.xml), - stream=self.stream) - - def __str__(self): - """Serialize the stanza's XML to a string.""" - return tostring(self.xml, xmlns='', - stanza_ns=self.namespace, - stream=self.stream) diff --git a/sleekxmpp/xmlstream/test.py b/sleekxmpp/xmlstream/test.py deleted file mode 100644 index a45fb8b..0000000 --- a/sleekxmpp/xmlstream/test.py +++ /dev/null @@ -1,23 +0,0 @@ -import xmlstream -import time -import socket -from handler.callback import Callback -from matcher.xpath import MatchXPath - -def server(): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('localhost', 5228)) - s.listen(1) - servers = [] - while True: - conn, addr = s.accept() - server = xmlstream.XMLStream(conn, 'localhost', 5228) - server.registerHandler(Callback('test', MatchXPath('test'), testHandler)) - server.process() - servers.append(server) - -def testHandler(xml): - print("weeeeeeeee!") - -server() diff --git a/sleekxmpp/xmlstream/test.xml b/sleekxmpp/xmlstream/test.xml deleted file mode 100644 index d20dd82..0000000 --- a/sleekxmpp/xmlstream/test.xml +++ /dev/null @@ -1,2 +0,0 @@ -<stream> -</stream> diff --git a/sleekxmpp/xmlstream/testclient.py b/sleekxmpp/xmlstream/testclient.py deleted file mode 100644 index 50eb6c5..0000000 --- a/sleekxmpp/xmlstream/testclient.py +++ /dev/null @@ -1,13 +0,0 @@ -import socket -import time - -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.connect(('localhost', 5228)) -s.send("<stream>") -#s.flush() -s.send("<test/>") -s.send("<test/>") -s.send("<test/>") -s.send("</stream>") -#s.flush() -s.close() diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py deleted file mode 100644 index 5852cba..0000000 --- a/sleekxmpp/xmlstream/tostring/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import sys - -# Import the correct tostring and xml_escape functions based on the Python -# version in order to properly handle Unicode. - -if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape -else: - from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape - -__all__ = ['tostring', 'xml_escape'] diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py deleted file mode 100644 index d8f5c5b..0000000 --- a/sleekxmpp/xmlstream/tostring/tostring.py +++ /dev/null @@ -1,95 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): - """ - Serialize an XML object to a Unicode string. - - If namespaces are provided using xmlns or stanza_ns, then elements - that use those namespaces will not include the xmlns attribute in - the output. - - Arguments: - xml -- The XML object to serialize. If the value is None, - then the XML object contained in this stanza - object will be used. - xmlns -- Optional namespace of an element wrapping the XML - object. - stanza_ns -- The namespace of the stanza object that contains - the XML object. - stream -- The XML stream that generated the XML object. - outbuffer -- Optional buffer for storing serializations during - recursive calls. - """ - # Add previous results to the start of the output. - output = [outbuffer] - - # Extract the element's tag name. - tag_name = xml.tag.split('}', 1)[-1] - - # Extract the element's namespace if it is defined. - if '}' in xml.tag: - tag_xmlns = xml.tag.split('}', 1)[0][1:] - else: - tag_xmlns = '' - - # Output the tag name and derived namespace of the element. - namespace = '' - if tag_xmlns not in ['', xmlns, stanza_ns]: - namespace = ' xmlns="%s"' % tag_xmlns - if stream and tag_xmlns in stream.namespace_map: - mapped_namespace = stream.namespace_map[tag_xmlns] - if mapped_namespace: - tag_name = "%s:%s" % (mapped_namespace, tag_name) - output.append("<%s" % tag_name) - output.append(namespace) - - # Output escaped attribute values. - for attrib, value in xml.attrib.items(): - if '{' not in attrib: - value = xml_escape(value) - output.append(' %s="%s"' % (attrib, value)) - - if len(xml) or xml.text: - # If there are additional child elements to serialize. - output.append(">") - if xml.text: - output.append(xml_escape(xml.text)) - if len(xml): - for child in xml.getchildren(): - output.append(tostring(child, tag_xmlns, stanza_ns, stream)) - output.append("</%s>" % tag_name) - elif xml.text: - # If we only have text content. - output.append(">%s</%s>" % (xml_escape(xml.text), tag_name)) - else: - # Empty element. - output.append(" />") - if xml.tail: - # If there is additional text after the element. - output.append(xml_escape(xml.tail)) - return ''.join(output) - - -def xml_escape(text): - """ - Convert special characters in XML to escape sequences. - - Arguments: - text -- The XML text to convert. - """ - text = list(text) - escapes = {'&': '&', - '<': '<', - '>': '>', - "'": ''', - '"': '"'} - for i, c in enumerate(text): - text[i] = escapes.get(c, c) - return ''.join(text) diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py deleted file mode 100644 index 0ee432c..0000000 --- a/sleekxmpp/xmlstream/tostring/tostring26.py +++ /dev/null @@ -1,101 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import unicode_literals -import types - - -def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): - """ - Serialize an XML object to a Unicode string. - - If namespaces are provided using xmlns or stanza_ns, then elements - that use those namespaces will not include the xmlns attribute in - the output. - - Arguments: - xml -- The XML object to serialize. If the value is None, - then the XML object contained in this stanza - object will be used. - xmlns -- Optional namespace of an element wrapping the XML - object. - stanza_ns -- The namespace of the stanza object that contains - the XML object. - stream -- The XML stream that generated the XML object. - outbuffer -- Optional buffer for storing serializations during - recursive calls. - """ - # Add previous results to the start of the output. - output = [outbuffer] - - # Extract the element's tag name. - tag_name = xml.tag.split('}', 1)[-1] - - # Extract the element's namespace if it is defined. - if '}' in xml.tag: - tag_xmlns = xml.tag.split('}', 1)[0][1:] - else: - tag_xmlns = u'' - - # Output the tag name and derived namespace of the element. - namespace = u'' - if tag_xmlns not in ['', xmlns, stanza_ns]: - namespace = u' xmlns="%s"' % tag_xmlns - if stream and tag_xmlns in stream.namespace_map: - mapped_namespace = stream.namespace_map[tag_xmlns] - if mapped_namespace: - tag_name = u"%s:%s" % (mapped_namespace, tag_name) - output.append(u"<%s" % tag_name) - output.append(namespace) - - # Output escaped attribute values. - for attrib, value in xml.attrib.items(): - if '{' not in attrib: - value = xml_escape(value) - output.append(u' %s="%s"' % (attrib, value)) - - if len(xml) or xml.text: - # If there are additional child elements to serialize. - output.append(u">") - if xml.text: - output.append(xml_escape(xml.text)) - if len(xml): - for child in xml.getchildren(): - output.append(tostring(child, tag_xmlns, stanza_ns, stream)) - output.append(u"</%s>" % tag_name) - elif xml.text: - # If we only have text content. - output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name)) - else: - # Empty element. - output.append(u" />") - if xml.tail: - # If there is additional text after the element. - output.append(xml_escape(xml.tail)) - return u''.join(output) - - -def xml_escape(text): - """ - Convert special characters in XML to escape sequences. - - Arguments: - text -- The XML text to convert. - """ - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) - escapes = {u'&': u'&', - u'<': u'<', - u'>': u'>', - u"'": u''', - u'"': u'"'} - for i, c in enumerate(text): - text[i] = escapes.get(c, c) - return u''.join(text) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py deleted file mode 100644 index 30b76ce..0000000 --- a/sleekxmpp/xmlstream/xmlstream.py +++ /dev/null @@ -1,948 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import with_statement, unicode_literals - -import copy -import logging -import socket as Socket -import ssl -import sys -import threading -import time -import types -import signal -try: - import queue -except ImportError: - import Queue as queue - -from sleekxmpp.thirdparty.statemachine import StateMachine -from sleekxmpp.xmlstream import Scheduler, tostring -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET - -# In Python 2.x, file socket objects are broken. A patched socket -# wrapper is provided for this case in filesocket.py. -if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26 - - -# The time in seconds to wait before timing out waiting for response stanzas. -RESPONSE_TIMEOUT = 10 - -# The number of threads to use to handle XML stream events. This is not the -# same as the number of custom event handling threads. HANDLER_THREADS must -# be at least 1. -HANDLER_THREADS = 1 - -# Flag indicating if the SSL library is available for use. -SSL_SUPPORT = True - - -log = logging.getLogger(__name__) - - -class RestartStream(Exception): - """ - Exception to restart stream processing, including - resending the stream header. - """ - - -class XMLStream(object): - """ - An XML stream connection manager and event dispatcher. - - The XMLStream class abstracts away the issues of establishing a - connection with a server and sending and receiving XML "stanzas". - A stanza is a complete XML element that is a direct child of a root - document element. Two streams are used, one for each communication - direction, over the same socket. Once the connection is closed, both - streams should be complete and valid XML documents. - - Three types of events are provided to manage the stream: - Stream -- Triggered based on received stanzas, similar in concept - to events in a SAX XML parser. - Custom -- Triggered manually. - Scheduled -- Triggered based on time delays. - - Typically, stanzas are first processed by a stream event handler which - will then trigger custom events to continue further processing, - especially since custom event handlers may run in individual threads. - - - Attributes: - address -- The hostname and port of the server. - default_ns -- The default XML namespace that will be applied - to all non-namespaced stanzas. - event_queue -- A queue of stream, custom, and scheduled - events to be processed. - filesocket -- A filesocket created from the main connection socket. - Required for ElementTree.iterparse. - namespace_map -- Optional mapping of namespaces to namespace prefixes. - scheduler -- A scheduler object for triggering events - after a given period of time. - send_queue -- A queue of stanzas to be sent on the stream. - socket -- The connection to the server. - ssl_support -- Indicates if a SSL library is available for use. - ssl_version -- The version of the SSL protocol to use. - Defaults to ssl.PROTOCOL_TLSv1. - state -- A state machine for managing the stream's - connection state. - stream_footer -- The start tag and any attributes for the stream's - root element. - stream_header -- The closing tag of the stream's root element. - use_ssl -- Flag indicating if SSL should be used. - use_tls -- Flag indicating if TLS should be used. - stop -- threading Event used to stop all threads. - auto_reconnect-- Flag to determine whether we auto reconnect. - - Methods: - add_event_handler -- Add a handler for a custom event. - add_handler -- Shortcut method for registerHandler. - connect -- Connect to the given server. - del_event_handler -- Remove a handler for a custom event. - disconnect -- Disconnect from the server and terminate - processing. - event -- Trigger a custom event. - get_id -- Return the current stream ID. - incoming_filter -- Optionally filter stanzas before processing. - new_id -- Generate a new, unique ID value. - process -- Read XML stanzas from the stream and apply - matching stream handlers. - reconnect -- Reestablish a connection to the server. - register_handler -- Add a handler for a stream event. - register_stanza -- Add a new stanza object type that may appear - as a direct child of the stream's root. - remove_handler -- Remove a stream handler. - remove_stanza -- Remove a stanza object type. - schedule -- Schedule an event handler to execute after a - given delay. - send -- Send a stanza object on the stream. - send_raw -- Send a raw string on the stream. - send_xml -- Send an XML string on the stream. - set_socket -- Set the stream's socket and generate a new - filesocket. - start_stream_handler -- Perform any stream initialization such - as handshakes. - start_tls -- Establish a TLS connection and restart - the stream. - """ - - def __init__(self, socket=None, host='', port=0): - """ - Establish a new XML stream. - - Arguments: - socket -- Use an existing socket for the stream. - Defaults to None to generate a new socket. - host -- The name of the target server. - Defaults to the empty string. - port -- The port to use for the connection. - Defaults to 0. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.startTLS = self.start_tls - self.registerStanza = self.register_stanza - self.removeStanza = self.remove_stanza - self.registerHandler = self.register_handler - self.removeHandler = self.remove_handler - self.setSocket = self.set_socket - self.sendRaw = self.send_raw - self.getId = self.get_id - self.getNewId = self.new_id - self.sendXML = self.send_xml - - self.ssl_support = SSL_SUPPORT - self.ssl_version = ssl.PROTOCOL_TLSv1 - - self.state = StateMachine(('disconnected', 'connected')) - self.state._set_state('disconnected') - - self.address = (host, int(port)) - self.filesocket = None - self.set_socket(socket) - - if sys.version_info < (3, 0): - self.socket_class = Socket26 - else: - self.socket_class = Socket.socket - - self.use_ssl = False - self.use_tls = False - - self.default_ns = '' - self.stream_header = "<stream>" - self.stream_footer = "</stream>" - - self.stop = threading.Event() - self.stream_end_event = threading.Event() - self.stream_end_event.set() - self.event_queue = queue.Queue() - self.send_queue = queue.Queue() - self.scheduler = Scheduler(self.event_queue, self.stop) - - self.namespace_map = {} - - self.__thread = {} - self.__root_stanza = [] - self.__handlers = [] - self.__event_handlers = {} - self.__event_handlers_lock = threading.Lock() - - self._id = 0 - self._id_lock = threading.Lock() - - self.auto_reconnect = True - self.is_client = False - - try: - if hasattr(signal, 'SIGHUP'): - signal.signal(signal.SIGHUP, self._handle_kill) - if hasattr(signal, 'SIGTERM'): - # Used in Windows - signal.signal(signal.SIGTERM, self._handle_kill) - except: - log.debug("Can not set interrupt signal handlers. " + \ - "SleekXMPP is not running from a main thread.") - - def _handle_kill(self, signum, frame): - """ - Capture kill event and disconnect cleanly after first - spawning the "killed" event. - """ - self.event("killed", direct=True) - self.disconnect() - - def new_id(self): - """ - Generate and return a new stream ID in hexadecimal form. - - Many stanzas, handlers, or matchers may require unique - ID values. Using this method ensures that all new ID values - are unique in this stream. - """ - with self._id_lock: - self._id += 1 - return self.get_id() - - def get_id(self): - """ - Return the current unique stream ID in hexadecimal form. - """ - return "%X" % self._id - - def connect(self, host='', port=0, use_ssl=False, - use_tls=True, reattempt=True): - """ - Create a new socket and connect to the server. - - Setting reattempt to True will cause connection attempts to be made - every second until a successful connection is established. - - Arguments: - host -- The name of the desired server for the connection. - port -- Port to connect to on the server. - use_ssl -- Flag indicating if SSL should be used. - use_tls -- Flag indicating if TLS should be used. - reattempt -- Flag indicating if the socket should reconnect - after disconnections. - """ - if host and port: - self.address = (host, int(port)) - - self.is_client = True - # Respect previous SSL and TLS usage directives. - if use_ssl is not None: - self.use_ssl = use_ssl - if use_tls is not None: - self.use_tls = use_tls - - # Repeatedly attempt to connect until a successful connection - # is established. - connected = self.state.transition('disconnected', 'connected', - func=self._connect) - while reattempt and not connected: - connected = self.state.transition('disconnected', 'connected', - func=self._connect) - return connected - - def _connect(self): - self.stop.clear() - self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM) - self.socket.settimeout(None) - if self.use_ssl and self.ssl_support: - log.debug("Socket Wrapped for SSL") - ssl_socket = ssl.wrap_socket(self.socket) - if hasattr(self.socket, 'socket'): - # We are using a testing socket, so preserve the top - # layer of wrapping. - self.socket.socket = ssl_socket - else: - self.socket = ssl_socket - - try: - log.debug("Connecting to %s:%s" % self.address) - self.socket.connect(self.address) - self.set_socket(self.socket, ignore=True) - #this event is where you should set your application state - self.event("connected", direct=True) - return True - except Socket.error as serr: - error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" - log.error(error_msg % (self.address[0], self.address[1], - serr.errno, serr.strerror)) - time.sleep(1) - return False - - def disconnect(self, reconnect=False): - """ - Terminate processing and close the XML streams. - - Optionally, the connection may be reconnected and - resume processing afterwards. - - Arguments: - reconnect -- Flag indicating if the connection - and processing should be restarted. - Defaults to False. - """ - self.state.transition('connected', 'disconnected', wait=0.0, - func=self._disconnect, args=(reconnect,)) - - def _disconnect(self, reconnect=False): - # Send the end of stream marker. - self.send_raw(self.stream_footer) - # Wait for confirmation that the stream was - # closed in the other direction. - if not reconnect: - self.auto_reconnect = False - self.stream_end_event.wait(4) - if not self.auto_reconnect: - self.stop.set() - try: - self.socket.close() - self.filesocket.close() - self.socket.shutdown(Socket.SHUT_RDWR) - except Socket.error as serr: - pass - finally: - #clear your application state - self.event("disconnected", direct=True) - return True - - def reconnect(self): - """ - Reset the stream's state and reconnect to the server. - """ - log.debug("reconnecting...") - self.state.transition('connected', 'disconnected', wait=2.0, - func=self._disconnect, args=(True,)) - log.debug("connecting...") - return self.state.transition('disconnected', 'connected', - wait=2.0, func=self._connect) - - def set_socket(self, socket, ignore=False): - """ - Set the socket to use for the stream. - - The filesocket will be recreated as well. - - Arguments: - socket -- The new socket to use. - ignore -- don't set the state - """ - self.socket = socket - if socket is not None: - # ElementTree.iterparse requires a file. - # 0 buffer files have to be binary. - - # Use the correct fileobject type based on the Python - # version to work around a broken implementation in - # Python 2.x. - if sys.version_info < (3, 0): - self.filesocket = FileSocket(self.socket) - else: - self.filesocket = self.socket.makefile('rb', 0) - if not ignore: - self.state._set_state('connected') - - def start_tls(self): - """ - Perform handshakes for TLS. - - If the handshake is successful, the XML stream will need - to be restarted. - """ - if self.ssl_support: - log.info("Negotiating TLS") - log.info("Using SSL version: %s" % str(self.ssl_version)) - ssl_socket = ssl.wrap_socket(self.socket, - ssl_version=self.ssl_version, - do_handshake_on_connect=False) - if hasattr(self.socket, 'socket'): - # We are using a testing socket, so preserve the top - # layer of wrapping. - self.socket.socket = ssl_socket - else: - self.socket = ssl_socket - self.socket.do_handshake() - self.set_socket(self.socket) - return True - else: - log.warning("Tried to enable TLS, but ssl module not found.") - return False - - def start_stream_handler(self, xml): - """ - Perform any initialization actions, such as handshakes, once the - stream header has been sent. - - Meant to be overridden. - """ - pass - - def register_stanza(self, stanza_class): - """ - Add a stanza object class as a known root stanza. A root stanza is - one that appears as a direct child of the stream's root element. - - Stanzas that appear as substanzas of a root stanza do not need to - be registered here. That is done using register_stanza_plugin() from - sleekxmpp.xmlstream.stanzabase. - - Stanzas that are not registered will not be converted into - stanza objects, but may still be processed using handlers and - matchers. - - Arguments: - stanza_class -- The top-level stanza object's class. - """ - self.__root_stanza.append(stanza_class) - - def remove_stanza(self, stanza_class): - """ - Remove a stanza from being a known root stanza. A root stanza is - one that appears as a direct child of the stream's root element. - - Stanzas that are not registered will not be converted into - stanza objects, but may still be processed using handlers and - matchers. - """ - del self.__root_stanza[stanza_class] - - def add_handler(self, mask, pointer, name=None, disposable=False, - threaded=False, filter=False, instream=False): - """ - A shortcut method for registering a handler using XML masks. - - Arguments: - mask -- An XML snippet matching the structure of the - stanzas that will be passed to this handler. - pointer -- The handler function itself. - name -- A unique name for the handler. A name will - be generated if one is not provided. - disposable -- Indicates if the handler should be discarded - after one use. - threaded -- Deprecated. Remains for backwards compatibility. - filter -- Deprecated. Remains for backwards compatibility. - instream -- Indicates if the handler should execute during - stream processing and not during normal event - processing. - """ - # To prevent circular dependencies, we must load the matcher - # and handler classes here. - from sleekxmpp.xmlstream.matcher import MatchXMLMask - from sleekxmpp.xmlstream.handler import XMLCallback - - if name is None: - name = 'add_handler_%s' % self.getNewId() - self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, - once=disposable, instream=instream)) - - def register_handler(self, handler, before=None, after=None): - """ - Add a stream event handler that will be executed when a matching - stanza is received. - - Arguments: - handler -- The handler object to execute. - """ - if handler.stream is None: - self.__handlers.append(handler) - handler.stream = self - - def remove_handler(self, name): - """ - Remove any stream event handlers with the given name. - - Arguments: - name -- The name of the handler. - """ - idx = 0 - for handler in self.__handlers: - if handler.name == name: - self.__handlers.pop(idx) - return True - idx += 1 - return False - - def add_event_handler(self, name, pointer, - threaded=False, disposable=False): - """ - Add a custom event handler that will be executed whenever - its event is manually triggered. - - Arguments: - name -- The name of the event that will trigger - this handler. - pointer -- The function to execute. - threaded -- If set to True, the handler will execute - in its own thread. Defaults to False. - disposable -- If set to True, the handler will be - discarded after one use. Defaults to False. - """ - if not name in self.__event_handlers: - self.__event_handlers[name] = [] - self.__event_handlers[name].append((pointer, threaded, disposable)) - - def del_event_handler(self, name, pointer): - """ - Remove a function as a handler for an event. - - Arguments: - name -- The name of the event. - pointer -- The function to remove as a handler. - """ - if not name in self.__event_handlers: - return - - # Need to keep handlers that do not use - # the given function pointer - def filter_pointers(handler): - return handler[0] != pointer - - self.__event_handlers[name] = filter(filter_pointers, - self.__event_handlers[name]) - - def event_handled(self, name): - """ - Indicates if an event has any associated handlers. - - Returns the number of registered handlers. - - Arguments: - name -- The name of the event to check. - """ - return len(self.__event_handlers.get(name, [])) - - def event(self, name, data={}, direct=False): - """ - Manually trigger a custom event. - - Arguments: - name -- The name of the event to trigger. - data -- Data that will be passed to each event handler. - Defaults to an empty dictionary. - direct -- Runs the event directly if True, skipping the - event queue. All event handlers will run in the - same thread. - """ - for handler in self.__event_handlers.get(name, []): - if direct: - try: - handler[0](copy.copy(data)) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(handler[0])) - if hasattr(data, 'exception'): - data.exception(e) - else: - self.event_queue.put(('event', handler, copy.copy(data))) - - if handler[2]: - # If the handler is disposable, we will go ahead and - # remove it now instead of waiting for it to be - # processed in the queue. - with self.__event_handlers_lock: - try: - h_index = self.__event_handlers[name].index(handler) - self.__event_handlers[name].pop(h_index) - except: - pass - - def schedule(self, name, seconds, callback, args=None, - kwargs=None, repeat=False): - """ - Schedule a callback function to execute after a given delay. - - Arguments: - name -- A unique name for the scheduled callback. - seconds -- The time in seconds to wait before executing. - callback -- A pointer to the function to execute. - args -- A tuple of arguments to pass to the function. - kwargs -- A dictionary of keyword arguments to pass to - the function. - repeat -- Flag indicating if the scheduled event should - be reset and repeat after executing. - """ - self.scheduler.add(name, seconds, callback, args, kwargs, - repeat, qpointer=self.event_queue) - - def incoming_filter(self, xml): - """ - Filter incoming XML objects before they are processed. - - Possible uses include remapping namespaces, or correcting elements - from sources with incorrect behavior. - - Meant to be overridden. - """ - return xml - - def send(self, data, mask=None, timeout=RESPONSE_TIMEOUT): - """ - A wrapper for send_raw for sending stanza objects. - - May optionally block until an expected response is received. - - Arguments: - data -- The stanza object to send on the stream. - mask -- Deprecated. An XML snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - timeout -- Time in seconds to wait for a response before - continuing. Defaults to RESPONSE_TIMEOUT. - """ - if hasattr(mask, 'xml'): - mask = mask.xml - data = str(data) - if mask is not None: - log.warning("Use of send mask waiters is deprecated.") - wait_for = Waiter("SendWait_%s" % self.new_id(), - MatchXMLMask(mask)) - self.register_handler(wait_for) - self.send_raw(data) - if mask is not None: - return wait_for.wait(timeout) - - def send_raw(self, data): - """ - Send raw data across the stream. - - Arguments: - data -- Any string value. - """ - self.send_queue.put(data) - return True - - def send_xml(self, data, mask=None, timeout=RESPONSE_TIMEOUT): - """ - Send an XML object on the stream, and optionally wait - for a response. - - Arguments: - data -- The XML object to send on the stream. - mask -- Deprecated. An XML snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - timeout -- Time in seconds to wait for a response before - continuing. Defaults to RESPONSE_TIMEOUT. - """ - return self.send(tostring(data), mask, timeout) - - def process(self, threaded=True): - """ - Initialize the XML streams and begin processing events. - - The number of threads used for processing stream events is determined - by HANDLER_THREADS. - - Arguments: - threaded -- If threaded=True then event dispatcher will run - in a separate thread, allowing for the stream to be - used in the background for another application. - Defaults to True. - - Event handlers and the send queue will be threaded - regardless of this parameter's value. - """ - self.scheduler.process(threaded=True) - - def start_thread(name, target): - self.__thread[name] = threading.Thread(name=name, target=target) - self.__thread[name].start() - - for t in range(0, HANDLER_THREADS): - log.debug("Starting HANDLER THREAD") - start_thread('stream_event_handler_%s' % t, self._event_runner) - - start_thread('send_thread', self._send_thread) - - if threaded: - # Run the XML stream in the background for another application. - start_thread('process', self._process) - else: - self._process() - - def _process(self): - """ - Start processing the XML streams. - - Processing will continue after any recoverable errors - if reconnections are allowed. - """ - firstrun = True - - # The body of this loop will only execute once per connection. - # Additional passes will be made only if an error occurs and - # reconnecting is permitted. - while firstrun or (self.auto_reconnect and not self.stop.isSet()): - firstrun = False - try: - if self.is_client: - self.send_raw(self.stream_header) - # The call to self.__read_xml will block and prevent - # the body of the loop from running until a disconnect - # occurs. After any reconnection, the stream header will - # be resent and processing will resume. - while not self.stop.isSet() and self.__read_xml(): - # Ensure the stream header is sent for any - # new connections. - if self.is_client: - self.send_raw(self.stream_header) - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _process") - self.stop.set() - except SystemExit: - log.debug("SystemExit in _process") - self.stop.set() - except Socket.error: - log.exception('Socket Error') - except: - if not self.stop.isSet(): - log.exception('Connection error.') - if not self.stop.isSet() and self.auto_reconnect: - self.reconnect() - else: - self.disconnect() - self.event_queue.put(('quit', None, None)) - self.scheduler.run = False - - def __read_xml(self): - """ - Parse the incoming XML stream, raising stream events for - each received stanza. - """ - depth = 0 - root = None - for (event, xml) in ET.iterparse(self.filesocket, (b'end', b'start')): - if event == b'start': - if depth == 0: - # We have received the start of the root element. - root = xml - # Perform any stream initialization actions, such - # as handshakes. - self.stream_end_event.clear() - self.start_stream_handler(root) - depth += 1 - if event == b'end': - depth -= 1 - if depth == 0: - # The stream's root element has closed, - # terminating the stream. - log.debug("End of stream recieved") - self.stream_end_event.set() - return False - elif depth == 1: - # We only raise events for stanzas that are direct - # children of the root element. - try: - self.__spawn_event(xml) - except RestartStream: - return True - if root: - # Keep the root element empty of children to - # save on memory use. - root.clear() - log.debug("Ending read XML loop") - - def _build_stanza(self, xml, default_ns=None): - """ - Create a stanza object from a given XML object. - - If a specialized stanza type is not found for the XML, then - a generic StanzaBase stanza will be returned. - - Arguments: - xml -- The XML object to convert into a stanza object. - default_ns -- Optional default namespace to use instead of the - stream's current default namespace. - """ - if default_ns is None: - default_ns = self.default_ns - stanza_type = StanzaBase - for stanza_class in self.__root_stanza: - if xml.tag == "{%s}%s" % (default_ns, stanza_class.name): - stanza_type = stanza_class - break - stanza = stanza_type(self, xml) - return stanza - - def __spawn_event(self, xml): - """ - Analyze incoming XML stanzas and convert them into stanza - objects if applicable and queue stream events to be processed - by matching handlers. - - Arguments: - xml -- The XML stanza to analyze. - """ - log.debug("RECV: %s" % tostring(xml, - xmlns=self.default_ns, - stream=self)) - # Apply any preprocessing filters. - xml = self.incoming_filter(xml) - - # Convert the raw XML object into a stanza object. If no registered - # stanza type applies, a generic StanzaBase stanza will be used. - stanza_type = StanzaBase - for stanza_class in self.__root_stanza: - if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name): - stanza_type = stanza_class - break - stanza = stanza_type(self, xml) - - # Match the stanza against registered handlers. Handlers marked - # to run "in stream" will be executed immediately; the rest will - # be queued. - unhandled = True - for handler in self.__handlers: - if handler.match(stanza): - stanza_copy = stanza_type(self, copy.deepcopy(xml)) - handler.prerun(stanza_copy) - self.event_queue.put(('stanza', handler, stanza_copy)) - try: - if handler.check_delete(): - self.__handlers.pop(self.__handlers.index(handler)) - except: - pass # not thread safe - unhandled = False - - # Some stanzas require responses, such as Iq queries. A default - # handler will be executed immediately for this case. - if unhandled: - stanza.unhandled() - - def _threaded_event_wrapper(self, func, args): - """ - Capture exceptions for event handlers that run - in individual threads. - - Arguments: - func -- The event handler to execute. - args -- Arguments to the event handler. - """ - try: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(func)) - if hasattr(args[0], 'exception'): - args[0].exception(e) - - def _event_runner(self): - """ - Process the event queue and execute handlers. - - The number of event runner threads is controlled by HANDLER_THREADS. - - Stream event handlers will all execute in this thread. Custom event - handlers may be spawned in individual threads. - """ - log.debug("Loading event runner") - try: - while not self.stop.isSet(): - try: - event = self.event_queue.get(True, timeout=5) - except queue.Empty: - event = None - if event is None: - continue - - etype, handler = event[0:2] - args = event[2:] - - if etype == 'stanza': - try: - handler.run(args[0]) - except Exception as e: - error_msg = 'Error processing stream handler: %s' - log.exception(error_msg % handler.name) - args[0].exception(e) - elif etype == 'schedule': - try: - log.debug(args) - handler(*args[0]) - except: - log.exception('Error processing scheduled task') - elif etype == 'event': - func, threaded, disposable = handler - try: - if threaded: - x = threading.Thread( - name="Event_%s" % str(func), - target=self._threaded_event_wrapper, - args=(func, args)) - x.start() - else: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(func)) - if hasattr(args[0], 'exception'): - args[0].exception(e) - elif etype == 'quit': - log.debug("Quitting event runner thread") - return False - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _event_runner") - self.disconnect() - return - except SystemExit: - self.disconnect() - self.event_queue.put(('quit', None, None)) - return - - def _send_thread(self): - """ - Extract stanzas from the send queue and send them on the stream. - """ - try: - while not self.stop.isSet(): - try: - data = self.send_queue.get(True, 1) - except queue.Empty: - continue - log.debug("SEND: %s" % data) - try: - self.socket.send(data.encode('utf-8')) - except: - log.warning("Failed to send %s" % data) - self.disconnect(self.auto_reconnect) - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _send_thread") - self.disconnect() - return - except SystemExit: - self.disconnect() - self.event_queue.put(('quit', None, None)) - return |
