diff options
author | Florent Le Coz <louiz@louiz.org> | 2014-07-17 14:19:04 +0200 |
---|---|---|
committer | Florent Le Coz <louiz@louiz.org> | 2014-07-17 14:19:04 +0200 |
commit | 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e (patch) | |
tree | 259377cc666f8b9c7954fc4e7b8f7a912bcfe101 /sleekxmpp | |
parent | e5582694c07236e6830c20361840360a1dde37f3 (diff) | |
download | slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.gz slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.bz2 slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.tar.xz slixmpp-5ab77c745270d7d5c016c1dc7ef2a82533a4b16e.zip |
Rename to slixmpp
Diffstat (limited to 'sleekxmpp')
325 files changed, 0 insertions, 37560 deletions
diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py deleted file mode 100644 index 85ee32b6..00000000 --- a/sleekxmpp/__init__.py +++ /dev/null @@ -1,30 +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 -if hasattr(logging, 'NullHandler'): - NullHandler = logging.NullHandler -else: - class NullHandler(logging.Handler): - def handle(self, record): - pass -logging.getLogger(__name__).addHandler(NullHandler()) -del NullHandler - - -from sleekxmpp.stanza import Message, Presence, Iq -from sleekxmpp.jid import JID, InvalidJID -from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin -from sleekxmpp.xmlstream.handler import * -from sleekxmpp.xmlstream import XMLStream, RestartStream -from sleekxmpp.xmlstream.matcher import * -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.clientxmpp import ClientXMPP -from sleekxmpp.componentxmpp import ComponentXMPP - -from sleekxmpp.version import __version__, __version_info__ diff --git a/sleekxmpp/api.py b/sleekxmpp/api.py deleted file mode 100644 index 8de61b34..00000000 --- a/sleekxmpp/api.py +++ /dev/null @@ -1,200 +0,0 @@ -from sleekxmpp.xmlstream import JID - - -class APIWrapper(object): - - def __init__(self, api, name): - self.api = api - self.name = name - if name not in self.api.settings: - self.api.settings[name] = {} - - def __getattr__(self, attr): - """Curry API management commands with the API name.""" - if attr == 'name': - return self.name - elif attr == 'settings': - return self.api.settings[self.name] - elif attr == 'register': - def partial(handler, op, jid=None, node=None, default=False): - register = getattr(self.api, attr) - return register(handler, self.name, op, jid, node, default) - return partial - elif attr == 'register_default': - def partial(handler, op, jid=None, node=None): - return getattr(self.api, attr)(handler, self.name, op) - return partial - elif attr in ('run', 'restore_default', 'unregister'): - def partial(*args, **kwargs): - return getattr(self.api, attr)(self.name, *args, **kwargs) - return partial - return None - - def __getitem__(self, attr): - def partial(jid=None, node=None, ifrom=None, args=None): - return self.api.run(self.name, attr, jid, node, ifrom, args) - return partial - - -class APIRegistry(object): - - def __init__(self, xmpp): - self._handlers = {} - self._handler_defaults = {} - self.xmpp = xmpp - self.settings = {} - - def _setup(self, ctype, op): - """Initialize the API callback dictionaries. - - :param string ctype: The name of the API to initialize. - :param string op: The API operation to initialize. - """ - if ctype not in self.settings: - self.settings[ctype] = {} - if ctype not in self._handler_defaults: - self._handler_defaults[ctype] = {} - if ctype not in self._handlers: - self._handlers[ctype] = {} - if op not in self._handlers[ctype]: - self._handlers[ctype][op] = {'global': None, - 'jid': {}, - 'node': {}} - - def wrap(self, ctype): - """Return a wrapper object that targets a specific API.""" - return APIWrapper(self, ctype) - - def purge(self, ctype): - """Remove all information for a given API.""" - del self.settings[ctype] - del self._handler_defaults[ctype] - del self._handlers[ctype] - - def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None): - """Execute an API callback, based on specificity. - - The API callback that is executed is chosen based on the combination - of the provided JID and node: - - JID | node | Handler - ============================== - Given | Given | Node handler - Given | None | JID handler - None | None | Global handler - - A node handler is responsible for servicing a single node at a single - JID, while a JID handler may respond for any node at a given JID, and - the global handler will answer to any JID+node combination. - - Handlers should check that the JID ``ifrom`` is authorized to perform - the desired action. - - :param string ctype: The name of the API to use. - :param string op: The API operation to perform. - :param JID jid: Optionally provide specific JID. - :param string node: Optionally provide specific node. - :param JID ifrom: Optionally provide the requesting JID. - :param tuple args: Optional positional arguments to the handler. - """ - self._setup(ctype, op) - - if not jid: - jid = self.xmpp.boundjid - elif jid and not isinstance(jid, JID): - jid = JID(jid) - elif jid == JID(''): - jid = self.xmpp.boundjid - - if node is None: - node = '' - - if self.xmpp.is_component: - if self.settings[ctype].get('component_bare', False): - jid = jid.bare - else: - jid = jid.full - else: - if self.settings[ctype].get('client_bare', False): - jid = jid.bare - else: - jid = jid.full - - jid = JID(jid) - - handler = self._handlers[ctype][op]['node'].get((jid, node), None) - if handler is None: - handler = self._handlers[ctype][op]['jid'].get(jid, None) - if handler is None: - handler = self._handlers[ctype][op].get('global', None) - - if handler: - try: - return handler(jid, node, ifrom, args) - except TypeError: - # To preserve backward compatibility, drop the ifrom - # parameter for existing handlers that don't understand it. - return handler(jid, node, args) - - def register(self, handler, ctype, op, jid=None, node=None, default=False): - """Register an API callback, with JID+node specificity. - - The API callback can later be executed based on the - specificity of the provided JID+node combination. - - See :meth:`~ApiRegistry.run` for more details. - - :param string ctype: The name of the API to use. - :param string op: The API operation to perform. - :param JID jid: Optionally provide specific JID. - :param string node: Optionally provide specific node. - """ - self._setup(ctype, op) - if jid is None and node is None: - if handler is None: - handler = self._handler_defaults[op] - self._handlers[ctype][op]['global'] = handler - elif jid is not None and node is None: - self._handlers[ctype][op]['jid'][jid] = handler - else: - self._handlers[ctype][op]['node'][(jid, node)] = handler - - if default: - self.register_default(handler, ctype, op) - - def register_default(self, handler, ctype, op): - """Register a default, global handler for an operation. - - :param func handler: The default, global handler for the operation. - :param string ctype: The name of the API to modify. - :param string op: The API operation to use. - """ - self._setup(ctype, op) - self._handler_defaults[ctype][op] = handler - - def unregister(self, ctype, op, jid=None, node=None): - """Remove an API callback. - - The API callback chosen for removal is based on the - specificity of the provided JID+node combination. - - See :meth:`~ApiRegistry.run` for more details. - - :param string ctype: The name of the API to use. - :param string op: The API operation to perform. - :param JID jid: Optionally provide specific JID. - :param string node: Optionally provide specific node. - """ - self._setup(ctype, op) - self.register(None, ctype, op, jid, node) - - def restore_default(self, ctype, op, jid=None, node=None): - """Reset an API callback to use a default handler. - - :param string ctype: The name of the API to use. - :param string op: The API operation to perform. - :param JID jid: Optionally provide specific JID. - :param string node: Optionally provide specific node. - """ - self.unregister(ctype, op, jid, node) - self.register(self._handler_defaults[ctype][op], ctype, op, jid, node) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py deleted file mode 100644 index bf0ae4df..00000000 --- a/sleekxmpp/basexmpp.py +++ /dev/null @@ -1,832 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.basexmpp - ~~~~~~~~~~~~~~~~~~ - - This module provides the common XMPP functionality - for both clients and components. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import with_statement, unicode_literals - -import sys -import logging -import threading - -from sleekxmpp import plugins, roster, stanza -from sleekxmpp.api import APIRegistry -from sleekxmpp.exceptions import IqError, IqTimeout - -from sleekxmpp.stanza import Message, Presence, Iq, StreamError -from sleekxmpp.stanza.roster import Roster -from sleekxmpp.stanza.nick import Nick -from sleekxmpp.stanza.htmlim import HTMLIM - -from sleekxmpp.xmlstream import XMLStream, JID -from sleekxmpp.xmlstream import ET, register_stanza_plugin -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.stanzabase import XML_NS - -from sleekxmpp.plugins import PluginManager, load_plugin - - -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): - from sleekxmpp.util.misc_ops import setdefaultencoding - 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. - - :param default_ns: Ensure that the correct default XML namespace - is used during initialization. - """ - - def __init__(self, jid='', default_ns='jabber:client'): - XMLStream.__init__(self) - - self.default_ns = default_ns - self.stream_ns = 'http://etherx.jabber.org/streams' - self.namespace_map[self.stream_ns] = 'stream' - - #: An identifier for the stream as given by the server. - self.stream_id = None - - #: The JabberID (JID) requested for this connection. - self.requested_jid = JID(jid, cache_lock=True) - - #: The JabberID (JID) used by this connection, - #: as set after session binding. This may even be a - #: different bare JID than what was requested. - self.boundjid = JID(jid, cache_lock=True) - - self._expected_server_name = self.boundjid.host - self._redirect_attempts = 0 - - #: The maximum number of consecutive see-other-host - #: redirections that will be followed before quitting. - self.max_redirects = 5 - - self.session_bind_event = threading.Event() - - #: A dictionary mapping plugin names to plugins. - self.plugin = PluginManager(self) - - #: Configuration options for whitelisted plugins. - #: If a plugin is registered without any configuration, - #: and there is an entry here, it will be used. - self.plugin_config = {} - - #: A list of plugins that will be loaded if - #: :meth:`register_plugins` is called. - self.plugin_whitelist = [] - - #: The main roster object. This roster supports multiple - #: owner JIDs, as in the case for components. For clients - #: which only have a single JID, see :attr:`client_roster`. - self.roster = roster.Roster(self) - self.roster.add(self.boundjid) - - #: The single roster for the bound JID. This is the - #: equivalent of:: - #: - #: self.roster[self.boundjid.bare] - self.client_roster = self.roster[self.boundjid] - - #: The distinction between clients and components can be - #: important, primarily for choosing how to handle the - #: ``'to'`` and ``'from'`` JIDs of stanzas. - self.is_component = False - - #: Messages may optionally be tagged with ID values. Setting - #: :attr:`use_message_ids` to `True` will assign all outgoing - #: messages an ID. Some plugin features require enabling - #: this option. - self.use_message_ids = False - - #: Presence updates may optionally be tagged with ID values. - #: Setting :attr:`use_message_ids` to `True` will assign all - #: outgoing messages an ID. - self.use_presence_ids = False - - #: The API registry is a way to process callbacks based on - #: JID+node combinations. Each callback in the registry is - #: marked with: - #: - #: - An API name, e.g. xep_0030 - #: - The name of an action, e.g. get_info - #: - The JID that will be affected - #: - The node that will be affected - #: - #: API handlers with no JID or node will act as global handlers, - #: while those with a JID and no node will service all nodes - #: for a JID, and handlers with both a JID and node will be - #: used only for that specific combination. The handler that - #: provides the most specificity will be used. - self.api = APIRegistry(self) - - #: Flag indicating that the initial presence broadcast has - #: been sent. Until this happens, some servers may not - #: behave as expected when sending stanzas. - self.sentpresence = False - - #: A reference to :mod:`sleekxmpp.stanza` to make accessing - #: stanza classes easier. - self.stanza = stanza - - 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.register_handler( - Callback('Stream Error', - MatchXPath("{%s}error" % self.stream_ns), - self._handle_stream_error)) - - self.add_event_handler('session_start', - self._handle_session_start) - self.add_event_handler('disconnected', - self._handle_disconnected) - self.add_event_handler('presence_available', - self._handle_available) - self.add_event_handler('presence_dnd', - self._handle_available) - self.add_event_handler('presence_xa', - self._handle_available) - self.add_event_handler('presence_chat', - self._handle_available) - self.add_event_handler('presence_away', - self._handle_available) - self.add_event_handler('presence_unavailable', - self._handle_unavailable) - self.add_event_handler('presence_subscribe', - self._handle_subscribe) - self.add_event_handler('presence_subscribed', - self._handle_subscribed) - self.add_event_handler('presence_unsubscribe', - self._handle_unsubscribe) - self.add_event_handler('presence_unsubscribed', - self._handle_unsubscribed) - self.add_event_handler('roster_subscription_request', - self._handle_new_subscription) - - # Set up the XML stream with XMPP's root stanzas. - self.register_stanza(Message) - self.register_stanza(Iq) - self.register_stanza(Presence) - self.register_stanza(StreamError) - - # Initialize a few default stanza plugins. - register_stanza_plugin(Iq, Roster) - register_stanza_plugin(Message, Nick) - - def start_stream_handler(self, xml): - """Save the stream ID once the streams have been established. - - :param xml: The incoming stream's root element. - """ - self.stream_id = xml.get('id', '') - self.stream_version = xml.get('version', '') - self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) - - if not self.is_component and not self.stream_version: - log.warning('Legacy XMPP 0.9 protocol detected.') - self.event('legacy_protocol') - - def process(self, *args, **kwargs): - """Initialize plugins and begin processing the XML stream. - - The number of threads used for processing stream events is determined - by :data:`HANDLER_THREADS`. - - :param bool block: If ``False``, then event dispatcher will run - in a separate thread, allowing for the stream to be - used in the background for another application. - Otherwise, ``process(block=True)`` blocks the current - thread. Defaults to ``False``. - :param bool threaded: **DEPRECATED** - If ``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``. This does **not** mean that no - threads are used at all if ``threaded=False``. - - Regardless of these threading options, these threads will - always exist: - - - The event queue processor - - The send queue processor - - The scheduler - """ - for name in self.plugin: - if not hasattr(self.plugin[name], 'post_inited'): - if hasattr(self.plugin[name], 'post_init'): - self.plugin[name].post_init() - self.plugin[name].post_inited = True - return XMLStream.process(self, *args, **kwargs) - - def register_plugin(self, plugin, pconfig={}, module=None): - """Register and configure a plugin for use in this stream. - - :param plugin: The name of the plugin class. Plugin names must - be unique. - :param pconfig: A dictionary of configuration data for the plugin. - Defaults to an empty dictionary. - :param module: Optional refence to the module containing the plugin - class if using custom plugins. - """ - - # Use the global plugin config cache, if applicable - if not pconfig: - pconfig = self.plugin_config.get(plugin, {}) - - if not self.plugin.registered(plugin): - load_plugin(plugin, module) - self.plugin.enable(plugin, pconfig) - - def register_plugins(self): - """Register and initialize all built-in plugins. - - Optionally, the list of plugins loaded may be limited to those - contained in :attr:`plugin_whitelist`. - - Plugin configurations stored in :attr:`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) - else: - raise NameError("Plugin %s not in plugins.__all__." % plugin) - - 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.""" - msg = Message(self, *args, **kwargs) - msg['lang'] = self.default_lang - return msg - - 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.""" - pres = Presence(self, *args, **kwargs) - pres['lang'] = self.default_lang - return pres - - def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): - """Create a new Iq stanza with a given Id and from JID. - - :param id: An ideally unique ID value for this stanza thread. - Defaults to 0. - :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type, - one of: ``'get'``, ``'set'``, ``'result'``, - or ``'error'``. - :param iquery: Optional namespace for adding a query element. - """ - iq = self.Iq() - iq['id'] = str(id) - iq['to'] = ito - iq['from'] = ifrom - iq['type'] = itype - iq['query'] = iquery - return iq - - def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None): - """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``. - - Optionally, a query element may be added. - - :param queryxmlns: The namespace of the query to use. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - :param iq: Optionally use an existing stanza instead - of generating a new one. - """ - if not iq: - iq = self.Iq() - iq['type'] = 'get' - iq['query'] = queryxmlns - if ito: - iq['to'] = ito - if ifrom: - iq['from'] = ifrom - return iq - - def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): - """ - Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type - ``'result'`` with the given ID value. - - :param id: An ideally unique ID value. May use :meth:`new_id()`. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - :param iq: Optionally use an existing stanza instead - of generating a new one. - """ - if not iq: - iq = self.Iq() - if id is None: - id = self.new_id() - iq['id'] = id - iq['type'] = 'result' - if ito: - iq['to'] = ito - if ifrom: - iq['from'] = ifrom - return iq - - def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None): - """ - Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``. - - Optionally, a substanza may be given to use as the - stanza's payload. - - :param sub: Either an - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza object or an - :class:`~xml.etree.ElementTree.Element` XML object - to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - :param iq: Optionally use an existing stanza instead - of generating a new one. - """ - if not iq: - iq = self.Iq() - iq['type'] = 'set' - if sub != None: - iq.append(sub) - if ito: - iq['to'] = ito - if ifrom: - iq['from'] = ifrom - return iq - - def make_iq_error(self, id, type='cancel', - condition='feature-not-implemented', - text=None, ito=None, ifrom=None, iq=None): - """ - Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. - - :param id: An ideally unique ID value. May use :meth:`new_id()`. - :param type: The type of the error, such as ``'cancel'`` or - ``'modify'``. Defaults to ``'cancel'``. - :param condition: The error condition. Defaults to - ``'feature-not-implemented'``. - :param text: A message describing the cause of the error. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - :param iq: Optionally use an existing stanza instead - of generating a new one. - """ - if not iq: - iq = self.Iq() - iq['id'] = id - iq['error']['type'] = type - iq['error']['condition'] = condition - iq['error']['text'] = text - if ito: - iq['to'] = ito - if ifrom: - iq['from'] = ifrom - return iq - - def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): - """ - Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza - to use the given query namespace. - - :param iq: Optionally use an existing stanza instead - of generating a new one. - :param xmlns: The query's namespace. - :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` - for this stanza. - :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` - to use for this stanza. - """ - if not iq: - iq = self.Iq() - iq['query'] = xmlns - if ito: - iq['to'] = ito - if ifrom: - iq['from'] = ifrom - return iq - - def make_query_roster(self, iq=None): - """Create a roster query element. - - :param iq: Optionally use an existing stanza instead - of generating a new one. - """ - 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 - :class:`~sleekxmpp.stanza.message.Message` stanza. - - :param mto: The recipient of the message. - :param mbody: The main contents of the message. - :param msubject: Optional subject for the message. - :param mtype: The message's type, such as ``'chat'`` or - ``'groupchat'``. - :param mhtml: Optional HTML body content in the form of a string. - :param 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. - :param 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, pnick=None): - """ - Create and initialize a new - :class:`~sleekxmpp.stanza.presence.Presence` stanza. - - :param pshow: The presence's show value. - :param pstatus: The presence's status message. - :param ppriority: This connection's priority. - :param pto: The recipient of a directed presence. - :param ptype: The type of presence, such as ``'subscribe'``. - :param pfrom: The sender of the presence. - :param pnick: Optional nickname of the presence's sender. - """ - presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) - if pshow is not None: - presence['type'] = pshow - if pfrom is None and self.is_component: - presence['from'] = self.boundjid.full - presence['priority'] = ppriority - presence['status'] = pstatus - presence['nick'] = pnick - return presence - - def send_message(self, mto, mbody, msubject=None, mtype=None, - mhtml=None, mfrom=None, mnick=None): - """ - Create, initialize, and send a new - :class:`~sleekxmpp.stanza.message.Message` stanza. - - :param mto: The recipient of the message. - :param mbody: The main contents of the message. - :param msubject: Optional subject for the message. - :param mtype: The message's type, such as ``'chat'`` or - ``'groupchat'``. - :param mhtml: Optional HTML body content in the form of a string. - :param 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. - :param mnick: Optional nickname of the sender. - """ - self.make_message(mto, mbody, msubject, mtype, - mhtml, mfrom, mnick).send() - - def send_presence(self, pshow=None, pstatus=None, ppriority=None, - pto=None, pfrom=None, ptype=None, pnick=None): - """ - Create, initialize, and send a new - :class:`~sleekxmpp.stanza.presence.Presence` stanza. - - :param pshow: The presence's show value. - :param pstatus: The presence's status message. - :param ppriority: This connection's priority. - :param pto: The recipient of a directed presence. - :param ptype: The type of presence, such as ``'subscribe'``. - :param pfrom: The sender of the presence. - :param pnick: Optional nickname of the presence's sender. - """ - self.make_presence(pshow, pstatus, ppriority, pto, - ptype, pfrom, pnick).send() - - def send_presence_subscription(self, pto, pfrom=None, - ptype='subscribe', pnick=None): - """ - Create, initialize, and send a new - :class:`~sleekxmpp.stanza.presence.Presence` stanza of - type ``'subscribe'``. - - :param pto: The recipient of a directed presence. - :param pfrom: The sender of the presence. - :param ptype: The type of presence, such as ``'subscribe'``. - :param pnick: Optional nickname of the presence's sender. - """ - self.make_presence(ptype=ptype, - pfrom=pfrom, - pto=JID(pto).bare, - pnick=pnick).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.resource") - 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 - - @property - def auto_authorize(self): - """Auto accept or deny subscription requests. - - If ``True``, auto accept subscription requests. - If ``False``, auto deny subscription requests. - If ``None``, don't automatically respond. - """ - return self.roster.auto_authorize - - @auto_authorize.setter - def auto_authorize(self, value): - self.roster.auto_authorize = value - - @property - def auto_subscribe(self): - """Auto send requests for mutual subscriptions. - - If ``True``, auto send mutual subscription requests. - """ - return self.roster.auto_subscribe - - @auto_subscribe.setter - def auto_subscribe(self, value): - self.roster.auto_subscribe = 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 = JID(jid, cache_lock=True) - - 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_session_start(self, event): - """Reset redirection attempt count.""" - self._redirect_attempts = 0 - - def _handle_disconnected(self, event): - """When disconnected, reset the roster""" - self.roster.reset() - self.session_bind_event.clear() - - def _handle_stream_error(self, error): - self.event('stream_error', error) - - if error['condition'] == 'see-other-host': - other_host = error['see_other_host'] - if not other_host: - log.warning("No other host specified.") - return - - if self._redirect_attempts > self.max_redirects: - log.error("Exceeded maximum number of redirection attempts.") - return - - self._redirect_attempts += 1 - - host = other_host - port = 5222 - - if '[' in other_host and ']' in other_host: - host = other_host.split(']')[0][1:] - elif ':' in other_host: - host = other_host.split(':')[0] - - port_sec = other_host.split(']')[-1] - if ':' in port_sec: - port = int(port_sec.split(':')[1]) - - self.address = (host, port) - self.default_domain = host - self.dns_records = None - self.reconnect_delay = None - self.reconnect() - - def _handle_message(self, msg): - """Process incoming message stanzas.""" - if not self.is_component and not msg['to'].bare: - msg['to'] = self.boundjid - self.event('message', msg) - - def _handle_available(self, pres): - self.roster[pres['to']][pres['from']].handle_available(pres) - - def _handle_unavailable(self, pres): - self.roster[pres['to']][pres['from']].handle_unavailable(pres) - - def _handle_new_subscription(self, pres): - """Attempt to automatically handle subscription requests. - - Subscriptions will be approved if the request is from - a whitelisted JID, of :attr:`auto_authorize` is True. They - will be rejected if :attr:`auto_authorize` is False. Setting - :attr:`auto_authorize` to ``None`` will disable automatic - subscription handling (except for whitelisted JIDs). - - If a subscription is accepted, a request for a mutual - subscription will be sent if :attr:`auto_subscribe` is ``True``. - """ - roster = self.roster[pres['to']] - item = self.roster[pres['to']][pres['from']] - if item['whitelisted']: - item.authorize() - if roster.auto_subscribe: - item.subscribe() - elif roster.auto_authorize: - item.authorize() - if roster.auto_subscribe: - item.subscribe() - elif roster.auto_authorize == False: - item.unauthorize() - - def _handle_removed_subscription(self, pres): - self.roster[pres['to']][pres['from']].handle_unauthorize(pres) - - def _handle_subscribe(self, pres): - self.roster[pres['to']][pres['from']].handle_subscribe(pres) - - def _handle_subscribed(self, pres): - self.roster[pres['to']][pres['from']].handle_subscribed(pres) - - def _handle_unsubscribe(self, pres): - self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) - - def _handle_unsubscribed(self, pres): - self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) - - def _handle_presence(self, presence): - """Process incoming presence stanzas. - - Update the roster with presence information. - """ - if not self.is_component and not presence['to'].bare: - presence['to'] = self.boundjid - - self.event('presence', presence) - 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 - - def exception(self, exception): - """Process any uncaught exceptions, notably - :class:`~sleekxmpp.exceptions.IqError` and - :class:`~sleekxmpp.exceptions.IqTimeout` exceptions. - - :param exception: An unhandled :class:`Exception` object. - """ - if isinstance(exception, IqError): - iq = exception.iq - log.error('%s: %s', iq['error']['condition'], - iq['error']['text']) - log.warning('You should catch IqError exceptions') - elif isinstance(exception, IqTimeout): - iq = exception.iq - log.error('Request timed out: %s', iq) - log.warning('You should catch IqTimeout exceptions') - elif isinstance(exception, SyntaxError): - # Hide stream parsing errors that occur when the - # stream is disconnected (they've been handled, we - # don't need to make a mess in the logs). - pass - else: - log.exception(exception) - - -# Restore the old, lowercased name for backwards compatibility. -basexmpp = BaseXMPP - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -BaseXMPP.registerPlugin = BaseXMPP.register_plugin -BaseXMPP.makeIq = BaseXMPP.make_iq -BaseXMPP.makeIqGet = BaseXMPP.make_iq_get -BaseXMPP.makeIqResult = BaseXMPP.make_iq_result -BaseXMPP.makeIqSet = BaseXMPP.make_iq_set -BaseXMPP.makeIqError = BaseXMPP.make_iq_error -BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query -BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster -BaseXMPP.makeMessage = BaseXMPP.make_message -BaseXMPP.makePresence = BaseXMPP.make_presence -BaseXMPP.sendMessage = BaseXMPP.send_message -BaseXMPP.sendPresence = BaseXMPP.send_presence -BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py deleted file mode 100644 index f837c0f2..00000000 --- a/sleekxmpp/clientxmpp.py +++ /dev/null @@ -1,333 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.clientxmpp - ~~~~~~~~~~~~~~~~~~~~ - - This module provides XMPP functionality that - is specific to client connections. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import absolute_import, unicode_literals - -import logging - -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import XMLStream -from sleekxmpp.xmlstream.matcher import StanzaPath, MatchXPath -from sleekxmpp.xmlstream.handler import Callback - -# Flag indicating if DNS SRV records are available for use. -try: - import dns.resolver -except ImportError: - DNSPYTHON = False -else: - DNSPYTHON = True - - -log = logging.getLogger(__name__) - - -class ClientXMPP(BaseXMPP): - - """ - SleekXMPP's client class. (Use only for good, not for evil.) - - Typical use pattern: - - .. code-block:: python - - xmpp = ClientXMPP('user@server.tld/resource', 'password') - # ... Register plugins and event handlers ... - xmpp.connect() - xmpp.process(block=False) # block=True will block the current - # thread. By default, block=False - - :param jid: The JID of the XMPP user account. - :param password: The password for the XMPP user account. - :param ssl: **Deprecated.** - :param plugin_config: A dictionary of plugin configurations. - :param plugin_whitelist: A list of approved plugins that - will be loaded when calling - :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. - :param escape_quotes: **Deprecated.** - """ - - def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], - escape_quotes=True, sasl_mech=None, lang='en'): - BaseXMPP.__init__(self, jid, 'jabber:client') - - self.escape_quotes = escape_quotes - self.plugin_config = plugin_config - self.plugin_whitelist = plugin_whitelist - self.default_port = 5222 - self.default_lang = lang - - self.credentials = {} - - self.password = password - - self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % ( - self.boundjid.host, - "xmlns:stream='%s'" % self.stream_ns, - "xmlns='%s'" % self.default_ns, - "xml:lang='%s'" % self.default_lang, - "version='1.0'") - self.stream_footer = "</stream:stream>" - - self.features = set() - self._stream_feature_handlers = {} - self._stream_feature_order = [] - - self.dns_service = 'xmpp-client' - - #TODO: Use stream state here - self.authenticated = False - self.sessionstarted = False - self.bound = False - self.bindfail = False - - self.add_event_handler('connected', self._reset_connection_state) - self.add_event_handler('session_bind', self._handle_session_bind) - self.add_event_handler('roster_update', self._handle_roster) - - self.register_stanza(StreamFeatures) - - self.register_handler( - Callback('Stream Features', - MatchXPath('{%s}features' % self.stream_ns), - self._handle_stream_features)) - self.register_handler( - Callback('Roster Update', - StanzaPath('iq@type=set/roster'), - lambda iq: self.event('roster_update', iq))) - - # Setup default stream features - self.register_plugin('feature_starttls') - self.register_plugin('feature_bind') - self.register_plugin('feature_session') - self.register_plugin('feature_rosterver') - self.register_plugin('feature_preapproval') - self.register_plugin('feature_mechanisms') - - if sasl_mech: - self['feature_mechanisms'].use_mech = sasl_mech - - @property - def password(self): - return self.credentials.get('password', '') - - @password.setter - def password(self, value): - self.credentials['password'] = value - - def connect(self, address=tuple(), reattempt=True, - use_tls=True, use_ssl=False): - """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. - - :param address: A tuple containing the server's host and port. - :param reattempt: If ``True``, repeat attempting to connect if an - error occurs. Defaults to ``True``. - :param use_tls: Indicates if TLS should be used for the - connection. Defaults to ``True``. - :param use_ssl: Indicates if the older SSL connection method - should be used. Defaults to ``False``. - """ - self.session_started_event.clear() - - # If an address was provided, disable using DNS SRV lookup; - # otherwise, use the domain from the client JID with the standard - # XMPP client port and allow SRV lookup. - if address: - self.dns_service = None - else: - address = (self.boundjid.host, 5222) - self.dns_service = 'xmpp-client' - - return XMLStream.connect(self, address[0], address[1], - use_tls=use_tls, use_ssl=use_ssl, - reattempt=reattempt) - - def register_feature(self, name, handler, restart=False, order=5000): - """Register a stream feature handler. - - :param name: The name of the stream feature. - :param handler: The function to execute if the feature is received. - :param restart: Indicates if feature processing should halt with - this feature. Defaults to ``False``. - :param order: The relative ordering in which the feature should - be negotiated. Lower values will be attempted - earlier when available. - """ - self._stream_feature_handlers[name] = (handler, restart) - self._stream_feature_order.append((order, name)) - self._stream_feature_order.sort() - - def unregister_feature(self, name, order): - if name in self._stream_feature_handlers: - del self._stream_feature_handlers[name] - self._stream_feature_order.remove((order, name)) - self._stream_feature_order.sort() - - def update_roster(self, jid, **kwargs): - """Add or change a roster item. - - :param jid: The JID of the entry to modify. - :param name: The user's nickname for this JID. - :param subscription: The subscription status. May be one of - ``'to'``, ``'from'``, ``'both'``, or - ``'none'``. If set to ``'remove'``, - the entry will be deleted. - :param groups: The roster groups that contain this item. - :param block: Specify if the roster request will block - until a response is received, or a timeout - occurs. Defaults to ``True``. - :param timeout: The length of time (in seconds) to wait - for a response before continuing if blocking - is used. Defaults to - :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. - :param callback: Optional reference to a stream handler function. - Will be executed when the roster is received. - Implies ``block=False``. - """ - current = self.client_roster[jid] - - name = kwargs.get('name', current['name']) - subscription = kwargs.get('subscription', current['subscription']) - groups = kwargs.get('groups', current['groups']) - - block = kwargs.get('block', True) - timeout = kwargs.get('timeout', None) - callback = kwargs.get('callback', None) - - return self.client_roster.update(jid, name, subscription, groups, - block, timeout, callback) - - def del_roster_item(self, jid): - """Remove an item from the roster. - - This is done by setting its subscription status to ``'remove'``. - - :param jid: The JID of the item to remove. - """ - return self.client_roster.remove(jid) - - def get_roster(self, block=True, timeout=None, callback=None): - """Request the roster from the server. - - :param block: Specify if the roster request will block until a - response is received, or a timeout occurs. - Defaults to ``True``. - :param timeout: The length of time (in seconds) to wait for a response - before continuing if blocking is used. - Defaults to - :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. - :param callback: Optional reference to a stream handler function. Will - be executed when the roster is received. - Implies ``block=False``. - """ - iq = self.Iq() - iq['type'] = 'get' - iq.enable('roster') - if 'rosterver' in self.features: - iq['roster']['ver'] = self.client_roster.version - - - if not block or callback is not None: - block = False - if callback is None: - callback = lambda resp: self.event('roster_update', resp) - else: - orig_cb = callback - def wrapped(resp): - self.event('roster_update', resp) - orig_cb(resp) - callback = wrapped - - response = iq.send(block, timeout, callback) - - if block: - self.event('roster_update', response) - return response - - def _reset_connection_state(self, event=None): - #TODO: Use stream state here - self.authenticated = False - self.sessionstarted = False - self.bound = False - self.bindfail = False - self.features = set() - - def _handle_stream_features(self, features): - """Process the received stream features. - - :param features: The features stanza. - """ - for order, name in self._stream_feature_order: - if name in features['features']: - handler, restart = self._stream_feature_handlers[name] - if handler(features) and restart: - # Don't continue if the feature requires - # restarting the XML stream. - return True - log.debug('Finished processing stream features.') - self.event('stream_negotiated') - - def _handle_roster(self, iq): - """Update the roster after receiving a roster stanza. - - :param iq: The roster stanza. - """ - if iq['type'] == 'set': - if iq['from'].bare and iq['from'].bare != self.boundjid.bare: - raise XMPPError(condition='service-unavailable') - - roster = self.client_roster - if iq['roster']['ver']: - roster.version = iq['roster']['ver'] - items = iq['roster']['items'] - - valid_subscriptions = ('to', 'from', 'both', 'none', 'remove') - for jid, item in items.items(): - if item['subscription'] in valid_subscriptions: - roster[jid]['name'] = item['name'] - roster[jid]['groups'] = item['groups'] - roster[jid]['from'] = item['subscription'] in ('from', 'both') - roster[jid]['to'] = item['subscription'] in ('to', 'both') - roster[jid]['pending_out'] = (item['ask'] == 'subscribe') - - roster[jid].save(remove=(item['subscription'] == 'remove')) - - if iq['type'] == 'set': - resp = self.Iq(stype='result', - sto=iq['from'], - sid=iq['id']) - resp.enable('roster') - resp.send() - - def _handle_session_bind(self, jid): - """Set the client roster to the JID set by the server. - - :param :class:`sleekxmpp.xmlstream.jid.JID` jid: The bound JID as - dictated by the server. The same as :attr:`boundjid`. - """ - self.client_roster = self.roster[jid] - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -ClientXMPP.updateRoster = ClientXMPP.update_roster -ClientXMPP.delRosterItem = ClientXMPP.del_roster_item -ClientXMPP.getRoster = ClientXMPP.get_roster -ClientXMPP.registerFeature = ClientXMPP.register_feature diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py deleted file mode 100644 index bac455e2..00000000 --- a/sleekxmpp/componentxmpp.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.clientxmpp - ~~~~~~~~~~~~~~~~~~~~ - - This module provides XMPP functionality that - is specific to external server component connections. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import absolute_import - -import logging -import sys -import hashlib - -from sleekxmpp.basexmpp import BaseXMPP -from sleekxmpp.xmlstream import XMLStream -from sleekxmpp.xmlstream import ET -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.xmlstream.handler import Callback - - -log = logging.getLogger(__name__) - - -class ComponentXMPP(BaseXMPP): - - """ - SleekXMPP's basic XMPP server component. - - Use only for good, not for evil. - - :param jid: The JID of the component. - :param secret: The secret or password for the component. - :param host: The server accepting the component. - :param port: The port used to connect to the server. - :param plugin_config: A dictionary of plugin configurations. - :param plugin_whitelist: A list of approved plugins that - will be loaded when calling - :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. - :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace - should be used instead of the standard - ``'jabber:component:accept'`` namespace. - Defaults to ``False``. - """ - - def __init__(self, jid, secret, host=None, port=None, - plugin_config={}, plugin_whitelist=[], use_jc_ns=False): - if use_jc_ns: - default_ns = 'jabber:client' - else: - default_ns = 'jabber:component:accept' - BaseXMPP.__init__(self, jid, 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.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)) - self.add_event_handler('presence_probe', - self._handle_probe) - - def connect(self, host=None, port=None, use_ssl=False, - use_tls=False, reattempt=True): - """Connect to the server. - - Setting ``reattempt`` to ``True`` will cause connection attempts to - be made every second until a successful connection is established. - - :param host: The name of the desired server for the connection. - Defaults to :attr:`server_host`. - :param port: Port to connect to on the server. - Defauts to :attr:`server_port`. - :param use_ssl: Flag indicating if SSL should be used by connecting - directly to a port using SSL. - :param use_tls: Flag indicating if TLS should be used, allowing for - connecting to a port without using SSL immediately and - later upgrading the connection. - :param reattempt: Flag indicating if the socket should reconnect - after disconnections. - """ - if host is None: - host = self.server_host - if port is None: - port = self.server_port - - self.server_name = self.boundjid.host - - if use_tls: - log.info("XEP-0114 components can not use TLS") - - log.debug("Connecting to %s:%s", host, port) - return XMLStream.connect(self, host=host, port=port, - use_ssl=use_ssl, - use_tls=False, - reattempt=reattempt) - - def incoming_filter(self, xml): - """ - Pre-process incoming XML stanzas by converting any - ``'jabber:client'`` namespaced elements to the component's - default namespace. - - :param xml: The XML stanza to pre-process. - """ - if xml.tag.startswith('{jabber:client}'): - xml.tag = xml.tag.replace('jabber:client', self.default_ns) - 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. - - :param xml: The incoming stream's root element. - """ - BaseXMPP.start_stream_handler(self, xml) - - # 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, now=True) - - def _handle_handshake(self, xml): - """The handshake has been accepted. - - :param xml: The reply handshake stanza. - """ - self.session_bind_event.set() - self.session_started_event.set() - self.event('session_bind', self.boundjid, direct=True) - self.event('session_start') - - def _handle_probe(self, pres): - self.roster[pres['to']][pres['from']].handle_probe(pres) diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py deleted file mode 100644 index 8a2aa75c..00000000 --- a/sleekxmpp/exceptions.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.exceptions - ~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - - -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 - :class:`~sleekxmpp.stanza.rootstanza.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. - - Extension information can be included to add additional XML elements - to the generated error stanza. - - :param condition: The XMPP defined error condition. - Defaults to ``'undefined-condition'``. - :param text: Human readable text describing the error. - :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``. - Defaults to ``'cancel'``. - :param extension: Tag name of the extension's XML content. - :param extension_ns: XML namespace of the extensions' XML content. - :param extension_args: Content and attributes for the extension - element. Same as the additional arguments to - the :class:`~xml.etree.ElementTree.Element` - constructor. - :param clear: Indicates if the stanza's contents should be - removed before replying with an error. - Defaults to ``True``. - """ - - def __init__(self, condition='undefined-condition', text='', - etype='cancel', extension=None, extension_ns=None, - extension_args=None, clear=True): - if extension_args is None: - extension_args = {} - - self.condition = condition - self.text = text - self.etype = etype - self.clear = clear - self.extension = extension - self.extension_ns = extension_ns - self.extension_args = extension_args - - -class IqTimeout(XMPPError): - - """ - An exception which indicates that an IQ request response has not been - received within the alloted time window. - """ - - def __init__(self, iq): - super(IqTimeout, self).__init__( - condition='remote-server-timeout', - etype='cancel') - - #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response - #: did not arrive before the timeout expired. - self.iq = iq - - -class IqError(XMPPError): - - """ - An exception raised when an Iq stanza of type 'error' is received - after making a blocking send call. - """ - - def __init__(self, iq): - super(IqError, self).__init__( - condition=iq['error']['condition'], - text=iq['error']['text'], - etype=iq['error']['type']) - - #: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza. - self.iq = iq diff --git a/sleekxmpp/features/__init__.py b/sleekxmpp/features/__init__.py deleted file mode 100644 index 869de7e9..00000000 --- a/sleekxmpp/features/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -__all__ = [ - 'feature_starttls', - 'feature_mechanisms', - 'feature_bind', - 'feature_session', - 'feature_rosterver', - 'feature_preapproval' -] diff --git a/sleekxmpp/features/feature_bind/__init__.py b/sleekxmpp/features/feature_bind/__init__.py deleted file mode 100644 index 9e0831dd..00000000 --- a/sleekxmpp/features/feature_bind/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_bind.bind import FeatureBind -from sleekxmpp.features.feature_bind.stanza import Bind - - -register_plugin(FeatureBind) - - -# Retain some backwards compatibility -feature_bind = FeatureBind diff --git a/sleekxmpp/features/feature_bind/bind.py b/sleekxmpp/features/feature_bind/bind.py deleted file mode 100644 index ee4c1e9b..00000000 --- a/sleekxmpp/features/feature_bind/bind.py +++ /dev/null @@ -1,65 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.jid import JID -from sleekxmpp.stanza import Iq, StreamFeatures -from sleekxmpp.features.feature_bind import stanza -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin - - -log = logging.getLogger(__name__) - - -class FeatureBind(BasePlugin): - - name = 'feature_bind' - description = 'RFC 6120: Stream Feature: Resource Binding' - dependencies = set() - stanza = stanza - - def plugin_init(self): - self.xmpp.register_feature('bind', - self._handle_bind_resource, - restart=False, - order=10000) - - register_stanza_plugin(Iq, stanza.Bind) - register_stanza_plugin(StreamFeatures, stanza.Bind) - - def _handle_bind_resource(self, features): - """ - Handle requesting a specific resource. - - Arguments: - features -- The stream features stanza. - """ - log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource) - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq.enable('bind') - if self.xmpp.requested_jid.resource: - iq['bind']['resource'] = self.xmpp.requested_jid.resource - response = iq.send(now=True) - - self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) - self.xmpp.bound = True - self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) - self.xmpp.session_bind_event.set() - - self.xmpp.features.add('bind') - - log.info("JID set to: %s", self.xmpp.boundjid.full) - - if 'session' not in features['features']: - log.debug("Established Session") - self.xmpp.sessionstarted = True - self.xmpp.session_started_event.set() - self.xmpp.event('session_start') diff --git a/sleekxmpp/features/feature_bind/stanza.py b/sleekxmpp/features/feature_bind/stanza.py deleted file mode 100644 index 8ce7536f..00000000 --- a/sleekxmpp/features/feature_bind/stanza.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class Bind(ElementBase): - - """ - """ - - name = 'bind' - namespace = 'urn:ietf:params:xml:ns:xmpp-bind' - interfaces = set(('resource', 'jid')) - sub_interfaces = interfaces - plugin_attrib = 'bind' diff --git a/sleekxmpp/features/feature_mechanisms/__init__.py b/sleekxmpp/features/feature_mechanisms/__init__.py deleted file mode 100644 index 9f7611ed..00000000 --- a/sleekxmpp/features/feature_mechanisms/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms -from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms -from sleekxmpp.features.feature_mechanisms.stanza import Auth -from sleekxmpp.features.feature_mechanisms.stanza import Success -from sleekxmpp.features.feature_mechanisms.stanza import Failure - - -register_plugin(FeatureMechanisms) - - -# Retain some backwards compatibility -feature_mechanisms = FeatureMechanisms diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py deleted file mode 100644 index 17ad5ed0..00000000 --- a/sleekxmpp/features/feature_mechanisms/mechanisms.py +++ /dev/null @@ -1,244 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import ssl -import logging - -from sleekxmpp.util import sasl -from sleekxmpp.util.stringprep_profiles import StringPrepError -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.features.feature_mechanisms import stanza - - -log = logging.getLogger(__name__) - - -class FeatureMechanisms(BasePlugin): - - name = 'feature_mechanisms' - description = 'RFC 6120: Stream Feature: SASL' - dependencies = set() - stanza = stanza - default_config = { - 'use_mech': None, - 'use_mechs': None, - 'min_mech': None, - 'sasl_callback': None, - 'security_callback': None, - 'encrypted_plain': True, - 'unencrypted_plain': False, - 'unencrypted_digest': False, - 'unencrypted_cram': False, - 'unencrypted_scram': True, - 'order': 100 - } - - def plugin_init(self): - if self.sasl_callback is None: - self.sasl_callback = self._default_credentials - - if self.security_callback is None: - self.security_callback = self._default_security - - creds = self.sasl_callback(set(['username']), set()) - if not self.use_mech and not creds['username']: - self.use_mech = 'ANONYMOUS' - - self.mech = None - self.mech_list = set() - self.attempted_mechs = set() - - register_stanza_plugin(StreamFeatures, stanza.Mechanisms) - - self.xmpp.register_stanza(stanza.Success) - self.xmpp.register_stanza(stanza.Failure) - self.xmpp.register_stanza(stanza.Auth) - self.xmpp.register_stanza(stanza.Challenge) - self.xmpp.register_stanza(stanza.Response) - self.xmpp.register_stanza(stanza.Abort) - - self.xmpp.register_handler( - Callback('SASL Success', - MatchXPath(stanza.Success.tag_name()), - self._handle_success, - instream=True)) - self.xmpp.register_handler( - Callback('SASL Failure', - MatchXPath(stanza.Failure.tag_name()), - self._handle_fail, - instream=True)) - self.xmpp.register_handler( - Callback('SASL Challenge', - MatchXPath(stanza.Challenge.tag_name()), - self._handle_challenge)) - - self.xmpp.register_feature('mechanisms', - self._handle_sasl_auth, - restart=True, - order=self.order) - - def _default_credentials(self, required_values, optional_values): - creds = self.xmpp.credentials - result = {} - values = required_values.union(optional_values) - for value in values: - if value == 'username': - result[value] = creds.get('username', self.xmpp.requested_jid.user) - elif value == 'email': - jid = self.xmpp.requested_jid.bare - result[value] = creds.get('email', jid) - elif value == 'channel_binding': - if hasattr(self.xmpp.socket, 'get_channel_binding'): - result[value] = self.xmpp.socket.get_channel_binding() - else: - log.debug("Channel binding not supported.") - log.debug("Use Python 3.3+ for channel binding and " + \ - "SCRAM-SHA-1-PLUS support") - result[value] = None - elif value == 'host': - result[value] = creds.get('host', self.xmpp.requested_jid.domain) - elif value == 'realm': - result[value] = creds.get('realm', self.xmpp.requested_jid.domain) - elif value == 'service-name': - result[value] = creds.get('service-name', self.xmpp._service_name) - elif value == 'service': - result[value] = creds.get('service', 'xmpp') - elif value in creds: - result[value] = creds[value] - return result - - def _default_security(self, values): - result = {} - for value in values: - if value == 'encrypted': - if 'starttls' in self.xmpp.features: - result[value] = True - elif isinstance(self.xmpp.socket, ssl.SSLSocket): - result[value] = True - else: - result[value] = False - else: - result[value] = self.config.get(value, False) - return result - - def _handle_sasl_auth(self, features): - """ - Handle authenticating using SASL. - - Arguments: - features -- The stream features stanza. - """ - if 'mechanisms' in self.xmpp.features: - # SASL authentication has already succeeded, but the - # server has incorrectly offered it again. - return False - - enforce_limit = False - limited_mechs = self.use_mechs - - if limited_mechs is None: - limited_mechs = set() - elif limited_mechs and not isinstance(limited_mechs, set): - limited_mechs = set(limited_mechs) - enforce_limit = True - - if self.use_mech: - limited_mechs.add(self.use_mech) - enforce_limit = True - - if enforce_limit: - self.use_mechs = limited_mechs - - self.mech_list = set(features['mechanisms']) - - return self._send_auth() - - def _send_auth(self): - mech_list = self.mech_list - self.attempted_mechs - try: - self.mech = sasl.choose(mech_list, - self.sasl_callback, - self.security_callback, - limit=self.use_mechs, - min_mech=self.min_mech) - except sasl.SASLNoAppropriateMechanism: - log.error("No appropriate login method.") - self.xmpp.event("no_auth", direct=True) - self.xmpp.event("failed_auth", direct=True) - self.attempted_mechs = set() - return self.xmpp.disconnect() - except StringPrepError: - log.exception("A credential value did not pass SASLprep.") - self.xmpp.disconnect() - - resp = stanza.Auth(self.xmpp) - resp['mechanism'] = self.mech.name - try: - resp['value'] = self.mech.process() - except sasl.SASLCancelled: - self.attempted_mechs.add(self.mech.name) - self._send_auth() - except sasl.SASLFailed: - self.attempted_mechs.add(self.mech.name) - self._send_auth() - except sasl.SASLMutualAuthFailed: - log.error("Mutual authentication failed! " + \ - "A security breach is possible.") - self.attempted_mechs.add(self.mech.name) - self.xmpp.disconnect() - else: - resp.send(now=True) - - return True - - def _handle_challenge(self, stanza): - """SASL challenge received. Process and send response.""" - resp = self.stanza.Response(self.xmpp) - try: - resp['value'] = self.mech.process(stanza['value']) - except sasl.SASLCancelled: - self.stanza.Abort(self.xmpp).send() - except sasl.SASLFailed: - self.stanza.Abort(self.xmpp).send() - except sasl.SASLMutualAuthFailed: - log.error("Mutual authentication failed! " + \ - "A security breach is possible.") - self.attempted_mechs.add(self.mech.name) - self.xmpp.disconnect() - else: - if resp.get_value() == '': - resp.del_value() - resp.send(now=True) - - def _handle_success(self, stanza): - """SASL authentication succeeded. Restart the stream.""" - try: - final = self.mech.process(stanza['value']) - except sasl.SASLMutualAuthFailed: - log.error("Mutual authentication failed! " + \ - "A security breach is possible.") - self.attempted_mechs.add(self.mech.name) - self.xmpp.disconnect() - else: - self.attempted_mechs = set() - self.xmpp.authenticated = True - self.xmpp.features.add('mechanisms') - self.xmpp.event('auth_success', stanza, direct=True) - raise RestartStream() - - def _handle_fail(self, stanza): - """SASL authentication failed. Disconnect and shutdown.""" - self.attempted_mechs.add(self.mech.name) - log.info("Authentication failed: %s", stanza['condition']) - self.xmpp.event("failed_auth", stanza, direct=True) - self._send_auth() - return True diff --git a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py b/sleekxmpp/features/feature_mechanisms/stanza/__init__.py deleted file mode 100644 index 38991d89..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms -from sleekxmpp.features.feature_mechanisms.stanza.auth import Auth -from sleekxmpp.features.feature_mechanisms.stanza.success import Success -from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure -from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge -from sleekxmpp.features.feature_mechanisms.stanza.response import Response -from sleekxmpp.features.feature_mechanisms.stanza.abort import Abort diff --git a/sleekxmpp/features/feature_mechanisms/stanza/abort.py b/sleekxmpp/features/feature_mechanisms/stanza/abort.py deleted file mode 100644 index aaca348d..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/abort.py +++ /dev/null @@ -1,24 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import StanzaBase - - -class Abort(StanzaBase): - - """ - """ - - name = 'abort' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set() - plugin_attrib = name - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() diff --git a/sleekxmpp/features/feature_mechanisms/stanza/auth.py b/sleekxmpp/features/feature_mechanisms/stanza/auth.py deleted file mode 100644 index 6b6f85a3..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/auth.py +++ /dev/null @@ -1,49 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import base64 - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import StanzaBase - - -class Auth(StanzaBase): - - """ - """ - - name = 'auth' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(('mechanism', 'value')) - plugin_attrib = name - - #: Some SASL mechs require sending values as is, - #: without converting base64. - plain_mechs = set(['X-MESSENGER-OAUTH2']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_value(self): - if not self['mechanism'] in self.plain_mechs: - return base64.b64decode(bytes(self.xml.text)) - else: - return self.xml.text - - def set_value(self, values): - if not self['mechanism'] in self.plain_mechs: - if values: - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') - elif values == b'': - self.xml.text = '=' - else: - self.xml.text = bytes(values).decode('utf-8') - - def del_value(self): - self.xml.text = '' diff --git a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py b/sleekxmpp/features/feature_mechanisms/stanza/challenge.py deleted file mode 100644 index 24290281..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/challenge.py +++ /dev/null @@ -1,39 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import base64 - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import StanzaBase - - -class Challenge(StanzaBase): - - """ - """ - - name = 'challenge' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(('value',)) - plugin_attrib = name - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_value(self): - return base64.b64decode(bytes(self.xml.text)) - - def set_value(self, values): - if values: - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') - else: - self.xml.text = '=' - - def del_value(self): - self.xml.text = '' diff --git a/sleekxmpp/features/feature_mechanisms/stanza/failure.py b/sleekxmpp/features/feature_mechanisms/stanza/failure.py deleted file mode 100644 index b9f32605..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/failure.py +++ /dev/null @@ -1,76 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import StanzaBase, ET - - -class Failure(StanzaBase): - - """ - """ - - name = 'failure' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(('condition', 'text')) - plugin_attrib = name - sub_interfaces = set(('text',)) - conditions = set(('aborted', 'account-disabled', 'credentials-expired', - 'encryption-required', 'incorrect-encoding', 'invalid-authzid', - 'invalid-mechanism', 'malformed-request', 'mechansism-too-weak', - 'not-authorized', 'temporary-auth-failure')) - - 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. - """ - # StanzaBase overrides self.namespace - self.namespace = Failure.namespace - - if StanzaBase.setup(self, xml): - #If we had to generate XML then set default values. - self['condition'] = 'not-authorized' - - self.xml.tag = self.tag_name() - - def get_condition(self): - """Return the condition element's name.""" - for child in self.xml: - if "{%s}" % self.namespace in child.tag: - cond = child.tag.split('}', 1)[-1] - if cond in self.conditions: - return cond - return 'not-authorized' - - 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.namespace, value))) - return self - - def del_condition(self): - """Remove the condition element.""" - for child in self.xml: - 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 diff --git a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py deleted file mode 100644 index bbd56813..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py +++ /dev/null @@ -1,53 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class Mechanisms(ElementBase): - - """ - """ - - name = 'mechanisms' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(('mechanisms', 'required')) - plugin_attrib = name - is_extension = True - - def get_required(self): - """ - """ - return True - - def get_mechanisms(self): - """ - """ - results = [] - mechs = self.findall('{%s}mechanism' % self.namespace) - if mechs: - for mech in mechs: - results.append(mech.text) - return results - - def set_mechanisms(self, values): - """ - """ - self.del_mechanisms() - for val in values: - mech = ET.Element('{%s}mechanism' % self.namespace) - mech.text = val - self.append(mech) - - def del_mechanisms(self): - """ - """ - mechs = self.findall('{%s}mechanism' % self.namespace) - if mechs: - for mech in mechs: - self.xml.remove(mech) diff --git a/sleekxmpp/features/feature_mechanisms/stanza/response.py b/sleekxmpp/features/feature_mechanisms/stanza/response.py deleted file mode 100644 index ca7624f1..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/response.py +++ /dev/null @@ -1,39 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import base64 - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import StanzaBase - - -class Response(StanzaBase): - - """ - """ - - name = 'response' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(('value',)) - plugin_attrib = name - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_value(self): - return base64.b64decode(bytes(self.xml.text)) - - def set_value(self, values): - if values: - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') - else: - self.xml.text = '=' - - def del_value(self): - self.xml.text = '' diff --git a/sleekxmpp/features/feature_mechanisms/stanza/success.py b/sleekxmpp/features/feature_mechanisms/stanza/success.py deleted file mode 100644 index 7a4eab8e..00000000 --- a/sleekxmpp/features/feature_mechanisms/stanza/success.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import base64 - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import StanzaBase - -class Success(StanzaBase): - - """ - """ - - name = 'success' - namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' - interfaces = set(['value']) - plugin_attrib = name - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_value(self): - return base64.b64decode(bytes(self.xml.text)) - - def set_value(self, values): - if values: - self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') - else: - self.xml.text = '=' - - def del_value(self): - self.xml.text = '' diff --git a/sleekxmpp/features/feature_preapproval/__init__.py b/sleekxmpp/features/feature_preapproval/__init__.py deleted file mode 100644 index ae8b6b70..00000000 --- a/sleekxmpp/features/feature_preapproval/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval -from sleekxmpp.features.feature_preapproval.stanza import PreApproval - - -register_plugin(FeaturePreApproval) diff --git a/sleekxmpp/features/feature_preapproval/preapproval.py b/sleekxmpp/features/feature_preapproval/preapproval.py deleted file mode 100644 index c7106ed3..00000000 --- a/sleekxmpp/features/feature_preapproval/preapproval.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.features.feature_preapproval import stanza -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin - - -log = logging.getLogger(__name__) - - -class FeaturePreApproval(BasePlugin): - - name = 'feature_preapproval' - description = 'RFC 6121: Stream Feature: Subscription Pre-Approval' - dependences = set() - stanza = stanza - - def plugin_init(self): - self.xmpp.register_feature('preapproval', - self._handle_preapproval, - restart=False, - order=9001) - - register_stanza_plugin(StreamFeatures, stanza.PreApproval) - - def _handle_preapproval(self, features): - """Save notice that the server support subscription pre-approvals. - - Arguments: - features -- The stream features stanza. - """ - log.debug("Server supports subscription pre-approvals.") - self.xmpp.features.add('preapproval') diff --git a/sleekxmpp/features/feature_preapproval/stanza.py b/sleekxmpp/features/feature_preapproval/stanza.py deleted file mode 100644 index 4a59bd16..00000000 --- a/sleekxmpp/features/feature_preapproval/stanza.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class PreApproval(ElementBase): - - name = 'sub' - namespace = 'urn:xmpp:features:pre-approval' - interfaces = set() - plugin_attrib = 'preapproval' diff --git a/sleekxmpp/features/feature_rosterver/__init__.py b/sleekxmpp/features/feature_rosterver/__init__.py deleted file mode 100644 index 33bbf416..00000000 --- a/sleekxmpp/features/feature_rosterver/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_rosterver.rosterver import FeatureRosterVer -from sleekxmpp.features.feature_rosterver.stanza import RosterVer - - -register_plugin(FeatureRosterVer) - - -# Retain some backwards compatibility -feature_rosterver = FeatureRosterVer diff --git a/sleekxmpp/features/feature_rosterver/rosterver.py b/sleekxmpp/features/feature_rosterver/rosterver.py deleted file mode 100644 index 2991f587..00000000 --- a/sleekxmpp/features/feature_rosterver/rosterver.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.features.feature_rosterver import stanza -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin - - -log = logging.getLogger(__name__) - - -class FeatureRosterVer(BasePlugin): - - name = 'feature_rosterver' - description = 'RFC 6121: Stream Feature: Roster Versioning' - dependences = set() - stanza = stanza - - def plugin_init(self): - self.xmpp.register_feature('rosterver', - self._handle_rosterver, - restart=False, - order=9000) - - register_stanza_plugin(StreamFeatures, stanza.RosterVer) - - def _handle_rosterver(self, features): - """Enable using roster versioning. - - Arguments: - features -- The stream features stanza. - """ - log.debug("Enabling roster versioning.") - self.xmpp.features.add('rosterver') diff --git a/sleekxmpp/features/feature_rosterver/stanza.py b/sleekxmpp/features/feature_rosterver/stanza.py deleted file mode 100644 index 025872fa..00000000 --- a/sleekxmpp/features/feature_rosterver/stanza.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class RosterVer(ElementBase): - - name = 'ver' - namespace = 'urn:xmpp:features:rosterver' - interfaces = set() - plugin_attrib = 'rosterver' diff --git a/sleekxmpp/features/feature_session/__init__.py b/sleekxmpp/features/feature_session/__init__.py deleted file mode 100644 index 28bb3f77..00000000 --- a/sleekxmpp/features/feature_session/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_session.session import FeatureSession -from sleekxmpp.features.feature_session.stanza import Session - - -register_plugin(FeatureSession) - - -# Retain some backwards compatibility -feature_session = FeatureSession diff --git a/sleekxmpp/features/feature_session/session.py b/sleekxmpp/features/feature_session/session.py deleted file mode 100644 index ceadd5f3..00000000 --- a/sleekxmpp/features/feature_session/session.py +++ /dev/null @@ -1,54 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Iq, StreamFeatures -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin - -from sleekxmpp.features.feature_session import stanza - - -log = logging.getLogger(__name__) - - -class FeatureSession(BasePlugin): - - name = 'feature_session' - description = 'RFC 3920: Stream Feature: Start Session' - dependencies = set() - stanza = stanza - - def plugin_init(self): - self.xmpp.register_feature('session', - self._handle_start_session, - restart=False, - order=10001) - - register_stanza_plugin(Iq, stanza.Session) - register_stanza_plugin(StreamFeatures, stanza.Session) - - def _handle_start_session(self, features): - """ - Handle the start of the session. - - Arguments: - feature -- The stream features element. - """ - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq.enable('session') - iq.send(now=True) - - self.xmpp.features.add('session') - - log.debug("Established Session") - self.xmpp.sessionstarted = True - self.xmpp.session_started_event.set() - self.xmpp.event('session_start') diff --git a/sleekxmpp/features/feature_session/stanza.py b/sleekxmpp/features/feature_session/stanza.py deleted file mode 100644 index 94e949ee..00000000 --- a/sleekxmpp/features/feature_session/stanza.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class Session(ElementBase): - - """ - """ - - name = 'session' - namespace = 'urn:ietf:params:xml:ns:xmpp-session' - interfaces = set() - plugin_attrib = 'session' diff --git a/sleekxmpp/features/feature_starttls/__init__.py b/sleekxmpp/features/feature_starttls/__init__.py deleted file mode 100644 index 68697ce5..00000000 --- a/sleekxmpp/features/feature_starttls/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.features.feature_starttls.starttls import FeatureSTARTTLS -from sleekxmpp.features.feature_starttls.stanza import * - - -register_plugin(FeatureSTARTTLS) - - -# Retain some backwards compatibility -feature_starttls = FeatureSTARTTLS diff --git a/sleekxmpp/features/feature_starttls/stanza.py b/sleekxmpp/features/feature_starttls/stanza.py deleted file mode 100644 index b968e134..00000000 --- a/sleekxmpp/features/feature_starttls/stanza.py +++ /dev/null @@ -1,45 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import StanzaBase, ElementBase - - -class STARTTLS(ElementBase): - - """ - """ - - name = 'starttls' - namespace = 'urn:ietf:params:xml:ns:xmpp-tls' - interfaces = set(('required',)) - plugin_attrib = name - - def get_required(self): - """ - """ - return True - - -class Proceed(StanzaBase): - - """ - """ - - name = 'proceed' - namespace = 'urn:ietf:params:xml:ns:xmpp-tls' - interfaces = set() - - -class Failure(StanzaBase): - - """ - """ - - name = 'failure' - namespace = 'urn:ietf:params:xml:ns:xmpp-tls' - interfaces = set() diff --git a/sleekxmpp/features/feature_starttls/starttls.py b/sleekxmpp/features/feature_starttls/starttls.py deleted file mode 100644 index eb5eee1d..00000000 --- a/sleekxmpp/features/feature_starttls/starttls.py +++ /dev/null @@ -1,66 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import StreamFeatures -from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.features.feature_starttls import stanza - - -log = logging.getLogger(__name__) - - -class FeatureSTARTTLS(BasePlugin): - - name = 'feature_starttls' - description = 'RFC 6120: Stream Feature: STARTTLS' - dependencies = set() - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('STARTTLS Proceed', - MatchXPath(stanza.Proceed.tag_name()), - self._handle_starttls_proceed, - instream=True)) - self.xmpp.register_feature('starttls', - self._handle_starttls, - restart=True, - order=self.config.get('order', 0)) - - self.xmpp.register_stanza(stanza.Proceed) - self.xmpp.register_stanza(stanza.Failure) - register_stanza_plugin(StreamFeatures, stanza.STARTTLS) - - def _handle_starttls(self, features): - """ - Handle notification that the server supports TLS. - - Arguments: - features -- The stream:features element. - """ - if 'starttls' in self.xmpp.features: - # We have already negotiated TLS, but the server is - # offering it again, against spec. - return False - elif not self.xmpp.use_tls: - return False - else: - self.xmpp.send(features['starttls'], now=True) - return True - - def _handle_starttls_proceed(self, proceed): - """Restart the XML stream when TLS is accepted.""" - log.debug("Starting TLS") - if self.xmpp.start_tls(): - self.xmpp.features.add('starttls') - raise RestartStream() diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py deleted file mode 100644 index ac5ba30d..00000000 --- a/sleekxmpp/jid.py +++ /dev/null @@ -1,638 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.jid - ~~~~~~~~~~~~~~~~~~~~~~~ - - This module allows for working with Jabber IDs (JIDs). - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import unicode_literals - -import re -import socket -import stringprep -import threading -import encodings.idna - -from copy import deepcopy - -from sleekxmpp.util import stringprep_profiles -from sleekxmpp.thirdparty import OrderedDict - -#: These characters are not allowed to appear in a JID. -ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ - '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ - '\x1a\x1b\x1c\x1d\x1e\x1f' + \ - ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' - -#: The basic regex pattern that a JID must match in order to determine -#: the local, domain, and resource parts. This regex does NOT do any -#: validation, which requires application of nodeprep, resourceprep, etc. -JID_PATTERN = re.compile( - "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" -) - -#: The set of escape sequences for the characters not allowed by nodeprep. -JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', - '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) - -#: A mapping of unallowed characters to their escape sequences. An escape -#: sequence for '\' is also included since it must also be escaped in -#: certain situations. -JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', - '"': '\\22', - '&': '\\26', - "'": '\\27', - '/': '\\2f', - ':': '\\3a', - '<': '\\3c', - '>': '\\3e', - '@': '\\40', - '\\': '\\5c'} - -#: The reverse mapping of escape sequences to their original forms. -JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', - '\\22': '"', - '\\26': '&', - '\\27': "'", - '\\2f': '/', - '\\3a': ':', - '\\3c': '<', - '\\3e': '>', - '\\40': '@', - '\\5c': '\\'} - -JID_CACHE = OrderedDict() -JID_CACHE_LOCK = threading.Lock() -JID_CACHE_MAX_SIZE = 1024 - -def _cache(key, parts, locked): - JID_CACHE[key] = (parts, locked) - if len(JID_CACHE) > JID_CACHE_MAX_SIZE: - with JID_CACHE_LOCK: - while len(JID_CACHE) > JID_CACHE_MAX_SIZE: - found = None - for key, item in JID_CACHE.items(): - if not item[1]: # if not locked - found = key - break - if not found: # more than MAX_SIZE locked - # warn? - break - del JID_CACHE[found] - -# pylint: disable=c0103 -#: The nodeprep profile of stringprep used to validate the local, -#: or username, portion of a JID. -nodeprep = stringprep_profiles.create( - nfkc=True, - bidi=True, - mappings=[ - stringprep_profiles.b1_mapping, - stringprep.map_table_b2], - prohibited=[ - stringprep.in_table_c11, - stringprep.in_table_c12, - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c7, - stringprep.in_table_c8, - stringprep.in_table_c9, - lambda c: c in ' \'"&/:<>@'], - unassigned=[stringprep.in_table_a1]) - -# pylint: disable=c0103 -#: The resourceprep profile of stringprep, which is used to validate -#: the resource portion of a JID. -resourceprep = stringprep_profiles.create( - nfkc=True, - bidi=True, - mappings=[stringprep_profiles.b1_mapping], - prohibited=[ - stringprep.in_table_c12, - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c7, - stringprep.in_table_c8, - stringprep.in_table_c9], - unassigned=[stringprep.in_table_a1]) - - -def _parse_jid(data): - """ - Parse string data into the node, domain, and resource - components of a JID, if possible. - - :param string data: A string that is potentially a JID. - - :raises InvalidJID: - - :returns: tuple of the validated local, domain, and resource strings - """ - match = JID_PATTERN.match(data) - if not match: - raise InvalidJID('JID could not be parsed') - - (node, domain, resource) = match.groups() - - node = _validate_node(node) - domain = _validate_domain(domain) - resource = _validate_resource(resource) - - return node, domain, resource - - -def _validate_node(node): - """Validate the local, or username, portion of a JID. - - :raises InvalidJID: - - :returns: The local portion of a JID, as validated by nodeprep. - """ - try: - if node is not None: - node = nodeprep(node) - - if not node: - raise InvalidJID('Localpart must not be 0 bytes') - if len(node) > 1023: - raise InvalidJID('Localpart must be less than 1024 bytes') - return node - except stringprep_profiles.StringPrepError: - raise InvalidJID('Invalid local part') - - -def _validate_domain(domain): - """Validate the domain portion of a JID. - - IP literal addresses are left as-is, if valid. Domain names - are stripped of any trailing label separators (`.`), and are - checked with the nameprep profile of stringprep. If the given - domain is actually a punyencoded version of a domain name, it - is converted back into its original Unicode form. Domains must - also not start or end with a dash (`-`). - - :raises InvalidJID: - - :returns: The validated domain name - """ - ip_addr = False - - # First, check if this is an IPv4 address - try: - socket.inet_aton(domain) - ip_addr = True - except socket.error: - pass - - # Check if this is an IPv6 address - if not ip_addr and hasattr(socket, 'inet_pton'): - try: - socket.inet_pton(socket.AF_INET6, domain.strip('[]')) - domain = '[%s]' % domain.strip('[]') - ip_addr = True - except (socket.error, ValueError): - pass - - if not ip_addr: - # This is a domain name, which must be checked further - - if domain and domain[-1] == '.': - domain = domain[:-1] - - domain_parts = [] - for label in domain.split('.'): - try: - label = encodings.idna.nameprep(label) - encodings.idna.ToASCII(label) - pass_nameprep = True - except UnicodeError: - pass_nameprep = False - - if not pass_nameprep: - raise InvalidJID('Could not encode domain as ASCII') - - if label.startswith('xn--'): - label = encodings.idna.ToUnicode(label) - - for char in label: - if char in ILLEGAL_CHARS: - raise InvalidJID('Domain contains illegal characters') - - if '-' in (label[0], label[-1]): - raise InvalidJID('Domain started or ended with -') - - domain_parts.append(label) - domain = '.'.join(domain_parts) - - if not domain: - raise InvalidJID('Domain must not be 0 bytes') - if len(domain) > 1023: - raise InvalidJID('Domain must be less than 1024 bytes') - - return domain - - -def _validate_resource(resource): - """Validate the resource portion of a JID. - - :raises InvalidJID: - - :returns: The local portion of a JID, as validated by resourceprep. - """ - try: - if resource is not None: - resource = resourceprep(resource) - - if not resource: - raise InvalidJID('Resource must not be 0 bytes') - if len(resource) > 1023: - raise InvalidJID('Resource must be less than 1024 bytes') - return resource - except stringprep_profiles.StringPrepError: - raise InvalidJID('Invalid resource') - - -def _escape_node(node): - """Escape the local portion of a JID.""" - result = [] - - for i, char in enumerate(node): - if char == '\\': - if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: - result.append('\\5c') - continue - result.append(char) - - for i, char in enumerate(result): - if char != '\\': - result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) - - escaped = ''.join(result) - - if escaped.startswith('\\20') or escaped.endswith('\\20'): - raise InvalidJID('Escaped local part starts or ends with "\\20"') - - _validate_node(escaped) - - return escaped - - -def _unescape_node(node): - """Unescape a local portion of a JID. - - .. note:: - The unescaped local portion is meant ONLY for presentation, - and should not be used for other purposes. - """ - unescaped = [] - seq = '' - for i, char in enumerate(node): - if char == '\\': - seq = node[i:i+3] - if seq not in JID_ESCAPE_SEQUENCES: - seq = '' - if seq: - if len(seq) == 3: - unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char)) - - # Pop character off the escape sequence, and ignore it - seq = seq[1:] - else: - unescaped.append(char) - unescaped = ''.join(unescaped) - - return unescaped - - -def _format_jid(local=None, domain=None, resource=None): - """Format the given JID components into a full or bare JID. - - :param string local: Optional. The local portion of the JID. - :param string domain: Required. The domain name portion of the JID. - :param strin resource: Optional. The resource portion of the JID. - - :return: A full or bare JID string. - """ - result = [] - if local: - result.append(local) - result.append('@') - if domain: - result.append(domain) - if resource: - result.append('/') - result.append(resource) - return ''.join(result) - - -class InvalidJID(ValueError): - """ - Raised when attempting to create a JID that does not pass validation. - - It can also be raised if modifying an existing JID in such a way as - to make it invalid, such trying to remove the domain from an existing - full JID while the local and resource portions still exist. - """ - -# pylint: disable=R0903 -class UnescapedJID(object): - - """ - .. versionadded:: 1.1.10 - """ - - def __init__(self, local, domain, resource): - self._jid = (local, domain, resource) - - # pylint: disable=R0911 - def __getattr__(self, name): - """Retrieve the given JID component. - - :param name: one of: user, server, domain, resource, - full, or bare. - """ - if name == 'resource': - return self._jid[2] or '' - elif name in ('user', 'username', 'local', 'node'): - return self._jid[0] or '' - elif name in ('server', 'domain', 'host'): - return self._jid[1] or '' - elif name in ('full', 'jid'): - return _format_jid(*self._jid) - elif name == 'bare': - return _format_jid(self._jid[0], self._jid[1]) - elif name == '_jid': - return getattr(super(JID, self), '_jid') - else: - return None - - def __str__(self): - """Use the full JID as the string value.""" - return _format_jid(*self._jid) - - def __repr__(self): - """Use the full JID as the representation.""" - return self.__str__() - - -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. - - **JID Properties:** - :jid: Alias for ``full``. - :full: The string value of the full JID. - :bare: The string value of the bare JID. - :user: The username portion of the JID. - :username: Alias for ``user``. - :local: Alias for ``user``. - :node: Alias for ``user``. - :domain: The domain name portion of the JID. - :server: Alias for ``domain``. - :host: Alias for ``domain``. - :resource: The resource portion of the JID. - - :param string jid: - A string of the form ``'[user@]domain[/resource]'``. - :param string local: - Optional. Specify the local, or username, portion - of the JID. If provided, it will override the local - value provided by the `jid` parameter. The given - local value will also be escaped if necessary. - :param string domain: - Optional. Specify the domain of the JID. If - provided, it will override the domain given by - the `jid` parameter. - :param string resource: - Optional. Specify the resource value of the JID. - If provided, it will override the domain given - by the `jid` parameter. - - :raises InvalidJID: - """ - - # pylint: disable=W0212 - def __init__(self, jid=None, **kwargs): - locked = kwargs.get('cache_lock', False) - in_local = kwargs.get('local', None) - in_domain = kwargs.get('domain', None) - in_resource = kwargs.get('resource', None) - parts = None - if in_local or in_domain or in_resource: - parts = (in_local, in_domain, in_resource) - - # only check cache if there is a jid string, or parts, not if there - # are both - self._jid = None - key = None - if (jid is not None) and (parts is None): - if isinstance(jid, JID): - # it's already good to go, and there are no additions - self._jid = jid._jid - return - key = jid - self._jid, locked = JID_CACHE.get(jid, (None, locked)) - elif jid is None and parts is not None: - key = parts - self._jid, locked = JID_CACHE.get(parts, (None, locked)) - if not self._jid: - if not jid: - parsed_jid = (None, None, None) - elif not isinstance(jid, JID): - parsed_jid = _parse_jid(jid) - else: - parsed_jid = jid._jid - - local, domain, resource = parsed_jid - - if 'local' in kwargs: - local = _escape_node(in_local) - if 'domain' in kwargs: - domain = _validate_domain(in_domain) - if 'resource' in kwargs: - resource = _validate_resource(in_resource) - - self._jid = (local, domain, resource) - if key: - _cache(key, self._jid, locked) - - def unescape(self): - """Return an unescaped JID object. - - Using an unescaped JID is preferred for displaying JIDs - to humans, and they should NOT be used for any other - purposes than for presentation. - - :return: :class:`UnescapedJID` - - .. versionadded:: 1.1.10 - """ - return UnescapedJID(_unescape_node(self._jid[0]), - self._jid[1], - self._jid[2]) - - def regenerate(self): - """No-op - - .. deprecated:: 1.1.10 - """ - pass - - def reset(self, data): - """Start fresh from a new JID string. - - :param string data: A string of the form ``'[user@]domain[/resource]'``. - - .. deprecated:: 1.1.10 - """ - self._jid = JID(data)._jid - - @property - def resource(self): - return self._jid[2] or '' - - @property - def user(self): - return self._jid[0] or '' - - @property - def local(self): - return self._jid[0] or '' - - @property - def node(self): - return self._jid[0] or '' - - @property - def username(self): - return self._jid[0] or '' - - @property - def bare(self): - return _format_jid(self._jid[0], self._jid[1]) - - @property - def server(self): - return self._jid[1] or '' - - @property - def domain(self): - return self._jid[1] or '' - - @property - def host(self): - return self._jid[1] or '' - - @property - def full(self): - return _format_jid(*self._jid) - - @property - def jid(self): - return _format_jid(*self._jid) - - @property - def bare(self): - return _format_jid(self._jid[0], self._jid[1]) - - - @resource.setter - def resource(self, value): - self._jid = JID(self, resource=value)._jid - - @user.setter - def user(self, value): - self._jid = JID(self, local=value)._jid - - @username.setter - def username(self, value): - self._jid = JID(self, local=value)._jid - - @local.setter - def local(self, value): - self._jid = JID(self, local=value)._jid - - @node.setter - def node(self, value): - self._jid = JID(self, local=value)._jid - - @server.setter - def server(self, value): - self._jid = JID(self, domain=value)._jid - - @domain.setter - def domain(self, value): - self._jid = JID(self, domain=value)._jid - - @host.setter - def host(self, value): - self._jid = JID(self, domain=value)._jid - - @full.setter - def full(self, value): - self._jid = JID(value)._jid - - @jid.setter - def jid(self, value): - self._jid = JID(value)._jid - - @bare.setter - def bare(self, value): - parsed = JID(value)._jid - self._jid = (parsed[0], parsed[1], self._jid[2]) - - - def __str__(self): - """Use the full JID as the string value.""" - return _format_jid(*self._jid) - - def __repr__(self): - """Use the full JID as the representation.""" - return self.__str__() - - # pylint: disable=W0212 - def __eq__(self, other): - """Two JIDs are equal if they have the same full JID value.""" - if isinstance(other, UnescapedJID): - return False - - other = JID(other) - return self._jid == other._jid - - # pylint: disable=W0212 - def __ne__(self, other): - """Two JIDs are considered unequal if they are not equal.""" - return not self == other - - def __hash__(self): - """Hash a JID based on the string version of its full JID.""" - return hash(self.__str__()) - - def __copy__(self): - """Generate a duplicate JID.""" - return JID(self) - - def __deepcopy__(self, memo): - """Generate a duplicate JID.""" - return JID(deepcopy(str(self), memo)) diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py deleted file mode 100644 index 951f31eb..00000000 --- a/sleekxmpp/plugins/__init__.py +++ /dev/null @@ -1,86 +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.plugins.base import PluginManager, PluginNotFound, BasePlugin -from sleekxmpp.plugins.base import register_plugin, load_plugin - - -__all__ = [ - # XEPS - 'xep_0004', # Data Forms - 'xep_0009', # Jabber-RPC - 'xep_0012', # Last Activity - 'xep_0013', # Flexible Offline Message Retrieval - 'xep_0016', # Privacy Lists - 'xep_0020', # Feature Negotiation - 'xep_0027', # Current Jabber OpenPGP Usage - 'xep_0030', # Service Discovery - 'xep_0033', # Extended Stanza Addresses - 'xep_0045', # Multi-User Chat (Client) - 'xep_0047', # In-Band Bytestreams - 'xep_0048', # Bookmarks - 'xep_0049', # Private XML Storage - 'xep_0050', # Ad-hoc Commands - 'xep_0054', # vcard-temp - 'xep_0059', # Result Set Management - 'xep_0060', # Pubsub (Client) - 'xep_0065', # SOCKS5 Bytestreams - 'xep_0066', # Out of Band Data - 'xep_0071', # XHTML-IM - 'xep_0077', # In-Band Registration -# 'xep_0078', # Non-SASL auth. Don't automatically load - 'xep_0079', # Advanced Message Processing - 'xep_0080', # User Location - 'xep_0082', # XMPP Date and Time Profiles - 'xep_0084', # User Avatar - 'xep_0085', # Chat State Notifications - 'xep_0086', # Legacy Error Codes - 'xep_0091', # Legacy Delayed Delivery - 'xep_0092', # Software Version - 'xep_0106', # JID Escaping - 'xep_0107', # User Mood - 'xep_0108', # User Activity - 'xep_0115', # Entity Capabilities - 'xep_0118', # User Tune - 'xep_0128', # Extended Service Discovery - 'xep_0131', # Standard Headers and Internet Metadata - 'xep_0133', # Service Administration - 'xep_0152', # Reachability Addresses - 'xep_0153', # vCard-Based Avatars - 'xep_0163', # Personal Eventing Protocol - 'xep_0172', # User Nickname - 'xep_0184', # Message Receipts - 'xep_0186', # Invisible Command - 'xep_0191', # Blocking Command - 'xep_0196', # User Gaming - 'xep_0198', # Stream Management - 'xep_0199', # Ping - 'xep_0202', # Entity Time - 'xep_0203', # Delayed Delivery - 'xep_0221', # Data Forms Media Element - 'xep_0222', # Persistent Storage of Public Data via Pubsub - 'xep_0223', # Persistent Storage of Private Data via Pubsub - 'xep_0224', # Attention - 'xep_0231', # Bits of Binary - 'xep_0235', # OAuth Over XMPP - 'xep_0242', # XMPP Client Compliance 2009 - 'xep_0249', # Direct MUC Invitations - 'xep_0256', # Last Activity in Presence - 'xep_0257', # Client Certificate Management for SASL EXTERNAL - 'xep_0258', # Security Labels in XMPP - 'xep_0270', # XMPP Compliance Suites 2010 - 'xep_0279', # Server IP Check - 'xep_0280', # Message Carbons - 'xep_0297', # Stanza Forwarding - 'xep_0302', # XMPP Compliance Suites 2012 - 'xep_0308', # Last Message Correction - 'xep_0313', # Message Archive Management - 'xep_0319', # Last User Interaction in Presence - 'xep_0323', # IoT Systems Sensor Data - 'xep_0325', # IoT Systems Control -] diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py deleted file mode 100644 index 67675908..00000000 --- a/sleekxmpp/plugins/base.py +++ /dev/null @@ -1,360 +0,0 @@ -# -*- encoding: utf-8 -*- - -""" - sleekxmpp.plugins.base - ~~~~~~~~~~~~~~~~~~~~~~ - - This module provides XMPP functionality that - is specific to client connections. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -import sys -import copy -import logging -import threading - - -if sys.version_info >= (3, 0): - unicode = str - - -log = logging.getLogger(__name__) - - -#: Associate short string names of plugins with implementations. The -#: plugin names are based on the spec used by the plugin, such as -#: `'xep_0030'` for a plugin that implements XEP-0030. -PLUGIN_REGISTRY = {} - -#: In order to do cascading plugin disabling, reverse dependencies -#: must be tracked. -PLUGIN_DEPENDENTS = {} - -#: Only allow one thread to manipulate the plugin registry at a time. -REGISTRY_LOCK = threading.RLock() - - -class PluginNotFound(Exception): - """Raised if an unknown plugin is accessed.""" - - -def register_plugin(impl, name=None): - """Add a new plugin implementation to the registry. - - :param class impl: The plugin class. - - The implementation class must provide a :attr:`~BasePlugin.name` - value that will be used as a short name for enabling and disabling - the plugin. The name should be based on the specification used by - the plugin. For example, a plugin implementing XEP-0030 would be - named `'xep_0030'`. - """ - if name is None: - name = impl.name - with REGISTRY_LOCK: - PLUGIN_REGISTRY[name] = impl - if name not in PLUGIN_DEPENDENTS: - PLUGIN_DEPENDENTS[name] = set() - for dep in impl.dependencies: - if dep not in PLUGIN_DEPENDENTS: - PLUGIN_DEPENDENTS[dep] = set() - PLUGIN_DEPENDENTS[dep].add(name) - - -def load_plugin(name, module=None): - """Find and import a plugin module so that it can be registered. - - This function is called to import plugins that have selected for - enabling, but no matching registered plugin has been found. - - :param str name: The name of the plugin. It is expected that - plugins are in packages matching their name, - even though the plugin class name does not - have to match. - :param str module: The name of the base module to search - for the plugin. - """ - try: - if not module: - try: - module = 'sleekxmpp.plugins.%s' % name - __import__(module) - mod = sys.modules[module] - except ImportError: - module = 'sleekxmpp.features.%s' % name - __import__(module) - mod = sys.modules[module] - elif isinstance(module, (str, unicode)): - __import__(module) - mod = sys.modules[module] - else: - mod = module - - # Add older style plugins to the registry. - if hasattr(mod, name): - plugin = getattr(mod, name) - if hasattr(plugin, 'xep') or hasattr(plugin, 'rfc'): - plugin.name = name - # Mark the plugin as an older style plugin so - # we can work around dependency issues. - plugin.old_style = True - register_plugin(plugin, name) - except ImportError: - log.exception("Unable to load plugin: %s", name) - - -class PluginManager(object): - def __init__(self, xmpp, config=None): - #: We will track all enabled plugins in a set so that we - #: can enable plugins in batches and pull in dependencies - #: without problems. - self._enabled = set() - - #: Maintain references to active plugins. - self._plugins = {} - - self._plugin_lock = threading.RLock() - - #: Globally set default plugin configuration. This will - #: be used for plugins that are auto-enabled through - #: dependency loading. - self.config = config if config else {} - - self.xmpp = xmpp - - def register(self, plugin, enable=True): - """Register a new plugin, and optionally enable it. - - :param class plugin: The implementation class of the plugin - to register. - :param bool enable: If ``True``, immediately enable the - plugin after registration. - """ - register_plugin(plugin) - if enable: - self.enable(plugin.name) - - def enable(self, name, config=None, enabled=None): - """Enable a plugin, including any dependencies. - - :param string name: The short name of the plugin. - :param dict config: Optional settings dictionary for - configuring plugin behaviour. - """ - top_level = False - if enabled is None: - enabled = set() - - with self._plugin_lock: - if name not in self._enabled: - enabled.add(name) - self._enabled.add(name) - if not self.registered(name): - load_plugin(name) - - plugin_class = PLUGIN_REGISTRY.get(name, None) - if not plugin_class: - raise PluginNotFound(name) - - if config is None: - config = self.config.get(name, None) - - plugin = plugin_class(self.xmpp, config) - self._plugins[name] = plugin - for dep in plugin.dependencies: - self.enable(dep, enabled=enabled) - plugin._init() - - if top_level: - for name in enabled: - if hasattr(self.plugins[name], 'old_style'): - # Older style plugins require post_init() - # to run just before stream processing begins, - # so we don't call it here. - pass - self.plugins[name].post_init() - - def enable_all(self, names=None, config=None): - """Enable all registered plugins. - - :param list names: A list of plugin names to enable. If - none are provided, all registered plugins - will be enabled. - :param dict config: A dictionary mapping plugin names to - configuration dictionaries, as used by - :meth:`~PluginManager.enable`. - """ - names = names if names else PLUGIN_REGISTRY.keys() - if config is None: - config = {} - for name in names: - self.enable(name, config.get(name, {})) - - def enabled(self, name): - """Check if a plugin has been enabled. - - :param string name: The name of the plugin to check. - :return: boolean - """ - return name in self._enabled - - def registered(self, name): - """Check if a plugin has been registered. - - :param string name: The name of the plugin to check. - :return: boolean - """ - return name in PLUGIN_REGISTRY - - def disable(self, name, _disabled=None): - """Disable a plugin, including any dependent upon it. - - :param string name: The name of the plugin to disable. - :param set _disabled: Private set used to track the - disabled status of plugins during - the cascading process. - """ - if _disabled is None: - _disabled = set() - with self._plugin_lock: - if name not in _disabled and name in self._enabled: - _disabled.add(name) - plugin = self._plugins.get(name, None) - if plugin is None: - raise PluginNotFound(name) - for dep in PLUGIN_DEPENDENTS[name]: - self.disable(dep, _disabled) - plugin._end() - if name in self._enabled: - self._enabled.remove(name) - del self._plugins[name] - - def __keys__(self): - """Return the set of enabled plugins.""" - return self._plugins.keys() - - def __getitem__(self, name): - """ - Allow plugins to be accessed through the manager as if - it were a dictionary. - """ - plugin = self._plugins.get(name, None) - if plugin is None: - raise PluginNotFound(name) - return plugin - - def __iter__(self): - """Return an iterator over the set of enabled plugins.""" - return self._plugins.__iter__() - - def __len__(self): - """Return the number of enabled plugins.""" - return len(self._plugins) - - -class BasePlugin(object): - - #: A short name for the plugin based on the implemented specification. - #: For example, a plugin for XEP-0030 would use `'xep_0030'`. - name = '' - - #: A longer name for the plugin, describing its purpose. For example, - #: a plugin for XEP-0030 would use `'Service Discovery'` as its - #: description value. - description = '' - - #: Some plugins may depend on others in order to function properly. - #: Any plugin names included in :attr:`~BasePlugin.dependencies` will - #: be initialized as needed if this plugin is enabled. - dependencies = set() - - #: The basic, standard configuration for the plugin, which may - #: be overridden when initializing the plugin. The configuration - #: fields included here may be accessed directly as attributes of - #: the plugin. For example, including the configuration field 'foo' - #: would mean accessing `plugin.foo` returns the current value of - #: `plugin.config['foo']`. - default_config = {} - - def __init__(self, xmpp, config=None): - self.xmpp = xmpp - if self.xmpp: - self.api = self.xmpp.api.wrap(self.name) - - #: A plugin's behaviour may be configurable, in which case those - #: configuration settings will be provided as a dictionary. - self.config = copy.copy(self.default_config) - if config: - self.config.update(config) - - def __getattr__(self, key): - """Provide direct access to configuration fields. - - If the standard configuration includes the option `'foo'`, then - accessing `self.foo` should be the same as `self.config['foo']`. - """ - if key in self.default_config: - return self.config.get(key, None) - else: - return object.__getattribute__(self, key) - - def __setattr__(self, key, value): - """Provide direct assignment to configuration fields. - - If the standard configuration includes the option `'foo'`, then - assigning to `self.foo` should be the same as assigning to - `self.config['foo']`. - """ - if key in self.default_config: - self.config[key] = value - else: - super(BasePlugin, self).__setattr__(key, value) - - def _init(self): - """Initialize plugin state, such as registering event handlers. - - Also sets up required event handlers. - """ - if self.xmpp is not None: - self.xmpp.add_event_handler('session_bind', self.session_bind) - if self.xmpp.session_bind_event.is_set(): - self.session_bind(self.xmpp.boundjid.full) - self.plugin_init() - log.debug('Loaded Plugin: %s', self.description) - - def _end(self): - """Cleanup plugin state, and prepare for plugin removal. - - Also removes required event handlers. - """ - if self.xmpp is not None: - self.xmpp.del_event_handler('session_bind', self.session_bind) - self.plugin_end() - log.debug('Disabled Plugin: %s' % self.description) - - def plugin_init(self): - """Initialize plugin state, such as registering event handlers.""" - pass - - def plugin_end(self): - """Cleanup plugin state, and prepare for plugin removal.""" - pass - - def session_bind(self, jid): - """Initialize plugin state based on the bound JID.""" - pass - - def post_init(self): - """Initialize any cross-plugin state. - - Only needed if the plugin has circular dependencies. - """ - pass - - -base_plugin = BasePlugin diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py deleted file mode 100644 index fc97a2ab..00000000 --- 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.boundjid.bare - iq['gmail']['q'] = query - iq['gmail']['newer-than-time'] = newer - return iq.send() diff --git a/sleekxmpp/plugins/google/__init__.py b/sleekxmpp/plugins/google/__init__.py deleted file mode 100644 index bd7ca123..00000000 --- a/sleekxmpp/plugins/google/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin, BasePlugin - -from sleekxmpp.plugins.google.gmail import Gmail -from sleekxmpp.plugins.google.auth import GoogleAuth -from sleekxmpp.plugins.google.settings import GoogleSettings -from sleekxmpp.plugins.google.nosave import GoogleNoSave - - -class Google(BasePlugin): - - """ - Google: Custom GTalk Features - - Also see: <https://developers.google.com/talk/jep_extensions/extensions> - """ - - name = 'google' - description = 'Google: Custom GTalk Features' - dependencies = set([ - 'gmail', - 'google_settings', - 'google_nosave', - 'google_auth' - ]) - - def __getitem__(self, attr): - if attr in ('settings', 'nosave', 'auth'): - return self.xmpp['google_%s' % attr] - elif attr == 'gmail': - return self.xmpp['gmail'] - else: - raise KeyError(attr) - - -register_plugin(Gmail) -register_plugin(GoogleAuth) -register_plugin(GoogleSettings) -register_plugin(GoogleNoSave) -register_plugin(Google) diff --git a/sleekxmpp/plugins/google/auth/__init__.py b/sleekxmpp/plugins/google/auth/__init__.py deleted file mode 100644 index 5a8feb0d..00000000 --- a/sleekxmpp/plugins/google/auth/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.google.auth import stanza -from sleekxmpp.plugins.google.auth.auth import GoogleAuth diff --git a/sleekxmpp/plugins/google/auth/auth.py b/sleekxmpp/plugins/google/auth/auth.py deleted file mode 100644 index 042bd404..00000000 --- a/sleekxmpp/plugins/google/auth/auth.py +++ /dev/null @@ -1,52 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.google.auth import stanza - - -log = logging.getLogger(__name__) - - -class GoogleAuth(BasePlugin): - - """ - Google: Auth Extensions (JID Domain Discovery, OAuth2) - - Also see: - <https://developers.google.com/talk/jep_extensions/jid_domain_change> - <https://developers.google.com/talk/jep_extensions/oauth> - """ - - name = 'google_auth' - description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)' - dependencies = set(['feature_mechanisms']) - stanza = stanza - - def plugin_init(self): - self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga' - - register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth, - stanza.GoogleAuth) - - self.xmpp.add_filter('out', self._auth) - - def plugin_end(self): - self.xmpp.del_filter('out', self._auth) - - def _auth(self, stanza): - if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth): - stanza.stream = self.xmpp - stanza['google']['client_uses_full_bind_result'] = True - if stanza['mechanism'] == 'X-OAUTH2': - stanza['google']['service'] = 'oauth2' - print(stanza) - return stanza diff --git a/sleekxmpp/plugins/google/auth/stanza.py b/sleekxmpp/plugins/google/auth/stanza.py deleted file mode 100644 index 49c5cba7..00000000 --- a/sleekxmpp/plugins/google/auth/stanza.py +++ /dev/null @@ -1,49 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class GoogleAuth(ElementBase): - name = 'auth' - namespace = 'http://www.google.com/talk/protocol/auth' - plugin_attrib = 'google' - interfaces = set(['client_uses_full_bind_result', 'service']) - - discovery_attr= '{%s}client-uses-full-bind-result' % namespace - service_attr= '{%s}service' % namespace - - def setup(self, xml): - """Don't create XML for the plugin.""" - self.xml = ET.Element('') - print('setting up google extension') - - def get_client_uses_full_bind_result(self): - return self.parent()._get_attr(self.disovery_attr) == 'true' - - def set_client_uses_full_bind_result(self, value): - print('>>>', value) - if value in (True, 'true'): - self.parent()._set_attr(self.discovery_attr, 'true') - else: - self.parent()._del_attr(self.discovery_attr) - - def del_client_uses_full_bind_result(self): - self.parent()._del_attr(self.discovery_attr) - - def get_service(self): - return self.parent()._get_attr(self.service_attr, '') - - def set_service(self, value): - if value: - self.parent()._set_attr(self.service_attr, value) - else: - self.parent()._del_attr(self.service_attr) - - def del_service(self): - self.parent()._del_attr(self.service_attr) diff --git a/sleekxmpp/plugins/google/gmail/__init__.py b/sleekxmpp/plugins/google/gmail/__init__.py deleted file mode 100644 index a92e363b..00000000 --- a/sleekxmpp/plugins/google/gmail/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.google.gmail import stanza -from sleekxmpp.plugins.google.gmail.notifications import Gmail diff --git a/sleekxmpp/plugins/google/gmail/notifications.py b/sleekxmpp/plugins/google/gmail/notifications.py deleted file mode 100644 index 509a95fd..00000000 --- a/sleekxmpp/plugins/google/gmail/notifications.py +++ /dev/null @@ -1,96 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Iq -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.google.gmail import stanza - - -log = logging.getLogger(__name__) - - -class Gmail(BasePlugin): - - """ - Google: Gmail Notifications - - Also see <https://developers.google.com/talk/jep_extensions/gmail>. - """ - - name = 'gmail' - description = 'Google: Gmail Notifications' - dependencies = set() - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, stanza.GmailQuery) - register_stanza_plugin(Iq, stanza.MailBox) - register_stanza_plugin(Iq, stanza.NewMail) - - self.xmpp.register_handler( - Callback('Gmail New Mail', - MatchXPath('{%s}iq/{%s}%s' % ( - self.xmpp.default_ns, - stanza.NewMail.namespace, - stanza.NewMail.name)), - self._handle_new_mail)) - - self._last_result_time = None - self._last_result_tid = None - - def plugin_end(self): - self.xmpp.remove_handler('Gmail New Mail') - - def _handle_new_mail(self, iq): - log.info('Gmail: New email!') - iq.reply().send() - self.xmpp.event('gmail_notification') - - def check(self, block=True, timeout=None, callback=None): - last_time = self._last_result_time - last_tid = self._last_result_tid - - if not block: - callback = lambda iq: self._update_last_results(iq, callback) - - resp = self.search(newer_time=last_time, - newer_tid=last_tid, - block=block, - timeout=timeout, - callback=callback) - - if block: - self._update_last_results(resp) - return resp - - def _update_last_results(self, iq, callback=None): - self._last_result_time = data['gmail_messages']['result_time'] - threads = data['gmail_messages']['threads'] - if threads: - self._last_result_tid = threads[0]['tid'] - if callback: - callback(iq) - - def search(self, query=None, newer_time=None, newer_tid=None, block=True, - timeout=None, callback=None): - if not query: - log.info('Gmail: Checking for new email') - else: - log.info('Gmail: Searching for emails matching: "%s"', query) - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = self.xmpp.boundjid.bare - iq['gmail']['search'] = query - iq['gmail']['newer_than_time'] = newer_time - iq['gmail']['newer_than_tid'] = newer_tid - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/google/gmail/stanza.py b/sleekxmpp/plugins/google/gmail/stanza.py deleted file mode 100644 index e7e308e1..00000000 --- a/sleekxmpp/plugins/google/gmail/stanza.py +++ /dev/null @@ -1,101 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class GmailQuery(ElementBase): - namespace = 'google:mail:notify' - name = 'query' - plugin_attrib = 'gmail' - interfaces = set(['newer_than_time', 'newer_than_tid', 'search']) - - def get_search(self): - return self._get_attr('q', '') - - def set_search(self, search): - self._set_attr('q', search) - - def del_search(self): - self._del_attr('q') - - def get_newer_than_time(self): - return self._get_attr('newer-than-time', '') - - def set_newer_than_time(self, value): - self._set_attr('newer-than-time', value) - - def del_newer_than_time(self): - self._del_attr('newer-than-time') - - def get_newer_than_tid(self): - return self._get_attr('newer-than-tid', '') - - def set_newer_than_tid(self, value): - self._set_attr('newer-than-tid', value) - - def del_newer_than_tid(self): - self._del_attr('newer-than-tid') - - -class MailBox(ElementBase): - namespace = 'google:mail:notify' - name = 'mailbox' - plugin_attrib = 'gmail_messages' - interfaces = set(['result_time', 'url', 'matched', 'estimate']) - - def get_matched(self): - return self._get_attr('total-matched', '') - - def get_estimate(self): - return self._get_attr('total-estimate', '') == '1' - - def get_result_time(self): - return self._get_attr('result-time', '') - - -class MailThread(ElementBase): - namespace = 'google:mail:notify' - name = 'mail-thread-info' - plugin_attrib = 'thread' - plugin_multi_attrib = 'threads' - interfaces = set(['tid', 'participation', 'messages', 'date', - 'senders', 'url', 'labels', 'subject', 'snippet']) - sub_interfaces = set(['labels', 'subject', 'snippet']) - - def get_senders(self): - result = [] - senders = self.xml.findall('{%s}senders/{%s}sender' % ( - self.namespace, self.namespace)) - - for sender in senders: - result.append(MailSender(xml=sender)) - - return result - - -class MailSender(ElementBase): - namespace = 'google:mail:notify' - name = 'sender' - plugin_attrib = name - interfaces = set(['address', 'name', 'originator', 'unread']) - - def get_originator(self): - return self.xml.attrib.get('originator', '0') == '1' - - def get_unread(self): - return self.xml.attrib.get('unread', '0') == '1' - - -class NewMail(ElementBase): - namespace = 'google:mail:notify' - name = 'new-mail' - plugin_attrib = 'gmail_notification' - - -register_stanza_plugin(MailBox, MailThread, iterable=True) diff --git a/sleekxmpp/plugins/google/nosave/__init__.py b/sleekxmpp/plugins/google/nosave/__init__.py deleted file mode 100644 index 57847af5..00000000 --- a/sleekxmpp/plugins/google/nosave/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.google.nosave import stanza -from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave diff --git a/sleekxmpp/plugins/google/nosave/nosave.py b/sleekxmpp/plugins/google/nosave/nosave.py deleted file mode 100644 index d6bef615..00000000 --- a/sleekxmpp/plugins/google/nosave/nosave.py +++ /dev/null @@ -1,83 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Iq, Message -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.google.nosave import stanza - - -log = logging.getLogger(__name__) - - -class GoogleNoSave(BasePlugin): - - """ - Google: Off the Record Chats - - NOTE: This is NOT an encryption method. - - Also see <https://developers.google.com/talk/jep_extensions/otr>. - """ - - name = 'google_nosave' - description = 'Google: Off the Record Chats' - dependencies = set(['google_settings']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, stanza.NoSave) - register_stanza_plugin(Iq, stanza.NoSaveQuery) - - self.xmpp.register_handler( - Callback('Google Nosave', - StanzaPath('iq@type=set/google_nosave'), - self._handle_nosave_change)) - - def plugin_end(self): - self.xmpp.remove_handler('Google Nosave') - - def enable(self, jid=None, block=True, timeout=None, callback=None): - if jid is None: - self.xmpp['google_settings'].update({'archiving_enabled': False}, - block=block, timeout=timeout, callback=callback) - else: - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['google_nosave']['item']['jid'] = jid - iq['google_nosave']['item']['value'] = True - return iq.send(block=block, timeout=timeout, callback=callback) - - def disable(self, jid=None, block=True, timeout=None, callback=None): - if jid is None: - self.xmpp['google_settings'].update({'archiving_enabled': True}, - block=block, timeout=timeout, callback=callback) - else: - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['google_nosave']['item']['jid'] = jid - iq['google_nosave']['item']['value'] = False - return iq.send(block=block, timeout=timeout, callback=callback) - - def get(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq.enable('google_nosave') - return iq.send(block=block, timeout=timeout, callback=callback) - - def _handle_nosave_change(self, iq): - reply = self.xmpp.Iq() - reply['type'] = 'result' - reply['id'] = iq['id'] - reply['to'] = iq['from'] - reply.send() - self.xmpp.event('google_nosave_change', iq) diff --git a/sleekxmpp/plugins/google/nosave/stanza.py b/sleekxmpp/plugins/google/nosave/stanza.py deleted file mode 100644 index d8701322..00000000 --- a/sleekxmpp/plugins/google/nosave/stanza.py +++ /dev/null @@ -1,59 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class NoSave(ElementBase): - name = 'x' - namespace = 'google:nosave' - plugin_attrib = 'google_nosave' - interfaces = set(['value']) - - def get_value(self): - return self._get_attr('value', '') == 'enabled' - - def set_value(self, value): - self._set_attr('value', 'enabled' if value else 'disabled') - - -class NoSaveQuery(ElementBase): - name = 'query' - namespace = 'google:nosave' - plugin_attrib = 'google_nosave' - interfaces = set() - - -class Item(ElementBase): - name = 'item' - namespace = 'google:nosave' - plugin_attrib = 'item' - plugin_multi_attrib = 'items' - interfaces = set(['jid', 'source', 'value']) - - def get_value(self): - return self._get_attr('value', '') == 'enabled' - - def set_value(self, value): - self._set_attr('value', 'enabled' if value else 'disabled') - - def get_jid(self): - return JID(self._get_attr('jid', '')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_source(self): - return JID(self._get_attr('source', '')) - - def set_source(self): - self._set_attr('source', str(value)) - - -register_stanza_plugin(NoSaveQuery, Item) diff --git a/sleekxmpp/plugins/google/settings/__init__.py b/sleekxmpp/plugins/google/settings/__init__.py deleted file mode 100644 index c3a0471d..00000000 --- a/sleekxmpp/plugins/google/settings/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.google.settings import stanza -from sleekxmpp.plugins.google.settings.settings import GoogleSettings diff --git a/sleekxmpp/plugins/google/settings/settings.py b/sleekxmpp/plugins/google/settings/settings.py deleted file mode 100644 index 7122ff56..00000000 --- a/sleekxmpp/plugins/google/settings/settings.py +++ /dev/null @@ -1,65 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Iq -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.google.settings import stanza - - -class GoogleSettings(BasePlugin): - - """ - Google: Gmail Notifications - - Also see <https://developers.google.com/talk/jep_extensions/usersettings>. - """ - - name = 'google_settings' - description = 'Google: User Settings' - dependencies = set() - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, stanza.UserSettings) - - self.xmpp.register_handler( - Callback('Google Settings', - StanzaPath('iq@type=set/google_settings'), - self._handle_settings_change)) - - def plugin_end(self): - self.xmpp.remove_handler('Google Settings') - - def get(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq.enable('google_settings') - return iq.send(block=block, timeout=timeout, callback=callback) - - def update(self, settings, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq.enable('google_settings') - - for setting, value in settings.items(): - iq['google_settings'][setting] = value - - return iq.send(block=block, timeout=timeout, callback=callback) - - def _handle_settings_change(self, iq): - reply = self.xmpp.Iq() - reply['type'] = 'result' - reply['id'] = iq['id'] - reply['to'] = iq['from'] - reply.send() - self.xmpp.event('google_settings_change', iq) diff --git a/sleekxmpp/plugins/google/settings/stanza.py b/sleekxmpp/plugins/google/settings/stanza.py deleted file mode 100644 index d8161770..00000000 --- a/sleekxmpp/plugins/google/settings/stanza.py +++ /dev/null @@ -1,110 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin - - -class UserSettings(ElementBase): - name = 'usersetting' - namespace = 'google:setting' - plugin_attrib = 'google_settings' - interfaces = set(['auto_accept_suggestions', - 'mail_notifications', - 'archiving_enabled', - 'gmail', - 'email_verified', - 'domain_privacy_notice', - 'display_name']) - - def _get_setting(self, setting): - xml = self.xml.find('{%s}%s' % (self.namespace, setting)) - if xml is not None: - return xml.attrib.get('value', '') == 'true' - return False - - def _set_setting(self, setting, value): - self._del_setting(setting) - if value in (True, False): - xml = ET.Element('{%s}%s' % (self.namespace, setting)) - xml.attrib['value'] = 'true' if value else 'false' - self.xml.append(xml) - - def _del_setting(self, setting): - xml = self.xml.find('{%s}%s' % (self.namespace, setting)) - if xml is not None: - self.xml.remove(xml) - - def get_display_name(self): - xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname')) - if xml is not None: - return xml.attrib.get('value', '') - return '' - - def set_display_name(self, value): - self._del_setting(setting) - if value: - xml = ET.Element('{%s}%s' % (self.namespace, 'displayname')) - xml.attrib['value'] = value - self.xml.append(xml) - - def del_display_name(self): - self._del_setting('displayname') - - def get_auto_accept_suggestions(self): - return self._get_setting('autoacceptsuggestions') - - def get_mail_notifications(self): - return self._get_setting('mailnotifications') - - def get_archiving_enabled(self): - return self._get_setting('archivingenabled') - - def get_gmail(self): - return self._get_setting('gmail') - - def get_email_verified(self): - return self._get_setting('emailverified') - - def get_domain_privacy_notice(self): - return self._get_setting('domainprivacynotice') - - def set_auto_accept_suggestions(self, value): - self._set_setting('autoacceptsuggestions', value) - - def set_mail_notifications(self, value): - self._set_setting('mailnotifications', value) - - def set_archiving_enabled(self, value): - self._set_setting('archivingenabled', value) - - def set_gmail(self, value): - self._set_setting('gmail', value) - - def set_email_verified(self, value): - self._set_setting('emailverified', value) - - def set_domain_privacy_notice(self, value): - self._set_setting('domainprivacynotice', value) - - def del_auto_accept_suggestions(self): - self._del_setting('autoacceptsuggestions') - - def del_mail_notifications(self): - self._del_setting('mailnotifications') - - def del_archiving_enabled(self): - self._del_setting('archivingenabled') - - def del_gmail(self): - self._del_setting('gmail') - - def del_email_verified(self): - self._del_setting('emailverified') - - def del_domain_privacy_notice(self): - self._del_setting('domainprivacynotice') diff --git a/sleekxmpp/plugins/xep_0004/__init__.py b/sleekxmpp/plugins/xep_0004/__init__.py deleted file mode 100644 index 2cd18ec8..00000000 --- a/sleekxmpp/plugins/xep_0004/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0004.stanza import Form -from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption -from sleekxmpp.plugins.xep_0004.dataforms import XEP_0004 - - -register_plugin(XEP_0004) - - -# Retain some backwards compatibility -xep_0004 = XEP_0004 -xep_0004.makeForm = xep_0004.make_form -xep_0004.buildForm = xep_0004.build_form diff --git a/sleekxmpp/plugins/xep_0004/dataforms.py b/sleekxmpp/plugins/xep_0004/dataforms.py deleted file mode 100644 index dde6e6a8..00000000 --- a/sleekxmpp/plugins/xep_0004/dataforms.py +++ /dev/null @@ -1,57 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Message -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0004 import stanza -from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption - - -class XEP_0004(BasePlugin): - - """ - XEP-0004: Data Forms - """ - - name = 'xep_0004' - description = 'XEP-0004: Data Forms' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('Data Form', - StanzaPath('message/form'), - self.handle_form)) - - register_stanza_plugin(FormField, FieldOption, iterable=True) - register_stanza_plugin(Form, FormField, iterable=True) - register_stanza_plugin(Message, Form) - - def plugin_end(self): - self.xmpp.remove_handler('Data Form') - self.xmpp['xep_0030'].del_feature(feature='jabber:x:data') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('jabber:x:data') - - def make_form(self, ftype='form', title='', instructions=''): - f = Form() - f['type'] = ftype - f['title'] = title - f['instructions'] = instructions - return f - - def handle_form(self, message): - self.xmpp.event("message_xform", message) - - def build_form(self, xml): - return Form(xml=xml) diff --git a/sleekxmpp/plugins/xep_0004/stanza/__init__.py b/sleekxmpp/plugins/xep_0004/stanza/__init__.py deleted file mode 100644 index 6ad35298..00000000 --- a/sleekxmpp/plugins/xep_0004/stanza/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption -from sleekxmpp.plugins.xep_0004.stanza.form import Form diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py deleted file mode 100644 index 51f85995..00000000 --- a/sleekxmpp/plugins/xep_0004/stanza/field.py +++ /dev/null @@ -1,183 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -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',)) - plugin_tag_map = {} - plugin_attrib_map = {} - - field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', - 'jid-single', 'list-multi', 'list-single', - 'text-multi', 'text-private', 'text-single')) - - true_values = set((True, '1', 'true')) - option_types = set(('list-multi', 'list-single')) - multi_line_types = set(('hidden', 'text-multi')) - multi_value_types = set(('hidden', 'jid-multi', - 'list-multi', 'text-multi')) - - def setup(self, xml=None): - if ElementBase.setup(self, xml): - self._type = None - else: - self._type = self['type'] - - def set_type(self, value): - self._set_attr('type', value) - if value: - self._type = value - - def add_option(self, label='', value=''): - if self._type is None or self._type in self.option_types: - opt = FieldOption() - opt['label'] = label - opt['value'] = value - self.append(opt) - else: - raise ValueError("Cannot add options to " + \ - "a %s field." % self['type']) - - def del_options(self): - optsXML = self.xml.findall('{%s}option' % self.namespace) - for optXML in optsXML: - self.xml.remove(optXML) - - def del_required(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - if reqXML is not None: - self.xml.remove(reqXML) - - def del_value(self): - valsXML = self.xml.findall('{%s}value' % self.namespace) - for valXML in valsXML: - self.xml.remove(valXML) - - def get_answer(self): - return self['value'] - - def get_options(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 get_required(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - return reqXML is not None - - def get_value(self, convert=True): - valsXML = self.xml.findall('{%s}value' % self.namespace) - if len(valsXML) == 0: - return None - elif self._type == 'boolean': - if convert: - return valsXML[0].text in self.true_values - return valsXML[0].text - elif self._type in self.multi_value_types or len(valsXML) > 1: - values = [] - for valXML in valsXML: - if valXML.text is None: - valXML.text = '' - values.append(valXML.text) - if self._type == 'text-multi' and convert: - values = "\n".join(values) - return values - else: - if valsXML[0].text is None: - return '' - return valsXML[0].text - - def set_answer(self, answer): - self['value'] = answer - - def set_false(self): - self['value'] = False - - def set_options(self, options): - for value in options: - if isinstance(value, dict): - self.add_option(**value) - else: - self.add_option(value=value) - - def set_required(self, required): - exists = self['required'] - if not exists and required: - self.xml.append(ET.Element('{%s}required' % self.namespace)) - elif exists and not required: - del self['required'] - - def set_true(self): - self['value'] = True - - def set_value(self, value): - del self['value'] - 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 isinstance(value, bool): - value = [value] - if not isinstance(value, list): - value = value.replace('\r', '') - value = value.split('\n') - 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',)) - - -FormField.addOption = FormField.add_option -FormField.delOptions = FormField.del_options -FormField.delRequired = FormField.del_required -FormField.delValue = FormField.del_value -FormField.getAnswer = FormField.get_answer -FormField.getOptions = FormField.get_options -FormField.getRequired = FormField.get_required -FormField.getValue = FormField.get_value -FormField.setAnswer = FormField.set_answer -FormField.setFalse = FormField.set_false -FormField.setOptions = FormField.set_options -FormField.setRequired = FormField.set_required -FormField.setTrue = FormField.set_true -FormField.setValue = FormField.set_value diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py deleted file mode 100644 index bbd8540f..00000000 --- a/sleekxmpp/plugins/xep_0004/stanza/form.py +++ /dev/null @@ -1,257 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import copy -import logging - -from sleekxmpp.thirdparty import OrderedDict - -from sleekxmpp.xmlstream import ElementBase, ET -from sleekxmpp.plugins.xep_0004.stanza import FormField - - -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 - - def setup(self, xml=None): - if ElementBase.setup(self, xml): - # If we had to generate xml - self['type'] = 'form' - - @property - def field(self): - return self['fields'] - - def set_type(self, ftype): - self._set_attr('type', ftype) - if ftype == 'submit': - fields = self['fields'] - for var in fields: - field = fields[var] - del field['type'] - del field['label'] - del field['desc'] - del field['required'] - del field['options'] - elif ftype == 'cancel': - del self['fields'] - - def add_field(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() - field['var'] = var - field['type'] = kwtype - field['value'] = value - if self['type'] in ('form', 'result'): - field['label'] = label - field['desc'] = desc - field['required'] = required - if options is not None: - field['options'] = options - else: - del field['type'] - self.append(field) - return field - - def getXML(self, type='submit'): - self['type'] = type - 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 add_item(self, values): - itemXML = ET.Element('{%s}item' % self.namespace) - self.xml.append(itemXML) - reported_vars = self['reported'].keys() - for var in reported_vars: - field = FormField() - field._type = self['reported'][var]['type'] - field['var'] = var - field['value'] = values.get(var, None) - itemXML.append(field.xml) - - def add_reported(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 del_fields(self): - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - self.xml.remove(fieldXML) - - def del_instructions(self): - instsXML = self.xml.findall('{%s}instructions') - for instXML in instsXML: - self.xml.remove(instXML) - - def del_items(self): - itemsXML = self.xml.find('{%s}item' % self.namespace) - for itemXML in itemsXML: - self.xml.remove(itemXML) - - def del_reported(self): - reportedXML = self.xml.find('{%s}reported' % self.namespace) - if reportedXML is not None: - self.xml.remove(reportedXML) - - def get_fields(self, use_dict=False): - fields = OrderedDict() - for stanza in self['substanzas']: - if isinstance(stanza, FormField): - fields[stanza['var']] = stanza - return fields - - def get_instructions(self): - instructions = '' - instsXML = self.xml.findall('{%s}instructions' % self.namespace) - return "\n".join([instXML.text for instXML in instsXML]) - - def get_items(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - item = OrderedDict() - 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 get_reported(self): - fields = OrderedDict() - xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, - FormField.namespace)) - for field in xml: - field = FormField(xml=field) - fields[field['var']] = field - return fields - - def get_values(self): - values = OrderedDict() - fields = self['fields'] - 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 set_fields(self, fields): - del self['fields'] - if not isinstance(fields, list): - fields = fields.items() - for var, field in fields: - field['var'] = var - self.add_field(**field) - - def set_instructions(self, instructions): - del self['instructions'] - if instructions in [None, '']: - return - if not isinstance(instructions, list): - instructions = instructions.split('\n') - for instruction in instructions: - inst = ET.Element('{%s}instructions' % self.namespace) - inst.text = instruction - self.xml.append(inst) - - def set_items(self, items): - for item in items: - self.add_item(item) - - def set_reported(self, reported): - for var in reported: - field = reported[var] - field['var'] = var - self.add_reported(var, **field) - - def set_values(self, values): - fields = self['fields'] - for field in values: - if field not in fields: - fields[field] = self.add_field(var=field) - fields[field]['value'] = values[field] - - def merge(self, other): - new = copy.copy(self) - if type(other) == dict: - new['values'] = other - return new - nfields = new['fields'] - ofields = other['fields'] - nfields.update(ofields) - new['fields'] = nfields - return new - - -Form.setType = Form.set_type -Form.addField = Form.add_field -Form.addItem = Form.add_item -Form.addReported = Form.add_reported -Form.delFields = Form.del_fields -Form.delInstructions = Form.del_instructions -Form.delItems = Form.del_items -Form.delReported = Form.del_reported -Form.getFields = Form.get_fields -Form.getInstructions = Form.get_instructions -Form.getItems = Form.get_items -Form.getReported = Form.get_reported -Form.getValues = Form.get_values -Form.setFields = Form.set_fields -Form.setInstructions = Form.set_instructions -Form.setItems = Form.set_items -Form.setReported = Form.set_reported -Form.setValues = Form.set_values diff --git a/sleekxmpp/plugins/xep_0009/__init__.py b/sleekxmpp/plugins/xep_0009/__init__.py deleted file mode 100644 index 0ce3cf2c..00000000 --- a/sleekxmpp/plugins/xep_0009/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0009 import stanza -from sleekxmpp.plugins.xep_0009.rpc import XEP_0009 -from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse - - -register_plugin(XEP_0009) - - -# Retain some backwards compatibility -xep_0009 = XEP_0009 diff --git a/sleekxmpp/plugins/xep_0009/binding.py b/sleekxmpp/plugins/xep_0009/binding.py deleted file mode 100644 index a55993ad..00000000 --- a/sleekxmpp/plugins/xep_0009/binding.py +++ /dev/null @@ -1,173 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET -import base64 -import logging -import time -import sys - -if sys.version_info > (3, 0): - unicode = str - -log = logging.getLogger(__name__) - -_namespace = 'jabber:iq:rpc' - -def fault2xml(fault): - value = dict() - value['faultCode'] = fault['code'] - value['faultString'] = fault['string'] - fault = ET.Element("fault", {'xmlns': _namespace}) - fault.append(_py2xml((value))) - return fault - -def xml2fault(params): - vals = [] - for value in params.findall('{%s}value' % _namespace): - vals.append(_xml2py(value)) - fault = dict() - fault['code'] = vals[0]['faultCode'] - fault['string'] = vals[0]['faultString'] - return fault - -def py2xml(*args): - params = ET.Element("{%s}params" % _namespace) - for x in args: - param = ET.Element("{%s}param" % _namespace) - param.append(_py2xml(x)) - params.append(param) #<params><param>... - return params - -def _py2xml(*args): - for x in args: - val = ET.Element("{%s}value" % _namespace) - if x is None: - nil = ET.Element("{%s}nil" % _namespace) - val.append(nil) - elif type(x) is int: - i4 = ET.Element("{%s}i4" % _namespace) - i4.text = str(x) - val.append(i4) - elif type(x) is bool: - boolean = ET.Element("{%s}boolean" % _namespace) - boolean.text = str(int(x)) - val.append(boolean) - elif type(x) in (str, unicode): - string = ET.Element("{%s}string" % _namespace) - string.text = x - val.append(string) - elif type(x) is float: - double = ET.Element("{%s}double" % _namespace) - double.text = str(x) - val.append(double) - elif type(x) is rpcbase64: - b64 = ET.Element("{%s}base64" % _namespace) - b64.text = x.encoded() - val.append(b64) - elif type(x) is rpctime: - iso = ET.Element("{%s}dateTime.iso8601" % _namespace) - iso.text = str(x) - val.append(iso) - elif type(x) in (list, tuple): - array = ET.Element("{%s}array" % _namespace) - data = ET.Element("{%s}data" % _namespace) - for y in x: - data.append(_py2xml(y)) - array.append(data) - val.append(array) - elif type(x) is dict: - struct = ET.Element("{%s}struct" % _namespace) - for y in x.keys(): - member = ET.Element("{%s}member" % _namespace) - name = ET.Element("{%s}name" % _namespace) - name.text = y - member.append(name) - member.append(_py2xml(x[y])) - struct.append(member) - val.append(struct) - return val - -def xml2py(params): - namespace = 'jabber:iq:rpc' - vals = [] - for param in params.findall('{%s}param' % namespace): - vals.append(_xml2py(param.find('{%s}value' % namespace))) - return vals - -def _xml2py(value): - namespace = 'jabber:iq:rpc' - if value.find('{%s}nil' % namespace) is not None: - return None - if value.find('{%s}i4' % namespace) is not None: - return int(value.find('{%s}i4' % namespace).text) - if value.find('{%s}int' % namespace) is not None: - return int(value.find('{%s}int' % namespace).text) - if value.find('{%s}boolean' % namespace) is not None: - return bool(int(value.find('{%s}boolean' % namespace).text)) - if value.find('{%s}string' % namespace) is not None: - return value.find('{%s}string' % namespace).text - if value.find('{%s}double' % namespace) is not None: - return float(value.find('{%s}double' % namespace).text) - if value.find('{%s}base64' % namespace) is not None: - return rpcbase64(value.find('{%s}base64' % namespace).text.encode()) - if value.find('{%s}Base64' % namespace) is not None: - # Older versions of XEP-0009 used Base64 - return rpcbase64(value.find('{%s}Base64' % namespace).text.encode()) - if value.find('{%s}dateTime.iso8601' % namespace) is not None: - return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text) - if value.find('{%s}struct' % namespace) is not None: - struct = {} - for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace): - struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace)) - return struct - if value.find('{%s}array' % namespace) is not None: - array = [] - for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace): - 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.b64decode(self.data) - - def __str__(self): - return self.decode().decode() - - def encoded(self): - return self.data.decode() - - - -class rpctime(object): - - def __init__(self,data=None): - #assume string data is in iso format YYYYMMDDTHH:MM:SS - if type(data) in (str, unicode): - 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() diff --git a/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py deleted file mode 100644 index 8c08e8f3..00000000 --- a/sleekxmpp/plugins/xep_0009/remote.py +++ /dev/null @@ -1,742 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from binding import py2xml, xml2py, xml2fault, fault2xml -from threading import RLock -import abc -import inspect -import logging -import sleekxmpp -import sys -import threading -import traceback - -log = logging.getLogger(__name__) - -def _intercept(method, name, public): - def _resolver(instance, *args, **kwargs): - log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args) - try: - value = method(instance, *args, **kwargs) - if value == NotImplemented: - raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__)) - return value - except InvocationException: - raise - except Exception as e: - raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e) - _resolver._rpc = public - _resolver._rpc_name = method.__name__ if name is None else name - return _resolver - -def remote(function_argument, public = True): - ''' - Decorator for methods which are remotely callable. This decorator - works in conjunction with classes which extend ABC Endpoint. - Example: - - @remote - def remote_method(arg1, arg2) - - Arguments: - function_argument -- a stand-in for either the actual method - OR a new name (string) for the method. In that case the - method is considered mapped: - Example: - - @remote("new_name") - def remote_method(arg1, arg2) - - public -- A flag which indicates if this method should be part - of the known dictionary of remote methods. Defaults to True. - Example: - - @remote(False) - def remote_method(arg1, arg2) - - Note: renaming and revising (public vs. private) can be combined. - Example: - - @remote("new_name", False) - def remote_method(arg1, arg2) - ''' - if hasattr(function_argument, '__call__'): - return _intercept(function_argument, None, public) - else: - if not isinstance(function_argument, basestring): - if not isinstance(function_argument, bool): - raise Exception('Expected an RPC method name or visibility modifier!') - else: - def _wrap_revised(function): - function = _intercept(function, None, function_argument) - return function - return _wrap_revised - def _wrap_remapped(function): - function = _intercept(function, function_argument, public) - return function - return _wrap_remapped - - -class ACL: - ''' - An Access Control List (ACL) is a list of rules, which are evaluated - in order until a match is found. The policy of the matching rule - is then applied. - - Rules are 3-tuples, consisting of a policy enumerated type, a JID - expression and a RCP resource expression. - - Examples: - [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions - [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions - [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'), - (ACL.DENY, '*', '*') ] deny everyone everything, except named - JID, which is allowed access to endpoint 'test' only. - - The use of wildcards is allowed in expressions, as follows: - '*' everyone, or everything (= all endpoints and methods) - 'test@xmpp.org/*' every JID regardless of JID resource - '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc' - 'frank@*' every 'frank', regardless of domain or JID res - 'system.*' all methods of endpoint 'system' - '*.reboot' all methods reboot regardless of endpoint - ''' - ALLOW = True - DENY = False - - @classmethod - def check(cls, rules, jid, resource): - if rules is None: - return cls.DENY # No rules means no access! - jid = str(jid) # Check the string representation of the JID. - if not jid: - return cls.DENY # Can't check an empty JID. - for rule in rules: - policy = cls._check(rule, jid, resource) - if policy is not None: - return policy - return cls.DENY # By default if not rule matches, deny access. - - @classmethod - def _check(cls, rule, jid, resource): - if cls._match(jid, rule[1]) and cls._match(resource, rule[2]): - return rule[0] - else: - return None - - @classmethod - def _next_token(cls, expression, index): - new_index = expression.find('*', index) - if new_index == 0: - return '' - else: - if new_index == -1: - return expression[index : ] - else: - return expression[index : new_index] - - @classmethod - def _match(cls, value, expression): - #! print "_match [VALUE] %s [EXPR] %s" % (value, expression) - index = 0 - position = 0 - while index < len(expression): - token = cls._next_token(expression, index) - #! print "[TOKEN] '%s'" % token - size = len(token) - if size > 0: - token_index = value.find(token, position) - if token_index == -1: - return False - else: - #! print "[INDEX-OF] %s" % token_index - position = token_index + len(token) - pass - if size == 0: - index += 1 - else: - index += size - #! print "index %s position %s" % (index, position) - return True - -ANY_ALL = [ (ACL.ALLOW, '*', '*') ] - - -class RemoteException(Exception): - ''' - Base exception for RPC. This exception is raised when a problem - occurs in the network layer. - ''' - - def __init__(self, message="", cause=None): - ''' - Initializes a new RemoteException. - - Arguments: - message -- The message accompanying this exception. - cause -- The underlying cause of this exception. - ''' - self._message = message - self._cause = cause - pass - - def __str__(self): - return repr(self._message) - - def get_message(self): - return self._message - - def get_cause(self): - return self._cause - - - -class InvocationException(RemoteException): - ''' - Exception raised when a problem occurs during the remote invocation - of a method. - ''' - pass - - - -class AuthorizationException(RemoteException): - ''' - Exception raised when the caller is not authorized to invoke the - remote method. - ''' - pass - - -class TimeoutException(Exception): - ''' - Exception raised when the synchronous execution of a method takes - longer than the given threshold because an underlying asynchronous - reply did not arrive in time. - ''' - pass - - -class Callback(object): - ''' - A base class for callback handlers. - ''' - __metaclass__ = abc.ABCMeta - - - @abc.abstractproperty - def set_value(self, value): - return NotImplemented - - @abc.abstractproperty - def cancel_with_error(self, exception): - return NotImplemented - - -class Future(Callback): - ''' - Represents the result of an asynchronous computation. - ''' - - def __init__(self): - ''' - Initializes a new Future. - ''' - self._value = None - self._exception = None - self._event = threading.Event() - pass - - def set_value(self, value): - ''' - Sets the value of this Future. Once the value is set, a caller - blocked on get_value will be able to continue. - ''' - self._value = value - self._event.set() - - def get_value(self, timeout=None): - ''' - Gets the value of this Future. This call will block until - the result is available, or until an optional timeout expires. - When this Future is cancelled with an error, - - Arguments: - timeout -- The maximum waiting time to obtain the value. - ''' - self._event.wait(timeout) - if self._exception: - raise self._exception - if not self._event.is_set(): - raise TimeoutException - return self._value - - def is_done(self): - ''' - Returns true if a value has been returned. - ''' - return self._event.is_set() - - def cancel_with_error(self, exception): - ''' - Cancels the Future because of an error. Once cancelled, a - caller blocked on get_value will be able to continue. - ''' - self._exception = exception - self._event.set() - - - -class Endpoint(object): - ''' - The Endpoint class is an abstract base class for all objects - participating in an RPC-enabled XMPP network. - - A user subclassing this class is required to implement the method: - FQN(self) - where FQN stands for Fully Qualified Name, an unambiguous name - which specifies which object an RPC call refers to. It is the - first part in a RPC method name '<fqn>.<method>'. - ''' - __metaclass__ = abc.ABCMeta - - - def __init__(self, session, target_jid): - ''' - Initialize a new Endpoint. This constructor should never be - invoked by a user, instead it will be called by the factories - which instantiate the RPC-enabled objects, of which only - the classes are provided by the user. - - Arguments: - session -- An RPC session instance. - target_jid -- the identity of the remote XMPP entity. - ''' - self.session = session - self.target_jid = target_jid - - @abc.abstractproperty - def FQN(self): - return NotImplemented - - def get_methods(self): - ''' - Returns a dictionary of all RPC method names provided by this - class. This method returns the actual method names as found - in the class definition which have been decorated with: - - @remote - def some_rpc_method(arg1, arg2) - - - Unless: - (1) the name has been remapped, in which case the new - name will be returned. - - @remote("new_name") - def some_rpc_method(arg1, arg2) - - (2) the method is set to hidden - - @remote(False) - def some_hidden_method(arg1, arg2) - ''' - result = dict() - for function in dir(self): - test_attr = getattr(self, function, None) - try: - if test_attr._rpc: - result[test_attr._rpc_name] = test_attr - except Exception: - pass - return result - - - -class Proxy(Endpoint): - ''' - Implementation of the Proxy pattern which is intended to wrap - around Endpoints in order to intercept calls, marshall them and - forward them to the remote object. - ''' - - def __init__(self, endpoint, callback = None): - ''' - Initializes a new Proxy. - - Arguments: - endpoint -- The endpoint which is proxified. - ''' - self._endpoint = endpoint - self._callback = callback - - def __getattribute__(self, name, *args): - if name in ('__dict__', '_endpoint', 'async', '_callback'): - return object.__getattribute__(self, name) - else: - attribute = self._endpoint.__getattribute__(name) - if hasattr(attribute, '__call__'): - try: - if attribute._rpc: - def _remote_call(*args, **kwargs): - log.debug("Remotely calling '%s.%s' with arguments %s.", self._endpoint.FQN(), attribute._rpc_name, args) - return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs) - return _remote_call - except: - pass # If the attribute doesn't exist, don't care! - return attribute - - def async(self, callback): - return Proxy(self._endpoint, callback) - - def get_endpoint(self): - ''' - Returns the proxified endpoint. - ''' - return self._endpoint - - def FQN(self): - return self._endpoint.FQN() - - -class JabberRPCEntry(object): - - - def __init__(self, endpoint_FQN, call): - self._endpoint_FQN = endpoint_FQN - self._call = call - - def call_method(self, args): - return_value = self._call(*args) - if return_value is None: - return return_value - else: - return self._return(return_value) - - def get_endpoint_FQN(self): - return self._endpoint_FQN - - def _return(self, *args): - return args - - -class RemoteSession(object): - ''' - A context object for a Jabber-RPC session. - ''' - - - def __init__(self, client, session_close_callback): - ''' - Initializes a new RPC session. - - Arguments: - client -- The SleekXMPP client associated with this session. - session_close_callback -- A callback called when the - session is closed. - ''' - self._client = client - self._session_close_callback = session_close_callback - self._event = threading.Event() - self._entries = {} - self._callbacks = {} - self._acls = {} - self._lock = RLock() - - def _wait(self): - self._event.wait() - - def _notify(self, event): - log.debug("RPC Session as %s started.", self._client.boundjid.full) - self._client.sendPresence() - self._event.set() - pass - - def _register_call(self, endpoint, method, name=None): - ''' - Registers a method from an endpoint as remotely callable. - ''' - if name is None: - name = method.__name__ - key = "%s.%s" % (endpoint, name) - log.debug("Registering call handler for %s (%s).", key, method) - with self._lock: - if key in self._entries: - raise KeyError("A handler for %s has already been regisered!" % endpoint) - self._entries[key] = JabberRPCEntry(endpoint, method) - return key - - def _register_acl(self, endpoint, acl): - log.debug("Registering ACL %s for endpoint %s.", repr(acl), endpoint) - with self._lock: - self._acls[endpoint] = acl - - def _register_callback(self, pid, callback): - with self._lock: - self._callbacks[pid] = callback - - def forget_callback(self, callback): - with self._lock: - pid = self._find_key(self._callbacks, callback) - if pid is not None: - del self._callback[pid] - else: - raise ValueError("Unknown callback!") - pass - - def _find_key(self, dict, value): - """return the key of dictionary dic given the value""" - search = [k for k, v in dict.iteritems() if v == value] - if len(search) == 0: - return None - else: - return search[0] - - def _unregister_call(self, key): - #removes the registered call - with self._lock: - if self._entries[key]: - del self._entries[key] - else: - raise ValueError() - - def new_proxy(self, target_jid, endpoint_cls): - ''' - Instantiates a new proxy object, which proxies to a remote - endpoint. This method uses a class reference without - constructor arguments to instantiate the proxy. - - Arguments: - target_jid -- the XMPP entity ID hosting the endpoint. - endpoint_cls -- The remote (duck) type. - ''' - try: - argspec = inspect.getargspec(endpoint_cls.__init__) - args = [None] * (len(argspec[0]) - 1) - result = endpoint_cls(*args) - Endpoint.__init__(result, self, target_jid) - return Proxy(result) - except: - traceback.print_exc(file=sys.stdout) - - def new_handler(self, acl, handler_cls, *args, **kwargs): - ''' - Instantiates a new handler object, which is called remotely - by others. The user can control the effect of the call by - implementing the remote method in the local endpoint class. The - returned reference can be called locally and will behave as a - regular instance. - - Arguments: - acl -- Access control list (see ACL class) - handler_clss -- The local (duck) type. - *args -- Constructor arguments for the local type. - **kwargs -- Constructor keyworded arguments for the local - type. - ''' - argspec = inspect.getargspec(handler_cls.__init__) - base_argspec = inspect.getargspec(Endpoint.__init__) - if(argspec == base_argspec): - result = handler_cls(self, self._client.boundjid.full) - else: - result = handler_cls(*args, **kwargs) - Endpoint.__init__(result, self, self._client.boundjid.full) - method_dict = result.get_methods() - for method_name, method in method_dict.iteritems(): - #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name) - self._register_call(result.FQN(), method, method_name) - self._register_acl(result.FQN(), acl) - return result - -# def is_available(self, targetCls, pto): -# return self._client.is_available(pto) - - def _call_remote(self, pto, pmethod, callback, *arguments): - iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments)) - pid = iq['id'] - if callback is None: - future = Future() - self._register_callback(pid, future) - iq.send() - return future.get_value(30) - else: - log.debug("[RemoteSession] _call_remote %s", callback) - self._register_callback(pid, callback) - iq.send() - - def close(self): - ''' - Closes this session. - ''' - self._client.disconnect(False) - self._session_close_callback() - - def _on_jabber_rpc_method_call(self, iq): - iq.enable('rpc_query') - params = iq['rpc_query']['method_call']['params'] - args = xml2py(params) - pmethod = iq['rpc_query']['method_call']['method_name'] - try: - with self._lock: - entry = self._entries[pmethod] - rules = self._acls[entry.get_endpoint_FQN()] - if ACL.check(rules, iq['from'], pmethod): - return_value = entry.call_method(args) - else: - raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from'])) - if return_value is None: - return_value = () - response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value)) - response.send() - except InvocationException as ie: - fault = dict() - fault['code'] = 500 - fault['string'] = ie.get_message() - self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault)) - except AuthorizationException as ae: - log.error(ae.get_message()) - error = self._client.plugin['xep_0009']._forbidden(iq) - error.send() - except Exception as e: - if isinstance(e, KeyError): - log.error("No handler available for %s!", pmethod) - error = self._client.plugin['xep_0009']._item_not_found(iq) - else: - traceback.print_exc(file=sys.stderr) - log.error("An unexpected problem occurred invoking method %s!", pmethod) - error = self._client.plugin['xep_0009']._undefined_condition(iq) - #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e - error.send() - - def _on_jabber_rpc_method_response(self, iq): - iq.enable('rpc_query') - args = xml2py(iq['rpc_query']['method_response']['params']) - pid = iq['id'] - with self._lock: - callback = self._callbacks[pid] - del self._callbacks[pid] - if(len(args) > 0): - callback.set_value(args[0]) - else: - callback.set_value(None) - pass - - def _on_jabber_rpc_method_response2(self, iq): - iq.enable('rpc_query') - if iq['rpc_query']['method_response']['fault'] is not None: - self._on_jabber_rpc_method_fault(iq) - else: - args = xml2py(iq['rpc_query']['method_response']['params']) - pid = iq['id'] - with self._lock: - callback = self._callbacks[pid] - del self._callbacks[pid] - if(len(args) > 0): - callback.set_value(args[0]) - else: - callback.set_value(None) - pass - - def _on_jabber_rpc_method_fault(self, iq): - iq.enable('rpc_query') - fault = xml2fault(iq['rpc_query']['method_response']['fault']) - pid = iq['id'] - with self._lock: - callback = self._callbacks[pid] - del self._callbacks[pid] - e = { - 500: InvocationException - }[fault['code']](fault['string']) - callback.cancel_with_error(e) - - def _on_jabber_rpc_error(self, iq): - pid = iq['id'] - pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query']) - code = iq['error']['code'] - type = iq['error']['type'] - condition = iq['error']['condition'] - #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition) - with self._lock: - callback = self._callbacks[pid] - del self._callbacks[pid] - e = { - 'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])), - 'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])), - 'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])), - }[condition] - if e is None: - RemoteException("An unexpected exception occurred at %s!" % iq['from']) - callback.cancel_with_error(e) - - -class Remote(object): - ''' - Bootstrap class for Jabber-RPC sessions. New sessions are openend - with an existing XMPP client, or one is instantiated on demand. - ''' - _instance = None - _sessions = dict() - _lock = threading.RLock() - - @classmethod - def new_session_with_client(cls, client, callback=None): - ''' - Opens a new session with a given client. - - Arguments: - client -- An XMPP client. - callback -- An optional callback which can be used to track - the starting state of the session. - ''' - with Remote._lock: - if(client.boundjid.bare in cls._sessions): - raise RemoteException("There already is a session associated with these credentials!") - else: - cls._sessions[client.boundjid.bare] = client; - def _session_close_callback(): - with Remote._lock: - del cls._sessions[client.boundjid.bare] - result = RemoteSession(client, _session_close_callback) - client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True) - client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True) - client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True) - client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True) - if callback is None: - start_event_handler = result._notify - else: - start_event_handler = callback - client.add_event_handler("session_start", start_event_handler) - if client.connect(): - client.process(threaded=True) - else: - raise RemoteException("Could not connect to XMPP server!") - pass - if callback is None: - result._wait() - return result - - @classmethod - def new_session(cls, jid, password, callback=None): - ''' - Opens a new session and instantiates a new XMPP client. - - Arguments: - jid -- The XMPP JID for logging in. - password -- The password for logging in. - callback -- An optional callback which can be used to track - the starting state of the session. - ''' - client = sleekxmpp.ClientXMPP(jid, password) - #? Register plug-ins. - client.registerPlugin('xep_0004') # Data Forms - client.registerPlugin('xep_0009') # Jabber-RPC - client.registerPlugin('xep_0030') # Service Discovery - client.registerPlugin('xep_0060') # PubSub - client.registerPlugin('xep_0199') # XMPP Ping - return cls.new_session_with_client(client, callback) - diff --git a/sleekxmpp/plugins/xep_0009/rpc.py b/sleekxmpp/plugins/xep_0009/rpc.py deleted file mode 100644 index 3378c650..00000000 --- a/sleekxmpp/plugins/xep_0009/rpc.py +++ /dev/null @@ -1,218 +0,0 @@ -"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-
-from sleekxmpp import Iq
-from sleekxmpp.xmlstream import ET, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import MatchXPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0009 import stanza
-from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0009(BasePlugin):
-
- name = 'xep_0009'
- description = 'XEP-0009: Jabber-RPC'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, RPCQuery)
- register_stanza_plugin(RPCQuery, MethodCall)
- register_stanza_plugin(RPCQuery, MethodResponse)
-
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
- self._handle_method_call)
- )
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
- self._handle_method_response)
- )
- self.xmpp.register_handler(
- Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
- self._handle_error)
- )
- self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call)
- self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response)
- self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault)
- self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error)
- self.xmpp.add_event_handler('error', self._handle_error)
- #self.activeCalls = []
-
- self.xmpp['xep_0030'].add_feature('jabber:iq:rpc')
- self.xmpp['xep_0030'].add_identity('automation','rpc')
-
- def make_iq_method_call(self, pto, pmethod, params):
- iq = self.xmpp.makeIqSet()
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_call']['method_name'] = pmethod
- iq['rpc_query']['method_call']['params'] = params
- return iq;
-
- def make_iq_method_response(self, pid, pto, params):
- iq = self.xmpp.makeIqResult(pid)
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_response']['params'] = params
- return iq
-
- def make_iq_method_response_fault(self, pid, pto, params):
- iq = self.xmpp.makeIqResult(pid)
- iq.attrib['to'] = pto
- iq.attrib['from'] = self.xmpp.boundjid.full
- iq.enable('rpc_query')
- iq['rpc_query']['method_response']['params'] = None
- iq['rpc_query']['method_response']['fault'] = params
- return iq
-
-# def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition):
-# iq = self.xmpp.makeIqError(pid)
-# iq.attrib['to'] = pto
-# iq.attrib['from'] = self.xmpp.boundjid.full
-# iq['error']['code'] = code
-# iq['error']['type'] = type
-# iq['error']['condition'] = condition
-# iq['rpc_query']['method_call']['method_name'] = pmethod
-# iq['rpc_query']['method_call']['params'] = params
-# return iq
-
- def _item_not_found(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload);
- iq['error']['code'] = '404'
- iq['error']['type'] = 'cancel'
- iq['error']['condition'] = 'item-not-found'
- return iq
-
- def _undefined_condition(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '500'
- iq['error']['type'] = 'cancel'
- iq['error']['condition'] = 'undefined-condition'
- return iq
-
- def _forbidden(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '403'
- iq['error']['type'] = 'auth'
- iq['error']['condition'] = 'forbidden'
- return iq
-
- def _recipient_unvailable(self, iq):
- payload = iq.get_payload()
- iq.reply().error().set_payload(payload)
- iq['error']['code'] = '404'
- iq['error']['type'] = 'wait'
- iq['error']['condition'] = 'recipient-unavailable'
- return iq
-
- def _handle_method_call(self, iq):
- type = iq['type']
- if type == 'set':
- log.debug("Incoming Jabber-RPC call from %s", iq['from'])
- self.xmpp.event('jabber_rpc_method_call', iq)
- else:
- if type == 'error' and ['rpc_query'] is None:
- self.handle_error(iq)
- else:
- log.debug("Incoming Jabber-RPC error from %s", iq['from'])
- self.xmpp.event('jabber_rpc_error', iq)
-
- def _handle_method_response(self, iq):
- if iq['rpc_query']['method_response']['fault'] is not None:
- log.debug("Incoming Jabber-RPC fault from %s", iq['from'])
- #self._on_jabber_rpc_method_fault(iq)
- self.xmpp.event('jabber_rpc_method_fault', iq)
- else:
- log.debug("Incoming Jabber-RPC response from %s", iq['from'])
- self.xmpp.event('jabber_rpc_method_response', iq)
-
- def _handle_error(self, iq):
- print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq)
- print("#######################")
- print("### NOT IMPLEMENTED ###")
- print("#######################")
-
- def _on_jabber_rpc_method_call(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC method call. 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('jabber_rpc_method_call') > 1:
- return
- # Reply with error by default
- error = self.client.plugin['xep_0009']._item_not_found(iq)
- error.send()
-
- def _on_jabber_rpc_method_response(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC method response. 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('jabber_rpc_method_response') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
- error.send()
-
- def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC fault response. 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('jabber_rpc_method_fault') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
- error.send()
-
- def _on_jabber_rpc_error(self, iq, forwarded=False):
- """
- A default handler for Jabber-RPC error response. 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('jabber_rpc_error') > 1:
- return
- error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
- error.send()
-
- def _send_fault(self, iq, fault_xml): #
- fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml)
- fault.send()
-
- def _send_error(self, iq):
- print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq)
- print("#######################")
- print("### NOT IMPLEMENTED ###")
- print("#######################")
-
- def _extract_method(self, stanza):
- xml = ET.fromstring("%s" % stanza)
- return xml.find("./methodCall/methodName").text
diff --git a/sleekxmpp/plugins/xep_0009/stanza/RPC.py b/sleekxmpp/plugins/xep_0009/stanza/RPC.py deleted file mode 100644 index 3d1c77a2..00000000 --- a/sleekxmpp/plugins/xep_0009/stanza/RPC.py +++ /dev/null @@ -1,64 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.stanzabase import ElementBase -from xml.etree import cElementTree as ET - - -class RPCQuery(ElementBase): - name = 'query' - namespace = 'jabber:iq:rpc' - plugin_attrib = 'rpc_query' - interfaces = set(()) - subinterfaces = set(()) - plugin_attrib_map = {} - plugin_tag_map = {} - - -class MethodCall(ElementBase): - name = 'methodCall' - namespace = 'jabber:iq:rpc' - plugin_attrib = 'method_call' - interfaces = set(('method_name', 'params')) - subinterfaces = set(()) - plugin_attrib_map = {} - plugin_tag_map = {} - - def get_method_name(self): - return self._get_sub_text('methodName') - - def set_method_name(self, value): - return self._set_sub_text('methodName', value) - - def get_params(self): - return self.xml.find('{%s}params' % self.namespace) - - def set_params(self, params): - self.append(params) - - -class MethodResponse(ElementBase): - name = 'methodResponse' - namespace = 'jabber:iq:rpc' - plugin_attrib = 'method_response' - interfaces = set(('params', 'fault')) - subinterfaces = set(()) - plugin_attrib_map = {} - plugin_tag_map = {} - - def get_params(self): - return self.xml.find('{%s}params' % self.namespace) - - def set_params(self, params): - self.append(params) - - def get_fault(self): - return self.xml.find('{%s}fault' % self.namespace) - - def set_fault(self, fault): - self.append(fault) diff --git a/sleekxmpp/plugins/xep_0009/stanza/__init__.py b/sleekxmpp/plugins/xep_0009/stanza/__init__.py deleted file mode 100644 index 5dcbf330..00000000 --- a/sleekxmpp/plugins/xep_0009/stanza/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse diff --git a/sleekxmpp/plugins/xep_0012/__init__.py b/sleekxmpp/plugins/xep_0012/__init__.py deleted file mode 100644 index 6b778fc1..00000000 --- a/sleekxmpp/plugins/xep_0012/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0012.stanza import LastActivity -from sleekxmpp.plugins.xep_0012.last_activity import XEP_0012 - - -register_plugin(XEP_0012) - - -# Retain some backwards compatibility -xep_0004 = XEP_0012 diff --git a/sleekxmpp/plugins/xep_0012/last_activity.py b/sleekxmpp/plugins/xep_0012/last_activity.py deleted file mode 100644 index 8790b47c..00000000 --- a/sleekxmpp/plugins/xep_0012/last_activity.py +++ /dev/null @@ -1,157 +0,0 @@ -"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import logging
-from datetime import datetime, timedelta
-
-from sleekxmpp.plugins import BasePlugin, register_plugin
-from sleekxmpp import Iq
-from sleekxmpp.exceptions import XMPPError
-from sleekxmpp.xmlstream import JID, register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0012(BasePlugin):
-
- """
- XEP-0012 Last Activity
- """
-
- name = 'xep_0012'
- description = 'XEP-0012: Last Activity'
- dependencies = set(['xep_0030'])
- stanza = stanza
-
- def plugin_init(self):
- register_stanza_plugin(Iq, LastActivity)
-
- self._last_activities = {}
-
- self.xmpp.register_handler(
- Callback('Last Activity',
- StanzaPath('iq@type=get/last_activity'),
- self._handle_get_last_activity))
-
- self.api.register(self._default_get_last_activity,
- 'get_last_activity',
- default=True)
- self.api.register(self._default_set_last_activity,
- 'set_last_activity',
- default=True)
- self.api.register(self._default_del_last_activity,
- 'del_last_activity',
- default=True)
-
- def plugin_end(self):
- self.xmpp.remove_handler('Last Activity')
- self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('jabber:iq:last')
-
- def begin_idle(self, jid=None, status=None):
- self.set_last_activity(jid, 0, status)
-
- def end_idle(self, jid=None):
- self.del_last_activity(jid)
-
- def start_uptime(self, status=None):
- self.set_last_activity(jid, 0, status)
-
- def set_last_activity(self, jid=None, seconds=None, status=None):
- self.api['set_last_activity'](jid, args={
- 'seconds': seconds,
- 'status': status})
-
- def del_last_activity(self, jid):
- self.api['del_last_activity'](jid)
-
- def get_last_activity(self, jid, local=False, ifrom=None, block=True,
- timeout=None, callback=None):
- if jid is not None and not isinstance(jid, JID):
- jid = JID(jid)
-
- if self.xmpp.is_component:
- if jid.domain == self.xmpp.boundjid.domain:
- local = True
- else:
- if str(jid) == str(self.xmpp.boundjid):
- local = True
- jid = jid.full
-
- if local or jid in (None, ''):
- log.debug("Looking up local last activity data for %s", jid)
- return self.api['get_last_activity'](jid, None, ifrom, None)
-
- iq = self.xmpp.Iq()
- iq['from'] = ifrom
- iq['to'] = jid
- iq['type'] = 'get'
- iq.enable('last_activity')
- return iq.send(timeout=timeout,
- block=block,
- callback=callback)
-
- def _handle_get_last_activity(self, iq):
- log.debug("Received last activity query from " + \
- "<%s> to <%s>.", iq['from'], iq['to'])
- reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq)
- reply.send()
-
- # =================================================================
- # Default in-memory implementations for storing last activity data.
- # =================================================================
-
- def _default_set_last_activity(self, jid, node, ifrom, data):
- seconds = data.get('seconds', None)
- if seconds is None:
- seconds = 0
-
- status = data.get('status', None)
- if status is None:
- status = ''
-
- self._last_activities[jid] = {
- 'seconds': datetime.now() - timedelta(seconds=seconds),
- 'status': status}
-
- def _default_del_last_activity(self, jid, node, ifrom, data):
- if jid in self._last_activities:
- del self._last_activities[jid]
-
- def _default_get_last_activity(self, jid, node, ifrom, iq):
- if not isinstance(iq, Iq):
- reply = self.xmpp.Iq()
- else:
- iq.reply()
- reply = iq
-
- if jid not in self._last_activities:
- raise XMPPError('service-unavailable')
-
- bare = JID(jid).bare
-
- if bare != self.xmpp.boundjid.bare:
- if bare in self.xmpp.roster[jid]:
- sub = self.xmpp.roster[jid][bare]['subscription']
- if sub not in ('from', 'both'):
- raise XMPPError('forbidden')
-
- td = datetime.now() - self._last_activities[jid]['seconds']
- seconds = td.seconds + td.days * 24 * 3600
- status = self._last_activities[jid]['status']
-
- reply['last_activity']['seconds'] = seconds
- reply['last_activity']['status'] = status
-
- return reply
diff --git a/sleekxmpp/plugins/xep_0012/stanza.py b/sleekxmpp/plugins/xep_0012/stanza.py deleted file mode 100644 index 079865b9..00000000 --- a/sleekxmpp/plugins/xep_0012/stanza.py +++ /dev/null @@ -1,32 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -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 = '' diff --git a/sleekxmpp/plugins/xep_0013/__init__.py b/sleekxmpp/plugins/xep_0013/__init__.py deleted file mode 100644 index ad400949..00000000 --- a/sleekxmpp/plugins/xep_0013/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0013.stanza import Offline -from sleekxmpp.plugins.xep_0013.offline import XEP_0013 - - -register_plugin(XEP_0013) diff --git a/sleekxmpp/plugins/xep_0013/offline.py b/sleekxmpp/plugins/xep_0013/offline.py deleted file mode 100644 index a0d992a7..00000000 --- a/sleekxmpp/plugins/xep_0013/offline.py +++ /dev/null @@ -1,134 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -import sleekxmpp -from sleekxmpp.stanza import Message, Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream.handler import Collector -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0013 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0013(BasePlugin): - - """ - XEP-0013 Flexible Offline Message Retrieval - """ - - name = 'xep_0013' - description = 'XEP-0013: Flexible Offline Message Retrieval' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, stanza.Offline) - register_stanza_plugin(Message, stanza.Offline) - - def get_count(self, **kwargs): - return self.xmpp['xep_0030'].get_info( - node='http://jabber.org/protocol/offline', - local=False, - **kwargs) - - def get_headers(self, **kwargs): - return self.xmpp['xep_0030'].get_items( - node='http://jabber.org/protocol/offline', - local=False, - **kwargs) - - def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None): - if not isinstance(nodes, (list, set)): - nodes = [nodes] - - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['from'] = ifrom - offline = iq['offline'] - for node in nodes: - item = stanza.Item() - item['node'] = node - item['action'] = 'view' - offline.append(item) - - collector = Collector( - 'Offline_Results_%s' % iq['id'], - StanzaPath('message/offline')) - self.xmpp.register_handler(collector) - - if not block and callback is not None: - def wrapped_cb(iq): - results = collector.stop() - if iq['type'] == 'result': - iq['offline']['results'] = results - callback(iq) - return iq.send(block=block, timeout=timeout, callback=wrapped_cb) - else: - try: - resp = iq.send(block=block, timeout=timeout, callback=callback) - resp['offline']['results'] = collector.stop() - return resp - except XMPPError as e: - collector.stop() - raise e - - def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None): - if not isinstance(nodes, (list, set)): - nodes = [nodes] - - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - offline = iq['offline'] - for node in nodes: - item = stanza.Item() - item['node'] = node - item['action'] = 'remove' - offline.append(item) - - return iq.send(block=block, timeout=timeout, callback=callback) - - def fetch(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq['offline']['fetch'] = True - - collector = Collector( - 'Offline_Results_%s' % iq['id'], - StanzaPath('message/offline')) - self.xmpp.register_handler(collector) - - if not block and callback is not None: - def wrapped_cb(iq): - results = collector.stop() - if iq['type'] == 'result': - iq['offline']['results'] = results - callback(iq) - return iq.send(block=block, timeout=timeout, callback=wrapped_cb) - else: - try: - resp = iq.send(block=block, timeout=timeout, callback=callback) - resp['offline']['results'] = collector.stop() - return resp - except XMPPError as e: - collector.stop() - raise e - - def purge(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq['offline']['purge'] = True - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0013/stanza.py b/sleekxmpp/plugins/xep_0013/stanza.py deleted file mode 100644 index c9c69786..00000000 --- a/sleekxmpp/plugins/xep_0013/stanza.py +++ /dev/null @@ -1,53 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class Offline(ElementBase): - name = 'offline' - namespace = 'http://jabber.org/protocol/offline' - plugin_attrib = 'offline' - interfaces = set(['fetch', 'purge', 'results']) - bool_interfaces = interfaces - - def setup(self, xml=None): - ElementBase.setup(self, xml) - self._results = [] - - # The results interface is meant only as an easy - # way to access the set of collected message responses - # from the query. - - def get_results(self): - return self._results - - def set_results(self, values): - self._results = values - - def del_results(self): - self._results = [] - - -class Item(ElementBase): - name = 'item' - namespace = 'http://jabber.org/protocol/offline' - plugin_attrib = 'item' - interfaces = set(['action', 'node', 'jid']) - - actions = set(['view', 'remove']) - - def get_jid(self): - return JID(self._get_attr('jid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - -register_stanza_plugin(Offline, Item, iterable=True) diff --git a/sleekxmpp/plugins/xep_0016/__init__.py b/sleekxmpp/plugins/xep_0016/__init__.py deleted file mode 100644 index 06704d26..00000000 --- a/sleekxmpp/plugins/xep_0016/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0016 import stanza -from sleekxmpp.plugins.xep_0016.stanza import Privacy -from sleekxmpp.plugins.xep_0016.privacy import XEP_0016 - - -register_plugin(XEP_0016) diff --git a/sleekxmpp/plugins/xep_0016/privacy.py b/sleekxmpp/plugins/xep_0016/privacy.py deleted file mode 100644 index 79fd68f0..00000000 --- a/sleekxmpp/plugins/xep_0016/privacy.py +++ /dev/null @@ -1,110 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Iq -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0016 import stanza -from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item - - -class XEP_0016(BasePlugin): - - name = 'xep_0016' - description = 'XEP-0016: Privacy Lists' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, Privacy) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Privacy.namespace) - - def get_privacy_lists(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq.enable('privacy') - return iq.send(block=block, timeout=timeout, callback=callback) - - def get_list(self, name, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['privacy']['list']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) - - def get_active(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['privacy'].enable('active') - return iq.send(block=block, timeout=timeout, callback=callback) - - def get_default(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['privacy'].enable('default') - return iq.send(block=block, timeout=timeout, callback=callback) - - def activate(self, name, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy']['active']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) - - def deactivate(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy'].enable('active') - return iq.send(block=block, timeout=timeout, callback=callback) - - def make_default(self, name, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy']['default']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) - - def remove_default(self, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy'].enable('default') - return iq.send(block=block, timeout=timeout, callback=callback) - - def edit_list(self, name, rules, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy']['list']['name'] = name - priv_list = iq['privacy']['list'] - - if not rules: - rules = [] - - for rule in rules: - if isinstance(rule, Item): - priv_list.append(rule) - continue - - priv_list.add_item( - rule['value'], - rule['action'], - rule['order'], - itype=rule.get('type', None), - iq=rule.get('iq', None), - message=rule.get('message', None), - presence_in=rule.get('presence_in', - rule.get('presence-in', None)), - presence_out=rule.get('presence_out', - rule.get('presence-out', None))) - - def remove_list(self, name, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['privacy']['list']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0016/stanza.py b/sleekxmpp/plugins/xep_0016/stanza.py deleted file mode 100644 index 3f9977fc..00000000 --- a/sleekxmpp/plugins/xep_0016/stanza.py +++ /dev/null @@ -1,103 +0,0 @@ -from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin - - -class Privacy(ElementBase): - name = 'query' - namespace = 'jabber:iq:privacy' - plugin_attrib = 'privacy' - interfaces = set() - - def add_list(self, name): - priv_list = List() - priv_list['name'] = name - self.append(priv_list) - return priv_list - - -class Active(ElementBase): - name = 'active' - namespace = 'jabber:iq:privacy' - plugin_attrib = name - interfaces = set(['name']) - - -class Default(ElementBase): - name = 'default' - namespace = 'jabber:iq:privacy' - plugin_attrib = name - interfaces = set(['name']) - - -class List(ElementBase): - name = 'list' - namespace = 'jabber:iq:privacy' - plugin_attrib = name - plugin_multi_attrib = 'lists' - interfaces = set(['name']) - - def add_item(self, value, action, order, itype=None, iq=False, - message=False, presence_in=False, presence_out=False): - item = Item() - item.values = {'type': itype, - 'value': value, - 'action': action, - 'order': order, - 'message': message, - 'iq': iq, - 'presence_in': presence_in, - 'presence_out': presence_out} - self.append(item) - return item - - -class Item(ElementBase): - name = 'item' - namespace = 'jabber:iq:privacy' - plugin_attrib = name - plugin_multi_attrib = 'items' - interfaces = set(['type', 'value', 'action', 'order', 'iq', - 'message', 'presence_in', 'presence_out']) - bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out']) - - type_values = ('', 'jid', 'group', 'subscription') - action_values = ('allow', 'deny') - - def set_type(self, value): - if value and value not in self.type_values: - raise ValueError('Unknown type value: %s' % value) - else: - self._set_attr('type', value) - - def set_action(self, value): - if value not in self.action_values: - raise ValueError('Unknown action value: %s' % value) - else: - self._set_attr('action', value) - - def set_presence_in(self, value): - keep = True if value else False - self._set_sub_text('presence-in', '', keep=keep) - - def get_presence_in(self): - pres = self.xml.find('{%s}presence-in' % self.namespace) - return pres is not None - - def del_presence_in(self): - self._del_sub('{%s}presence-in' % self.namespace) - - def set_presence_out(self, value): - keep = True if value else False - self._set_sub_text('presence-in', '', keep=keep) - - def get_presence_out(self): - pres = self.xml.find('{%s}presence-in' % self.namespace) - return pres is not None - - def del_presence_out(self): - self._del_sub('{%s}presence-in' % self.namespace) - - -register_stanza_plugin(Privacy, Active) -register_stanza_plugin(Privacy, Default) -register_stanza_plugin(Privacy, List, iterable=True) -register_stanza_plugin(List, Item, iterable=True) diff --git a/sleekxmpp/plugins/xep_0020/__init__.py b/sleekxmpp/plugins/xep_0020/__init__.py deleted file mode 100644 index c6aafe97..00000000 --- a/sleekxmpp/plugins/xep_0020/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0020 import stanza -from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation -from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020 - - -register_plugin(XEP_0020) diff --git a/sleekxmpp/plugins/xep_0020/feature_negotiation.py b/sleekxmpp/plugins/xep_0020/feature_negotiation.py deleted file mode 100644 index 7cb82cd5..00000000 --- a/sleekxmpp/plugins/xep_0020/feature_negotiation.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq, Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation -from sleekxmpp.plugins.xep_0004 import Form - - -log = logging.getLogger(__name__) - - -class XEP_0020(BasePlugin): - - name = 'xep_0020' - description = 'XEP-0020: Feature Negotiation' - dependencies = set(['xep_0004', 'xep_0030']) - stanza = stanza - - def plugin_init(self): - self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace) - - register_stanza_plugin(FeatureNegotiation, Form) - - register_stanza_plugin(Iq, FeatureNegotiation) - register_stanza_plugin(Message, FeatureNegotiation) diff --git a/sleekxmpp/plugins/xep_0020/stanza.py b/sleekxmpp/plugins/xep_0020/stanza.py deleted file mode 100644 index 13e4056e..00000000 --- a/sleekxmpp/plugins/xep_0020/stanza.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class FeatureNegotiation(ElementBase): - - name = 'feature' - namespace = 'http://jabber.org/protocol/feature-neg' - plugin_attrib = 'feature_neg' - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0027/__init__.py b/sleekxmpp/plugins/xep_0027/__init__.py deleted file mode 100644 index b6ed9676..00000000 --- a/sleekxmpp/plugins/xep_0027/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0027.stanza import Signed, Encrypted -from sleekxmpp.plugins.xep_0027.gpg import XEP_0027 - - -register_plugin(XEP_0027) diff --git a/sleekxmpp/plugins/xep_0027/gpg.py b/sleekxmpp/plugins/xep_0027/gpg.py deleted file mode 100644 index 52c1c461..00000000 --- a/sleekxmpp/plugins/xep_0027/gpg.py +++ /dev/null @@ -1,170 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.thirdparty import GPG - -from sleekxmpp.stanza import Presence, Message -from sleekxmpp.plugins.base import BasePlugin, register_plugin -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.xep_0027 import stanza, Signed, Encrypted - - -def _extract_data(data, kind): - stripped = [] - begin_headers = False - begin_data = False - for line in data.split('\n'): - if not begin_headers and 'BEGIN PGP %s' % kind in line: - begin_headers = True - continue - if begin_headers and line.strip() == '': - begin_data = True - continue - if 'END PGP %s' % kind in line: - return '\n'.join(stripped) - if begin_data: - stripped.append(line) - return '' - - -class XEP_0027(BasePlugin): - - name = 'xep_0027' - description = 'XEP-0027: Current Jabber OpenPGP Usage' - dependencies = set() - stanza = stanza - default_config = { - 'gpg_binary': 'gpg', - 'gpg_home': '', - 'use_agent': True, - 'keyring': None, - 'key_server': 'pgp.mit.edu' - } - - def plugin_init(self): - self.gpg = GPG(gnupghome=self.gpg_home, - gpgbinary=self.gpg_binary, - use_agent=self.use_agent, - keyring=self.keyring) - - self.xmpp.add_filter('out', self._sign_presence) - - self._keyids = {} - - self.api.register(self._set_keyid, 'set_keyid', default=True) - self.api.register(self._get_keyid, 'get_keyid', default=True) - self.api.register(self._del_keyid, 'del_keyid', default=True) - self.api.register(self._get_keyids, 'get_keyids', default=True) - - register_stanza_plugin(Presence, Signed) - register_stanza_plugin(Message, Encrypted) - - self.xmpp.add_event_handler('unverified_signed_presence', - self._handle_unverified_signed_presence, - threaded=True) - - self.xmpp.register_handler( - Callback('Signed Presence', - StanzaPath('presence/signed'), - self._handle_signed_presence)) - - self.xmpp.register_handler( - Callback('Encrypted Message', - StanzaPath('message/encrypted'), - self._handle_encrypted_message)) - - def plugin_end(self): - self.xmpp.remove_handler('Encrypted Message') - self.xmpp.remove_handler('Signed Presence') - self.xmpp.del_filter('out', self._sign_presence) - self.xmpp.del_event_handler('unverified_signed_presence', - self._handle_unverified_signed_presence) - - def _sign_presence(self, stanza): - if isinstance(stanza, Presence): - if stanza['type'] == 'available' or \ - stanza['type'] in Presence.showtypes: - stanza['signed'] = stanza['status'] - return stanza - - def sign(self, data, jid=None): - keyid = self.get_keyid(jid) - if keyid: - signed = self.gpg.sign(data, keyid=keyid) - return _extract_data(signed.data, 'SIGNATURE') - - def encrypt(self, data, jid=None): - keyid = self.get_keyid(jid) - if keyid: - enc = self.gpg.encrypt(data, keyid) - return _extract_data(enc.data, 'MESSAGE') - - def decrypt(self, data, jid=None): - template = '-----BEGIN PGP MESSAGE-----\n' + \ - '\n' + \ - '%s\n' + \ - '-----END PGP MESSAGE-----\n' - dec = self.gpg.decrypt(template % data) - return dec.data - - def verify(self, data, sig, jid=None): - template = '-----BEGIN PGP SIGNED MESSAGE-----\n' + \ - 'Hash: SHA1\n' + \ - '\n' + \ - '%s\n' + \ - '-----BEGIN PGP SIGNATURE-----\n' + \ - '\n' + \ - '%s\n' + \ - '-----END PGP SIGNATURE-----\n' - v = self.gpg.verify(template % (data, sig)) - return v - - def set_keyid(self, jid=None, keyid=None): - self.api['set_keyid'](jid, args=keyid) - - def get_keyid(self, jid=None): - return self.api['get_keyid'](jid) - - def del_keyid(self, jid=None): - self.api['del_keyid'](jid) - - def get_keyids(self): - return self.api['get_keyids']() - - def _handle_signed_presence(self, pres): - self.xmpp.event('unverified_signed_presence', pres) - - def _handle_unverified_signed_presence(self, pres): - verified = self.verify(pres['status'], pres['signed']) - if verified.key_id: - if not self.get_keyid(pres['from']): - known_keyids = [e['keyid'] for e in self.gpg.list_keys()] - if verified.key_id not in known_keyids: - self.gpg.recv_keys(self.key_server, verified.key_id) - self.set_keyid(jid=pres['from'], keyid=verified.key_id) - self.xmpp.event('signed_presence', pres) - - def _handle_encrypted_message(self, msg): - self.xmpp.event('encrypted_message', msg) - - # ================================================================= - - def _set_keyid(self, jid, node, ifrom, keyid): - self._keyids[jid] = keyid - - def _get_keyid(self, jid, node, ifrom, keyid): - return self._keyids.get(jid, None) - - def _del_keyid(self, jid, node, ifrom, keyid): - if jid in self._keyids: - del self._keyids[jid] - - def _get_keyids(self, jid, node, ifrom, data): - return self._keyids diff --git a/sleekxmpp/plugins/xep_0027/stanza.py b/sleekxmpp/plugins/xep_0027/stanza.py deleted file mode 100644 index 08f2032b..00000000 --- a/sleekxmpp/plugins/xep_0027/stanza.py +++ /dev/null @@ -1,53 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class Signed(ElementBase): - name = 'x' - namespace = 'jabber:x:signed' - plugin_attrib = 'signed' - interfaces = set(['signed']) - is_extension = True - - def set_signed(self, value): - parent = self.parent() - xmpp = parent.stream - data = xmpp['xep_0027'].sign(value, parent['from']) - if data: - self.xml.text = data - else: - del parent['signed'] - - def get_signed(self): - return self.xml.text - - -class Encrypted(ElementBase): - name = 'x' - namespace = 'jabber:x:encrypted' - plugin_attrib = 'encrypted' - interfaces = set(['encrypted']) - is_extension = True - - def set_encrypted(self, value): - parent = self.parent() - xmpp = parent.stream - data = xmpp['xep_0027'].encrypt(value, parent['to']) - if data: - self.xml.text = data - else: - del parent['encrypted'] - - def get_encrypted(self): - parent = self.parent() - xmpp = parent.stream - if self.xml.text: - return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) - return None diff --git a/sleekxmpp/plugins/xep_0030/__init__.py b/sleekxmpp/plugins/xep_0030/__init__.py deleted file mode 100644 index 0d1de65b..00000000 --- a/sleekxmpp/plugins/xep_0030/__init__.py +++ /dev/null @@ -1,23 +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.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0030 import stanza -from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems -from sleekxmpp.plugins.xep_0030.static import StaticDisco -from sleekxmpp.plugins.xep_0030.disco import XEP_0030 - - -register_plugin(XEP_0030) - -# Retain some backwards compatibility -xep_0030 = XEP_0030 -XEP_0030.getInfo = XEP_0030.get_info -XEP_0030.getItems = XEP_0030.get_items -XEP_0030.make_static = XEP_0030.restore_defaults diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py deleted file mode 100644 index 8a397923..00000000 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ /dev/null @@ -1,740 +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 sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0030 import stanza, DiscoInfo, DiscoItems -from sleekxmpp.plugins.xep_0030 import StaticDisco - - -log = logging.getLogger(__name__) - - -class XEP_0030(BasePlugin): - - """ - XEP-0030: Service Discovery - - Service discovery in XMPP allows entities to discover information about - other agents in the network, such as the feature sets supported by a - client, or signposts to other, related entities. - - Also see <http://www.xmpp.org/extensions/xep-0030.html>. - - The XEP-0030 plugin works using a hierarchy of dynamic - node handlers, ranging from global handlers to specific - JID+node handlers. The default set of handlers operate - in a static manner, storing disco information in memory. - However, custom handlers may use any available backend - storage mechanism desired, such as SQLite or Redis. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Stream Handlers: - Disco Info -- Any Iq stanze that includes a query with the - namespace http://jabber.org/protocol/disco#info. - Disco Items -- Any Iq stanze that includes a query with the - namespace http://jabber.org/protocol/disco#items. - - Events: - disco_info -- Received a disco#info Iq query result. - disco_items -- Received a disco#items Iq query result. - disco_info_query -- Received a disco#info Iq query request. - disco_items_query -- Received a disco#items Iq query request. - - Attributes: - stanza -- A reference to the module containing the - stanza classes provided by this plugin. - static -- Object containing the default set of - static node handlers. - default_handlers -- A dictionary mapping operations to the default - global handler (by default, the static handlers). - xmpp -- The main SleekXMPP object. - - Methods: - set_node_handler -- Assign a handler to a JID/node combination. - del_node_handler -- Remove a handler from a JID/node combination. - get_info -- Retrieve disco#info data, locally or remote. - get_items -- Retrieve disco#items data, locally or remote. - set_identities -- - set_features -- - set_items -- - del_items -- - del_identity -- - del_feature -- - del_item -- - add_identity -- - add_feature -- - add_item -- - """ - - name = 'xep_0030' - description = 'XEP-0030: Service Discovery' - dependencies = set() - stanza = stanza - default_config = { - 'use_cache': True, - 'wrap_results': False - } - - def plugin_init(self): - """ - Start the XEP-0030 plugin. - """ - self.xmpp.register_handler( - Callback('Disco Info', - StanzaPath('iq/disco_info'), - self._handle_disco_info)) - - self.xmpp.register_handler( - Callback('Disco Items', - StanzaPath('iq/disco_items'), - self._handle_disco_items)) - - register_stanza_plugin(Iq, DiscoInfo) - register_stanza_plugin(Iq, DiscoItems) - - self.static = StaticDisco(self.xmpp, self) - - self._disco_ops = [ - 'get_info', 'set_info', 'set_identities', 'set_features', - 'get_items', 'set_items', 'del_items', 'add_identity', - 'del_identity', 'add_feature', 'del_feature', 'add_item', - 'del_item', 'del_identities', 'del_features', 'cache_info', - 'get_cached_info', 'supports', 'has_identity'] - - for op in self._disco_ops: - self.api.register(getattr(self.static, op), op, default=True) - - def _add_disco_op(self, op, default_handler): - self.api.register(default_handler, op) - self.api.register_default(default_handler, op) - - def set_node_handler(self, htype, jid=None, node=None, handler=None): - """ - Add a node handler for the given hierarchy level and - handler type. - - Node handlers are ordered in a hierarchy where the - most specific handler is executed. Thus, a fallback, - global handler can be used for the majority of cases - with a few node specific handler that override the - global behavior. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Handler types: - get_info - get_items - set_identities - set_features - set_items - del_items - del_identities - del_identity - del_feature - del_features - del_item - add_identity - add_feature - add_item - - Arguments: - htype -- The operation provided by the handler. - jid -- The JID the handler applies to. May be narrowed - further if a node is given. - node -- The particular node the handler is for. If no JID - is given, then the self.xmpp.boundjid.full is - assumed. - handler -- The handler function to use. - """ - self.api.register(handler, htype, jid, node) - - def del_node_handler(self, htype, jid, node): - """ - Remove a handler type for a JID and node combination. - - The next handler in the hierarchy will be used if one - exists. If removing the global handler, make sure that - other handlers exist to process existing nodes. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Arguments: - htype -- The type of handler to remove. - jid -- The JID from which to remove the handler. - node -- The node from which to remove the handler. - """ - self.api.unregister(htype, jid, node) - - def restore_defaults(self, jid=None, node=None, handlers=None): - """ - Change all or some of a node's handlers to the default - handlers. Useful for manually overriding the contents - of a node that would otherwise be handled by a JID level - or global level dynamic handler. - - The default is to use the built-in static handlers, but that - may be changed by modifying self.default_handlers. - - Arguments: - jid -- The JID owning the node to modify. - node -- The node to change to using static handlers. - handlers -- Optional list of handlers to change to the - default version. If provided, only these - handlers will be changed. Otherwise, all - handlers will use the default version. - """ - if handlers is None: - handlers = self._disco_ops - for op in handlers: - self.api.restore_default(op, jid, node) - - def supports(self, jid=None, node=None, feature=None, local=False, - cached=True, ifrom=None): - """ - Check if a JID supports a given feature. - - Return values: - True -- The feature is supported - False -- The feature is not listed as supported - None -- Nothing could be found due to a timeout - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - feature -- The name of the feature to check. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - ifrom -- Specifiy the sender's JID. - """ - data = {'feature': feature, - 'local': local, - 'cached': cached} - return self.api['supports'](jid, node, ifrom, data) - - def has_identity(self, jid=None, node=None, category=None, itype=None, - lang=None, local=False, cached=True, ifrom=None): - """ - Check if a JID provides a given identity. - - Return values: - True -- The identity is provided - False -- The identity is not listed - None -- Nothing could be found due to a timeout - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - category -- The category of the identity to check. - itype -- The type of the identity to check. - lang -- The language of the identity to check. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - ifrom -- Specifiy the sender's JID. - """ - data = {'category': category, - 'itype': itype, - 'lang': lang, - 'local': local, - 'cached': cached} - return self.api['has_identity'](jid, node, ifrom, data) - - def get_info(self, jid=None, node=None, local=None, - cached=None, **kwargs): - """ - Retrieve the disco#info results from a given JID/node combination. - - Info may be retrieved from both local resources and remote agents; - the local parameter indicates if the information should be gathered - by executing the local node handlers, or if a disco#info stanza - must be generated and sent. - - If requesting items from a local JID/node, then only a DiscoInfo - stanza will be returned. Otherwise, an Iq stanza will be returned. - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - ifrom -- Specifiy the sender's JID. - block -- If true, block and wait for the stanzas' reply. - timeout -- The time in seconds to block while waiting for - a reply. If None, then wait indefinitely. The - timeout value is only used when block=True. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - timeout_callback -- Optional callback to execute when no result - has been received in timeout seconds. - """ - if local is None: - if jid is not None and not isinstance(jid, JID): - jid = JID(jid) - if self.xmpp.is_component: - if jid.domain == self.xmpp.boundjid.domain: - local = True - else: - if str(jid) == str(self.xmpp.boundjid): - local = True - jid = jid.full - elif jid in (None, ''): - local = True - - if local: - log.debug("Looking up local disco#info data " + \ - "for %s, node %s.", jid, node) - info = self.api['get_info'](jid, node, - kwargs.get('ifrom', None), - kwargs) - info = self._fix_default_info(info) - return self._wrap(kwargs.get('ifrom', None), jid, info) - - if cached: - log.debug("Looking up cached disco#info data " + \ - "for %s, node %s.", jid, node) - info = self.api['get_cached_info'](jid, node, - kwargs.get('ifrom', None), - kwargs) - if info is not None: - return self._wrap(kwargs.get('ifrom', None), jid, info) - - iq = self.xmpp.Iq() - # Check dfrom parameter for backwards compatibility - iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) - iq['to'] = jid - iq['type'] = 'get' - iq['disco_info']['node'] = node if node else '' - return iq.send(timeout=kwargs.get('timeout', None), - block=kwargs.get('block', True), - callback=kwargs.get('callback', None), - timeout_callback=kwargs.get('timeout_callback', None)) - - def set_info(self, jid=None, node=None, info=None): - """ - Set the disco#info data for a JID/node based on an existing - disco#info stanza. - """ - if isinstance(info, Iq): - info = info['disco_info'] - self.api['set_info'](jid, node, None, info) - - def get_items(self, jid=None, node=None, local=False, **kwargs): - """ - Retrieve the disco#items results from a given JID/node combination. - - Items may be retrieved from both local resources and remote agents; - the local parameter indicates if the items should be gathered by - executing the local node handlers, or if a disco#items stanza must - be generated and sent. - - If requesting items from a local JID/node, then only a DiscoItems - stanza will be returned. Otherwise, an Iq stanza will be returned. - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the items. - ifrom -- Specifiy the sender's JID. - block -- If true, block and wait for the stanzas' reply. - timeout -- The time in seconds to block while waiting for - a reply. If None, then wait indefinitely. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - iterator -- If True, return a result set iterator using - the XEP-0059 plugin, if the plugin is loaded. - Otherwise the parameter is ignored. - timeout_callback -- Optional callback to execute when no result - has been received in timeout seconds. - """ - if local or local is None and jid is None: - items = self.api['get_items'](jid, node, - kwargs.get('ifrom', None), - kwargs) - return self._wrap(kwargs.get('ifrom', None), jid, items) - - iq = self.xmpp.Iq() - # Check dfrom parameter for backwards compatibility - iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) - iq['to'] = jid - iq['type'] = 'get' - iq['disco_items']['node'] = node if node else '' - if kwargs.get('iterator', False) and self.xmpp['xep_0059']: - return self.xmpp['xep_0059'].iterate(iq, 'disco_items') - else: - return iq.send(timeout=kwargs.get('timeout', None), - block=kwargs.get('block', True), - callback=kwargs.get('callback', None), - timeout_callback=kwargs.get('timeout_callback', None)) - - def set_items(self, jid=None, node=None, **kwargs): - """ - Set or replace all items for the specified JID/node combination. - - The given items must be in a list or set where each item is a - tuple of the form: (jid, node, name). - - Arguments: - jid -- The JID to modify. - node -- Optional node to modify. - items -- A series of items in tuple format. - """ - self.api['set_items'](jid, node, None, kwargs) - - def del_items(self, jid=None, node=None, **kwargs): - """ - Remove all items from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- Optional node to modify. - """ - self.api['del_items'](jid, node, None, kwargs) - - def add_item(self, jid='', name='', node=None, subnode='', ijid=None): - """ - Add a new item element to the given JID/node combination. - - Each item is required to have a JID, but may also specify - a node value to reference non-addressable entities. - - Arguments: - jid -- The JID for the item. - name -- Optional name for the item. - node -- The node to modify. - subnode -- Optional node for the item. - ijid -- The JID to modify. - """ - if not jid: - jid = self.xmpp.boundjid.full - kwargs = {'ijid': jid, - 'name': name, - 'inode': subnode} - self.api['add_item'](ijid, node, None, kwargs) - - def del_item(self, jid=None, node=None, **kwargs): - """ - Remove a single item from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - ijid -- The item's JID. - inode -- The item's node. - """ - self.api['del_item'](jid, node, None, kwargs) - - def add_identity(self, category='', itype='', name='', - node=None, jid=None, lang=None): - """ - Add a new identity to the given JID/node combination. - - Each identity must be unique in terms of all four identity - components: category, type, name, and language. - - Multiple, identical category/type pairs are allowed only - if the xml:lang values are different. Likewise, multiple - category/type/xml:lang pairs are allowed so long as the - names are different. A category and type is always required. - - Arguments: - category -- The identity's category. - itype -- The identity's type. - name -- Optional name for the identity. - lang -- Optional two-letter language code. - node -- The node to modify. - jid -- The JID to modify. - """ - kwargs = {'category': category, - 'itype': itype, - 'name': name, - 'lang': lang} - self.api['add_identity'](jid, node, None, kwargs) - - def add_feature(self, feature, node=None, jid=None): - """ - Add a feature to a JID/node combination. - - Arguments: - feature -- The namespace of the supported feature. - node -- The node to modify. - jid -- The JID to modify. - """ - kwargs = {'feature': feature} - self.api['add_feature'](jid, node, None, kwargs) - - def del_identity(self, jid=None, node=None, **kwargs): - """ - Remove an identity from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - category -- The identity's category. - itype -- The identity's type value. - name -- Optional, human readable name for the identity. - lang -- Optional, the identity's xml:lang value. - """ - self.api['del_identity'](jid, node, None, kwargs) - - def del_feature(self, jid=None, node=None, **kwargs): - """ - Remove a feature from a given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - feature -- The feature's namespace. - """ - self.api['del_feature'](jid, node, None, kwargs) - - def set_identities(self, jid=None, node=None, **kwargs): - """ - Add or replace all identities for the given JID/node combination. - - The identities must be in a set where each identity is a tuple - of the form: (category, type, lang, name) - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - identities -- A set of identities in tuple form. - lang -- Optional, xml:lang value. - """ - self.api['set_identities'](jid, node, None, kwargs) - - def del_identities(self, jid=None, node=None, **kwargs): - """ - Remove all identities for a JID/node combination. - - If a language is specified, only identities using that - language will be removed. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - lang -- Optional. If given, only remove identities - using this xml:lang value. - """ - self.api['del_identities'](jid, node, None, kwargs) - - def set_features(self, jid=None, node=None, **kwargs): - """ - Add or replace the set of supported features - for a JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - features -- The new set of supported features. - """ - self.api['set_features'](jid, node, None, kwargs) - - def del_features(self, jid=None, node=None, **kwargs): - """ - Remove all features from a JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - """ - self.api['del_features'](jid, node, None, kwargs) - - def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}): - """ - Execute the most specific node handler for the given - JID/node combination. - - Arguments: - htype -- The handler type to execute. - jid -- The JID requested. - node -- The node requested. - data -- Optional, custom data to pass to the handler. - """ - return self.api[htype](jid, node, ifrom, data) - - def _handle_disco_info(self, iq): - """ - Process an incoming disco#info stanza. If it is a get - request, find and return the appropriate identities - and features. If it is an info result, fire the - disco_info event. - - Arguments: - iq -- The incoming disco#items stanza. - """ - if iq['type'] == 'get': - log.debug("Received disco info query from " + \ - "<%s> to <%s>.", iq['from'], iq['to']) - info = self.api['get_info'](iq['to'], - iq['disco_info']['node'], - iq['from'], - iq) - if isinstance(info, Iq): - info['id'] = iq['id'] - info.send() - else: - iq.reply() - if info: - info = self._fix_default_info(info) - iq.set_payload(info.xml) - iq.send() - elif iq['type'] == 'result': - log.debug("Received disco info result from " + \ - "<%s> to <%s>.", iq['from'], iq['to']) - if self.use_cache: - log.debug("Caching disco info result from " \ - "<%s> to <%s>.", iq['from'], iq['to']) - if self.xmpp.is_component: - ito = iq['to'].full - else: - ito = None - self.api['cache_info'](iq['from'], - iq['disco_info']['node'], - ito, - iq) - self.xmpp.event('disco_info', iq) - - def _handle_disco_items(self, iq): - """ - Process an incoming disco#items stanza. If it is a get - request, find and return the appropriate items. If it - is an items result, fire the disco_items event. - - Arguments: - iq -- The incoming disco#items stanza. - """ - if iq['type'] == 'get': - log.debug("Received disco items query from " + \ - "<%s> to <%s>.", iq['from'], iq['to']) - items = self.api['get_items'](iq['to'], - iq['disco_items']['node'], - iq['from'], - iq) - if isinstance(items, Iq): - items.send() - else: - iq.reply() - if items: - iq.set_payload(items.xml) - iq.send() - elif iq['type'] == 'result': - log.debug("Received disco items result from " + \ - "%s to %s.", iq['from'], iq['to']) - self.xmpp.event('disco_items', iq) - - def _fix_default_info(self, info): - """ - Disco#info results for a JID are required to include at least - one identity and feature. As a default, if no other identity is - provided, SleekXMPP will use either the generic component or the - bot client identity. A the standard disco#info feature will also be - added if no features are provided. - - Arguments: - info -- The disco#info quest (not the full Iq stanza) to modify. - """ - result = info - if isinstance(info, Iq): - info = info['disco_info'] - if not info['node']: - if not info['identities']: - if self.xmpp.is_component: - log.debug("No identity found for this entity. " + \ - "Using default component identity.") - info.add_identity('component', 'generic') - else: - log.debug("No identity found for this entity. " + \ - "Using default client identity.") - info.add_identity('client', 'bot') - if not info['features']: - log.debug("No features found for this entity. " + \ - "Using default disco#info feature.") - info.add_feature(info.namespace) - return result - - def _wrap(self, ito, ifrom, payload, force=False): - """ - Ensure that results are wrapped in an Iq stanza - if self.wrap_results has been set to True. - - Arguments: - ito -- The JID to use as the 'to' value - ifrom -- The JID to use as the 'from' value - payload -- The disco data to wrap - force -- Force wrapping, regardless of self.wrap_results - """ - if (force or self.wrap_results) and not isinstance(payload, Iq): - iq = self.xmpp.Iq() - # Since we're simulating a result, we have to treat - # the 'from' and 'to' values opposite the normal way. - iq['to'] = self.xmpp.boundjid if ito is None else ito - iq['from'] = self.xmpp.boundjid if ifrom is None else ifrom - iq['type'] = 'result' - iq.append(payload) - return iq - return payload diff --git a/sleekxmpp/plugins/xep_0030/stanza/__init__.py b/sleekxmpp/plugins/xep_0030/stanza/__init__.py deleted file mode 100644 index 0d97cf3d..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/__init__.py +++ /dev/null @@ -1,10 +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.plugins.xep_0030.stanza.info import DiscoInfo -from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems diff --git a/sleekxmpp/plugins/xep_0030/stanza/info.py b/sleekxmpp/plugins/xep_0030/stanza/info.py deleted file mode 100644 index 25d1d07f..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/info.py +++ /dev/null @@ -1,276 +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.xmlstream import ElementBase, ET - - -class DiscoInfo(ElementBase): - - """ - XMPP allows for users and agents to find the identities and features - supported by other entities in the XMPP network through service discovery, - or "disco". In particular, the "disco#info" query type for <iq> stanzas is - used to request the list of identities and features offered by a JID. - - An identity is a combination of a category and type, such as the 'client' - category with a type of 'pc' to indicate the agent is a human operated - client with a GUI, or a category of 'gateway' with a type of 'aim' to - identify the agent as a gateway for the legacy AIM protocol. See - <http://xmpp.org/registrar/disco-categories.html> for a full list of - accepted category and type combinations. - - Features are simply a set of the namespaces that identify the supported - features. For example, a client that supports service discovery will - include the feature 'http://jabber.org/protocol/disco#info'. - - Since clients and components may operate in several roles at once, identity - and feature information may be grouped into "nodes". If one were to write - all of the identities and features used by a client, then node names would - be like section headings. - - Example disco#info stanzas: - <iq type="get"> - <query xmlns="http://jabber.org/protocol/disco#info" /> - </iq> - - <iq type="result"> - <query xmlns="http://jabber.org/protocol/disco#info"> - <identity category="client" type="bot" name="SleekXMPP Bot" /> - <feature var="http://jabber.org/protocol/disco#info" /> - <feature var="jabber:x:data" /> - <feature var="urn:xmpp:ping" /> - </query> - </iq> - - Stanza Interface: - node -- The name of the node to either - query or return info from. - identities -- A set of 4-tuples, where each tuple contains - the category, type, xml:lang, and name - of an identity. - features -- A set of namespaces for features. - - Methods: - add_identity -- Add a new, single identity. - del_identity -- Remove a single identity. - get_identities -- Return all identities in tuple form. - set_identities -- Use multiple identities, each given in tuple form. - del_identities -- Remove all identities. - add_feature -- Add a single feature. - del_feature -- Remove a single feature. - get_features -- Return a list of all features. - set_features -- Use a given list of features. - del_features -- Remove all features. - """ - - name = 'query' - namespace = 'http://jabber.org/protocol/disco#info' - plugin_attrib = 'disco_info' - interfaces = set(('node', 'features', 'identities')) - lang_interfaces = set(('identities',)) - - # Cache identities and features - _identities = set() - _features = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches identity and feature information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - - self._identities = set([id[0:3] for id in self['identities']]) - self._features = self['features'] - - def add_identity(self, category, itype, name=None, lang=None): - """ - Add a new identity element. Each identity must be unique - in terms of all four identity components. - - Multiple, identical category/type pairs are allowed only - if the xml:lang values are different. Likewise, multiple - category/type/xml:lang pairs are allowed so long as the names - are different. In any case, a category and type are required. - - Arguments: - category -- The general category to which the agent belongs. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional standard xml:lang value. - """ - identity = (category, itype, lang) - if identity not in self._identities: - self._identities.add(identity) - id_xml = ET.Element('{%s}identity' % self.namespace) - id_xml.attrib['category'] = category - id_xml.attrib['type'] = itype - if lang: - id_xml.attrib['{%s}lang' % self.xml_ns] = lang - if name: - id_xml.attrib['name'] = name - self.xml.append(id_xml) - return True - return False - - def del_identity(self, category, itype, name=None, lang=None): - """ - Remove a given identity. - - Arguments: - category -- The general category to which the agent belonged. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional, standard xml:lang value. - """ - identity = (category, itype, lang) - if identity in self._identities: - self._identities.remove(identity) - for id_xml in self.findall('{%s}identity' % self.namespace): - id = (id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None)) - if id == identity: - self.xml.remove(id_xml) - return True - return False - - def get_identities(self, lang=None, dedupe=True): - """ - Return a set of all identities in tuple form as so: - (category, type, lang, name) - - If a language was specified, only return identities using - that language. - - Arguments: - lang -- Optional, standard xml:lang value. - dedupe -- If True, de-duplicate identities, otherwise - return a list of all identities. - """ - if dedupe: - identities = set() - else: - identities = [] - for id_xml in self.findall('{%s}identity' % self.namespace): - xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) - if lang is None or xml_lang == lang: - id = (id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None), - id_xml.attrib.get('name', None)) - if dedupe: - identities.add(id) - else: - identities.append(id) - return identities - - def set_identities(self, identities, lang=None): - """ - Add or replace all identities. The identities must be a in set - where each identity is a tuple of the form: - (category, type, lang, name) - - If a language is specifified, any identities using that language - will be removed to be replaced with the given identities. - - NOTE: An identity's language will not be changed regardless of - the value of lang. - - Arguments: - identities -- A set of identities in tuple form. - lang -- Optional, standard xml:lang value. - """ - self.del_identities(lang) - for identity in identities: - category, itype, lang, name = identity - self.add_identity(category, itype, name, lang) - - def del_identities(self, lang=None): - """ - Remove all identities. If a language was specified, only - remove identities using that language. - - Arguments: - lang -- Optional, standard xml:lang value. - """ - for id_xml in self.findall('{%s}identity' % self.namespace): - if lang is None: - self.xml.remove(id_xml) - elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang: - self._identities.remove(( - id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None))) - self.xml.remove(id_xml) - - def add_feature(self, feature): - """ - Add a single, new feature. - - Arguments: - feature -- The namespace of the supported feature. - """ - if feature not in self._features: - self._features.add(feature) - feature_xml = ET.Element('{%s}feature' % self.namespace) - feature_xml.attrib['var'] = feature - self.xml.append(feature_xml) - return True - return False - - def del_feature(self, feature): - """ - Remove a single feature. - - Arguments: - feature -- The namespace of the removed feature. - """ - if feature in self._features: - self._features.remove(feature) - for feature_xml in self.findall('{%s}feature' % self.namespace): - if feature_xml.attrib['var'] == feature: - self.xml.remove(feature_xml) - return True - return False - - def get_features(self, dedupe=True): - """Return the set of all supported features.""" - if dedupe: - features = set() - else: - features = [] - for feature_xml in self.findall('{%s}feature' % self.namespace): - if dedupe: - features.add(feature_xml.attrib['var']) - else: - features.append(feature_xml.attrib['var']) - return features - - def set_features(self, features): - """ - Add or replace the set of supported features. - - Arguments: - features -- The new set of supported features. - """ - self.del_features() - for feature in features: - self.add_feature(feature) - - def del_features(self): - """Remove all features.""" - self._features = set() - for feature_xml in self.findall('{%s}feature' % self.namespace): - self.xml.remove(feature_xml) diff --git a/sleekxmpp/plugins/xep_0030/stanza/items.py b/sleekxmpp/plugins/xep_0030/stanza/items.py deleted file mode 100644 index 10458614..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/items.py +++ /dev/null @@ -1,152 +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.xmlstream import ElementBase, register_stanza_plugin - - -class DiscoItems(ElementBase): - - """ - Example disco#items stanzas: - <iq type="get"> - <query xmlns="http://jabber.org/protocol/disco#items" /> - </iq> - - <iq type="result"> - <query xmlns="http://jabber.org/protocol/disco#items"> - <item jid="chat.example.com" - node="xmppdev" - name="XMPP Dev" /> - <item jid="chat.example.com" - node="sleekdev" - name="SleekXMPP Dev" /> - </query> - </iq> - - Stanza Interface: - node -- The name of the node to either - query or return info from. - items -- A list of 3-tuples, where each tuple contains - the JID, node, and name of an item. - - Methods: - add_item -- Add a single new item. - del_item -- Remove a single item. - get_items -- Return all items. - set_items -- Set or replace all items. - del_items -- Remove all items. - """ - - name = 'query' - namespace = 'http://jabber.org/protocol/disco#items' - plugin_attrib = 'disco_items' - interfaces = set(('node', 'items')) - - # Cache items - _items = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._items = set([item[0:2] for item in self['items']]) - - def add_item(self, jid, node=None, name=None): - """ - Add a new item element. Each item is required to have a - JID, but may also specify a node value to reference - non-addressable entitities. - - Arguments: - jid -- The JID for the item. - node -- Optional additional information to reference - non-addressable items. - name -- Optional human readable name for the item. - """ - if (jid, node) not in self._items: - self._items.add((jid, node)) - item = DiscoItem(parent=self) - item['jid'] = jid - item['node'] = node - item['name'] = name - self.iterables.append(item) - return True - return False - - def del_item(self, jid, node=None): - """ - Remove a single item. - - Arguments: - jid -- JID of the item to remove. - node -- Optional extra identifying information. - """ - if (jid, node) in self._items: - for item_xml in self.findall('{%s}item' % self.namespace): - item = (item_xml.attrib['jid'], - item_xml.attrib.get('node', None)) - if item == (jid, node): - self.xml.remove(item_xml) - return True - return False - - def get_items(self): - """Return all items.""" - items = set() - for item in self['substanzas']: - if isinstance(item, DiscoItem): - items.add((item['jid'], item['node'], item['name'])) - return items - - def set_items(self, items): - """ - Set or replace all items. The given items must be in a - list or set where each item is a tuple of the form: - (jid, node, name) - - Arguments: - items -- A series of items in tuple format. - """ - self.del_items() - for item in items: - jid, node, name = item - self.add_item(jid, node, name) - - def del_items(self): - """Remove all items.""" - self._items = set() - items = [i for i in self.iterables if isinstance(i, DiscoItem)] - for item in items: - self.xml.remove(item.xml) - self.iterables.remove(item) - - -class DiscoItem(ElementBase): - name = 'item' - namespace = 'http://jabber.org/protocol/disco#items' - plugin_attrib = name - interfaces = set(('jid', 'node', 'name')) - - def get_node(self): - """Return the item's node name or ``None``.""" - return self._get_attr('node', None) - - def get_name(self): - """Return the item's human readable name, or ``None``.""" - return self._get_attr('name', None) - - -register_stanza_plugin(DiscoItems, DiscoItem, iterable=True) diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py deleted file mode 100644 index dd5317d1..00000000 --- a/sleekxmpp/plugins/xep_0030/static.py +++ /dev/null @@ -1,430 +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 threading - -from sleekxmpp import Iq -from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout -from sleekxmpp.xmlstream import JID -from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems - - -log = logging.getLogger(__name__) - - -class StaticDisco(object): - - """ - While components will likely require fully dynamic handling - of service discovery information, most clients and simple bots - only need to manage a few disco nodes that will remain mostly - static. - - StaticDisco provides a set of node handlers that will store - static sets of disco info and items in memory. - - Attributes: - nodes -- A dictionary mapping (JID, node) tuples to a dict - containing a disco#info and a disco#items stanza. - xmpp -- The main SleekXMPP object. - """ - - def __init__(self, xmpp, disco): - """ - Create a static disco interface. Sets of disco#info and - disco#items are maintained for every given JID and node - combination. These stanzas are used to store disco - information in memory without any additional processing. - - Arguments: - xmpp -- The main SleekXMPP object. - """ - self.nodes = {} - self.xmpp = xmpp - self.disco = disco - self.lock = threading.RLock() - - def add_node(self, jid=None, node=None, ifrom=None): - """ - Create a new set of stanzas for the provided - JID and node combination. - - Arguments: - jid -- The JID that will own the new stanzas. - node -- The node that will own the new stanzas. - """ - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(), - 'items': DiscoItems()} - self.nodes[(jid, node, ifrom)]['info']['node'] = node - self.nodes[(jid, node, ifrom)]['items']['node'] = node - - def get_node(self, jid=None, node=None, ifrom=None): - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - self.add_node(jid, node, ifrom) - return self.nodes[(jid, node, ifrom)] - - def node_exists(self, jid=None, node=None, ifrom=None): - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - return False - return True - - # ================================================================= - # Node Handlers - # - # Each handler accepts four arguments: jid, node, ifrom, and data. - # The jid and node parameters together determine the set of info - # and items stanzas that will be retrieved or added. Additionally, - # the ifrom value allows for cached results when results vary based - # on the requester's JID. The data parameter is a dictionary with - # additional parameters that will be passed to other calls. - # - # This implementation does not allow different responses based on - # the requester's JID, except for cached results. To do that, - # register a custom node handler. - - def supports(self, jid, node, ifrom, data): - """ - Check if a JID supports a given feature. - - The data parameter may provide: - feature -- The feature to check for support. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - """ - feature = data.get('feature', None) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - if not feature: - return False - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - features = info['disco_info']['features'] - return feature in features - except IqError: - return False - except IqTimeout: - return None - - def has_identity(self, jid, node, ifrom, data): - """ - Check if a JID has a given identity. - - The data parameter may provide: - category -- The category of the identity to check. - itype -- The type of the identity to check. - lang -- The language of the identity to check. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - """ - identity = (data.get('category', None), - data.get('itype', None), - data.get('lang', None)) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - trunc = lambda i: (i[0], i[1], i[2]) - return identity in map(trunc, info['disco_info']['identities']) - except IqError: - return False - except IqTimeout: - return None - - def get_info(self, jid, node, ifrom, data): - """ - Return the stored info data for the requested JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - if not node: - return DiscoInfo() - else: - raise XMPPError(condition='item-not-found') - else: - return self.get_node(jid, node)['info'] - - def set_info(self, jid, node, ifrom, data): - """ - Set the entire info stanza for a JID/node at once. - - The data parameter is a disco#info substanza. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'] = data - - def del_info(self, jid, node, ifrom, data): - """ - Reset the info stanza for a given JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'] = DiscoInfo() - - def get_items(self, jid, node, ifrom, data): - """ - Return the stored items data for the requested JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - if not node: - return DiscoItems() - else: - raise XMPPError(condition='item-not-found') - else: - return self.get_node(jid, node)['items'] - - def set_items(self, jid, node, ifrom, data): - """ - Replace the stored items data for a JID/node combination. - - The data parameter may provide: - items -- A set of items in tuple format. - """ - with self.lock: - items = data.get('items', set()) - self.add_node(jid, node) - self.get_node(jid, node)['items']['items'] = items - - def del_items(self, jid, node, ifrom, data): - """ - Reset the items stanza for a given JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['items'] = DiscoItems() - - def add_identity(self, jid, node, ifrom, data): - """ - Add a new identity to te JID/node combination. - - The data parameter may provide: - category -- The general category to which the agent belongs. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional standard xml:lang value. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'].add_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) - - def set_identities(self, jid, node, ifrom, data): - """ - Add or replace all identities for a JID/node combination. - - The data parameter should include: - identities -- A list of identities in tuple form: - (category, type, name, lang) - """ - with self.lock: - identities = data.get('identities', set()) - self.add_node(jid, node) - self.get_node(jid, node)['info']['identities'] = identities - - def del_identity(self, jid, node, ifrom, data): - """ - Remove an identity from a JID/node combination. - - The data parameter may provide: - category -- The general category to which the agent belonged. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional, standard xml:lang value. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'].del_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) - - def del_identities(self, jid, node, ifrom, data): - """ - Remove all identities from a JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - del self.get_node(jid, node)['info']['identities'] - - def add_feature(self, jid, node, ifrom, data): - """ - Add a feature to a JID/node combination. - - The data parameter should include: - feature -- The namespace of the supported feature. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'].add_feature( - data.get('feature', '')) - - def set_features(self, jid, node, ifrom, data): - """ - Add or replace all features for a JID/node combination. - - The data parameter should include: - features -- The new set of supported features. - """ - with self.lock: - features = data.get('features', set()) - self.add_node(jid, node) - self.get_node(jid, node)['info']['features'] = features - - def del_feature(self, jid, node, ifrom, data): - """ - Remove a feature from a JID/node combination. - - The data parameter should include: - feature -- The namespace of the removed feature. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'].del_feature( - data.get('feature', '')) - - def del_features(self, jid, node, ifrom, data): - """ - Remove all features from a JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - return - del self.get_node(jid, node)['info']['features'] - - def add_item(self, jid, node, ifrom, data): - """ - Add an item to a JID/node combination. - - The data parameter may include: - ijid -- The JID for the item. - inode -- Optional additional information to reference - non-addressable items. - name -- Optional human readable name for the item. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['items'].add_item( - data.get('ijid', ''), - node=data.get('inode', ''), - name=data.get('name', '')) - - def del_item(self, jid, node, ifrom, data): - """ - Remove an item from a JID/node combination. - - The data parameter may include: - ijid -- JID of the item to remove. - inode -- Optional extra identifying information. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['items'].del_item( - data.get('ijid', ''), - node=data.get('inode', None)) - - def cache_info(self, jid, node, ifrom, data): - """ - Cache disco information for an external JID. - - The data parameter is the Iq result stanza - containing the disco info to cache, or - the disco#info substanza itself. - """ - with self.lock: - if isinstance(data, Iq): - data = data['disco_info'] - - self.add_node(jid, node, ifrom) - self.get_node(jid, node, ifrom)['info'] = data - - def get_cached_info(self, jid, node, ifrom, data): - """ - Retrieve cached disco info data. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node, ifrom): - return None - else: - return self.get_node(jid, node, ifrom)['info'] diff --git a/sleekxmpp/plugins/xep_0033/__init__.py b/sleekxmpp/plugins/xep_0033/__init__.py deleted file mode 100644 index ba8152c4..00000000 --- a/sleekxmpp/plugins/xep_0033/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0033 import stanza -from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address -from sleekxmpp.plugins.xep_0033.addresses import XEP_0033 - - -register_plugin(XEP_0033) - -# Retain some backwards compatibility -xep_0033 = XEP_0033 -Addresses.addAddress = Addresses.add_address diff --git a/sleekxmpp/plugins/xep_0033/addresses.py b/sleekxmpp/plugins/xep_0033/addresses.py deleted file mode 100644 index 13cb7267..00000000 --- a/sleekxmpp/plugins/xep_0033/addresses.py +++ /dev/null @@ -1,37 +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 sleekxmpp import Message, Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0033 import stanza, Addresses - - -class XEP_0033(BasePlugin): - - """ - XEP-0033: Extended Stanza Addressing - """ - - name = 'xep_0033' - description = 'XEP-0033: Extended Stanza Addressing' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, Addresses) - register_stanza_plugin(Presence, Addresses) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Addresses.namespace) - diff --git a/sleekxmpp/plugins/xep_0033/stanza.py b/sleekxmpp/plugins/xep_0033/stanza.py deleted file mode 100644 index 1ff9fb20..00000000 --- a/sleekxmpp/plugins/xep_0033/stanza.py +++ /dev/null @@ -1,131 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin - - -class Addresses(ElementBase): - - name = 'addresses' - namespace = 'http://jabber.org/protocol/address' - plugin_attrib = 'addresses' - interfaces = set() - - def add_address(self, atype='to', jid='', node='', uri='', - desc='', delivered=False): - addr = Address(parent=self) - addr['type'] = atype - addr['jid'] = jid - addr['node'] = node - addr['uri'] = uri - addr['desc'] = desc - addr['delivered'] = delivered - - return addr - - # Additional methods for manipulating sets of addresses - # based on type are generated below. - - -class Address(ElementBase): - - name = 'address' - namespace = 'http://jabber.org/protocol/address' - plugin_attrib = 'address' - interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered']) - - address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) - - def get_jid(self): - return JID(self._get_attr('jid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_delivered(self): - value = self._get_attr('delivered', False) - return value and value.lower() in ('true', '1') - - def set_delivered(self, delivered): - if delivered: - self._set_attr('delivered', 'true') - else: - del self['delivered'] - - def set_uri(self, uri): - if uri: - del self['jid'] - del self['node'] - self._set_attr('uri', uri) - else: - self._del_attr('uri') - - -# ===================================================================== -# Auto-generate address type filters for the Addresses class. - -def _addr_filter(atype): - def _type_filter(addr): - if isinstance(addr, Address): - if atype == 'all' or addr['type'] == atype: - return True - return False - return _type_filter - - -def _build_methods(atype): - - def get_multi(self): - return list(filter(_addr_filter(atype), self)) - - def set_multi(self, value): - del self[atype] - for addr in value: - - # Support assigning dictionary versions of addresses - # instead of full Address objects. - if not isinstance(addr, Address): - if atype != 'all': - addr['type'] = atype - elif 'atype' in addr and 'type' not in addr: - addr['type'] = addr['atype'] - addrObj = Address() - addrObj.values = addr - addr = addrObj - - self.append(addr) - - def del_multi(self): - res = list(filter(_addr_filter(atype), self)) - for addr in res: - self.iterables.remove(addr) - self.xml.remove(addr.xml) - - return get_multi, set_multi, del_multi - - -for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'): - get_multi, set_multi, del_multi = _build_methods(atype) - - Addresses.interfaces.add(atype) - setattr(Addresses, "get_%s" % atype, get_multi) - setattr(Addresses, "set_%s" % atype, set_multi) - setattr(Addresses, "del_%s" % atype, del_multi) - - # To retain backwards compatibility: - setattr(Addresses, "get%s" % atype.title(), get_multi) - setattr(Addresses, "set%s" % atype.title(), set_multi) - setattr(Addresses, "del%s" % atype.title(), del_multi) - if atype == 'all': - Addresses.interfaces.add('addresses') - setattr(Addresses, "getAddresses", get_multi) - setattr(Addresses, "setAddresses", set_multi) - setattr(Addresses, "delAddresses", del_multi) - - -register_stanza_plugin(Addresses, Address, iterable=True) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py deleted file mode 100644 index ca5ed1ef..00000000 --- a/sleekxmpp/plugins/xep_0045.py +++ /dev/null @@ -1,402 +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 - -import logging - -from sleekxmpp import Presence -from sleekxmpp.plugins import BasePlugin, register_plugin -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET -from sleekxmpp.xmlstream.handler.callback import Callback -from sleekxmpp.xmlstream.matcher.xpath import MatchXPath -from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask -from sleekxmpp.exceptions import IqError, IqTimeout - - -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(BasePlugin): - - """ - Implements XEP-0045 Multi-User Chat - """ - - name = 'xep_0045' - description = 'XEP-0045: Multi-User Chat' - dependencies = set(['xep_0030', 'xep_0004']) - - def plugin_init(self): - self.rooms = {} - self.ourNicks = {} - self.xep = '0045' - # load MUC support in presence stanzas - register_stanza_plugin(Presence, MUCPresence) - self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) - self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message)) - self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) - self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) - self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change)) - self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % ( - self.xmpp.default_ns, - 'http://jabber.org/protocol/muc#user', - 'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) - - def handle_groupchat_invite(self, inv): - """ Handle an invite into a muc. - """ - logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv) - if inv['from'] not in self.rooms.keys(): - self.xmpp.event("groupchat_invite", inv) - - def handle_config_change(self, msg): - """Handle a MUC configuration change (with status code).""" - self.xmpp.event('groupchat_config_status', msg) - self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg) - - 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'] - entry['alt_nick'] = pr['nick'] - 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_error_message(self, msg): - """ Handle a message error event in a muc. - """ - self.xmpp.event('groupchat_message_error', msg) - self.xmpp.event("muc::%s::message_error" % 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 configureRoom(self, room, form=None, ifrom=None): - if form is None: - form = self.getRoomConfig(room, ifrom=ifrom) - 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) - # For now, swallow errors to preserve existing API - try: - result = iq.send() - except IqError: - return False - except IqTimeout: - return False - return True - - def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None): - """ Join the specified room, requesting 'maxhistory' lines of history. - """ - stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom) - x = ET.Element('{http://jabber.org/protocol/muc}x') - if password: - passelement = ET.Element('{http://jabber.org/protocol/muc}password') - passelement.text = password - x.append(passelement) - if maxhistory: - history = ET.Element('{http://jabber.org/protocol/muc}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('{http://jabber.org/protocol/muc#owner}destroy') - if altroom: - destroy.attrib['jid'] = altroom - xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason') - xreason.text = reason - destroy.append(xreason) - query.append(destroy) - iq.append(query) - # For now, swallow errors to preserve existing API - try: - r = iq.send() - except IqError: - return False - except IqTimeout: - return False - return True - - def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None): - """ 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('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'nick':nick}) - else: - item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'jid':jid}) - query.append(item) - iq = self.xmpp.makeIqSet(query) - iq['to'] = room - iq['from'] = ifrom - # For now, swallow errors to preserve existing API - try: - result = iq.send() - except IqError: - return False - except IqTimeout: - return False - return True - - def setRole(self, room, nick, role): - """ Change role property of a nick in a room. - Typically, roles are temporary (they last only as long as you are in the - room), whereas affiliations are permanent (they last across groupchat - sessions). - """ - if role not in ('moderator', 'participant', 'visitor', 'none'): - raise TypeError - query = ET.Element('{http://jabber.org/protocol/muc#admin}query') - item = ET.Element('item', {'role':role, 'nick':nick}) - 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='', mfrom=''): - """ Invite a jid to a room.""" - msg = self.xmpp.makeMessage(room) - msg['from'] = mfrom - 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('{http://jabber.org/protocol/muc#user}reason') - rxml.text = reason - invite.append(rxml) - x.append(invite) - msg.append(x) - self.xmpp.send(msg) - - def leaveMUC(self, room, nick, msg='', pfrom=None): - """ Leave the specified room. - """ - if msg: - self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom) - else: - self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom) - del self.rooms[room] - - def getRoomConfig(self, room, ifrom=''): - iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') - iq['to'] = room - iq['from'] = ifrom - # For now, swallow errors to preserve existing API - try: - result = iq.send() - except IqError: - raise ValueError - except IqTimeout: - 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, ifrom=None): - 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['to'] = room - iq['from'] = ifrom - iq.send() - - def setRoomConfig(self, room, config, ifrom=''): - 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'] = ifrom - 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() - - -xep_0045 = XEP_0045 -register_plugin(XEP_0045) diff --git a/sleekxmpp/plugins/xep_0047/__init__.py b/sleekxmpp/plugins/xep_0047/__init__.py deleted file mode 100644 index 5cd7df2e..00000000 --- a/sleekxmpp/plugins/xep_0047/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0047 import stanza -from sleekxmpp.plugins.xep_0047.stanza import Open, Close, Data -from sleekxmpp.plugins.xep_0047.stream import IBBytestream -from sleekxmpp.plugins.xep_0047.ibb import XEP_0047 - - -register_plugin(XEP_0047) - - -# Retain some backwards compatibility -xep_0047 = XEP_0047 diff --git a/sleekxmpp/plugins/xep_0047/ibb.py b/sleekxmpp/plugins/xep_0047/ibb.py deleted file mode 100644 index 62dddac2..00000000 --- a/sleekxmpp/plugins/xep_0047/ibb.py +++ /dev/null @@ -1,215 +0,0 @@ -import uuid -import logging -import threading - -from sleekxmpp import Message, Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0047 import stanza, Open, Close, Data, IBBytestream - - -log = logging.getLogger(__name__) - - -class XEP_0047(BasePlugin): - - name = 'xep_0047' - description = 'XEP-0047: In-band Bytestreams' - dependencies = set(['xep_0030']) - stanza = stanza - default_config = { - 'block_size': 4096, - 'max_block_size': 8192, - 'window_size': 1, - 'auto_accept': False, - } - - def plugin_init(self): - self._streams = {} - self._pending_streams = {} - self._pending_lock = threading.Lock() - self._stream_lock = threading.Lock() - - self._preauthed_sids_lock = threading.Lock() - self._preauthed_sids = {} - - register_stanza_plugin(Iq, Open) - register_stanza_plugin(Iq, Close) - register_stanza_plugin(Iq, Data) - register_stanza_plugin(Message, Data) - - self.xmpp.register_handler(Callback( - 'IBB Open', - StanzaPath('iq@type=set/ibb_open'), - self._handle_open_request)) - - self.xmpp.register_handler(Callback( - 'IBB Close', - StanzaPath('iq@type=set/ibb_close'), - self._handle_close)) - - self.xmpp.register_handler(Callback( - 'IBB Data', - StanzaPath('iq@type=set/ibb_data'), - self._handle_data)) - - self.xmpp.register_handler(Callback( - 'IBB Message Data', - StanzaPath('message/ibb_data'), - self._handle_data)) - - self.api.register(self._authorized, 'authorized', default=True) - self.api.register(self._authorized_sid, 'authorized_sid', default=True) - self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) - self.api.register(self._get_stream, 'get_stream', default=True) - self.api.register(self._set_stream, 'set_stream', default=True) - self.api.register(self._del_stream, 'del_stream', default=True) - - def plugin_end(self): - self.xmpp.remove_handler('IBB Open') - self.xmpp.remove_handler('IBB Close') - self.xmpp.remove_handler('IBB Data') - self.xmpp.remove_handler('IBB Message Data') - self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') - - def _get_stream(self, jid, sid, peer_jid, data): - return self._streams.get((jid, sid, peer_jid), None) - - def _set_stream(self, jid, sid, peer_jid, stream): - self._streams[(jid, sid, peer_jid)] = stream - - def _del_stream(self, jid, sid, peer_jid, data): - with self._stream_lock: - if (jid, sid, peer_jid) in self._streams: - del self._streams[(jid, sid, peer_jid)] - - def _accept_stream(self, iq): - receiver = iq['to'] - sender = iq['from'] - sid = iq['ibb_open']['sid'] - - if self.api['authorized_sid'](receiver, sid, sender, iq): - return True - return self.api['authorized'](receiver, sid, sender, iq) - - def _authorized(self, jid, sid, ifrom, iq): - if self.auto_accept: - if iq['ibb_open']['block_size'] <= self.max_block_size: - return True - return False - - def _authorized_sid(self, jid, sid, ifrom, iq): - with self._preauthed_sids_lock: - if (jid, sid, ifrom) in self._preauthed_sids: - del self._preauthed_sids[(jid, sid, ifrom)] - return True - return False - - def _preauthorize_sid(self, jid, sid, ifrom, data): - with self._preauthed_sids_lock: - self._preauthed_sids[(jid, sid, ifrom)] = True - - def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False, - ifrom=None, block=True, timeout=None, callback=None): - if sid is None: - sid = str(uuid.uuid4()) - if block_size is None: - block_size = self.block_size - - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - iq['ibb_open']['block_size'] = block_size - iq['ibb_open']['sid'] = sid - iq['ibb_open']['stanza'] = 'iq' - - stream = IBBytestream(self.xmpp, sid, block_size, - iq['from'], iq['to'], window, - use_messages) - - with self._stream_lock: - self._pending_streams[iq['id']] = stream - - self._pending_streams[iq['id']] = stream - - if block: - resp = iq.send(timeout=timeout) - self._handle_opened_stream(resp) - return stream - else: - cb = None - if callback is not None: - def chained(resp): - self._handle_opened_stream(resp) - callback(resp) - cb = chained - else: - cb = self._handle_opened_stream - return iq.send(block=block, timeout=timeout, callback=cb) - - def _handle_opened_stream(self, iq): - if iq['type'] == 'result': - with self._stream_lock: - stream = self._pending_streams.get(iq['id'], None) - if stream is not None: - log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) - stream.self_jid = iq['to'] - stream.peer_jid = iq['from'] - stream.stream_started.set() - self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) - self.xmpp.event('ibb_stream_start', stream) - self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream) - - with self._stream_lock: - if iq['id'] in self._pending_streams: - del self._pending_streams[iq['id']] - - def _handle_open_request(self, iq): - sid = iq['ibb_open']['sid'] - size = iq['ibb_open']['block_size'] or self.block_size - - log.debug('Received IBB stream request from %s', iq['from']) - - if not sid: - raise XMPPError(etype='modify', condition='bad-request') - - if not self._accept_stream(iq): - raise XMPPError(etype='modify', condition='not-acceptable') - - if size > self.max_block_size: - raise XMPPError('resource-constraint') - - stream = IBBytestream(self.xmpp, sid, size, - iq['to'], iq['from'], - self.window_size) - stream.stream_started.set() - self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) - iq.reply() - iq.send() - - self.xmpp.event('ibb_stream_start', stream) - self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream) - - def _handle_data(self, stanza): - sid = stanza['ibb_data']['sid'] - stream = self.api['get_stream'](stanza['to'], sid, stanza['from']) - if stream is not None and stanza['from'] == stream.peer_jid: - stream._recv_data(stanza) - else: - raise XMPPError('item-not-found') - - def _handle_close(self, iq): - sid = iq['ibb_close']['sid'] - stream = self.api['get_stream'](iq['to'], sid, iq['from']) - if stream is not None and iq['from'] == stream.peer_jid: - stream._closed(iq) - self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid) - else: - raise XMPPError('item-not-found') diff --git a/sleekxmpp/plugins/xep_0047/stanza.py b/sleekxmpp/plugins/xep_0047/stanza.py deleted file mode 100644 index 7e5d2fed..00000000 --- a/sleekxmpp/plugins/xep_0047/stanza.py +++ /dev/null @@ -1,67 +0,0 @@ -import re -import base64 - -from sleekxmpp.util import bytes -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import ElementBase - - -VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*') - - -def to_b64(data): - return bytes(base64.b64encode(bytes(data))).decode('utf-8') - - -def from_b64(data): - return bytes(base64.b64decode(bytes(data))) - - -class Open(ElementBase): - name = 'open' - namespace = 'http://jabber.org/protocol/ibb' - plugin_attrib = 'ibb_open' - interfaces = set(('block_size', 'sid', 'stanza')) - - def get_block_size(self): - return int(self._get_attr('block-size')) - - def set_block_size(self, value): - self._set_attr('block-size', str(value)) - - def del_block_size(self): - self._del_attr('block-size') - - -class Data(ElementBase): - name = 'data' - namespace = 'http://jabber.org/protocol/ibb' - plugin_attrib = 'ibb_data' - interfaces = set(('seq', 'sid', 'data')) - sub_interfaces = set(['data']) - - def get_seq(self): - return int(self._get_attr('seq', '0')) - - def set_seq(self, value): - self._set_attr('seq', str(value)) - - def get_data(self): - b64_data = self.xml.text.strip() - if VALID_B64.match(b64_data).group() == b64_data: - return from_b64(b64_data) - else: - raise XMPPError('not-acceptable') - - def set_data(self, value): - self.xml.text = to_b64(value) - - def del_data(self): - self.xml.text = '' - - -class Close(ElementBase): - name = 'close' - namespace = 'http://jabber.org/protocol/ibb' - plugin_attrib = 'ibb_close' - interfaces = set(['sid']) diff --git a/sleekxmpp/plugins/xep_0047/stream.py b/sleekxmpp/plugins/xep_0047/stream.py deleted file mode 100644 index 9651edf8..00000000 --- a/sleekxmpp/plugins/xep_0047/stream.py +++ /dev/null @@ -1,148 +0,0 @@ -import socket -import threading -import logging - -from sleekxmpp.stanza import Iq -from sleekxmpp.util import Queue -from sleekxmpp.exceptions import XMPPError - - -log = logging.getLogger(__name__) - - -class IBBytestream(object): - - def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False): - self.xmpp = xmpp - self.sid = sid - self.block_size = block_size - self.window_size = window_size - self.use_messages = use_messages - - if jid is None: - jid = xmpp.boundjid - self.self_jid = jid - self.peer_jid = peer - - self.send_seq = -1 - self.recv_seq = -1 - - self._send_seq_lock = threading.Lock() - self._recv_seq_lock = threading.Lock() - - self.stream_started = threading.Event() - self.stream_in_closed = threading.Event() - self.stream_out_closed = threading.Event() - - self.recv_queue = Queue() - - self.send_window = threading.BoundedSemaphore(value=self.window_size) - self.window_ids = set() - self.window_empty = threading.Event() - self.window_empty.set() - - def send(self, data): - if not self.stream_started.is_set() or \ - self.stream_out_closed.is_set(): - raise socket.error - data = data[0:self.block_size] - self.send_window.acquire() - with self._send_seq_lock: - self.send_seq = (self.send_seq + 1) % 65535 - seq = self.send_seq - if self.use_messages: - msg = self.xmpp.Message() - msg['to'] = self.peer_jid - msg['from'] = self.self_jid - msg['id'] = self.xmpp.new_id() - msg['ibb_data']['sid'] = self.sid - msg['ibb_data']['seq'] = seq - msg['ibb_data']['data'] = data - msg.send() - self.send_window.release() - else: - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = self.peer_jid - iq['from'] = self.self_jid - iq['ibb_data']['sid'] = self.sid - iq['ibb_data']['seq'] = seq - iq['ibb_data']['data'] = data - self.window_empty.clear() - self.window_ids.add(iq['id']) - iq.send(block=False, callback=self._recv_ack) - return len(data) - - def sendall(self, data): - sent_len = 0 - while sent_len < len(data): - sent_len += self.send(data[sent_len:]) - - def _recv_ack(self, iq): - self.window_ids.remove(iq['id']) - if not self.window_ids: - self.window_empty.set() - self.send_window.release() - if iq['type'] == 'error': - self.close() - - def _recv_data(self, stanza): - with self._recv_seq_lock: - new_seq = stanza['ibb_data']['seq'] - if new_seq != (self.recv_seq + 1) % 65535: - self.close() - raise XMPPError('unexpected-request') - self.recv_seq = new_seq - - data = stanza['ibb_data']['data'] - if len(data) > self.block_size: - self.close() - raise XMPPError('not-acceptable') - - self.recv_queue.put(data) - self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data}) - - if isinstance(stanza, Iq): - stanza.reply() - stanza.send() - - def recv(self, *args, **kwargs): - return self.read(block=True) - - def read(self, block=True, timeout=None, **kwargs): - if not self.stream_started.is_set() or \ - self.stream_in_closed.is_set(): - raise socket.error - if timeout is not None: - block = True - try: - return self.recv_queue.get(block, timeout) - except: - return None - - def close(self): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = self.peer_jid - iq['from'] = self.self_jid - iq['ibb_close']['sid'] = self.sid - self.stream_out_closed.set() - iq.send(block=False, - callback=lambda x: self.stream_in_closed.set()) - self.xmpp.event('ibb_stream_end', self) - - def _closed(self, iq): - self.stream_in_closed.set() - self.stream_out_closed.set() - iq.reply() - iq.send() - self.xmpp.event('ibb_stream_end', self) - - def makefile(self, *args, **kwargs): - return self - - def connect(*args, **kwargs): - return None - - def shutdown(self, *args, **kwargs): - return None diff --git a/sleekxmpp/plugins/xep_0048/__init__.py b/sleekxmpp/plugins/xep_0048/__init__.py deleted file mode 100644 index 2c98d061..00000000 --- a/sleekxmpp/plugins/xep_0048/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL -from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048 - - -register_plugin(XEP_0048) diff --git a/sleekxmpp/plugins/xep_0048/bookmarks.py b/sleekxmpp/plugins/xep_0048/bookmarks.py deleted file mode 100644 index 0bb5ae38..00000000 --- a/sleekxmpp/plugins/xep_0048/bookmarks.py +++ /dev/null @@ -1,76 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL - - -log = logging.getLogger(__name__) - - -class XEP_0048(BasePlugin): - - name = 'xep_0048' - description = 'XEP-0048: Bookmarks' - dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223']) - stanza = stanza - default_config = { - 'auto_join': False, - 'storage_method': 'xep_0049' - } - - def plugin_init(self): - register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks) - - self.xmpp['xep_0049'].register(Bookmarks) - self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks) - - self.xmpp.add_event_handler('session_start', self._autojoin) - - def plugin_end(self): - self.xmpp.del_event_handler('session_start', self._autojoin) - - def _autojoin(self, __): - if not self.auto_join: - return - - try: - result = self.get_bookmarks(method=self.storage_method) - except XMPPError: - return - - if self.storage_method == 'xep_0223': - bookmarks = result['pubsub']['items']['item']['bookmarks'] - else: - bookmarks = result['private']['bookmarks'] - - for conf in bookmarks['conferences']: - if conf['autojoin']: - log.debug('Auto joining %s as %s', conf['jid'], conf['nick']) - self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'], - password=conf['password']) - - def set_bookmarks(self, bookmarks, method=None, **iqargs): - if not method: - method = self.storage_method - return self.xmpp[method].store(bookmarks, **iqargs) - - def get_bookmarks(self, method=None, **iqargs): - if not method: - method = self.storage_method - - loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks' - - return self.xmpp[method].retrieve(loc, **iqargs) diff --git a/sleekxmpp/plugins/xep_0048/stanza.py b/sleekxmpp/plugins/xep_0048/stanza.py deleted file mode 100644 index 21829392..00000000 --- a/sleekxmpp/plugins/xep_0048/stanza.py +++ /dev/null @@ -1,65 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin - - -class Bookmarks(ElementBase): - name = 'storage' - namespace = 'storage:bookmarks' - plugin_attrib = 'bookmarks' - interfaces = set() - - def add_conference(self, jid, nick, name=None, autojoin=None, password=None): - conf = Conference() - conf['jid'] = jid - conf['nick'] = nick - if name is None: - name = jid - conf['name'] = name - conf['autojoin'] = autojoin - conf['password'] = password - self.append(conf) - - def add_url(self, url, name=None): - saved_url = URL() - saved_url['url'] = url - if name is None: - name = url - saved_url['name'] = name - self.append(saved_url) - - -class Conference(ElementBase): - name = 'conference' - namespace = 'storage:bookmarks' - plugin_attrib = 'conference' - plugin_multi_attrib = 'conferences' - interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name']) - sub_interfaces = set(['nick', 'password']) - - def get_autojoin(self): - value = self._get_attr('autojoin') - return value in ('1', 'true') - - def set_autojoin(self, value): - del self['autojoin'] - if value in ('1', 'true', True): - self._set_attr('autojoin', 'true') - - -class URL(ElementBase): - name = 'url' - namespace = 'storage:bookmarks' - plugin_attrib = 'url' - plugin_multi_attrib = 'urls' - interfaces = set(['url', 'name']) - - -register_stanza_plugin(Bookmarks, Conference, iterable=True) -register_stanza_plugin(Bookmarks, URL, iterable=True) diff --git a/sleekxmpp/plugins/xep_0049/__init__.py b/sleekxmpp/plugins/xep_0049/__init__.py deleted file mode 100644 index b0c4f904..00000000 --- a/sleekxmpp/plugins/xep_0049/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0049.stanza import PrivateXML -from sleekxmpp.plugins.xep_0049.private_storage import XEP_0049 - - -register_plugin(XEP_0049) diff --git a/sleekxmpp/plugins/xep_0049/private_storage.py b/sleekxmpp/plugins/xep_0049/private_storage.py deleted file mode 100644 index ef6cbdde..00000000 --- a/sleekxmpp/plugins/xep_0049/private_storage.py +++ /dev/null @@ -1,53 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0049 import stanza, PrivateXML - - -log = logging.getLogger(__name__) - - -class XEP_0049(BasePlugin): - - name = 'xep_0049' - description = 'XEP-0049: Private XML Storage' - dependencies = set([]) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, PrivateXML) - - def register(self, stanza): - register_stanza_plugin(PrivateXML, stanza, iterable=True) - - def store(self, data, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - - if not isinstance(data, list): - data = [data] - - for elem in data: - iq['private'].append(elem) - - return iq.send(block=block, timeout=timeout, callback=callback) - - def retrieve(self, name, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['from'] = ifrom - iq['private'].enable(name) - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0049/stanza.py b/sleekxmpp/plugins/xep_0049/stanza.py deleted file mode 100644 index d424e2f0..00000000 --- a/sleekxmpp/plugins/xep_0049/stanza.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET, ElementBase - - -class PrivateXML(ElementBase): - - name = 'query' - namespace = 'jabber:iq:private' - plugin_attrib = 'private' - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0050/__init__.py b/sleekxmpp/plugins/xep_0050/__init__.py deleted file mode 100644 index 640b182d..00000000 --- a/sleekxmpp/plugins/xep_0050/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0050.stanza import Command -from sleekxmpp.plugins.xep_0050.adhoc import XEP_0050 - - -register_plugin(XEP_0050) - - -# Retain some backwards compatibility -xep_0050 = XEP_0050 diff --git a/sleekxmpp/plugins/xep_0050/adhoc.py b/sleekxmpp/plugins/xep_0050/adhoc.py deleted file mode 100644 index e5594c3f..00000000 --- a/sleekxmpp/plugins/xep_0050/adhoc.py +++ /dev/null @@ -1,688 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import time - -from sleekxmpp import Iq -from sleekxmpp.exceptions import IqError -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0050 import stanza -from sleekxmpp.plugins.xep_0050 import Command -from sleekxmpp.plugins.xep_0004 import Form - - -log = logging.getLogger(__name__) - - -class XEP_0050(BasePlugin): - - """ - XEP-0050: Ad-Hoc Commands - - XMPP's Adhoc Commands provides a generic workflow mechanism for - interacting with applications. The result is similar to menu selections - and multi-step dialogs in normal desktop applications. Clients do not - need to know in advance what commands are provided by any particular - application or agent. While adhoc commands provide similar functionality - to Jabber-RPC, adhoc commands are used primarily for human interaction. - - Also see <http://xmpp.org/extensions/xep-0050.html> - - Configuration Values: - threaded -- Indicates if command events should be threaded. - Defaults to True. - - Events: - command_execute -- Received a command with action="execute" - command_next -- Received a command with action="next" - command_complete -- Received a command with action="complete" - command_cancel -- Received a command with action="cancel" - - Attributes: - threaded -- Indicates if command events should be threaded. - Defaults to True. - commands -- A dictionary mapping JID/node pairs to command - names and handlers. - sessions -- A dictionary or equivalent backend mapping - session IDs to dictionaries containing data - relevant to a command's session. - - Methods: - plugin_init -- Overrides base_plugin.plugin_init - post_init -- Overrides base_plugin.post_init - new_session -- Return a new session ID. - prep_handlers -- Placeholder. May call with a list of handlers - to prepare them for use with the session storage - backend, if needed. - set_backend -- Replace the default session storage with some - external storage mechanism, such as a database. - The provided backend wrapper must be able to - act using the same syntax as a dictionary. - add_command -- Add a command for use by external entitites. - get_commands -- Retrieve a list of commands provided by a - remote agent. - send_command -- Send a command request to a remote agent. - start_command -- Command user API: initiate a command session - continue_command -- Command user API: proceed to the next step - cancel_command -- Command user API: cancel a command - complete_command -- Command user API: finish a command - terminate_command -- Command user API: delete a command's session - """ - - name = 'xep_0050' - description = 'XEP-0050: Ad-Hoc Commands' - dependencies = set(['xep_0030', 'xep_0004']) - stanza = stanza - default_config = { - 'threaded': True, - 'session_db': None - } - - def plugin_init(self): - """Start the XEP-0050 plugin.""" - self.sessions = self.session_db - if self.sessions is None: - self.sessions = {} - - self.commands = {} - - self.xmpp.register_handler( - Callback("Ad-Hoc Execute", - StanzaPath('iq@type=set/command'), - self._handle_command)) - - register_stanza_plugin(Iq, Command) - register_stanza_plugin(Command, Form) - - self.xmpp.add_event_handler('command_execute', - self._handle_command_start, - threaded=self.threaded) - self.xmpp.add_event_handler('command_next', - self._handle_command_next, - threaded=self.threaded) - self.xmpp.add_event_handler('command_cancel', - self._handle_command_cancel, - threaded=self.threaded) - self.xmpp.add_event_handler('command_complete', - self._handle_command_complete, - threaded=self.threaded) - - def plugin_end(self): - self.xmpp.del_event_handler('command_execute', - self._handle_command_start) - self.xmpp.del_event_handler('command_next', - self._handle_command_next) - self.xmpp.del_event_handler('command_cancel', - self._handle_command_cancel) - self.xmpp.del_event_handler('command_complete', - self._handle_command_complete) - self.xmpp.remove_handler('Ad-Hoc Execute') - self.xmpp['xep_0030'].del_feature(feature=Command.namespace) - self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Command.namespace) - self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) - - def set_backend(self, db): - """ - Replace the default session storage dictionary with - a generic, external data storage mechanism. - - The replacement backend must be able to interact through - the same syntax and interfaces as a normal dictionary. - - Arguments: - db -- The new session storage mechanism. - """ - self.sessions = db - - def prep_handlers(self, handlers, **kwargs): - """ - Prepare a list of functions for use by the backend service. - - Intended to be replaced by the backend service as needed. - - Arguments: - handlers -- A list of function pointers - **kwargs -- Any additional parameters required by the backend. - """ - pass - - # ================================================================= - # Server side (command provider) API - - def add_command(self, jid=None, node=None, name='', handler=None): - """ - Make a new command available to external entities. - - Access control may be implemented in the provided handler. - - Command workflow is done across a sequence of command handlers. The - first handler is given the initial Iq stanza of the request in order - to support access control. Subsequent handlers are given only the - payload items of the command. All handlers will receive the command's - session data. - - Arguments: - jid -- The JID that will expose the command. - node -- The node associated with the command. - name -- A human readable name for the command. - handler -- A function that will generate the response to the - initial command request, as well as enforcing any - access control policies. - """ - if jid is None: - jid = self.xmpp.boundjid - elif not isinstance(jid, JID): - jid = JID(jid) - item_jid = jid.full - - self.xmpp['xep_0030'].add_identity(category='automation', - itype='command-list', - name='Ad-Hoc commands', - node=Command.namespace, - jid=jid) - self.xmpp['xep_0030'].add_item(jid=item_jid, - name=name, - node=Command.namespace, - subnode=node, - ijid=jid) - self.xmpp['xep_0030'].add_identity(category='automation', - itype='command-node', - name=name, - node=node, - jid=jid) - self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid) - - self.commands[(item_jid, node)] = (name, handler) - - def new_session(self): - """Return a new session ID.""" - return str(time.time()) + '-' + self.xmpp.new_id() - - def _handle_command(self, iq): - """Raise command events based on the command action.""" - self.xmpp.event('command_%s' % iq['command']['action'], iq) - - def _handle_command_start(self, iq): - """ - Process an initial request to execute a command. - - Arguments: - iq -- The command execution request. - """ - sessionid = self.new_session() - node = iq['command']['node'] - key = (iq['to'].full, node) - name, handler = self.commands.get(key, ('Not found', None)) - if not handler: - log.debug('Command not found: %s, %s', key, self.commands) - - payload = [] - for stanza in iq['command']['substanzas']: - payload.append(stanza) - - if len(payload) == 1: - payload = payload[0] - - interfaces = set([item.plugin_attrib for item in payload]) - payload_classes = set([item.__class__ for item in payload]) - - initial_session = {'id': sessionid, - 'from': iq['from'], - 'to': iq['to'], - 'node': node, - 'payload': payload, - 'interfaces': interfaces, - 'payload_classes': payload_classes, - 'notes': None, - 'has_next': False, - 'allow_complete': False, - 'allow_prev': False, - 'past': [], - 'next': None, - 'prev': None, - 'cancel': None} - - session = handler(iq, initial_session) - - self._process_command_response(iq, session) - - def _handle_command_next(self, iq): - """ - Process a request for the next step in the workflow - for a command with multiple steps. - - Arguments: - iq -- The command continuation request. - """ - sessionid = iq['command']['sessionid'] - session = self.sessions.get(sessionid) - - if session: - handler = session['next'] - interfaces = session['interfaces'] - results = [] - for stanza in iq['command']['substanzas']: - if stanza.plugin_attrib in interfaces: - results.append(stanza) - if len(results) == 1: - results = results[0] - - session = handler(results, session) - - self._process_command_response(iq, session) - else: - raise XMPPError('item-not-found') - - def _handle_command_prev(self, iq): - """ - Process a request for the prev step in the workflow - for a command with multiple steps. - - Arguments: - iq -- The command continuation request. - """ - sessionid = iq['command']['sessionid'] - session = self.sessions.get(sessionid) - - if session: - handler = session['prev'] - interfaces = session['interfaces'] - results = [] - for stanza in iq['command']['substanzas']: - if stanza.plugin_attrib in interfaces: - results.append(stanza) - if len(results) == 1: - results = results[0] - - session = handler(results, session) - - self._process_command_response(iq, session) - else: - raise XMPPError('item-not-found') - - def _process_command_response(self, iq, session): - """ - Generate a command reply stanza based on the - provided session data. - - Arguments: - iq -- The command request stanza. - session -- A dictionary of relevant session data. - """ - sessionid = session['id'] - - payload = session['payload'] - if payload is None: - payload = [] - if not isinstance(payload, list): - payload = [payload] - - interfaces = session.get('interfaces', set()) - payload_classes = session.get('payload_classes', set()) - - interfaces.update(set([item.plugin_attrib for item in payload])) - payload_classes.update(set([item.__class__ for item in payload])) - - session['interfaces'] = interfaces - session['payload_classes'] = payload_classes - - self.sessions[sessionid] = session - - for item in payload: - register_stanza_plugin(Command, item.__class__, iterable=True) - - iq.reply() - iq['command']['node'] = session['node'] - iq['command']['sessionid'] = session['id'] - - if session['next'] is None: - iq['command']['actions'] = [] - iq['command']['status'] = 'completed' - elif session['has_next']: - actions = ['next'] - if session['allow_complete']: - actions.append('complete') - if session['allow_prev']: - actions.append('prev') - iq['command']['actions'] = actions - iq['command']['status'] = 'executing' - else: - iq['command']['actions'] = ['complete'] - iq['command']['status'] = 'executing' - - iq['command']['notes'] = session['notes'] - - for item in payload: - iq['command'].append(item) - - iq.send() - - def _handle_command_cancel(self, iq): - """ - Process a request to cancel a command's execution. - - Arguments: - iq -- The command cancellation request. - """ - node = iq['command']['node'] - sessionid = iq['command']['sessionid'] - - session = self.sessions.get(sessionid) - - if session: - handler = session['cancel'] - if handler: - handler(iq, session) - del self.sessions[sessionid] - iq.reply() - iq['command']['node'] = node - iq['command']['sessionid'] = sessionid - iq['command']['status'] = 'canceled' - iq['command']['notes'] = session['notes'] - iq.send() - else: - raise XMPPError('item-not-found') - - - def _handle_command_complete(self, iq): - """ - Process a request to finish the execution of command - and terminate the workflow. - - All data related to the command session will be removed. - - Arguments: - iq -- The command completion request. - """ - node = iq['command']['node'] - sessionid = iq['command']['sessionid'] - session = self.sessions.get(sessionid) - - if session: - handler = session['next'] - interfaces = session['interfaces'] - results = [] - for stanza in iq['command']['substanzas']: - if stanza.plugin_attrib in interfaces: - results.append(stanza) - if len(results) == 1: - results = results[0] - - if handler: - handler(results, session) - - del self.sessions[sessionid] - - iq.reply() - iq['command']['node'] = node - iq['command']['sessionid'] = sessionid - iq['command']['actions'] = [] - iq['command']['status'] = 'completed' - iq['command']['notes'] = session['notes'] - iq.send() - else: - raise XMPPError('item-not-found') - - # ================================================================= - # Client side (command user) API - - def get_commands(self, jid, **kwargs): - """ - Return a list of commands provided by a given JID. - - Arguments: - jid -- The JID to query for commands. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the items. - ifrom -- Specifiy the sender's JID. - block -- If true, block and wait for the stanzas' reply. - timeout -- The time in seconds to block while waiting for - a reply. If None, then wait indefinitely. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - iterator -- If True, return a result set iterator using - the XEP-0059 plugin, if the plugin is loaded. - Otherwise the parameter is ignored. - """ - return self.xmpp['xep_0030'].get_items(jid=jid, - node=Command.namespace, - **kwargs) - - def send_command(self, jid, node, ifrom=None, action='execute', - payload=None, sessionid=None, flow=False, **kwargs): - """ - Create and send a command stanza, without using the provided - workflow management APIs. - - Arguments: - jid -- The JID to send the command request or result. - node -- The node for the command. - ifrom -- Specify the sender's JID. - action -- May be one of: execute, cancel, complete, - or cancel. - payload -- Either a list of payload items, or a single - payload item such as a data form. - sessionid -- The current session's ID value. - flow -- If True, process the Iq result using the - command workflow methods contained in the - session instead of returning the response - stanza itself. Defaults to False. - 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 - callback -- Optional reference to a stream handler - function. Will be executed when a reply - stanza is received if flow=False. - """ - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - iq['command']['node'] = node - iq['command']['action'] = action - if sessionid is not None: - iq['command']['sessionid'] = sessionid - if payload is not None: - if not isinstance(payload, list): - payload = [payload] - for item in payload: - iq['command'].append(item) - if not flow: - return iq.send(**kwargs) - else: - if kwargs.get('block', True): - try: - result = iq.send(**kwargs) - except IqError as err: - result = err.iq - self._handle_command_result(result) - else: - iq.send(block=False, callback=self._handle_command_result) - - def start_command(self, jid, node, session, ifrom=None, block=False): - """ - Initiate executing a command provided by a remote agent. - - The default workflow provided is non-blocking, but a blocking - version may be used with block=True. - - The provided session dictionary should contain: - next -- A handler for processing the command result. - error -- A handler for processing any error stanzas - generated by the request. - - Arguments: - jid -- The JID to send the command request. - node -- The node for the desired command. - session -- A dictionary of relevant session data. - ifrom -- Optionally specify the sender's JID. - block -- If True, block execution until a result - is received. Defaults to False. - """ - session['jid'] = jid - session['node'] = node - session['timestamp'] = time.time() - session['block'] = block - if 'payload' not in session: - session['payload'] = None - - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - session['from'] = ifrom - iq['command']['node'] = node - iq['command']['action'] = 'execute' - if session['payload'] is not None: - payload = session['payload'] - if not isinstance(payload, list): - payload = list(payload) - for stanza in payload: - iq['command'].append(stanza) - sessionid = 'client:pending_' + iq['id'] - session['id'] = sessionid - self.sessions[sessionid] = session - if session['block']: - try: - result = iq.send(block=True) - except IqError as err: - result = err.iq - self._handle_command_result(result) - else: - iq.send(block=False, callback=self._handle_command_result) - - def continue_command(self, session, direction='next'): - """ - Execute the next action of the command. - - Arguments: - session -- All stored data relevant to the current - command session. - """ - sessionid = 'client:' + session['id'] - self.sessions[sessionid] = session - - self.send_command(session['jid'], - session['node'], - ifrom=session.get('from', None), - action=direction, - payload=session.get('payload', None), - sessionid=session['id'], - flow=True, - block=session['block']) - - def cancel_command(self, session): - """ - Cancel the execution of a command. - - Arguments: - session -- All stored data relevant to the current - command session. - """ - sessionid = 'client:' + session['id'] - self.sessions[sessionid] = session - - self.send_command(session['jid'], - session['node'], - ifrom=session.get('from', None), - action='cancel', - payload=session.get('payload', None), - sessionid=session['id'], - flow=True, - block=session['block']) - - def complete_command(self, session): - """ - Finish the execution of a command workflow. - - Arguments: - session -- All stored data relevant to the current - command session. - """ - sessionid = 'client:' + session['id'] - self.sessions[sessionid] = session - - self.send_command(session['jid'], - session['node'], - ifrom=session.get('from', None), - action='complete', - payload=session.get('payload', None), - sessionid=session['id'], - flow=True, - block=session['block']) - - def terminate_command(self, session): - """ - Delete a command's session after a command has completed - or an error has occured. - - Arguments: - session -- All stored data relevant to the current - command session. - """ - sessionid = 'client:' + session['id'] - try: - del self.sessions[sessionid] - except Exception as e: - log.error("Error deleting adhoc command session: %s" % e.message) - - def _handle_command_result(self, iq): - """ - Process the results of a command request. - - Will execute the 'next' handler stored in the session - data, or the 'error' handler depending on the Iq's type. - - Arguments: - iq -- The command response. - """ - sessionid = 'client:' + iq['command']['sessionid'] - pending = False - - if sessionid not in self.sessions: - pending = True - pendingid = 'client:pending_' + iq['id'] - if pendingid not in self.sessions: - return - sessionid = pendingid - - session = self.sessions[sessionid] - sessionid = 'client:' + iq['command']['sessionid'] - session['id'] = iq['command']['sessionid'] - - self.sessions[sessionid] = session - - if pending: - del self.sessions[pendingid] - - handler_type = 'next' - if iq['type'] == 'error': - handler_type = 'error' - handler = session.get(handler_type, None) - if handler: - handler(iq, session) - elif iq['type'] == 'error': - self.terminate_command(session) - - if iq['command']['status'] == 'completed': - self.terminate_command(session) diff --git a/sleekxmpp/plugins/xep_0050/stanza.py b/sleekxmpp/plugins/xep_0050/stanza.py deleted file mode 100644 index 2367c77b..00000000 --- a/sleekxmpp/plugins/xep_0050/stanza.py +++ /dev/null @@ -1,185 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class Command(ElementBase): - - """ - XMPP's Adhoc Commands provides a generic workflow mechanism for - interacting with applications. The result is similar to menu selections - and multi-step dialogs in normal desktop applications. Clients do not - need to know in advance what commands are provided by any particular - application or agent. While adhoc commands provide similar functionality - to Jabber-RPC, adhoc commands are used primarily for human interaction. - - Also see <http://xmpp.org/extensions/xep-0050.html> - - Example command stanzas: - <iq type="set"> - <command xmlns="http://jabber.org/protocol/commands" - node="run_foo" - action="execute" /> - </iq> - - <iq type="result"> - <command xmlns="http://jabber.org/protocol/commands" - node="run_foo" - sessionid="12345" - status="executing"> - <actions> - <complete /> - </actions> - <note type="info">Information!</note> - <x xmlns="jabber:x:data"> - <field var="greeting" - type="text-single" - label="Greeting" /> - </x> - </command> - </iq> - - Stanza Interface: - action -- The action to perform. - actions -- The set of allowable next actions. - node -- The node associated with the command. - notes -- A list of tuples for informative notes. - sessionid -- A unique identifier for a command session. - status -- May be one of: canceled, completed, or executing. - - Attributes: - actions -- A set of allowed action values. - statuses -- A set of allowed status values. - next_actions -- A set of allowed next action names. - - Methods: - get_action -- Return the requested action. - get_actions -- Return the allowable next actions. - set_actions -- Set the allowable next actions. - del_actions -- Remove the current set of next actions. - get_notes -- Return a list of informative note data. - set_notes -- Set informative notes. - del_notes -- Remove any note data. - add_note -- Add a single note. - """ - - name = 'command' - namespace = 'http://jabber.org/protocol/commands' - plugin_attrib = 'command' - interfaces = set(('action', 'sessionid', 'node', - 'status', 'actions', 'notes')) - actions = set(('cancel', 'complete', 'execute', 'next', 'prev')) - statuses = set(('canceled', 'completed', 'executing')) - next_actions = set(('prev', 'next', 'complete')) - - def get_action(self): - """ - Return the value of the action attribute. - - If the Iq stanza's type is "set" then use a default - value of "execute". - """ - if self.parent()['type'] == 'set': - return self._get_attr('action', default='execute') - return self._get_attr('action') - - def set_actions(self, values): - """ - Assign the set of allowable next actions. - - Arguments: - values -- A list containing any combination of: - 'prev', 'next', and 'complete' - """ - self.del_actions() - if values: - self._set_sub_text('{%s}actions' % self.namespace, '', True) - actions = self.find('{%s}actions' % self.namespace) - for val in values: - if val in self.next_actions: - action = ET.Element('{%s}%s' % (self.namespace, val)) - actions.append(action) - - def get_actions(self): - """ - Return the set of allowable next actions. - """ - actions = set() - actions_xml = self.find('{%s}actions' % self.namespace) - if actions_xml is not None: - for action in self.next_actions: - action_xml = actions_xml.find('{%s}%s' % (self.namespace, - action)) - if action_xml is not None: - actions.add(action) - return actions - - def del_actions(self): - """ - Remove all allowable next actions. - """ - self._del_sub('{%s}actions' % self.namespace) - - def get_notes(self): - """ - Return a list of note information. - - Example: - [('info', 'Some informative data'), - ('warning', 'Use caution'), - ('error', 'The command ran, but had errors')] - """ - notes = [] - notes_xml = self.findall('{%s}note' % self.namespace) - for note in notes_xml: - notes.append((note.attrib.get('type', 'info'), - note.text)) - return notes - - def set_notes(self, notes): - """ - Add multiple notes to the command result. - - Each note is a tuple, with the first item being one of: - 'info', 'warning', or 'error', and the second item being - any human readable message. - - Example: - [('info', 'Some informative data'), - ('warning', 'Use caution'), - ('error', 'The command ran, but had errors')] - - - Arguments: - notes -- A list of tuples of note information. - """ - self.del_notes() - for note in notes: - self.add_note(note[1], note[0]) - - def del_notes(self): - """ - Remove all notes associated with the command result. - """ - notes_xml = self.findall('{%s}note' % self.namespace) - for note in notes_xml: - self.xml.remove(note) - - def add_note(self, msg='', ntype='info'): - """ - Add a single note annotation to the command. - - Arguments: - msg -- A human readable message. - ntype -- One of: 'info', 'warning', 'error' - """ - xml = ET.Element('{%s}note' % self.namespace) - xml.attrib['type'] = ntype - xml.text = msg - self.xml.append(xml) diff --git a/sleekxmpp/plugins/xep_0054/__init__.py b/sleekxmpp/plugins/xep_0054/__init__.py deleted file mode 100644 index d460cc8a..00000000 --- a/sleekxmpp/plugins/xep_0054/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0054.stanza import VCardTemp -from sleekxmpp.plugins.xep_0054.vcard_temp import XEP_0054 - - -register_plugin(XEP_0054) diff --git a/sleekxmpp/plugins/xep_0054/stanza.py b/sleekxmpp/plugins/xep_0054/stanza.py deleted file mode 100644 index 72da0b51..00000000 --- a/sleekxmpp/plugins/xep_0054/stanza.py +++ /dev/null @@ -1,561 +0,0 @@ -import base64 -import datetime as dt - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID -from sleekxmpp.plugins import xep_0082 - - -class VCardTemp(ElementBase): - name = 'vCard' - namespace = 'vcard-temp' - plugin_attrib = 'vcard_temp' - interfaces = set(['FN', 'VERSION']) - sub_interfaces = set(['FN', 'VERSION']) - - -class Name(ElementBase): - name = 'N' - namespace = 'vcard-temp' - plugin_attrib = name - interfaces = set(['FAMILY', 'GIVEN', 'MIDDLE', 'PREFIX', 'SUFFIX']) - sub_interfaces = interfaces - - def _set_component(self, name, value): - if isinstance(value, list): - value = ','.join(value) - if value is not None: - self._set_sub_text(name, value, keep=True) - else: - self._del_sub(name) - - def _get_component(self, name): - value = self._get_sub_text(name, '') - if ',' in value: - value = [v.strip() for v in value.split(',')] - return value - - def set_family(self, value): - self._set_component('FAMILY', value) - - def get_family(self): - return self._get_component('FAMILY') - - def set_given(self, value): - self._set_component('GIVEN', value) - - def get_given(self): - return self._get_component('GIVEN') - - def set_middle(self, value): - print(value) - self._set_component('MIDDLE', value) - - def get_middle(self): - return self._get_component('MIDDLE') - - def set_prefix(self, value): - self._set_component('PREFIX', value) - - def get_prefix(self): - return self._get_component('PREFIX') - - def set_suffix(self, value): - self._set_component('SUFFIX', value) - - def get_suffix(self): - return self._get_component('SUFFIX') - - -class Nickname(ElementBase): - name = 'NICKNAME' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'nicknames' - interfaces = set([name]) - is_extension = True - - def set_nickname(self, value): - if not value: - self.xml.text = '' - return - - if not isinstance(value, list): - value = [value] - - self.xml.text = ','.join(value) - - def get_nickname(self): - if self.xml.text: - return self.xml.text.split(',') - - -class Email(ElementBase): - name = 'EMAIL' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'emails' - interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID']) - sub_interfaces = set(['USERID']) - bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400']) - - -class Address(ElementBase): - name = 'ADR' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'addresses' - interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL', - 'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY', - 'REGION', 'PCODE', 'CTRY']) - sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY', - 'REGION', 'PCODE', 'CTRY']) - bool_interfaces = set(['HOME', 'WORK', 'DOM', 'INTL', 'PREF']) - - -class Telephone(ElementBase): - name = 'TEL' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'telephone_numbers' - interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', - 'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', - 'PREF', 'NUMBER']) - sub_interfaces = set(['NUMBER']) - bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', - 'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', - 'ISDN', 'PCS', 'PREF']) - - def setup(self, xml=None): - super(Telephone, self).setup(xml=xml) - self._set_sub_text('NUMBER', '', keep=True) - - def set_number(self, value): - self._set_sub_text('NUMBER', value, keep=True) - - def del_number(self): - self._set_sub_text('NUMBER', '', keep=True) - - -class Label(ElementBase): - name = 'LABEL' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'labels' - interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', - 'PREF', 'lines']) - bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', - 'INT', 'PREF']) - - def add_line(self, value): - line = ET.Element('{%s}LINE' % self.namespace) - line.text = value - self.xml.append(line) - - def get_lines(self): - lines = self.xml.find('{%s}LINE' % self.namespace) - if lines is None: - return [] - return [line.text for line in lines] - - def set_lines(self, values): - self.del_lines() - for line in values: - self.add_line(line) - - def del_lines(self): - lines = self.xml.find('{%s}LINE' % self.namespace) - if lines is None: - return - for line in lines: - self.xml.remove(line) - - -class Geo(ElementBase): - name = 'GEO' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'geolocations' - interfaces = set(['LAT', 'LON']) - sub_interfaces = interfaces - - -class Org(ElementBase): - name = 'ORG' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'organizations' - interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits']) - sub_interfaces = set(['ORGNAME', 'ORGUNIT']) - - def add_orgunit(self, value): - orgunit = ET.Element('{%s}ORGUNIT' % self.namespace) - orgunit.text = value - self.xml.append(orgunit) - - def get_orgunits(self): - orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace) - if orgunits is None: - return [] - return [orgunit.text for orgunit in orgunits] - - def set_orgunits(self, values): - self.del_orgunits() - for orgunit in values: - self.add_orgunit(orgunit) - - def del_orgunits(self): - orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace) - if orgunits is None: - return - for orgunit in orgunits: - self.xml.remove(orgunit) - - -class Photo(ElementBase): - name = 'PHOTO' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'photos' - interfaces = set(['TYPE', 'EXTVAL']) - sub_interfaces = interfaces - - -class Logo(ElementBase): - name = 'LOGO' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'logos' - interfaces = set(['TYPE', 'EXTVAL']) - sub_interfaces = interfaces - - -class Sound(ElementBase): - name = 'SOUND' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'sounds' - interfaces = set(['PHONETC', 'EXTVAL']) - sub_interfaces = interfaces - - -class BinVal(ElementBase): - name = 'BINVAL' - namespace = 'vcard-temp' - plugin_attrib = name - interfaces = set(['BINVAL']) - is_extension = True - - def setup(self, xml=None): - self.xml = ET.Element('') - return True - - def set_binval(self, value): - self.del_binval() - parent = self.parent() - if value: - xml = ET.Element('{%s}BINVAL' % self.namespace) - xml.text = bytes(base64.b64encode(value)).decode('utf-8') - parent.append(xml) - - def get_binval(self): - parent = self.parent() - xml = parent.find('{%s}BINVAL' % self.namespace) - if xml is not None: - return base64.b64decode(bytes(xml.text)) - return b'' - - def del_binval(self): - self.parent()._del_sub('{%s}BINVAL' % self.namespace) - - -class Classification(ElementBase): - name = 'CLASS' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'classifications' - interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL']) - bool_interfaces = interfaces - - -class Categories(ElementBase): - name = 'CATEGORIES' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'categories' - interfaces = set([name]) - is_extension = True - - def set_categories(self, values): - self.del_categories() - for keyword in values: - item = ET.Element('{%s}KEYWORD' % self.namespace) - item.text = keyword - self.xml.append(item) - - def get_categories(self): - items = self.xml.findall('{%s}KEYWORD' % self.namespace) - if items is None: - return [] - keywords = [] - for item in items: - keywords.append(item.text) - return keywords - - def del_categories(self): - items = self.xml.findall('{%s}KEYWORD' % self.namespace) - for item in items: - self.xml.remove(item) - - -class Birthday(ElementBase): - name = 'BDAY' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'birthdays' - interfaces = set([name]) - is_extension = True - - def set_bday(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self.xml.text = value - - def get_bday(self): - if not self.xml.text: - return None - return xep_0082.parse(self.xml.text) - - -class Rev(ElementBase): - name = 'REV' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'revision_dates' - interfaces = set([name]) - is_extension = True - - def set_rev(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self.xml.text = value - - def get_rev(self): - if not self.xml.text: - return None - return xep_0082.parse(self.xml.text) - - -class Title(ElementBase): - name = 'TITLE' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'titles' - interfaces = set([name]) - is_extension = True - - def set_title(self, value): - self.xml.text = value - - def get_title(self): - return self.xml.text - - -class Role(ElementBase): - name = 'ROLE' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'roles' - interfaces = set([name]) - is_extension = True - - def set_role(self, value): - self.xml.text = value - - def get_role(self): - return self.xml.text - - -class Note(ElementBase): - name = 'NOTE' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'notes' - interfaces = set([name]) - is_extension = True - - def set_note(self, value): - self.xml.text = value - - def get_note(self): - return self.xml.text - - -class Desc(ElementBase): - name = 'DESC' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'descriptions' - interfaces = set([name]) - is_extension = True - - def set_desc(self, value): - self.xml.text = value - - def get_desc(self): - return self.xml.text - - -class URL(ElementBase): - name = 'URL' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'urls' - interfaces = set([name]) - is_extension = True - - def set_url(self, value): - self.xml.text = value - - def get_url(self): - return self.xml.text - - -class UID(ElementBase): - name = 'UID' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'uids' - interfaces = set([name]) - is_extension = True - - def set_uid(self, value): - self.xml.text = value - - def get_uid(self): - return self.xml.text - - -class ProdID(ElementBase): - name = 'PRODID' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'product_ids' - interfaces = set([name]) - is_extension = True - - def set_prodid(self, value): - self.xml.text = value - - def get_prodid(self): - return self.xml.text - - -class Mailer(ElementBase): - name = 'MAILER' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'mailers' - interfaces = set([name]) - is_extension = True - - def set_mailer(self, value): - self.xml.text = value - - def get_mailer(self): - return self.xml.text - - -class SortString(ElementBase): - name = 'SORT-STRING' - namespace = 'vcard-temp' - plugin_attrib = 'SORT_STRING' - plugin_multi_attrib = 'sort_strings' - interfaces = set([name]) - is_extension = True - - def set_sort_string(self, value): - self.xml.text = value - - def get_sort_string(self): - return self.xml.text - - -class Agent(ElementBase): - name = 'AGENT' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'agents' - interfaces = set(['EXTVAL']) - sub_interfaces = interfaces - - -class JabberID(ElementBase): - name = 'JABBERID' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'jids' - interfaces = set([name]) - is_extension = True - - def set_jabberid(self, value): - self.xml.text = JID(value).bare - - def get_jabberid(self): - return JID(self.xml.text) - - -class TimeZone(ElementBase): - name = 'TZ' - namespace = 'vcard-temp' - plugin_attrib = name - plugin_multi_attrib = 'timezones' - interfaces = set([name]) - is_extension = True - - def set_tz(self, value): - time = xep_0082.time(offset=value) - if time[-1] == 'Z': - self.xml.text = 'Z' - else: - self.xml.text = time[-6:] - - def get_tz(self): - if not self.xml.text: - return xep_0082.tzutc() - time = xep_0082.parse('00:00:00%s' % self.xml.text) - return time.tzinfo - - -register_stanza_plugin(VCardTemp, Name) -register_stanza_plugin(VCardTemp, Address, iterable=True) -register_stanza_plugin(VCardTemp, Agent, iterable=True) -register_stanza_plugin(VCardTemp, Birthday, iterable=True) -register_stanza_plugin(VCardTemp, Categories, iterable=True) -register_stanza_plugin(VCardTemp, Desc, iterable=True) -register_stanza_plugin(VCardTemp, Email, iterable=True) -register_stanza_plugin(VCardTemp, Geo, iterable=True) -register_stanza_plugin(VCardTemp, JabberID, iterable=True) -register_stanza_plugin(VCardTemp, Label, iterable=True) -register_stanza_plugin(VCardTemp, Logo, iterable=True) -register_stanza_plugin(VCardTemp, Mailer, iterable=True) -register_stanza_plugin(VCardTemp, Note, iterable=True) -register_stanza_plugin(VCardTemp, Nickname, iterable=True) -register_stanza_plugin(VCardTemp, Org, iterable=True) -register_stanza_plugin(VCardTemp, Photo, iterable=True) -register_stanza_plugin(VCardTemp, ProdID, iterable=True) -register_stanza_plugin(VCardTemp, Rev, iterable=True) -register_stanza_plugin(VCardTemp, Role, iterable=True) -register_stanza_plugin(VCardTemp, SortString, iterable=True) -register_stanza_plugin(VCardTemp, Sound, iterable=True) -register_stanza_plugin(VCardTemp, Telephone, iterable=True) -register_stanza_plugin(VCardTemp, Title, iterable=True) -register_stanza_plugin(VCardTemp, TimeZone, iterable=True) -register_stanza_plugin(VCardTemp, UID, iterable=True) -register_stanza_plugin(VCardTemp, URL, iterable=True) - -register_stanza_plugin(Photo, BinVal) -register_stanza_plugin(Logo, BinVal) -register_stanza_plugin(Sound, BinVal) - -register_stanza_plugin(Agent, VCardTemp) diff --git a/sleekxmpp/plugins/xep_0054/vcard_temp.py b/sleekxmpp/plugins/xep_0054/vcard_temp.py deleted file mode 100644 index 97da8c7c..00000000 --- a/sleekxmpp/plugins/xep_0054/vcard_temp.py +++ /dev/null @@ -1,146 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import JID, Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0054 import VCardTemp, stanza - - -log = logging.getLogger(__name__) - - -class XEP_0054(BasePlugin): - - """ - XEP-0054: vcard-temp - """ - - name = 'xep_0054' - description = 'XEP-0054: vcard-temp' - dependencies = set(['xep_0030', 'xep_0082']) - stanza = stanza - - def plugin_init(self): - """ - Start the XEP-0054 plugin. - """ - register_stanza_plugin(Iq, VCardTemp) - - - self.api.register(self._set_vcard, 'set_vcard', default=True) - self.api.register(self._get_vcard, 'get_vcard', default=True) - self.api.register(self._del_vcard, 'del_vcard', default=True) - - self._vcard_cache = {} - - self.xmpp.register_handler( - Callback('VCardTemp', - StanzaPath('iq/vcard_temp'), - self._handle_get_vcard)) - - def plugin_end(self): - self.xmpp.remove_handler('VCardTemp') - self.xmpp['xep_0030'].del_feature(feature='vcard-temp') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('vcard-temp') - - def make_vcard(self): - return VCardTemp() - - def get_vcard(self, jid=None, ifrom=None, local=None, cached=False, - block=True, callback=None, timeout=None): - if local is None: - if jid is not None and not isinstance(jid, JID): - jid = JID(jid) - if self.xmpp.is_component: - if jid.domain == self.xmpp.boundjid.domain: - local = True - else: - if str(jid) == str(self.xmpp.boundjid): - local = True - jid = jid.full - elif jid in (None, ''): - local = True - - if local: - vcard = self.api['get_vcard'](jid, None, ifrom) - if not isinstance(vcard, Iq): - iq = self.xmpp.Iq() - if vcard is None: - vcard = VCardTemp() - iq.append(vcard) - return iq - return vcard - - if cached: - vcard = self.api['get_vcard'](jid, None, ifrom) - if vcard is not None: - if not isinstance(vcard, Iq): - iq = self.xmpp.Iq() - iq.append(vcard) - return iq - return vcard - - iq = self.xmpp.Iq() - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'get' - iq.enable('vcard_temp') - - vcard = iq.send(block=block, callback=callback, timeout=timeout) - - if block: - self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp']) - return vcard - - def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None, - callback=None, timeout=None): - self.api['set_vcard'](jid, None, ifrom, vcard) - if self.xmpp.is_component: - return - - iq = self.xmpp.Iq() - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'set' - iq.append(vcard) - return iq.send(block=block, callback=callback, timeout=timeout) - - def _handle_get_vcard(self, iq): - if iq['type'] == 'result': - self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) - return - elif iq['type'] == 'get': - vcard = self.api['get_vcard'](iq['from'].bare) - if isinstance(vcard, Iq): - vcard.send() - else: - iq.reply() - iq.append(vcard) - iq.send() - elif iq['type'] == 'set': - raise XMPPError('service-unavailable') - - # ================================================================= - - def _set_vcard(self, jid, node, ifrom, vcard): - self._vcard_cache[jid.bare] = vcard - - def _get_vcard(self, jid, node, ifrom, vcard): - return self._vcard_cache.get(jid.bare, None) - - def _del_vcard(self, jid, node, ifrom, vcard): - if jid.bare in self._vcard_cache: - del self._vcard_cache[jid.bare] diff --git a/sleekxmpp/plugins/xep_0059/__init__.py b/sleekxmpp/plugins/xep_0059/__init__.py deleted file mode 100644 index 3464ce32..00000000 --- a/sleekxmpp/plugins/xep_0059/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0059.stanza import Set -from sleekxmpp.plugins.xep_0059.rsm import ResultIterator, XEP_0059 - - -register_plugin(XEP_0059) - -# Retain some backwards compatibility -xep_0059 = XEP_0059 diff --git a/sleekxmpp/plugins/xep_0059/rsm.py b/sleekxmpp/plugins/xep_0059/rsm.py deleted file mode 100644 index d73b45bc..00000000 --- a/sleekxmpp/plugins/xep_0059/rsm.py +++ /dev/null @@ -1,145 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -import sleekxmpp -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin, register_plugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0059 import stanza, Set -from sleekxmpp.exceptions import XMPPError - - -log = logging.getLogger(__name__) - - -class ResultIterator(): - - """ - An iterator for Result Set Managment - """ - - def __init__(self, query, interface, results='substanzas', amount=10, - start=None, reverse=False): - """ - Arguments: - query -- The template query - interface -- The substanza of the query, for example disco_items - results -- The query stanza's interface which provides a - countable list of query results. - amount -- The max amounts of items to request per iteration - start -- From which item id to start - reverse -- If True, page backwards through the results - - Example: - q = Iq() - q['to'] = 'pubsub.example.com' - q['disco_items']['node'] = 'blog' - for i in ResultIterator(q, 'disco_items', '10'): - print i['disco_items']['items'] - - """ - self.query = query - self.amount = amount - self.start = start - self.interface = interface - self.results = results - self.reverse = reverse - self._stop = False - - def __iter__(self): - return self - - def __next__(self): - return self.next() - - def next(self): - """ - Return the next page of results from a query. - - Note: If using backwards paging, then the next page of - results will be the items before the current page - of items. - """ - if self._stop: - raise StopIteration - self.query[self.interface]['rsm']['before'] = self.reverse - self.query['id'] = self.query.stream.new_id() - self.query[self.interface]['rsm']['max'] = str(self.amount) - - if self.start and self.reverse: - self.query[self.interface]['rsm']['before'] = self.start - elif self.start: - self.query[self.interface]['rsm']['after'] = self.start - - try: - r = self.query.send(block=True) - - if not r[self.interface]['rsm']['first'] and \ - not r[self.interface]['rsm']['last']: - raise StopIteration - - if r[self.interface]['rsm']['count'] and \ - r[self.interface]['rsm']['first_index']: - count = int(r[self.interface]['rsm']['count']) - first = int(r[self.interface]['rsm']['first_index']) - num_items = len(r[self.interface][self.results]) - if first + num_items == count: - self._stop = True - - if self.reverse: - self.start = r[self.interface]['rsm']['first'] - else: - self.start = r[self.interface]['rsm']['last'] - - return r - except XMPPError: - raise StopIteration - - -class XEP_0059(BasePlugin): - - """ - XEP-0050: Result Set Management - """ - - name = 'xep_0059' - description = 'XEP-0059: Result Set Management' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - """ - Start the XEP-0059 plugin. - """ - register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems, - self.stanza.Set) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Set.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Set.namespace) - - def iterate(self, stanza, interface, results='substanzas'): - """ - Create a new result set iterator for a given stanza query. - - Arguments: - stanza -- A stanza object to serve as a template for - queries made each iteration. For example, a - basic disco#items query. - interface -- The name of the substanza to which the - result set management stanza should be - appended. For example, for disco#items queries - the interface 'disco_items' should be used. - results -- The name of the interface containing the - query results (typically just 'substanzas'). - """ - return ResultIterator(stanza, interface, results) diff --git a/sleekxmpp/plugins/xep_0059/stanza.py b/sleekxmpp/plugins/xep_0059/stanza.py deleted file mode 100644 index 48f5c8a0..00000000 --- a/sleekxmpp/plugins/xep_0059/stanza.py +++ /dev/null @@ -1,108 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET -from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems - - -class Set(ElementBase): - - """ - XEP-0059 (Result Set Managment) can be used to manage the - results of queries. For example, limiting the number of items - per response or starting at certain positions. - - Example set stanzas: - <iq type="get"> - <query xmlns="http://jabber.org/protocol/disco#items"> - <set xmlns="http://jabber.org/protocol/rsm"> - <max>2</max> - </set> - </query> - </iq> - - <iq type="result"> - <query xmlns="http://jabber.org/protocol/disco#items"> - <item jid="conference.example.com" /> - <item jid="pubsub.example.com" /> - <set xmlns="http://jabber.org/protocol/rsm"> - <first>conference.example.com</first> - <last>pubsub.example.com</last> - </set> - </query> - </iq> - - Stanza Interface: - first_index -- The index attribute of <first> - after -- The id defining from which item to start - before -- The id defining from which item to - start when browsing backwards - max -- Max amount per response - first -- Id for the first item in the response - last -- Id for the last item in the response - index -- Used to set an index to start from - count -- The number of remote items available - - Methods: - set_first_index -- Sets the index attribute for <first> and - creates the element if it doesn't exist - get_first_index -- Returns the value of the index - attribute for <first> - del_first_index -- Removes the index attribute for <first> - but keeps the element - set_before -- Sets the value of <before>, if the value is True - then the element will be created without a value - get_before -- Returns the value of <before>, if it is - empty it will return True - - """ - namespace = 'http://jabber.org/protocol/rsm' - name = 'set' - plugin_attrib = 'rsm' - sub_interfaces = set(('first', 'after', 'before', 'count', - 'index', 'last', 'max')) - interfaces = set(('first_index', 'first', 'after', 'before', - 'count', 'index', 'last', 'max')) - - def set_first_index(self, val): - fi = self.find("{%s}first" % (self.namespace)) - if fi is not None: - if val: - fi.attrib['index'] = val - elif 'index' in fi.attrib: - del fi.attrib['index'] - elif val: - fi = ET.Element("{%s}first" % (self.namespace)) - fi.attrib['index'] = val - self.xml.append(fi) - - def get_first_index(self): - fi = self.find("{%s}first" % (self.namespace)) - if fi is not None: - return fi.attrib.get('index', '') - - def del_first_index(self): - fi = self.xml.find("{%s}first" % (self.namespace)) - if fi is not None: - del fi.attrib['index'] - - def set_before(self, val): - b = self.xml.find("{%s}before" % (self.namespace)) - if b is None and val is True: - self._set_sub_text('{%s}before' % self.namespace, '', True) - else: - self._set_sub_text('{%s}before' % self.namespace, val) - - def get_before(self): - b = self.xml.find("{%s}before" % (self.namespace)) - if b is not None and not b.text: - return True - elif b is not None: - return b.text - else: - return None diff --git a/sleekxmpp/plugins/xep_0060/__init__.py b/sleekxmpp/plugins/xep_0060/__init__.py deleted file mode 100644 index 86e2f472..00000000 --- a/sleekxmpp/plugins/xep_0060/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0060.pubsub import XEP_0060 -from sleekxmpp.plugins.xep_0060 import stanza - - -register_plugin(XEP_0060) - - -# Retain some backwards compatibility -xep_0060 = XEP_0060 diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py deleted file mode 100644 index bec5f565..00000000 --- a/sleekxmpp/plugins/xep_0060/pubsub.py +++ /dev/null @@ -1,577 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0060 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0060(BasePlugin): - - """ - XEP-0060 Publish Subscribe - """ - - name = 'xep_0060' - description = 'XEP-0060: Publish-Subscribe' - dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131']) - stanza = stanza - - def plugin_init(self): - self.node_event_map = {} - - self.xmpp.register_handler( - Callback('Pubsub Event: Items', - StanzaPath('message/pubsub_event/items'), - self._handle_event_items)) - self.xmpp.register_handler( - Callback('Pubsub Event: Purge', - StanzaPath('message/pubsub_event/purge'), - self._handle_event_purge)) - self.xmpp.register_handler( - Callback('Pubsub Event: Delete', - StanzaPath('message/pubsub_event/delete'), - self._handle_event_delete)) - self.xmpp.register_handler( - Callback('Pubsub Event: Configuration', - StanzaPath('message/pubsub_event/configuration'), - self._handle_event_configuration)) - self.xmpp.register_handler( - Callback('Pubsub Event: Subscription', - StanzaPath('message/pubsub_event/subscription'), - self._handle_event_subscription)) - - self.xmpp['xep_0131'].supported_headers.add('SubID') - - def plugin_end(self): - self.xmpp.remove_handler('Pubsub Event: Items') - self.xmpp.remove_handler('Pubsub Event: Purge') - self.xmpp.remove_handler('Pubsub Event: Delete') - self.xmpp.remove_handler('Pubsub Event: Configuration') - self.xmpp.remove_handler('Pubsub Event: Subscription') - - def _handle_event_items(self, msg): - """Raise events for publish and retraction notifications.""" - node = msg['pubsub_event']['items']['node'] - - multi = len(msg['pubsub_event']['items']) > 1 - values = {} - if multi: - values = msg.values - del values['pubsub_event'] - - for item in msg['pubsub_event']['items']: - event_name = self.node_event_map.get(node, None) - event_type = 'publish' - if item.name == 'retract': - event_type = 'retract' - - if multi: - condensed = self.xmpp.Message() - condensed.values = values - condensed['pubsub_event']['items']['node'] = node - condensed['pubsub_event']['items'].append(item) - self.xmpp.event('pubsub_%s' % event_type, msg) - if event_name: - self.xmpp.event('%s_%s' % (event_name, event_type), - condensed) - else: - self.xmpp.event('pubsub_%s' % event_type, msg) - if event_name: - self.xmpp.event('%s_%s' % (event_name, event_type), msg) - - def _handle_event_purge(self, msg): - """Raise events for node purge notifications.""" - node = msg['pubsub_event']['purge']['node'] - event_name = self.node_event_map.get(node, None) - - self.xmpp.event('pubsub_purge', msg) - if event_name: - self.xmpp.event('%s_purge' % event_name, msg) - - def _handle_event_delete(self, msg): - """Raise events for node deletion notifications.""" - node = msg['pubsub_event']['delete']['node'] - event_name = self.node_event_map.get(node, None) - - self.xmpp.event('pubsub_delete', msg) - if event_name: - self.xmpp.event('%s_delete' % event_name, msg) - - def _handle_event_configuration(self, msg): - """Raise events for node configuration notifications.""" - node = msg['pubsub_event']['configuration']['node'] - event_name = self.node_event_map.get(node, None) - - self.xmpp.event('pubsub_config', msg) - if event_name: - self.xmpp.event('%s_config' % event_name, msg) - - def _handle_event_subscription(self, msg): - """Raise events for node subscription notifications.""" - node = msg['pubsub_event']['subscription']['node'] - event_name = self.node_event_map.get(node, None) - - self.xmpp.event('pubsub_subscription', msg) - if event_name: - self.xmpp.event('%s_subscription' % event_name, msg) - - def map_node_event(self, node, event_name): - """ - Map node names to events. - - When a pubsub event is received for the given node, - raise the provided event. - - For example:: - - map_node_event('http://jabber.org/protocol/tune', - 'user_tune') - - will produce the events 'user_tune_publish' and 'user_tune_retract' - when the respective notifications are received from the node - 'http://jabber.org/protocol/tune', among other events. - - Arguments: - node -- The node name to map to an event. - event_name -- The name of the event to raise when a - notification from the given node is received. - """ - self.node_event_map[node] = event_name - - def create_node(self, jid, node, config=None, ntype=None, ifrom=None, - block=True, callback=None, timeout=None): - """ - Create and configure a new pubsub node. - - A server MAY use a different name for the node than the one provided, - so be sure to check the result stanza for a server assigned name. - - If no configuration form is provided, the node will be created using - the server's default configuration. To get the default configuration - use get_node_config(). - - Arguments: - jid -- The JID of the pubsub service. - node -- Optional name of the node to create. If no name is - provided, the server MAY generate a node ID for you. - The server can also assign a different name than the - one you provide; check the result stanza to see if - the server assigned a name. - config -- Optional XEP-0004 data form of configuration settings. - ntype -- The type of node to create. Servers typically default - to using 'leaf' if no type is provided. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub']['create']['node'] = node - - if config is not None: - form_type = 'http://jabber.org/protocol/pubsub#node_config' - if 'FORM_TYPE' in config['fields']: - config.field['FORM_TYPE']['value'] = form_type - else: - config.add_field(var='FORM_TYPE', - ftype='hidden', - value=form_type) - if ntype: - if 'pubsub#node_type' in config['fields']: - config.field['pubsub#node_type']['value'] = ntype - else: - config.add_field(var='pubsub#node_type', value=ntype) - iq['pubsub']['configure'].append(config) - - return iq.send(block=block, callback=callback, timeout=timeout) - - def subscribe(self, jid, node, bare=True, subscribee=None, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Subscribe to updates from a pubsub node. - - The rules for determining the JID that is subscribing to the node are: - 1. If subscribee is given, use that as provided. - 2. If ifrom was given, use the bare or full version based on bare. - 3. Otherwise, use self.xmpp.boundjid based on bare. - - Arguments: - jid -- The pubsub service JID. - node -- The node to subscribe to. - bare -- Indicates if the subscribee is a bare or full JID. - Defaults to True for a bare JID. - subscribee -- The JID that is subscribing to the node. - options -- - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub']['subscribe']['node'] = node - - if subscribee is None: - if ifrom: - if bare: - subscribee = JID(ifrom).bare - else: - subscribee = ifrom - else: - if bare: - subscribee = self.xmpp.boundjid.bare - else: - subscribee = self.xmpp.boundjid - - iq['pubsub']['subscribe']['jid'] = subscribee - if options is not None: - iq['pubsub']['options'].append(options) - return iq.send(block=block, callback=callback, timeout=timeout) - - def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Unubscribe from updates from a pubsub node. - - The rules for determining the JID that is unsubscribing - from the node are: - 1. If subscribee is given, use that as provided. - 2. If ifrom was given, use the bare or full version based on bare. - 3. Otherwise, use self.xmpp.boundjid based on bare. - - Arguments: - jid -- The pubsub service JID. - node -- The node to subscribe to. - subid -- The specific subscription, if multiple subscriptions - exist for this JID/node combination. - bare -- Indicates if the subscribee is a bare or full JID. - Defaults to True for a bare JID. - subscribee -- The JID that is subscribing to the node. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub']['unsubscribe']['node'] = node - - if subscribee is None: - if ifrom: - if bare: - subscribee = JID(ifrom).bare - else: - subscribee = ifrom - else: - if bare: - subscribee = self.xmpp.boundjid.bare - else: - subscribee = self.xmpp.boundjid - - iq['pubsub']['unsubscribe']['jid'] = subscribee - iq['pubsub']['unsubscribe']['subid'] = subid - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_subscriptions(self, jid, node=None, ifrom=None, block=True, - callback=None, timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub']['subscriptions']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_affiliations(self, jid, node=None, ifrom=None, block=True, - callback=None, timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub']['affiliations']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_subscription_options(self, jid, node=None, user_jid=None, - ifrom=None, block=True, callback=None, - timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - if user_jid is None: - iq['pubsub']['default']['node'] = node - else: - iq['pubsub']['options']['node'] = node - iq['pubsub']['options']['jid'] = user_jid - return iq.send(block=block, callback=callback, timeout=timeout) - - def set_subscription_options(self, jid, node, user_jid, options, - ifrom=None, block=True, callback=None, - timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub']['options']['node'] = node - iq['pubsub']['options']['jid'] = user_jid - iq['pubsub']['options'].append(options) - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_node_config(self, jid, node=None, ifrom=None, block=True, - callback=None, timeout=None): - """ - Retrieve the configuration for a node, or the pubsub service's - default configuration for new nodes. - - Arguments: - jid -- The JID of the pubsub service. - node -- The node to retrieve the configuration for. If None, - the default configuration for new nodes will be - requested. Defaults to None. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - if node is None: - iq['pubsub_owner']['default'] - else: - iq['pubsub_owner']['configure']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_node_subscriptions(self, jid, node, ifrom=None, block=True, - callback=None, timeout=None): - """ - Retrieve the subscriptions associated with a given node. - - Arguments: - jid -- The JID of the pubsub service. - node -- The node to retrieve subscriptions from. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub_owner']['subscriptions']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_node_affiliations(self, jid, node, ifrom=None, block=True, - callback=None, timeout=None): - """ - Retrieve the affiliations associated with a given node. - - Arguments: - jid -- The JID of the pubsub service. - node -- The node to retrieve affiliations from. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub_owner']['affiliations']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def delete_node(self, jid, node, ifrom=None, block=True, - callback=None, timeout=None): - """ - Delete a a pubsub node. - - Arguments: - jid -- The JID of the pubsub service. - node -- The node to delete. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub_owner']['delete']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def set_node_config(self, jid, node, config, ifrom=None, block=True, - callback=None, timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub_owner']['configure']['node'] = node - iq['pubsub_owner']['configure'].append(config) - return iq.send(block=block, callback=callback, timeout=timeout) - - def publish(self, jid, node, id=None, payload=None, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Add a new item to a node, or edit an existing item. - - For services that support it, you can use the publish command - as an event signal by not including an ID or payload. - - When including a payload and you do not provide an ID then - the service will generally create an ID for you. - - Publish options may be specified, and how those options - are processed is left to the service, such as treating - the options as preconditions that the node's settings - must match. - - Arguments: - jid -- The JID of the pubsub service. - node -- The node to publish the item to. - id -- Optionally specify the ID of the item. - payload -- The item content to publish. - options -- A form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub']['publish']['node'] = node - if id is not None: - iq['pubsub']['publish']['item']['id'] = id - if payload is not None: - iq['pubsub']['publish']['item']['payload'] = payload - iq['pubsub']['publish_options'] = options - return iq.send(block=block, callback=callback, timeout=timeout) - - def retract(self, jid, node, id, notify=None, ifrom=None, block=True, - callback=None, timeout=None): - """ - Delete a single item from a node. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - - iq['pubsub']['retract']['node'] = node - iq['pubsub']['retract']['notify'] = notify - iq['pubsub']['retract']['item']['id'] = id - return iq.send(block=block, callback=callback, timeout=timeout) - - def purge(self, jid, node, ifrom=None, block=True, callback=None, - timeout=None): - """ - Remove all items from a node. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub_owner']['purge']['node'] = node - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_nodes(self, *args, **kwargs): - """ - Discover the nodes provided by a Pubsub service, using disco. - """ - return self.xmpp['xep_0030'].get_items(*args, **kwargs) - - def get_item(self, jid, node, item_id, ifrom=None, block=True, - callback=None, timeout=None): - """ - Retrieve the content of an individual item. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - item = stanza.Item() - item['id'] = item_id - iq['pubsub']['items']['node'] = node - iq['pubsub']['items'].append(item) - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_items(self, jid, node, item_ids=None, max_items=None, - iterator=False, ifrom=None, block=False, - callback=None, timeout=None): - """ - Request the contents of a node's items. - - The desired items can be specified, or a query for the last - few published items can be used. - - Pubsub services may use result set management for nodes with - many items, so an iterator can be returned if needed. - """ - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') - iq['pubsub']['items']['node'] = node - iq['pubsub']['items']['max_items'] = max_items - - if item_ids is not None: - for item_id in item_ids: - item = stanza.Item() - item['id'] = item_id - iq['pubsub']['items'].append(item) - - if iterator: - return self.xmpp['xep_0059'].iterate(iq, 'pubsub') - else: - return iq.send(block=block, callback=callback, timeout=timeout) - - def get_item_ids(self, jid, node, ifrom=None, block=True, - callback=None, timeout=None, iterator=False): - """ - Retrieve the ItemIDs hosted by a given node, using disco. - """ - return self.xmpp['xep_0030'].get_items(jid, node, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout, - iterator=iterator) - - def modify_affiliations(self, jid, node, affiliations=None, ifrom=None, - block=True, callback=None, timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub_owner']['affiliations']['node'] = node - - if affiliations is None: - affiliations = [] - - for jid, affiliation in affiliations: - aff = stanza.OwnerAffiliation() - aff['jid'] = jid - aff['affiliation'] = affiliation - iq['pubsub_owner']['affiliations'].append(aff) - - return iq.send(block=block, callback=callback, timeout=timeout) - - def modify_subscriptions(self, jid, node, subscriptions=None, ifrom=None, - block=True, callback=None, timeout=None): - iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') - iq['pubsub_owner']['subscriptions']['node'] = node - - if subscriptions is None: - subscriptions = [] - - for jid, subscription in subscriptions: - sub = stanza.OwnerSubscription() - sub['jid'] = jid - sub['subscription'] = subscription - iq['pubsub_owner']['subscriptions'].append(sub) - - return iq.send(block=block, callback=callback, timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0060/stanza/__init__.py b/sleekxmpp/plugins/xep_0060/stanza/__init__.py deleted file mode 100644 index 37f52f0e..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.xep_0060.stanza.pubsub import * -from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import * -from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import * -from sleekxmpp.plugins.xep_0060.stanza.pubsub_errors import * diff --git a/sleekxmpp/plugins/xep_0060/stanza/base.py b/sleekxmpp/plugins/xep_0060/stanza/base.py deleted file mode 100644 index d0b7851e..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/base.py +++ /dev/null @@ -1,29 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET - - -class OptionalSetting(object): - - interfaces = set(('required',)) - - def set_required(self, value): - if value in (True, 'true', 'True', '1'): - self.xml.append(ET.Element("{%s}required" % self.namespace)) - elif self['required']: - self.del_required() - - def get_required(self): - required = self.xml.find("{%s}required" % self.namespace) - return required is not None - - def del_required(self): - required = self.xml.find("{%s}required" % self.namespace) - if required is not None: - self.xml.remove(required) diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py deleted file mode 100644 index c1907a13..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py +++ /dev/null @@ -1,272 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Iq, Message -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID -from sleekxmpp.plugins import xep_0004 -from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting - - -class Pubsub(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'pubsub' - plugin_attrib = name - interfaces = set(tuple()) - - -class Affiliations(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliations' - plugin_attrib = name - interfaces = set(('node',)) - - -class Affiliation(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliation' - plugin_attrib = name - interfaces = set(('node', 'affiliation', 'jid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class Subscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'node', 'subscription', 'subid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class Subscriptions(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscriptions' - plugin_attrib = name - interfaces = set(('node',)) - - -class SubscribeOptions(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe-options' - plugin_attrib = 'suboptions' - interfaces = set(('required',)) - - -class Item(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'item' - plugin_attrib = name - interfaces = set(('id', 'payload')) - - def set_payload(self, value): - del self['payload'] - if isinstance(value, ElementBase): - if value.tag_name() in self.plugin_tag_map: - self.init_plugin(value.plugin_attrib, existing_xml=value.xml) - self.xml.append(value.xml) - else: - self.xml.append(value) - - def get_payload(self): - childs = list(self.xml) - if len(childs) > 0: - return childs[0] - - def del_payload(self): - for child in self.xml: - self.xml.remove(child) - - -class Items(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'items' - plugin_attrib = name - interfaces = set(('node', 'max_items')) - - def set_max_items(self, value): - self._set_attr('max_items', str(value)) - - -class Create(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'create' - plugin_attrib = name - interfaces = set(('node',)) - - -class Default(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'default' - plugin_attrib = name - interfaces = set(('node', 'type')) - - def get_type(self): - t = self._get_attr('type') - if not t: - return 'leaf' - return t - - -class Publish(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'publish' - plugin_attrib = name - interfaces = set(('node',)) - - -class Retract(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'retract' - plugin_attrib = name - interfaces = set(('node', 'notify')) - - def get_notify(self): - notify = self._get_attr('notify') - if notify in ('0', 'false'): - return False - elif notify in ('1', 'true'): - return True - return None - - def set_notify(self, value): - del self['notify'] - if value is None: - return - elif value in (True, '1', 'true', 'True'): - self._set_attr('notify', 'true') - else: - self._set_attr('notify', 'false') - - -class Unsubscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'unsubscribe' - plugin_attrib = name - interfaces = set(('node', 'jid', 'subid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class Subscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe' - plugin_attrib = name - interfaces = set(('node', 'jid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class Configure(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'configure' - plugin_attrib = name - interfaces = set(('node', 'type')) - - def getType(self): - t = self._get_attr('type') - if not t: - t == 'leaf' - return t - - -class Options(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'options' - plugin_attrib = name - interfaces = set(('jid', 'node', 'options')) - - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) - - def get_options(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form(xml=config) - return form - - def set_options(self, value): - self.xml.append(value.getXML()) - return self - - def del_options(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class PublishOptions(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'publish-options' - plugin_attrib = 'publish_options' - interfaces = set(('publish_options',)) - is_extension = True - - def get_publish_options(self): - config = self.xml.find('{jabber:x:data}x') - if config is None: - return None - form = xep_0004.Form(xml=config) - return form - - def set_publish_options(self, value): - if value is None: - self.del_publish_options() - else: - self.xml.append(value.getXML()) - return self - - def del_publish_options(self): - config = self.xml.find('{jabber:x:data}x') - if config is not None: - self.xml.remove(config) - self.parent().xml.remove(self.xml) - - -register_stanza_plugin(Iq, Pubsub) -register_stanza_plugin(Pubsub, Affiliations) -register_stanza_plugin(Pubsub, Configure) -register_stanza_plugin(Pubsub, Create) -register_stanza_plugin(Pubsub, Default) -register_stanza_plugin(Pubsub, Items) -register_stanza_plugin(Pubsub, Options) -register_stanza_plugin(Pubsub, Publish) -register_stanza_plugin(Pubsub, PublishOptions) -register_stanza_plugin(Pubsub, Retract) -register_stanza_plugin(Pubsub, Subscribe) -register_stanza_plugin(Pubsub, Subscription) -register_stanza_plugin(Pubsub, Subscriptions) -register_stanza_plugin(Pubsub, Unsubscribe) -register_stanza_plugin(Affiliations, Affiliation, iterable=True) -register_stanza_plugin(Configure, xep_0004.Form) -register_stanza_plugin(Items, Item, iterable=True) -register_stanza_plugin(Publish, Item, iterable=True) -register_stanza_plugin(Retract, Item) -register_stanza_plugin(Subscribe, Options) -register_stanza_plugin(Subscription, SubscribeOptions) -register_stanza_plugin(Subscriptions, Subscription, iterable=True) diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py deleted file mode 100644 index 59cf1a50..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py +++ /dev/null @@ -1,86 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class PubsubErrorCondition(ElementBase): - - plugin_attrib = 'pubsub' - interfaces = set(('condition', 'unsupported')) - plugin_attrib_map = {} - plugin_tag_map = {} - conditions = set(('closed-node', 'configuration-required', 'invalid-jid', - 'invalid-options', 'invalid-payload', 'invalid-subid', - 'item-forbidden', 'item-required', 'jid-required', - 'max-items-exceeded', 'max-nodes-exceeded', - 'nodeid-required', 'not-in-roster-group', - 'not-subscribed', 'payload-too-big', - 'payload-required', 'pending-subscription', - 'presence-subscription-required', 'subid-required', - 'too-many-subscriptions', 'unsupported')) - condition_ns = 'http://jabber.org/protocol/pubsub#errors' - - def setup(self, xml): - """Don't create XML for the plugin.""" - self.xml = ET.Element('') - - def get_condition(self): - """Return the condition element's name.""" - for child in self.parent().xml: - if "{%s}" % self.condition_ns in child.tag: - cond = child.tag.split('}', 1)[-1] - if cond in self.conditions: - return cond - 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'] - cond = ET.Element("{%s}%s" % (self.condition_ns, value)) - self.parent().xml.append(cond) - return self - - def del_condition(self): - """Remove the condition element.""" - for child in self.parent().xml: - if "{%s}" % self.condition_ns in child.tag: - tag = child.tag.split('}', 1)[-1] - if tag in self.conditions: - self.parent().xml.remove(child) - return self - - def get_unsupported(self): - """Return the name of an unsupported feature""" - xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns) - if xml is not None: - return xml.attrib.get('feature', '') - return '' - - def set_unsupported(self, value): - """Mark a feature as unsupported""" - self.del_unsupported() - xml = ET.Element('{%s}unsupported' % self.condition_ns) - xml.attrib['feature'] = value - self.parent().xml.append(xml) - - def del_unsupported(self): - """Delete an unsupported feature condition.""" - xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns) - if xml is not None: - self.parent().xml.remove(xml) - - -register_stanza_plugin(Error, PubsubErrorCondition) diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py deleted file mode 100644 index 32f217fa..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_event.py +++ /dev/null @@ -1,151 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp import Message -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID -from sleekxmpp.plugins.xep_0004 import Form -from sleekxmpp.plugins import xep_0082 - - -class Event(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'event' - plugin_attrib = 'pubsub_event' - interfaces = set() - - -class EventItem(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'item' - plugin_attrib = name - interfaces = set(('id', 'payload', 'node', 'publisher')) - - def set_payload(self, value): - self.xml.append(value) - - def get_payload(self): - childs = list(self.xml) - if len(childs) > 0: - return childs[0] - - def del_payload(self): - for child in self.xml: - self.xml.remove(child) - - -class EventRetract(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'retract' - plugin_attrib = name - interfaces = set(('id',)) - - -class EventItems(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'items' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventCollection(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'collection' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventAssociate(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'associate' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventDisassociate(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'disassociate' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventConfiguration(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'configuration' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventPurge(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'purge' - plugin_attrib = name - interfaces = set(('node',)) - - -class EventDelete(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'delete' - plugin_attrib = name - interfaces = set(('node', 'redirect')) - - def set_redirect(self, uri): - del self['redirect'] - redirect = ET.Element('{%s}redirect' % self.namespace) - redirect.attrib['uri'] = uri - self.xml.append(redirect) - - def get_redirect(self): - redirect = self.xml.find('{%s}redirect' % self.namespace) - if redirect is not None: - return redirect.attrib.get('uri', '') - return '' - - def del_redirect(self): - redirect = self.xml.find('{%s}redirect' % self.namespace) - if redirect is not None: - self.xml.remove(redirect) - - -class EventSubscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#event' - name = 'subscription' - plugin_attrib = name - interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription')) - - def get_expiry(self): - expiry = self._get_attr('expiry') - if expiry.lower() == 'presence': - return expiry - return xep_0082.parse(expiry) - - def set_expiry(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_attr('expiry', value) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -register_stanza_plugin(Message, Event) -register_stanza_plugin(Event, EventCollection) -register_stanza_plugin(Event, EventConfiguration) -register_stanza_plugin(Event, EventPurge) -register_stanza_plugin(Event, EventDelete) -register_stanza_plugin(Event, EventItems) -register_stanza_plugin(Event, EventSubscription) -register_stanza_plugin(EventCollection, EventAssociate) -register_stanza_plugin(EventCollection, EventDisassociate) -register_stanza_plugin(EventConfiguration, Form) -register_stanza_plugin(EventItems, EventItem, iterable=True) -register_stanza_plugin(EventItems, EventRetract, iterable=True) diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py deleted file mode 100644 index d975a46d..00000000 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py +++ /dev/null @@ -1,134 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Iq -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID -from sleekxmpp.plugins.xep_0004 import Form -from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting -from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation -from sleekxmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions - - -class PubsubOwner(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'pubsub' - plugin_attrib = 'pubsub_owner' - interfaces = set(tuple()) - - -class DefaultConfig(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'default' - plugin_attrib = name - interfaces = set(('node', 'config')) - - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) - - def get_config(self): - return self['form'] - - def set_config(self, value): - del self['from'] - self.append(value) - return self - - -class OwnerAffiliations(Affiliations): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node',)) - - def append(self, affiliation): - if not isinstance(affiliation, OwnerAffiliation): - raise TypeError - self.xml.append(affiliation.xml) - - -class OwnerAffiliation(Affiliation): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('affiliation', 'jid')) - - -class OwnerConfigure(Configure): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'configure' - plugin_attrib = name - interfaces = set(('node',)) - - -class OwnerDefault(OwnerConfigure): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node',)) - - -class OwnerDelete(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'delete' - plugin_attrib = name - interfaces = set(('node',)) - - -class OwnerPurge(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'purge' - plugin_attrib = name - interfaces = set(('node',)) - - -class OwnerRedirect(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'redirect' - plugin_attrib = name - interfaces = set(('node', 'jid')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class OwnerSubscriptions(Subscriptions): - name = 'subscriptions' - namespace = 'http://jabber.org/protocol/pubsub#owner' - plugin_attrib = name - interfaces = set(('node',)) - - def append(self, subscription): - if not isinstance(subscription, OwnerSubscription): - raise TypeError - self.xml.append(subscription.xml) - - -class OwnerSubscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'subscription')) - - def set_jid(self, value): - self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -register_stanza_plugin(Iq, PubsubOwner) -register_stanza_plugin(PubsubOwner, DefaultConfig) -register_stanza_plugin(PubsubOwner, OwnerAffiliations) -register_stanza_plugin(PubsubOwner, OwnerConfigure) -register_stanza_plugin(PubsubOwner, OwnerDefault) -register_stanza_plugin(PubsubOwner, OwnerDelete) -register_stanza_plugin(PubsubOwner, OwnerPurge) -register_stanza_plugin(PubsubOwner, OwnerSubscriptions) -register_stanza_plugin(DefaultConfig, Form) -register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True) -register_stanza_plugin(OwnerConfigure, Form) -register_stanza_plugin(OwnerDefault, Form) -register_stanza_plugin(OwnerDelete, OwnerRedirect) -register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True) diff --git a/sleekxmpp/plugins/xep_0065/__init__.py b/sleekxmpp/plugins/xep_0065/__init__.py deleted file mode 100644 index feca2ef1..00000000 --- a/sleekxmpp/plugins/xep_0065/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0065.stanza import Socks5 -from sleekxmpp.plugins.xep_0065.proxy import XEP_0065 - - -register_plugin(XEP_0065) diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py deleted file mode 100644 index fdd9f97e..00000000 --- a/sleekxmpp/plugins/xep_0065/proxy.py +++ /dev/null @@ -1,292 +0,0 @@ -import logging -import threading -import socket - -from hashlib import sha1 -from uuid import uuid4 - -from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 - -from sleekxmpp.stanza import Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.base import base_plugin - -from sleekxmpp.plugins.xep_0065 import stanza, Socks5 - - -log = logging.getLogger(__name__) - - -class XEP_0065(base_plugin): - - name = 'xep_0065' - description = "Socks5 Bytestreams" - dependencies = set(['xep_0030']) - default_config = { - 'auto_accept': False - } - - def plugin_init(self): - register_stanza_plugin(Iq, Socks5) - - self._proxies = {} - self._sessions = {} - self._sessions_lock = threading.Lock() - - self._preauthed_sids_lock = threading.Lock() - self._preauthed_sids = {} - - self.xmpp.register_handler( - Callback('Socks5 Bytestreams', - StanzaPath('iq@type=set/socks/streamhost'), - self._handle_streamhost)) - - self.api.register(self._authorized, 'authorized', default=True) - self.api.register(self._authorized_sid, 'authorized_sid', default=True) - self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Socks5.namespace) - - def plugin_end(self): - self.xmpp.remove_handler('Socks5 Bytestreams') - self.xmpp.remove_handler('Socks5 Streamhost Used') - self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace) - - def get_socket(self, sid): - """Returns the socket associated to the SID.""" - return self._sessions.get(sid, None) - - def handshake(self, to, ifrom=None, sid=None, timeout=None): - """ Starts the handshake to establish the socks5 bytestreams - connection. - """ - if not self._proxies: - self._proxies = self.discover_proxies() - - if sid is None: - sid = uuid4().hex - - used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout) - proxy = used['socks']['streamhost_used']['jid'] - - if proxy not in self._proxies: - log.warning('Received unknown SOCKS5 proxy: %s', proxy) - return - - with self._sessions_lock: - self._sessions[sid] = self._connect_proxy( - sid, - self.xmpp.boundjid, - to, - self._proxies[proxy][0], - self._proxies[proxy][1], - peer=to) - - # Request that the proxy activate the session with the target. - self.activate(proxy, sid, to, timeout=timeout) - socket = self.get_socket(sid) - self.xmpp.event('stream:%s:%s' % (sid, to), socket) - return socket - - def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None): - if sid is None: - sid = uuid4().hex - - # Requester initiates S5B negotiation with Target by sending - # IQ-set that includes the JabberID and network address of - # StreamHost as well as the StreamID (SID) of the proposed - # bytestream. - iq = self.xmpp.Iq() - iq['to'] = to - iq['from'] = ifrom - iq['type'] = 'set' - iq['socks']['sid'] = sid - for proxy, (host, port) in self._proxies.items(): - iq['socks'].add_streamhost(proxy, host, port) - return iq.send(block=block, timeout=timeout, callback=callback) - - def discover_proxies(self, jid=None, ifrom=None, timeout=None): - """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server.""" - if jid is None: - if self.xmpp.is_component: - jid = self.xmpp.server - else: - jid = self.xmpp.boundjid.server - - discovered = set() - - disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout) - - for item in disco_items['disco_items']['items']: - try: - disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout) - except XMPPError: - continue - else: - # Verify that the identity is a bytestream proxy. - identities = disco_info['disco_info']['identities'] - for identity in identities: - if identity[0] == 'proxy' and identity[1] == 'bytestreams': - discovered.add(disco_info['from']) - - for jid in discovered: - try: - addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout) - self._proxies[jid] = (addr['socks']['streamhost']['host'], - addr['socks']['streamhost']['port']) - except XMPPError: - continue - - return self._proxies - - def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None): - """Get the network information of a proxy.""" - iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom) - iq.enable('socks') - return iq.send(block=block, timeout=timeout, callback=callback) - - def _handle_streamhost(self, iq): - """Handle incoming SOCKS5 session request.""" - sid = iq['socks']['sid'] - if not sid: - raise XMPPError(etype='modify', condition='bad-request') - - if not self._accept_stream(iq): - raise XMPPError(etype='modify', condition='not-acceptable') - - streamhosts = iq['socks']['streamhosts'] - conn = None - used_streamhost = None - - sender = iq['from'] - for streamhost in streamhosts: - try: - conn = self._connect_proxy(sid, - sender, - self.xmpp.boundjid, - streamhost['host'], - streamhost['port'], - peer=sender) - used_streamhost = streamhost['jid'] - break - except socket.error: - continue - else: - raise XMPPError(etype='cancel', condition='item-not-found') - - iq.reply() - with self._sessions_lock: - self._sessions[sid] = conn - iq['socks']['sid'] = sid - iq['socks']['streamhost_used']['jid'] = used_streamhost - iq.send() - self.xmpp.event('socks5_stream', conn) - self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn) - - def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None): - """Activate the socks5 session that has been negotiated.""" - iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom) - iq['socks']['sid'] = sid - iq['socks']['activate'] = target - iq.send(block=block, timeout=timeout, callback=callback) - - def deactivate(self, sid): - """Closes the proxy socket associated with this SID.""" - sock = self._sessions.get(sid) - if sock: - try: - # sock.close() will also delete sid from self._sessions (see _connect_proxy) - sock.close() - except socket.error: - pass - # Though this should not be neccessary remove the closed session anyway - with self._sessions_lock: - if sid in self._sessions: - log.warn(('SOCKS5 session with sid = "%s" was not ' + - 'removed from _sessions by sock.close()') % sid) - del self._sessions[sid] - - def close(self): - """Closes all proxy sockets.""" - for sid, sock in self._sessions.items(): - sock.close() - with self._sessions_lock: - self._sessions = {} - - def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None): - """ Establishes a connection between the client and the server-side - Socks5 proxy. - - sid : The StreamID. <str> - requester : The JID of the requester. <str> - target : The JID of the target. <str> - proxy_host : The hostname or the IP of the proxy. <str> - proxy_port : The port of the proxy. <str> or <int> - peer : The JID for the other side of the stream, regardless - of target or requester status. - """ - # Because the xep_0065 plugin uses the proxy_port as string, - # the Proxy class accepts the proxy_port argument as a string - # or an integer. Here, we force to use the port as an integer. - proxy_port = int(proxy_port) - - sock = socksocket() - sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port) - - # The hostname MUST be SHA1(SID + Requester JID + Target JID) - # where the output is hexadecimal-encoded (not binary). - digest = sha1() - digest.update(sid) - digest.update(str(requester)) - digest.update(str(target)) - - dest = digest.hexdigest() - - # The port MUST be 0. - sock.connect((dest, 0)) - log.info('Socket connected.') - - _close = sock.close - def close(*args, **kwargs): - with self._sessions_lock: - if sid in self._sessions: - del self._sessions[sid] - _close() - log.info('Socket closed.') - sock.close = close - - sock.peer_jid = peer - sock.self_jid = target if requester == peer else requester - - self.xmpp.event('socks_connected', sid) - return sock - - def _accept_stream(self, iq): - receiver = iq['to'] - sender = iq['from'] - sid = iq['socks']['sid'] - - if self.api['authorized_sid'](receiver, sid, sender, iq): - return True - return self.api['authorized'](receiver, sid, sender, iq) - - def _authorized(self, jid, sid, ifrom, iq): - return self.auto_accept - - def _authorized_sid(self, jid, sid, ifrom, iq): - with self._preauthed_sids_lock: - log.debug('>>> authed sids: %s', self._preauthed_sids) - log.debug('>>> lookup: %s %s %s', jid, sid, ifrom) - if (jid, sid, ifrom) in self._preauthed_sids: - del self._preauthed_sids[(jid, sid, ifrom)] - return True - return False - - def _preauthorize_sid(self, jid, sid, ifrom, data): - log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data) - with self._preauthed_sids_lock: - self._preauthed_sids[(jid, sid, ifrom)] = True diff --git a/sleekxmpp/plugins/xep_0065/stanza.py b/sleekxmpp/plugins/xep_0065/stanza.py deleted file mode 100644 index e48bf1b5..00000000 --- a/sleekxmpp/plugins/xep_0065/stanza.py +++ /dev/null @@ -1,47 +0,0 @@ -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class Socks5(ElementBase): - name = 'query' - namespace = 'http://jabber.org/protocol/bytestreams' - plugin_attrib = 'socks' - interfaces = set(['sid', 'activate']) - sub_interfaces = set(['activate']) - - def add_streamhost(self, jid, host, port): - sh = StreamHost(parent=self) - sh['jid'] = jid - sh['host'] = host - sh['port'] = port - - -class StreamHost(ElementBase): - name = 'streamhost' - namespace = 'http://jabber.org/protocol/bytestreams' - plugin_attrib = 'streamhost' - plugin_multi_attrib = 'streamhosts' - interfaces = set(['host', 'jid', 'port']) - - def set_jid(self, value): - return self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -class StreamHostUsed(ElementBase): - name = 'streamhost-used' - namespace = 'http://jabber.org/protocol/bytestreams' - plugin_attrib = 'streamhost_used' - interfaces = set(['jid']) - - def set_jid(self, value): - return self._set_attr('jid', str(value)) - - def get_jid(self): - return JID(self._get_attr('jid')) - - -register_stanza_plugin(Socks5, StreamHost, iterable=True) -register_stanza_plugin(Socks5, StreamHostUsed) diff --git a/sleekxmpp/plugins/xep_0066/__init__.py b/sleekxmpp/plugins/xep_0066/__init__.py deleted file mode 100644 index 68a50180..00000000 --- a/sleekxmpp/plugins/xep_0066/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0066 import stanza -from sleekxmpp.plugins.xep_0066.stanza import OOB, OOBTransfer -from sleekxmpp.plugins.xep_0066.oob import XEP_0066 - - -register_plugin(XEP_0066) - - -# Retain some backwards compatibility -xep_0066 = XEP_0066 diff --git a/sleekxmpp/plugins/xep_0066/oob.py b/sleekxmpp/plugins/xep_0066/oob.py deleted file mode 100644 index 959c15a2..00000000 --- a/sleekxmpp/plugins/xep_0066/oob.py +++ /dev/null @@ -1,158 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Message, Presence, Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0066 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0066(BasePlugin): - - """ - XEP-0066: Out of Band Data - - Out of Band Data is a basic method for transferring files between - XMPP agents. The URL of the resource in question is sent to the receiving - entity, which then downloads the resource before responding to the OOB - request. OOB is also used as a generic means to transmit URLs in other - stanzas to indicate where to find additional information. - - Also see <http://www.xmpp.org/extensions/xep-0066.html>. - - Events: - oob_transfer -- Raised when a request to download a resource - has been received. - - Methods: - send_oob -- Send a request to another entity to download a file - or other addressable resource. - """ - - name = 'xep_0066' - description = 'XEP-0066: Out of Band Data' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - """Start the XEP-0066 plugin.""" - - self.url_handlers = {'global': self._default_handler, - 'jid': {}} - - register_stanza_plugin(Iq, stanza.OOBTransfer) - register_stanza_plugin(Message, stanza.OOB) - register_stanza_plugin(Presence, stanza.OOB) - - self.xmpp.register_handler( - Callback('OOB Transfer', - StanzaPath('iq@type=set/oob_transfer'), - self._handle_transfer)) - - def plugin_end(self): - self.xmpp.remove_handler('OOB Transfer') - self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace) - self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) - self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) - - def register_url_handler(self, jid=None, handler=None): - """ - Register a handler to process download requests, either for all - JIDs or a single JID. - - Arguments: - jid -- If None, then set the handler as a global default. - handler -- If None, then remove the existing handler for the - given JID, or reset the global handler if the JID - is None. - """ - if jid is None: - if handler is not None: - self.url_handlers['global'] = handler - else: - self.url_handlers['global'] = self._default_handler - else: - if handler is not None: - self.url_handlers['jid'][jid] = handler - else: - del self.url_handlers['jid'][jid] - - def send_oob(self, to, url, desc=None, ifrom=None, **iqargs): - """ - Initiate a basic file transfer by sending the URL of - a file or other resource. - - Arguments: - url -- The URL of the resource to transfer. - desc -- An optional human readable description of the item - that is to be transferred. - ifrom -- Specifiy the sender's JID. - block -- If true, block and wait for the stanzas' reply. - timeout -- The time in seconds to block while waiting for - a reply. If None, then wait indefinitely. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - """ - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = to - iq['from'] = ifrom - iq['oob_transfer']['url'] = url - iq['oob_transfer']['desc'] = desc - return iq.send(**iqargs) - - def _run_url_handler(self, iq): - """ - Execute the appropriate handler for a transfer request. - - Arguments: - iq -- The Iq stanza containing the OOB transfer request. - """ - if iq['to'] in self.url_handlers['jid']: - return self.url_handlers['jid'][iq['to']](iq) - else: - if self.url_handlers['global']: - self.url_handlers['global'](iq) - else: - raise XMPPError('service-unavailable') - - def _default_handler(self, iq): - """ - As a safe default, don't actually download files. - - Register a new handler using self.register_url_handler to - screen requests and download files. - - Arguments: - iq -- The Iq stanza containing the OOB transfer request. - """ - raise XMPPError('service-unavailable') - - def _handle_transfer(self, iq): - """ - Handle receiving an out-of-band transfer request. - - Arguments: - iq -- An Iq stanza containing an OOB transfer request. - """ - log.debug('Received out-of-band data request for %s from %s:' % ( - iq['oob_transfer']['url'], iq['from'])) - self._run_url_handler(iq) - iq.reply().send() diff --git a/sleekxmpp/plugins/xep_0066/stanza.py b/sleekxmpp/plugins/xep_0066/stanza.py deleted file mode 100644 index 21387485..00000000 --- a/sleekxmpp/plugins/xep_0066/stanza.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class OOBTransfer(ElementBase): - - """ - """ - - name = 'query' - namespace = 'jabber:iq:oob' - plugin_attrib = 'oob_transfer' - interfaces = set(('url', 'desc', 'sid')) - sub_interfaces = set(('url', 'desc')) - - -class OOB(ElementBase): - - """ - """ - - name = 'x' - namespace = 'jabber:x:oob' - plugin_attrib = 'oob' - interfaces = set(('url', 'desc')) - sub_interfaces = interfaces diff --git a/sleekxmpp/plugins/xep_0071/__init__.py b/sleekxmpp/plugins/xep_0071/__init__.py deleted file mode 100644 index c21e9265..00000000 --- a/sleekxmpp/plugins/xep_0071/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM -from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071 - - -register_plugin(XEP_0071) diff --git a/sleekxmpp/plugins/xep_0071/stanza.py b/sleekxmpp/plugins/xep_0071/stanza.py deleted file mode 100644 index d5ff1a1b..00000000 --- a/sleekxmpp/plugins/xep_0071/stanza.py +++ /dev/null @@ -1,81 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Message -from sleekxmpp.util import unicode -from sleekxmpp.thirdparty import OrderedDict -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring - - -XHTML_NS = 'http://www.w3.org/1999/xhtml' - - -class XHTML_IM(ElementBase): - - namespace = 'http://jabber.org/protocol/xhtml-im' - name = 'html' - interfaces = set(['body']) - lang_interfaces = set(['body']) - plugin_attrib = name - - def set_body(self, content, lang=None): - if lang is None: - lang = self.get_lang() - self.del_body(lang) - if lang == '*': - for sublang, subcontent in content.items(): - self.set_body(subcontent, sublang) - else: - if isinstance(content, type(ET.Element('test'))): - content = unicode(ET.tostring(content)) - else: - content = unicode(content) - header = '<body xmlns="%s"' % XHTML_NS - if lang: - header = '%s xml:lang="%s"' % (header, lang) - content = '%s>%s</body>' % (header, content) - xhtml = ET.fromstring(content) - self.xml.append(xhtml) - - def get_body(self, lang=None): - """Return the contents of the HTML body.""" - if lang is None: - lang = self.get_lang() - - bodies = self.xml.findall('{%s}body' % XHTML_NS) - - if lang == '*': - result = OrderedDict() - for body in bodies: - body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '') - body_result = [] - body_result.append(body.text if body.text else '') - for child in body: - body_result.append(tostring(child, xmlns=XHTML_NS)) - body_result.append(body.tail if body.tail else '') - result[body_lang] = ''.join(body_result) - return result - else: - for body in bodies: - if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang: - result = [] - result.append(body.text if body.text else '') - for child in body: - result.append(tostring(child, xmlns=XHTML_NS)) - result.append(body.tail if body.tail else '') - return ''.join(result) - return '' - - def del_body(self, lang=None): - if lang is None: - lang = self.get_lang() - bodies = self.xml.findall('{%s}body' % XHTML_NS) - for body in bodies: - if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang: - self.xml.remove(body) - return diff --git a/sleekxmpp/plugins/xep_0071/xhtml_im.py b/sleekxmpp/plugins/xep_0071/xhtml_im.py deleted file mode 100644 index 096a00aa..00000000 --- a/sleekxmpp/plugins/xep_0071/xhtml_im.py +++ /dev/null @@ -1,30 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.stanza import Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM - - -class XEP_0071(BasePlugin): - - name = 'xep_0071' - description = 'XEP-0071: XHTML-IM' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, XHTML_IM) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace) diff --git a/sleekxmpp/plugins/xep_0077/__init__.py b/sleekxmpp/plugins/xep_0077/__init__.py deleted file mode 100644 index 779ae0ac..00000000 --- a/sleekxmpp/plugins/xep_0077/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0077.stanza import Register, RegisterFeature -from sleekxmpp.plugins.xep_0077.register import XEP_0077 - - -register_plugin(XEP_0077) - - -# Retain some backwards compatibility -xep_0077 = XEP_0077 diff --git a/sleekxmpp/plugins/xep_0077/register.py b/sleekxmpp/plugins/xep_0077/register.py deleted file mode 100644 index ee07548b..00000000 --- a/sleekxmpp/plugins/xep_0077/register.py +++ /dev/null @@ -1,115 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import ssl - -from sleekxmpp.stanza import StreamFeatures, Iq -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0077 import stanza, Register, RegisterFeature - - -log = logging.getLogger(__name__) - - -class XEP_0077(BasePlugin): - - """ - XEP-0077: In-Band Registration - """ - - name = 'xep_0077' - description = 'XEP-0077: In-Band Registration' - dependencies = set(['xep_0004', 'xep_0066']) - stanza = stanza - default_config = { - 'create_account': True, - 'force_registration': False, - 'order': 50 - } - - def plugin_init(self): - register_stanza_plugin(StreamFeatures, RegisterFeature) - register_stanza_plugin(Iq, Register) - - if not self.xmpp.is_component: - self.xmpp.register_feature('register', - self._handle_register_feature, - restart=False, - order=self.order) - - register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) - register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) - - self.xmpp.add_event_handler('connected', self._force_registration) - - def plugin_end(self): - if not self.xmpp.is_component: - self.xmpp.unregister_feature('register', self.order) - - def _force_registration(self, event): - if self.force_registration: - self.xmpp.add_filter('in', self._force_stream_feature) - - def _force_stream_feature(self, stanza): - if isinstance(stanza, StreamFeatures): - if self.xmpp.use_tls or self.xmpp.use_ssl: - if 'starttls' not in self.xmpp.features: - return stanza - elif not isinstance(self.xmpp.socket, ssl.SSLSocket): - return stanza - if 'mechanisms' not in self.xmpp.features: - log.debug('Forced adding in-band registration stream feature') - stanza.enable('register') - self.xmpp.del_filter('in', self._force_stream_feature) - return stanza - - def _handle_register_feature(self, features): - if 'mechanisms' in self.xmpp.features: - # We have already logged in with an account - return False - - if self.create_account and self.xmpp.event_handled('register'): - form = self.get_registration() - self.xmpp.event('register', form, direct=True) - return True - return False - - def get_registration(self, jid=None, ifrom=None, block=True, - timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = ifrom - iq.enable('register') - return iq.send(block=block, timeout=timeout, - callback=callback, now=True) - - def cancel_registration(self, jid=None, ifrom=None, block=True, - timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - iq['register']['remove'] = True - return iq.send(block=block, timeout=timeout, callback=callback) - - def change_password(self, password, jid=None, ifrom=None, block=True, - timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - if self.xmpp.is_component: - ifrom = JID(ifrom) - iq['register']['username'] = ifrom.user - else: - iq['register']['username'] = self.xmpp.boundjid.user - iq['register']['password'] = password - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0077/stanza.py b/sleekxmpp/plugins/xep_0077/stanza.py deleted file mode 100644 index e06c1910..00000000 --- a/sleekxmpp/plugins/xep_0077/stanza.py +++ /dev/null @@ -1,73 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import unicode_literals - -from sleekxmpp.xmlstream import ElementBase, ET - - -class Register(ElementBase): - - namespace = 'jabber:iq:register' - name = 'query' - plugin_attrib = 'register' - interfaces = set(('username', 'password', 'email', 'nick', 'name', - 'first', 'last', 'address', 'city', 'state', 'zip', - 'phone', 'url', 'date', 'misc', 'text', 'key', - 'registered', 'remove', 'instructions', 'fields')) - sub_interfaces = interfaces - form_fields = set(('username', 'password', 'email', 'nick', 'name', - 'first', 'last', 'address', 'city', 'state', 'zip', - 'phone', 'url', 'date', 'misc', 'text', 'key')) - - def get_registered(self): - present = self.xml.find('{%s}registered' % self.namespace) - return present is not None - - def get_remove(self): - present = self.xml.find('{%s}remove' % self.namespace) - return present is not None - - def set_registered(self, value): - if value: - self.add_field('registered') - else: - del self['registered'] - - def set_remove(self, value): - if value: - self.add_field('remove') - else: - del self['remove'] - - def add_field(self, value): - self._set_sub_text(value, '', keep=True) - - def get_fields(self): - fields = set() - for field in self.form_fields: - if self.xml.find('{%s}%s' % (self.namespace, field)) is not None: - fields.add(field) - return fields - - def set_fields(self, fields): - del self['fields'] - for field in fields: - self._set_sub_text(field, '', keep=True) - - def del_fields(self): - for field in self.form_fields: - self._del_sub(field) - - -class RegisterFeature(ElementBase): - - name = 'register' - namespace = 'http://jabber.org/features/iq-register' - plugin_attrib = name - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0078/__init__.py b/sleekxmpp/plugins/xep_0078/__init__.py deleted file mode 100644 index 2ea72ffb..00000000 --- a/sleekxmpp/plugins/xep_0078/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0078 import stanza -from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature -from sleekxmpp.plugins.xep_0078.legacyauth import XEP_0078 - - -register_plugin(XEP_0078) - - -# Retain some backwards compatibility -xep_0078 = XEP_0078 diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py deleted file mode 100644 index da6bfa2c..00000000 --- a/sleekxmpp/plugins/xep_0078/legacyauth.py +++ /dev/null @@ -1,147 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import uuid -import logging -import hashlib -import random -import sys - -from sleekxmpp.jid import JID -from sleekxmpp.exceptions import IqError, IqTimeout -from sleekxmpp.stanza import Iq, StreamFeatures -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0078 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0078(BasePlugin): - - """ - XEP-0078 NON-SASL Authentication - - This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin - unless you are forced to use an old XMPP server implementation. - """ - - name = 'xep_0078' - description = 'XEP-0078: Non-SASL Authentication' - dependencies = set() - stanza = stanza - default_config = { - 'order': 15 - } - - def plugin_init(self): - self.xmpp.register_feature('auth', - self._handle_auth, - restart=False, - order=self.order) - - self.xmpp.add_event_handler('legacy_protocol', - self._handle_legacy_protocol) - - register_stanza_plugin(Iq, stanza.IqAuth) - register_stanza_plugin(StreamFeatures, stanza.AuthFeature) - - def plugin_end(self): - self.xmpp.del_event_handler('legacy_protocol', - self._handle_legacy_protocol) - self.xmpp.unregister_feature('auth', self.order) - - def _handle_auth(self, features): - # If we can or have already authenticated with SASL, do nothing. - if 'mechanisms' in features['features']: - return False - return self.authenticate() - - def _handle_legacy_protocol(self, event): - self.authenticate() - - def authenticate(self): - if self.xmpp.authenticated: - return False - - log.debug("Starting jabber:iq:auth Authentication") - - # Step 1: Request the auth form - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = self.xmpp.requested_jid.host - iq['auth']['username'] = self.xmpp.requested_jid.user - - try: - resp = iq.send(now=True) - except IqError as err: - log.info("Authentication failed: %s", err.iq['error']['condition']) - self.xmpp.event('failed_auth', direct=True) - self.xmpp.disconnect() - return True - except IqTimeout: - log.info("Authentication failed: %s", 'timeout') - self.xmpp.event('failed_auth', direct=True) - self.xmpp.disconnect() - return True - - # Step 2: Fill out auth form for either password or digest auth - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['auth']['username'] = self.xmpp.requested_jid.user - - # A resource is required, so create a random one if necessary - resource = self.xmpp.requested_jid.resource - if not resource: - resource = str(uuid.uuid4()) - - iq['auth']['resource'] = resource - - if 'digest' in resp['auth']['fields']: - log.debug('Authenticating via jabber:iq:auth Digest') - if sys.version_info < (3, 0): - stream_id = bytes(self.xmpp.stream_id) - password = bytes(self.xmpp.password) - else: - stream_id = bytes(self.xmpp.stream_id, encoding='utf-8') - password = bytes(self.xmpp.password, encoding='utf-8') - - digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest() - iq['auth']['digest'] = digest - else: - log.warning('Authenticating via jabber:iq:auth Plain.') - iq['auth']['password'] = self.xmpp.password - - # Step 3: Send credentials - try: - result = iq.send(now=True) - except IqError as err: - log.info("Authentication failed") - self.xmpp.event("failed_auth", direct=True) - self.xmpp.disconnect() - except IqTimeout: - log.info("Authentication failed") - self.xmpp.event("failed_auth", direct=True) - self.xmpp.disconnect() - - self.xmpp.features.add('auth') - - self.xmpp.authenticated = True - - self.xmpp.boundjid = JID(self.xmpp.requested_jid, - resource=resource, - cache_lock=True) - self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) - - log.debug("Established Session") - self.xmpp.sessionstarted = True - self.xmpp.session_started_event.set() - self.xmpp.event('session_start') - - return True diff --git a/sleekxmpp/plugins/xep_0078/stanza.py b/sleekxmpp/plugins/xep_0078/stanza.py deleted file mode 100644 index c8b26071..00000000 --- a/sleekxmpp/plugins/xep_0078/stanza.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 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 IqAuth(ElementBase): - namespace = 'jabber:iq:auth' - name = 'query' - plugin_attrib = 'auth' - interfaces = set(('fields', 'username', 'password', 'resource', 'digest')) - sub_interfaces = set(('username', 'password', 'resource', 'digest')) - plugin_tag_map = {} - plugin_attrib_map = {} - - def get_fields(self): - fields = set() - for field in self.sub_interfaces: - if self.xml.find('{%s}%s' % (self.namespace, field)) is not None: - fields.add(field) - return fields - - def set_resource(self, value): - self._set_sub_text('resource', value, keep=True) - - def set_password(self, value): - self._set_sub_text('password', value, keep=True) - - -class AuthFeature(ElementBase): - namespace = 'http://jabber.org/features/iq-auth' - name = 'auth' - plugin_attrib = 'auth' - interfaces = set() - plugin_tag_map = {} - plugin_attrib_map = {} diff --git a/sleekxmpp/plugins/xep_0079/__init__.py b/sleekxmpp/plugins/xep_0079/__init__.py deleted file mode 100644 index 09e66715..00000000 --- a/sleekxmpp/plugins/xep_0079/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0079.stanza import ( - AMP, Rule, InvalidRules, UnsupportedConditions, - UnsupportedActions, FailedRules, FailedRule, - AMPFeature) -from sleekxmpp.plugins.xep_0079.amp import XEP_0079 - - -register_plugin(XEP_0079) diff --git a/sleekxmpp/plugins/xep_0079/amp.py b/sleekxmpp/plugins/xep_0079/amp.py deleted file mode 100644 index 918fb841..00000000 --- a/sleekxmpp/plugins/xep_0079/amp.py +++ /dev/null @@ -1,79 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -from sleekxmpp.stanza import Message, Error, StreamFeatures -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0079 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0079(BasePlugin): - - """ - XEP-0079 Advanced Message Processing - """ - - name = 'xep_0079' - description = 'XEP-0079: Advanced Message Processing' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, stanza.AMP) - register_stanza_plugin(Error, stanza.InvalidRules) - register_stanza_plugin(Error, stanza.UnsupportedConditions) - register_stanza_plugin(Error, stanza.UnsupportedActions) - register_stanza_plugin(Error, stanza.FailedRules) - - self.xmpp.register_handler( - Callback('AMP Response', - MatchMany([ - StanzaPath('message/error/failed_rules'), - StanzaPath('message/amp') - ]), - self._handle_amp_response)) - - if not self.xmpp.is_component: - self.xmpp.register_feature('amp', - self._handle_amp_feature, - restart=False, - order=9000) - register_stanza_plugin(StreamFeatures, stanza.AMPFeature) - - def plugin_end(self): - self.xmpp.remove_handler('AMP Response') - - def _handle_amp_response(self, msg): - log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') - if msg['type'] == 'error': - self.xmpp.event('amp_error', msg) - elif msg['amp']['status'] in ('alert', 'notify'): - self.xmpp.event('amp_%s' % msg['amp']['status'], msg) - - def _handle_amp_feature(self, features): - log.debug('Advanced Message Processing is available.') - self.xmpp.features.add('amp') - - def discover_support(self, jid=None, **iqargs): - if jid is None: - if self.xmpp.is_component: - jid = self.xmpp.server_host - else: - jid = self.xmpp.boundjid.host - - return self.xmpp['xep_0030'].get_info( - jid=jid, - node='http://jabber.org/protocol/amp', - **iqargs) diff --git a/sleekxmpp/plugins/xep_0079/stanza.py b/sleekxmpp/plugins/xep_0079/stanza.py deleted file mode 100644 index cb6932d6..00000000 --- a/sleekxmpp/plugins/xep_0079/stanza.py +++ /dev/null @@ -1,96 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import unicode_literals - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class AMP(ElementBase): - namespace = 'http://jabber.org/protocol/amp' - name = 'amp' - plugin_attrib = 'amp' - interfaces = set(['from', 'to', 'status', 'per_hop']) - - def get_from(self): - return JID(self._get_attr('from')) - - def set_from(self, value): - return self._set_attr('from', str(value)) - - def get_to(self): - return JID(self._get_attr('from')) - - def set_to(self, value): - return self._set_attr('to', str(value)) - - def get_per_hop(self): - return self._get_attr('per-hop') == 'true' - - def set_per_hop(self, value): - if value: - return self._set_attr('per-hop', 'true') - else: - return self._del_attr('per-hop') - - def del_per_hop(self): - return self._del_attr('per-hop') - - def add_rule(self, action, condition, value): - rule = Rule(parent=self) - rule['action'] = action - rule['condition'] = condition - rule['value'] = value - - -class Rule(ElementBase): - namespace = 'http://jabber.org/protocol/amp' - name = 'rule' - plugin_attrib = name - plugin_multi_attrib = 'rules' - interfaces = set(['action', 'condition', 'value']) - - -class InvalidRules(ElementBase): - namespace = 'http://jabber.org/protocol/amp' - name = 'invalid-rules' - plugin_attrib = 'invalid_rules' - - -class UnsupportedConditions(ElementBase): - namespace = 'http://jabber.org/protocol/amp' - name = 'unsupported-conditions' - plugin_attrib = 'unsupported_conditions' - - -class UnsupportedActions(ElementBase): - namespace = 'http://jabber.org/protocol/amp' - name = 'unsupported-actions' - plugin_attrib = 'unsupported_actions' - - -class FailedRule(Rule): - namespace = 'http://jabber.org/protocol/amp#errors' - - -class FailedRules(ElementBase): - namespace = 'http://jabber.org/protocol/amp#errors' - name = 'failed-rules' - plugin_attrib = 'failed_rules' - - -class AMPFeature(ElementBase): - namespace = 'http://jabber.org/features/amp' - name = 'amp' - - -register_stanza_plugin(AMP, Rule, iterable=True) -register_stanza_plugin(InvalidRules, Rule, iterable=True) -register_stanza_plugin(UnsupportedConditions, Rule, iterable=True) -register_stanza_plugin(UnsupportedActions, Rule, iterable=True) -register_stanza_plugin(FailedRules, FailedRule, iterable=True) diff --git a/sleekxmpp/plugins/xep_0080/__init__.py b/sleekxmpp/plugins/xep_0080/__init__.py deleted file mode 100644 index cad23d22..00000000 --- a/sleekxmpp/plugins/xep_0080/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0080.stanza import Geoloc -from sleekxmpp.plugins.xep_0080.geoloc import XEP_0080 - - -register_plugin(XEP_0080) diff --git a/sleekxmpp/plugins/xep_0080/geoloc.py b/sleekxmpp/plugins/xep_0080/geoloc.py deleted file mode 100644 index ba594cce..00000000 --- a/sleekxmpp/plugins/xep_0080/geoloc.py +++ /dev/null @@ -1,125 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -import sleekxmpp -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0080 import stanza, Geoloc - - -log = logging.getLogger(__name__) - - -class XEP_0080(BasePlugin): - - """ - XEP-0080: User Location - """ - - name = 'xep_0080' - description = 'XEP-0080: User Location' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_end(self): - self.xmpp['xep_0163'].remove_interest(Geoloc.namespace) - self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_location', Geoloc) - - def publish_location(self, **kwargs): - """ - Publish the user's current location. - - Arguments: - accuracy -- Horizontal GPS error in meters. - alt -- Altitude in meters above or below sea level. - area -- A named area such as a campus or neighborhood. - bearing -- GPS bearing (direction in which the entity is - heading to reach its next waypoint), measured in - decimal degrees relative to true north. - building -- A specific building on a street or in an area. - country -- The nation where the user is located. - countrycode -- The ISO 3166 two-letter country code. - datum -- GPS datum. - description -- A natural-language name for or description of - the location. - error -- Horizontal GPS error in arc minutes. Obsoleted by - the accuracy parameter. - floor -- A particular floor in a building. - lat -- Latitude in decimal degrees North. - locality -- A locality within the administrative region, such - as a town or city. - lon -- Longitude in decimal degrees East. - postalcode -- A code used for postal delivery. - region -- An administrative region of the nation, such - as a state or province. - room -- A particular room in a building. - speed -- The speed at which the entity is moving, - in meters per second. - street -- A thoroughfare within the locality, or a crossing - of two thoroughfares. - text -- A catch-all element that captures any other - information about the location. - timestamp -- UTC timestamp specifying the moment when the - reading was taken. - uri -- A URI or URL pointing to information about - the location. - - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - options = kwargs.get('options', None) - ifrom = kwargs.get('ifrom', None) - block = kwargs.get('block', None) - callback = kwargs.get('callback', None) - timeout = kwargs.get('timeout', None) - for param in ('ifrom', 'block', 'callback', 'timeout', 'options'): - if param in kwargs: - del kwargs[param] - - geoloc = Geoloc() - geoloc.values = kwargs - - return self.xmpp['xep_0163'].publish(geoloc, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user location information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - geoloc = Geoloc() - return self.xmpp['xep_0163'].publish(geoloc, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0080/stanza.py b/sleekxmpp/plugins/xep_0080/stanza.py deleted file mode 100644 index 8f466516..00000000 --- a/sleekxmpp/plugins/xep_0080/stanza.py +++ /dev/null @@ -1,266 +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 -from sleekxmpp.plugins import xep_0082 - - -class Geoloc(ElementBase): - - """ - XMPP's <geoloc> stanza allows entities to know the current - geographical or physical location of an entity. (XEP-0080: User Location) - - Example <geoloc> stanzas: - <geoloc xmlns='http://jabber.org/protocol/geoloc'/> - - <geoloc xmlns='http://jabber.org/protocol/geoloc' xml:lang='en'> - <accuracy>20</accuracy> - <country>Italy</country> - <lat>45.44</lat> - <locality>Venice</locality> - <lon>12.33</lon> - </geoloc> - - Stanza Interface: - accuracy -- Horizontal GPS error in meters. - alt -- Altitude in meters above or below sea level. - area -- A named area such as a campus or neighborhood. - bearing -- GPS bearing (direction in which the entity is - heading to reach its next waypoint), measured in - decimal degrees relative to true north. - building -- A specific building on a street or in an area. - country -- The nation where the user is located. - countrycode -- The ISO 3166 two-letter country code. - datum -- GPS datum. - description -- A natural-language name for or description of - the location. - error -- Horizontal GPS error in arc minutes. Obsoleted by - the accuracy parameter. - floor -- A particular floor in a building. - lat -- Latitude in decimal degrees North. - locality -- A locality within the administrative region, such - as a town or city. - lon -- Longitude in decimal degrees East. - postalcode -- A code used for postal delivery. - region -- An administrative region of the nation, such - as a state or province. - room -- A particular room in a building. - speed -- The speed at which the entity is moving, - in meters per second. - street -- A thoroughfare within the locality, or a crossing - of two thoroughfares. - text -- A catch-all element that captures any other - information about the location. - timestamp -- UTC timestamp specifying the moment when the - reading was taken. - uri -- A URI or URL pointing to information about - the location. - """ - - namespace = 'http://jabber.org/protocol/geoloc' - name = 'geoloc' - interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building', - 'country', 'countrycode', 'datum', 'dscription', - 'error', 'floor', 'lat', 'locality', 'lon', - 'postalcode', 'region', 'room', 'speed', 'street', - 'text', 'timestamp', 'uri')) - sub_interfaces = interfaces - plugin_attrib = name - - def exception(self, e): - """ - Override exception passback for presence. - """ - pass - - def set_accuracy(self, accuracy): - """ - Set the value of the <accuracy> element. - - Arguments: - accuracy -- Horizontal GPS error in meters - """ - self._set_sub_text('accuracy', text=str(accuracy)) - return self - - def get_accuracy(self): - """ - Return the value of the <accuracy> element as an integer. - """ - p = self._get_sub_text('accuracy') - if not p: - return None - else: - try: - return int(p) - except ValueError: - return None - - def set_alt(self, alt): - """ - Set the value of the <alt> element. - - Arguments: - alt -- Altitude in meters above or below sea level - """ - self._set_sub_text('alt', text=str(alt)) - return self - - def get_alt(self): - """ - Return the value of the <alt> element as an integer. - """ - p = self._get_sub_text('alt') - if not p: - return None - else: - try: - return int(p) - except ValueError: - return None - - def set_bearing(self, bearing): - """ - Set the value of the <bearing> element. - - Arguments: - bearing -- GPS bearing (direction in which the entity is heading - to reach its next waypoint), measured in decimal - degrees relative to true north - """ - self._set_sub_text('bearing', text=str(bearing)) - return self - - def get_bearing(self): - """ - Return the value of the <bearing> element as a float. - """ - p = self._get_sub_text('bearing') - if not p: - return None - else: - try: - return float(p) - except ValueError: - return None - - def set_error(self, error): - """ - Set the value of the <error> element. - - Arguments: - error -- Horizontal GPS error in arc minutes; this - element is deprecated in favor of <accuracy/> - """ - self._set_sub_text('error', text=str(error)) - return self - - def get_error(self): - """ - Return the value of the <error> element as a float. - """ - p = self._get_sub_text('error') - if not p: - return None - else: - try: - return float(p) - except ValueError: - return None - - def set_lat(self, lat): - """ - Set the value of the <lat> element. - - Arguments: - lat -- Latitude in decimal degrees North - """ - self._set_sub_text('lat', text=str(lat)) - return self - - def get_lat(self): - """ - Return the value of the <lat> element as a float. - """ - p = self._get_sub_text('lat') - if not p: - return None - else: - try: - return float(p) - except ValueError: - return None - - def set_lon(self, lon): - """ - Set the value of the <lon> element. - - Arguments: - lon -- Longitude in decimal degrees East - """ - self._set_sub_text('lon', text=str(lon)) - return self - - def get_lon(self): - """ - Return the value of the <lon> element as a float. - """ - p = self._get_sub_text('lon') - if not p: - return None - else: - try: - return float(p) - except ValueError: - return None - - def set_speed(self, speed): - """ - Set the value of the <speed> element. - - Arguments: - speed -- The speed at which the entity is moving, - in meters per second - """ - self._set_sub_text('speed', text=str(speed)) - return self - - def get_speed(self): - """ - Return the value of the <speed> element as a float. - """ - p = self._get_sub_text('speed') - if not p: - return None - else: - try: - return float(p) - except ValueError: - return None - - def set_timestamp(self, timestamp): - """ - Set the value of the <timestamp> element. - - Arguments: - timestamp -- UTC timestamp specifying the moment when - the reading was taken - """ - self._set_sub_text('timestamp', text=str(xep_0082.datetime(timestamp))) - return self - - def get_timestamp(self): - """ - Return the value of the <timestamp> element as a DateTime. - """ - p = self._get_sub_text('timestamp') - if not p: - return None - else: - return xep_0082.datetime(p) diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py deleted file mode 100644 index 26eb68fa..00000000 --- a/sleekxmpp/plugins/xep_0082.py +++ /dev/null @@ -1,228 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp.plugins import BasePlugin, register_plugin -from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso - - -# ===================================================================== -# To make it easier for stanzas without direct access to plugin objects -# to use the XEP-0082 utility methods, we will define them as top-level -# functions and then just reference them in the plugin itself. - -def parse(time_str): - """ - Convert a string timestamp into a datetime object. - - Arguments: - time_str -- A formatted timestamp string. - """ - return parse_iso(time_str) - - -def format_date(time_obj): - """ - Return a formatted string version of a date object. - - Format: - YYYY-MM-DD - - Arguments: - time_obj -- A date or datetime object. - """ - if isinstance(time_obj, dt.datetime): - time_obj = time_obj.date() - return time_obj.isoformat() - - -def format_time(time_obj): - """ - Return a formatted string version of a time object. - - format: - hh:mm:ss[.sss][TZD] - - arguments: - time_obj -- A time or datetime object. - """ - if isinstance(time_obj, dt.datetime): - time_obj = time_obj.timetz() - timestamp = time_obj.isoformat() - if time_obj.tzinfo == tzutc(): - timestamp = timestamp[:-6] - return '%sZ' % timestamp - return timestamp - - -def format_datetime(time_obj): - """ - Return a formatted string version of a datetime object. - - Format: - YYYY-MM-DDThh:mm:ss[.sss]TZD - - arguments: - time_obj -- A datetime object. - """ - timestamp = time_obj.isoformat('T') - if time_obj.tzinfo == tzutc(): - timestamp = timestamp[:-6] - return '%sZ' % timestamp - return timestamp - - -def date(year=None, month=None, day=None, obj=False): - """ - Create a date only timestamp for the given instant. - - Unspecified components default to their current counterparts. - - Arguments: - year -- Integer value of the year (4 digits) - month -- Integer value of the month - day -- Integer value of the day of the month. - obj -- If True, return the date object instead - of a formatted string. Defaults to False. - """ - today = dt.datetime.utcnow() - if year is None: - year = today.year - if month is None: - month = today.month - if day is None: - day = today.day - value = dt.date(year, month, day) - if obj: - return value - return format_date(value) - - -def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): - """ - Create a time only timestamp for the given instant. - - Unspecified components default to their current counterparts. - - Arguments: - hour -- Integer value of the hour. - min -- Integer value of the number of minutes. - sec -- Integer value of the number of seconds. - micro -- Integer value of the number of microseconds. - offset -- Either a positive or negative number of seconds - to offset from UTC to match a desired timezone, - or a tzinfo object. - obj -- If True, return the time object instead - of a formatted string. Defaults to False. - """ - now = dt.datetime.utcnow() - if hour is None: - hour = now.hour - if min is None: - min = now.minute - if sec is None: - sec = now.second - if micro is None: - micro = now.microsecond - if offset is None: - offset = tzutc() - elif not isinstance(offset, dt.tzinfo): - offset = tzoffset(None, offset) - value = dt.time(hour, min, sec, micro, offset) - if obj: - return value - return format_time(value) - - -def datetime(year=None, month=None, day=None, hour=None, - min=None, sec=None, micro=None, offset=None, - separators=True, obj=False): - """ - Create a datetime timestamp for the given instant. - - Unspecified components default to their current counterparts. - - Arguments: - year -- Integer value of the year (4 digits) - month -- Integer value of the month - day -- Integer value of the day of the month. - hour -- Integer value of the hour. - min -- Integer value of the number of minutes. - sec -- Integer value of the number of seconds. - micro -- Integer value of the number of microseconds. - offset -- Either a positive or negative number of seconds - to offset from UTC to match a desired timezone, - or a tzinfo object. - obj -- If True, return the datetime object instead - of a formatted string. Defaults to False. - """ - now = dt.datetime.utcnow() - if year is None: - year = now.year - if month is None: - month = now.month - if day is None: - day = now.day - if hour is None: - hour = now.hour - if min is None: - min = now.minute - if sec is None: - sec = now.second - if micro is None: - micro = now.microsecond - if offset is None: - offset = tzutc() - elif not isinstance(offset, dt.tzinfo): - offset = tzoffset(None, offset) - - value = dt.datetime(year, month, day, hour, - min, sec, micro, offset) - if obj: - return value - return format_datetime(value) - - -class XEP_0082(BasePlugin): - - """ - XEP-0082: XMPP Date and Time Profiles - - XMPP uses a subset of the formats allowed by ISO 8601 as a matter of - pragmatism based on the relatively few formats historically used by - the XMPP. - - Also see <http://www.xmpp.org/extensions/xep-0082.html>. - - Methods: - date -- Create a time stamp using the Date profile. - datetime -- Create a time stamp using the DateTime profile. - time -- Create a time stamp using the Time profile. - format_date -- Format an existing date object. - format_datetime -- Format an existing datetime object. - format_time -- Format an existing time object. - parse -- Convert a time string into a Python datetime object. - """ - - name = 'xep_0082' - description = 'XEP-0082: XMPP Date and Time Profiles' - dependencies = set() - - def plugin_init(self): - """Start the XEP-0082 plugin.""" - self.date = date - self.datetime = datetime - self.time = time - self.format_date = format_date - self.format_datetime = format_datetime - self.format_time = format_time - self.parse = parse - - -register_plugin(XEP_0082) diff --git a/sleekxmpp/plugins/xep_0084/__init__.py b/sleekxmpp/plugins/xep_0084/__init__.py deleted file mode 100644 index 6b87573f..00000000 --- a/sleekxmpp/plugins/xep_0084/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0084 import stanza -from sleekxmpp.plugins.xep_0084.stanza import Data, MetaData -from sleekxmpp.plugins.xep_0084.avatar import XEP_0084 - - -register_plugin(XEP_0084) diff --git a/sleekxmpp/plugins/xep_0084/avatar.py b/sleekxmpp/plugins/xep_0084/avatar.py deleted file mode 100644 index 677a888d..00000000 --- a/sleekxmpp/plugins/xep_0084/avatar.py +++ /dev/null @@ -1,111 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import hashlib -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0084 import stanza, Data, MetaData - - -log = logging.getLogger(__name__) - - -class XEP_0084(BasePlugin): - - name = 'xep_0084' - description = 'XEP-0084: User Avatar' - dependencies = set(['xep_0163', 'xep_0060']) - stanza = stanza - - def plugin_init(self): - pubsub_stanza = self.xmpp['xep_0060'].stanza - register_stanza_plugin(pubsub_stanza.Item, Data) - register_stanza_plugin(pubsub_stanza.EventItem, Data) - - self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace) - self.xmpp['xep_0163'].remove_interest(MetaData.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) - - def generate_id(self, data): - return hashlib.sha1(data).hexdigest() - - def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True, - callback=None, timeout=None): - return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def publish_avatar(self, data, ifrom=None, block=True, callback=None, - timeout=None): - payload = Data() - payload['value'] = data - return self.xmpp['xep_0163'].publish(payload, - id=self.generate_id(data), - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def publish_avatar_metadata(self, items=None, pointers=None, - ifrom=None, block=True, - callback=None, timeout=None): - metadata = MetaData() - if items is None: - items = [] - if not isinstance(items, (list, set)): - items = [items] - for info in items: - metadata.add_info(info['id'], info['type'], info['bytes'], - height=info.get('height', ''), - width=info.get('width', ''), - url=info.get('url', '')) - - if pointers is not None: - for pointer in pointers: - metadata.add_pointer(pointer) - - return self.xmpp['xep_0163'].publish(metadata, - id=info['id'], - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing avatar metadata information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - metadata = MetaData() - return self.xmpp['xep_0163'].publish(metadata, - node=MetaData.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0084/stanza.py b/sleekxmpp/plugins/xep_0084/stanza.py deleted file mode 100644 index 22f11b72..00000000 --- a/sleekxmpp/plugins/xep_0084/stanza.py +++ /dev/null @@ -1,78 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from base64 import b64encode, b64decode - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin - - -class Data(ElementBase): - name = 'data' - namespace = 'urn:xmpp:avatar:data' - plugin_attrib = 'avatar_data' - interfaces = set(['value']) - - def get_value(self): - if self.xml.text: - return b64decode(bytes(self.xml.text)) - return '' - - def set_value(self, value): - if value: - self.xml.text = b64encode(bytes(value)) - else: - self.xml.text = '' - - def del_value(self): - self.xml.text = '' - - -class MetaData(ElementBase): - name = 'metadata' - namespace = 'urn:xmpp:avatar:metadata' - plugin_attrib = 'avatar_metadata' - interfaces = set() - - def add_info(self, id, itype, ibytes, height=None, width=None, url=None): - info = Info() - info.values = {'id': id, - 'type': itype, - 'bytes': '%s' % ibytes, - 'height': height, - 'width': width, - 'url': url} - self.append(info) - - def add_pointer(self, xml): - if not isinstance(xml, Pointer): - pointer = Pointer() - pointer.append(xml) - self.append(pointer) - else: - self.append(xml) - - -class Info(ElementBase): - name = 'info' - namespace = 'urn:xmpp:avatar:metadata' - plugin_attrib = 'info' - plugin_multi_attrib = 'items' - interfaces = set(['bytes', 'height', 'id', 'type', 'url', 'width']) - - -class Pointer(ElementBase): - name = 'pointer' - namespace = 'urn:xmpp:avatar:metadata' - plugin_attrib = 'pointer' - plugin_multi_attrib = 'pointers' - interfaces = set() - - -register_stanza_plugin(MetaData, Info, iterable=True) -register_stanza_plugin(MetaData, Pointer, iterable=True) diff --git a/sleekxmpp/plugins/xep_0085/__init__.py b/sleekxmpp/plugins/xep_0085/__init__.py deleted file mode 100644 index 445d5059..00000000 --- a/sleekxmpp/plugins/xep_0085/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0085.stanza import ChatState -from sleekxmpp.plugins.xep_0085.chat_states import XEP_0085 - - -register_plugin(XEP_0085) - - -# Retain some backwards compatibility -xep_0085 = XEP_0085 diff --git a/sleekxmpp/plugins/xep_0085/chat_states.py b/sleekxmpp/plugins/xep_0085/chat_states.py deleted file mode 100644 index 17f82afd..00000000 --- a/sleekxmpp/plugins/xep_0085/chat_states.py +++ /dev/null @@ -1,56 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -import sleekxmpp -from sleekxmpp.stanza import Message -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0085 import stanza, ChatState - - -log = logging.getLogger(__name__) - - -class XEP_0085(BasePlugin): - - """ - XEP-0085 Chat State Notifications - """ - - name = 'xep_0085' - description = 'XEP-0085: Chat State Notifications' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('Chat State', - StanzaPath('message/chat_state'), - self._handle_chat_state)) - - register_stanza_plugin(Message, stanza.Active) - register_stanza_plugin(Message, stanza.Composing) - register_stanza_plugin(Message, stanza.Gone) - register_stanza_plugin(Message, stanza.Inactive) - register_stanza_plugin(Message, stanza.Paused) - - def plugin_end(self): - self.xmpp.remove_handler('Chat State') - - def session_bind(self, jid): - self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) - - def _handle_chat_state(self, msg): - state = msg['chat_state'] - log.debug("Chat State: %s, %s", state, msg['from'].jid) - self.xmpp.event('chatstate', msg) - self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0085/stanza.py b/sleekxmpp/plugins/xep_0085/stanza.py deleted file mode 100644 index c2cafb19..00000000 --- a/sleekxmpp/plugins/xep_0085/stanza.py +++ /dev/null @@ -1,94 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import sleekxmpp -from sleekxmpp.xmlstream import ElementBase, ET - - -class ChatState(ElementBase): - - """ - Example chat state stanzas: - <message> - <active xmlns="http://jabber.org/protocol/chatstates" /> - </message> - - <message> - <paused xmlns="http://jabber.org/protocol/chatstates" /> - </message> - - Stanza Interfaces: - chat_state - - Attributes: - states - - Methods: - get_chat_state - set_chat_state - del_chat_state - """ - - name = '' - namespace = 'http://jabber.org/protocol/chatstates' - plugin_attrib = 'chat_state' - interfaces = set(('chat_state',)) - sub_interfaces = interfaces - is_extension = True - - states = set(('active', 'composing', 'gone', 'inactive', 'paused')) - - def setup(self, xml=None): - self.xml = ET.Element('') - return True - - def get_chat_state(self): - parent = self.parent() - for state in self.states: - state_xml = parent.find('{%s}%s' % (self.namespace, state)) - if state_xml is not None: - self.xml = state_xml - return state - return '' - - def set_chat_state(self, state): - self.del_chat_state() - parent = self.parent() - if state in self.states: - self.xml = ET.Element('{%s}%s' % (self.namespace, state)) - parent.append(self.xml) - elif state not in [None, '']: - raise ValueError('Invalid chat state') - - def del_chat_state(self): - parent = self.parent() - for state in self.states: - state_xml = parent.find('{%s}%s' % (self.namespace, state)) - if state_xml is not None: - self.xml = ET.Element('') - parent.xml.remove(state_xml) - - -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' diff --git a/sleekxmpp/plugins/xep_0086/__init__.py b/sleekxmpp/plugins/xep_0086/__init__.py deleted file mode 100644 index 94600e85..00000000 --- a/sleekxmpp/plugins/xep_0086/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0086.stanza import LegacyError -from sleekxmpp.plugins.xep_0086.legacy_error import XEP_0086 - - -register_plugin(XEP_0086) - - -# Retain some backwards compatibility -xep_0086 = XEP_0086 diff --git a/sleekxmpp/plugins/xep_0086/legacy_error.py b/sleekxmpp/plugins/xep_0086/legacy_error.py deleted file mode 100644 index f7d0ac9c..00000000 --- a/sleekxmpp/plugins/xep_0086/legacy_error.py +++ /dev/null @@ -1,46 +0,0 @@ -"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-from sleekxmpp.stanza import Error
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins.xep_0086 import stanza, LegacyError
-
-
-class XEP_0086(BasePlugin):
-
- """
- XEP-0086: Error Condition Mappings
-
- Older XMPP implementations used code based error messages, similar
- to HTTP response codes. Since then, error condition elements have
- been introduced. XEP-0086 provides a mapping between the new
- condition elements and a combination of error types and the older
- response codes.
-
- Also see <http://xmpp.org/extensions/xep-0086.html>.
-
- Configuration Values:
- override -- Indicates if applying legacy error codes should
- be done automatically. Defaults to True.
- If False, then inserting legacy error codes can
- be done using:
- iq['error']['legacy']['condition'] = ...
- """
-
- name = 'xep_0086'
- description = 'XEP-0086: Error Condition Mappings'
- dependencies = set()
- stanza = stanza
- default_config = {
- 'override': True
- }
-
- def plugin_init(self):
- register_stanza_plugin(Error, LegacyError,
- overrides=self.override)
diff --git a/sleekxmpp/plugins/xep_0086/stanza.py b/sleekxmpp/plugins/xep_0086/stanza.py deleted file mode 100644 index d4909806..00000000 --- a/sleekxmpp/plugins/xep_0086/stanza.py +++ /dev/null @@ -1,91 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class LegacyError(ElementBase): - - """ - Older XMPP implementations used code based error messages, similar - to HTTP response codes. Since then, error condition elements have - been introduced. XEP-0086 provides a mapping between the new - condition elements and a combination of error types and the older - response codes. - - Also see <http://xmpp.org/extensions/xep-0086.html>. - - Example legacy error stanzas: - <error xmlns="jabber:client" code="501" type="cancel"> - <feature-not-implemented - xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> - </error> - - <error code="402" type="auth"> - <payment-required - xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> - </error> - - Attributes: - error_map -- A map of error conditions to error types and - code values. - Methods: - setup -- Overrides ElementBase.setup - set_condition -- Remap the type and code interfaces when a - condition is set. - """ - - name = 'legacy' - namespace = Error.namespace - plugin_attrib = name - interfaces = set(('condition',)) - overrides = ['set_condition'] - - 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 setup(self, xml): - """Don't create XML for the plugin.""" - self.xml = ET.Element('') - - def set_condition(self, value): - """ - Set the error type and code based on the given error - condition value. - - Arguments: - value -- The new error condition. - """ - self.parent().set_condition(value) - - error_data = self.error_map.get(value, None) - if error_data is not None: - if error_data[0] is not None: - self.parent()['type'] = error_data[0] - self.parent()['code'] = error_data[1] diff --git a/sleekxmpp/plugins/xep_0091/__init__.py b/sleekxmpp/plugins/xep_0091/__init__.py deleted file mode 100644 index 04f21ef5..00000000 --- a/sleekxmpp/plugins/xep_0091/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0091 import stanza -from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay -from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091 - - -register_plugin(XEP_0091) diff --git a/sleekxmpp/plugins/xep_0091/legacy_delay.py b/sleekxmpp/plugins/xep_0091/legacy_delay.py deleted file mode 100644 index 7323d468..00000000 --- a/sleekxmpp/plugins/xep_0091/legacy_delay.py +++ /dev/null @@ -1,29 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.stanza import Message, Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0091 import stanza - - -class XEP_0091(BasePlugin): - - """ - XEP-0091: Legacy Delayed Delivery - """ - - name = 'xep_0091' - description = 'XEP-0091: Legacy Delayed Delivery' - dependencies = set() - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, stanza.LegacyDelay) - register_stanza_plugin(Presence, stanza.LegacyDelay) diff --git a/sleekxmpp/plugins/xep_0091/stanza.py b/sleekxmpp/plugins/xep_0091/stanza.py deleted file mode 100644 index 17e55764..00000000 --- a/sleekxmpp/plugins/xep_0091/stanza.py +++ /dev/null @@ -1,47 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.plugins import xep_0082 - - -class LegacyDelay(ElementBase): - - name = 'x' - namespace = 'jabber:x:delay' - plugin_attrib = 'legacy_delay' - interfaces = set(('from', 'stamp', 'text')) - - def get_from(self): - from_ = self._get_attr('from') - return JID(from_) if from_ else None - - def set_from(self, value): - self._set_attr('from', str(value)) - - def get_stamp(self): - timestamp = self._get_attr('stamp') - return xep_0082.parse('%sZ' % timestamp) if timestamp else None - - def set_stamp(self, value): - if isinstance(value, dt.datetime): - value = value.astimezone(xep_0082.tzutc) - value = xep_0082.format_datetime(value) - self._set_attr('stamp', value[0:19].replace('-', '')) - - def get_text(self): - return self.xml.text - - def set_text(self, value): - self.xml.text = value - - def del_text(self): - self.xml.text = '' diff --git a/sleekxmpp/plugins/xep_0092/__init__.py b/sleekxmpp/plugins/xep_0092/__init__.py deleted file mode 100644 index 293eaae6..00000000 --- a/sleekxmpp/plugins/xep_0092/__init__.py +++ /dev/null @@ -1,20 +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.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0092 import stanza -from sleekxmpp.plugins.xep_0092.stanza import Version -from sleekxmpp.plugins.xep_0092.version import XEP_0092 - - -register_plugin(XEP_0092) - - -# Retain some backwards compatibility -xep_0092 = XEP_0092 diff --git a/sleekxmpp/plugins/xep_0092/stanza.py b/sleekxmpp/plugins/xep_0092/stanza.py deleted file mode 100644 index 77654e37..00000000 --- a/sleekxmpp/plugins/xep_0092/stanza.py +++ /dev/null @@ -1,42 +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 - - -class Version(ElementBase): - - """ - XMPP allows for an agent to advertise the name and version of the - underlying software libraries, as well as the operating system - that the agent is running on. - - Example version stanzas: - <iq type="get"> - <query xmlns="jabber:iq:version" /> - </iq> - - <iq type="result"> - <query xmlns="jabber:iq:version"> - <name>SleekXMPP</name> - <version>1.0</version> - <os>Linux</os> - </query> - </iq> - - Stanza Interface: - name -- The human readable name of the software. - version -- The specific version of the software. - os -- The name of the operating system running the program. - """ - - name = 'query' - namespace = 'jabber:iq:version' - plugin_attrib = 'software_version' - interfaces = set(('name', 'version', 'os')) - sub_interfaces = interfaces diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py deleted file mode 100644 index b16ad516..00000000 --- a/sleekxmpp/plugins/xep_0092/version.py +++ /dev/null @@ -1,85 +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 sleekxmpp -from sleekxmpp import Iq -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0092 import Version, stanza - - -log = logging.getLogger(__name__) - - -class XEP_0092(BasePlugin): - - """ - XEP-0092: Software Version - """ - - name = 'xep_0092' - description = 'XEP-0092: Software Version' - dependencies = set(['xep_0030']) - stanza = stanza - default_config = { - 'software_name': 'SleekXMPP', - 'version': sleekxmpp.__version__, - 'os': '' - } - - def plugin_init(self): - """ - Start the XEP-0092 plugin. - """ - if 'name' in self.config: - self.software_name = self.config['name'] - - self.xmpp.register_handler( - Callback('Software Version', - StanzaPath('iq@type=get/software_version'), - self._handle_version)) - - register_stanza_plugin(Iq, Version) - - def plugin_end(self): - self.xmpp.remove_handler('Software Version') - self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version') - - def session_bind(self, jid): - self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') - - def _handle_version(self, iq): - """ - Respond to a software version query. - - Arguments: - iq -- The Iq stanza containing the software version query. - """ - iq.reply() - iq['software_version']['name'] = self.software_name - iq['software_version']['version'] = self.version - iq['software_version']['os'] = self.os - iq.send() - - def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None): - """ - Retrieve the software version of a remote agent. - - Arguments: - jid -- The JID of the entity to query. - """ - iq = self.xmpp.Iq() - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'get' - iq['query'] = Version.namespace - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0095/__init__.py b/sleekxmpp/plugins/xep_0095/__init__.py deleted file mode 100644 index 4465ef5c..00000000 --- a/sleekxmpp/plugins/xep_0095/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0095 import stanza -from sleekxmpp.plugins.xep_0095.stanza import SI -from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095 - - -register_plugin(XEP_0095) diff --git a/sleekxmpp/plugins/xep_0095/stanza.py b/sleekxmpp/plugins/xep_0095/stanza.py deleted file mode 100644 index 34999a11..00000000 --- a/sleekxmpp/plugins/xep_0095/stanza.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class SI(ElementBase): - name = 'si' - namespace = 'http://jabber.org/protocol/si' - plugin_attrib = 'si' - interfaces = set(['id', 'mime_type', 'profile']) - - def get_mime_type(self): - return self._get_attr('mime-type', 'application/octet-stream') - - def set_mime_type(self, value): - self._set_attr('mime-type', value) - - def del_mime_type(self): - self._del_attr('mime-type') diff --git a/sleekxmpp/plugins/xep_0095/stream_initiation.py b/sleekxmpp/plugins/xep_0095/stream_initiation.py deleted file mode 100644 index 927248a5..00000000 --- a/sleekxmpp/plugins/xep_0095/stream_initiation.py +++ /dev/null @@ -1,214 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import threading - -from uuid import uuid4 - -from sleekxmpp import Iq, Message -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0095 import stanza, SI - - -log = logging.getLogger(__name__) - - -SOCKS5 = 'http://jabber.org/protocol/bytestreams' -IBB = 'http://jabber.org/protocol/ibb' - - -class XEP_0095(BasePlugin): - - name = 'xep_0095' - description = 'XEP-0095: Stream Initiation' - dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065']) - stanza = stanza - - def plugin_init(self): - self._profiles = {} - self._methods = {} - self._methods_order = [] - self._pending_lock = threading.Lock() - self._pending= {} - - self.register_method(SOCKS5, 'xep_0065', 100) - self.register_method(IBB, 'xep_0047', 50) - - register_stanza_plugin(Iq, SI) - register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation) - - self.xmpp.register_handler( - Callback('SI Request', - StanzaPath('iq@type=set/si'), - self._handle_request)) - - self.api.register(self._add_pending, 'add_pending', default=True) - self.api.register(self._get_pending, 'get_pending', default=True) - self.api.register(self._del_pending, 'del_pending', default=True) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(SI.namespace) - - def plugin_end(self): - self.xmpp.remove_handler('SI Request') - self.xmpp['xep_0030'].del_feature(feature=SI.namespace) - - def register_profile(self, profile_name, plugin): - self._profiles[profile_name] = plugin - - def unregister_profile(self, profile_name): - try: - del self._profiles[profile_name] - except KeyError: - pass - - def register_method(self, method, plugin_name, order=50): - self._methods[method] = (plugin_name, order) - self._methods_order.append((order, method, plugin_name)) - self._methods_order.sort() - - def unregister_method(self, method): - if method in self._methods: - plugin_name, order = self._methods[method] - del self._methods[method] - self._methods_order.remove((order, method, plugin_name)) - self._methods_order.sort() - - def _handle_request(self, iq): - profile = iq['si']['profile'] - sid = iq['si']['id'] - - if not sid: - raise XMPPError(etype='modify', condition='bad-request') - if profile not in self._profiles: - raise XMPPError( - etype='modify', - condition='bad-request', - extension='bad-profile', - extension_ns=SI.namespace) - - neg = iq['si']['feature_neg']['form']['fields'] - options = neg['stream-method']['options'] or [] - methods = [] - for opt in options: - methods.append(opt['value']) - for method in methods: - if method in self._methods: - supported = True - break - else: - raise XMPPError('bad-request', - extension='no-valid-streams', - extension_ns=SI.namespace) - - selected_method = None - log.debug('Available: %s', methods) - for order, method, plugin in self._methods_order: - log.debug('Testing: %s', method) - if method in methods: - selected_method = method - break - - receiver = iq['to'] - sender = iq['from'] - - self.api['add_pending'](receiver, sid, sender, { - 'response_id': iq['id'], - 'method': selected_method, - 'profile': profile - }) - self.xmpp.event('si_request', iq) - - def offer(self, jid, sid=None, mime_type=None, profile=None, - methods=None, payload=None, ifrom=None, - **iqargs): - if sid is None: - sid = uuid4().hex - if methods is None: - methods = list(self._methods.keys()) - if not isinstance(methods, (list, tuple, set)): - methods = [methods] - - si = self.xmpp.Iq() - si['to'] = jid - si['from'] = ifrom - si['type'] = 'set' - si['si']['id'] = sid - si['si']['mime_type'] = mime_type - si['si']['profile'] = profile - if not isinstance(payload, (list, tuple, set)): - payload = [payload] - for item in payload: - si['si'].append(item) - si['si']['feature_neg']['form'].add_field( - var='stream-method', - ftype='list-single', - options=methods) - return si.send(**iqargs) - - def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None): - stream = self.api['get_pending'](ifrom, sid, jid) - iq = self.xmpp.Iq() - iq['id'] = stream['response_id'] - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'result' - if payload: - iq['si'].append(payload) - iq['si']['feature_neg']['form']['type'] = 'submit' - iq['si']['feature_neg']['form'].add_field( - var='stream-method', - ftype='list-single', - value=stream['method']) - - if ifrom is None: - ifrom = self.xmpp.boundjid - - method_plugin = self._methods[stream['method']][0] - self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid) - - self.api['del_pending'](ifrom, sid, jid) - - if stream_handler: - self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid), - stream_handler, - threaded=True, - disposable=True) - return iq.send() - - def decline(self, jid, sid, ifrom=None): - stream = self.api['get_pending'](ifrom, sid, jid) - if not stream: - return - iq = self.xmpp.Iq() - iq['id'] = stream['response_id'] - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'error' - iq['error']['condition'] = 'forbidden' - iq['error']['text'] = 'Offer declined' - self.api['del_pending'](ifrom, sid, jid) - return iq.send() - - def _add_pending(self, jid, node, ifrom, data): - with self._pending_lock: - self._pending[(jid, node, ifrom)] = data - - def _get_pending(self, jid, node, ifrom, data): - with self._pending_lock: - return self._pending.get((jid, node, ifrom), None) - - def _del_pending(self, jid, node, ifrom, data): - with self._pending_lock: - if (jid, node, ifrom) in self._pending: - del self._pending[(jid, node, ifrom)] diff --git a/sleekxmpp/plugins/xep_0096/__init__.py b/sleekxmpp/plugins/xep_0096/__init__.py deleted file mode 100644 index 5f836169..00000000 --- a/sleekxmpp/plugins/xep_0096/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0096 import stanza -from sleekxmpp.plugins.xep_0096.stanza import File -from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096 - - -register_plugin(XEP_0096) diff --git a/sleekxmpp/plugins/xep_0096/file_transfer.py b/sleekxmpp/plugins/xep_0096/file_transfer.py deleted file mode 100644 index 6873c7f5..00000000 --- a/sleekxmpp/plugins/xep_0096/file_transfer.py +++ /dev/null @@ -1,58 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq, Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0096 import stanza, File - - -log = logging.getLogger(__name__) - - -class XEP_0096(BasePlugin): - - name = 'xep_0096' - description = 'XEP-0096: SI File Transfer' - dependencies = set(['xep_0095']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File) - - self.xmpp['xep_0095'].register_profile(File.namespace, self) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(File.namespace) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=File.namespace) - self.xmpp['xep_0095'].unregister_profile(File.namespace, self) - - def request_file_transfer(self, jid, sid=None, name=None, size=None, - desc=None, hash=None, date=None, - allow_ranged=False, mime_type=None, - **iqargs): - data = File() - data['name'] = name - data['size'] = size - data['date'] = date - data['desc'] = desc - if allow_ranged: - data.enable('range') - - return self.xmpp['xep_0095'].offer(jid, - sid=sid, - mime_type=mime_type, - profile=File.namespace, - payload=data, - **iqargs) diff --git a/sleekxmpp/plugins/xep_0096/stanza.py b/sleekxmpp/plugins/xep_0096/stanza.py deleted file mode 100644 index 65eb5bc5..00000000 --- a/sleekxmpp/plugins/xep_0096/stanza.py +++ /dev/null @@ -1,48 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin -from sleekxmpp.plugins import xep_0082 - - -class File(ElementBase): - name = 'file' - namespace = 'http://jabber.org/protocol/si/profile/file-transfer' - plugin_attrib = 'file' - interfaces = set(['name', 'size', 'date', 'hash', 'desc']) - sub_interfaces = set(['desc']) - - def set_size(self, value): - self._set_attr('size', str(value)) - - def get_date(self): - timestamp = self._get_attr('date') - return xep_0082.parse(timestamp) - - def set_date(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_attr('date', value) - - -class Range(ElementBase): - name = 'range' - namespace = 'http://jabber.org/protocol/si/profile/file-transfer' - plugin_attrib = 'range' - interfaces = set(['length', 'offset']) - - def set_length(self, value): - self._set_attr('length', str(value)) - - def set_offset(self, value): - self._set_attr('offset', str(value)) - - -register_stanza_plugin(File, Range) diff --git a/sleekxmpp/plugins/xep_0106.py b/sleekxmpp/plugins/xep_0106.py deleted file mode 100644 index 1859a77b..00000000 --- a/sleekxmpp/plugins/xep_0106.py +++ /dev/null @@ -1,26 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.plugins import BasePlugin, register_plugin - - -class XEP_0106(BasePlugin): - - name = 'xep_0106' - description = 'XEP-0106: JID Escaping' - dependencies = set(['xep_0030']) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping') - - -register_plugin(XEP_0106) diff --git a/sleekxmpp/plugins/xep_0107/__init__.py b/sleekxmpp/plugins/xep_0107/__init__.py deleted file mode 100644 index 04302df8..00000000 --- a/sleekxmpp/plugins/xep_0107/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0107 import stanza -from sleekxmpp.plugins.xep_0107.stanza import UserMood -from sleekxmpp.plugins.xep_0107.user_mood import XEP_0107 - - -register_plugin(XEP_0107) diff --git a/sleekxmpp/plugins/xep_0107/stanza.py b/sleekxmpp/plugins/xep_0107/stanza.py deleted file mode 100644 index 2c5814ea..00000000 --- a/sleekxmpp/plugins/xep_0107/stanza.py +++ /dev/null @@ -1,55 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class UserMood(ElementBase): - - name = 'mood' - namespace = 'http://jabber.org/protocol/mood' - plugin_attrib = 'mood' - interfaces = set(['value', 'text']) - sub_interfaces = set(['text']) - moods = set(['afraid', 'amazed', 'amorous', 'angry', 'annoyed', 'anxious', - 'aroused', 'ashamed', 'bored', 'brave', 'calm', 'cautious', - 'cold', 'confident', 'confused', 'contemplative', 'contented', - 'cranky', 'crazy', 'creative', 'curious', 'dejected', - 'depressed', 'disappointed', 'disgusted', 'dismayed', - 'distracted', 'embarrassed', 'envious', 'excited', - 'flirtatious', 'frustrated', 'grateful', 'grieving', 'grumpy', - 'guilty', 'happy', 'hopeful', 'hot', 'humbled', 'humiliated', - 'hungry', 'hurt', 'impressed', 'in_awe', 'in_love', - 'indignant', 'interested', 'intoxicated', 'invincible', - 'jealous', 'lonely', 'lost', 'lucky', 'mean', 'moody', - 'nervous', 'neutral', 'offended', 'outraged', 'playful', - 'proud', 'relaxed', 'relieved', 'remorseful', 'restless', - 'sad', 'sarcastic', 'satisfied', 'serious', 'shocked', - 'shy', 'sick', 'sleepy', 'spontaneous', 'stressed', 'strong', - 'surprised', 'thankful', 'thirsty', 'tired', 'undefined', - 'weak', 'worried']) - - def set_value(self, value): - self.del_value() - if value in self.moods: - self._set_sub_text(value, '', keep=True) - else: - raise ValueError('Unknown mood value') - - def get_value(self): - for child in self.xml: - if child.tag.startswith('{%s}' % self.namespace): - elem_name = child.tag.split('}')[-1] - if elem_name in self.moods: - return elem_name - return '' - - def del_value(self): - curr_value = self.get_value() - if curr_value: - self._set_sub_text(curr_value, '', keep=False) diff --git a/sleekxmpp/plugins/xep_0107/user_mood.py b/sleekxmpp/plugins/xep_0107/user_mood.py deleted file mode 100644 index 2d2f3551..00000000 --- a/sleekxmpp/plugins/xep_0107/user_mood.py +++ /dev/null @@ -1,93 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Message -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0107 import stanza, UserMood - - -log = logging.getLogger(__name__) - - -class XEP_0107(BasePlugin): - - """ - XEP-0107: User Mood - """ - - name = 'xep_0107' - description = 'XEP-0107: User Mood' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, UserMood) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace) - self.xmpp['xep_0163'].remove_interest(UserMood.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_mood', UserMood) - - def publish_mood(self, value=None, text=None, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Publish the user's current mood. - - Arguments: - value -- The name of the mood to publish. - text -- Optional natural-language description or reason - for the mood. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - mood = UserMood() - mood['value'] = value - mood['text'] = text - return self.xmpp['xep_0163'].publish(mood, - node=UserMood.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user mood information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - mood = UserMood() - return self.xmpp['xep_0163'].publish(mood, - node=UserMood.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0108/__init__.py b/sleekxmpp/plugins/xep_0108/__init__.py deleted file mode 100644 index 34d45113..00000000 --- a/sleekxmpp/plugins/xep_0108/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0108 import stanza -from sleekxmpp.plugins.xep_0108.stanza import UserActivity -from sleekxmpp.plugins.xep_0108.user_activity import XEP_0108 - - -register_plugin(XEP_0108) diff --git a/sleekxmpp/plugins/xep_0108/stanza.py b/sleekxmpp/plugins/xep_0108/stanza.py deleted file mode 100644 index 4650160a..00000000 --- a/sleekxmpp/plugins/xep_0108/stanza.py +++ /dev/null @@ -1,83 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class UserActivity(ElementBase): - - name = 'activity' - namespace = 'http://jabber.org/protocol/activity' - plugin_attrib = 'activity' - interfaces = set(['value', 'text']) - sub_interfaces = set(['text']) - general = set(['doing_chores', 'drinking', 'eating', 'exercising', - 'grooming', 'having_appointment', 'inactive', 'relaxing', - 'talking', 'traveling', 'undefined', 'working']) - specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries', - 'cleaning', 'coding', 'commuting', 'cooking', 'cycling', - 'dancing', 'day_off', 'doing_maintenance', - 'doing_the_dishes', 'doing_the_laundry', 'driving', - 'fishing', 'gaming', 'gardening', 'getting_a_haircut', - 'going_out', 'hanging_out', 'having_a_beer', - 'having_a_snack', 'having_breakfast', 'having_coffee', - 'having_dinner', 'having_lunch', 'having_tea', 'hiding', - 'hiking', 'in_a_car', 'in_a_meeting', 'in_real_life', - 'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', - 'on_a_trip', 'on_the_phone', 'on_vacation', - 'on_video_phone', 'other', 'partying', 'playing_sports', - 'praying', 'reading', 'rehearsing', 'running', - 'running_an_errand', 'scheduled_holiday', 'shaving', - 'shopping', 'skiing', 'sleeping', 'smoking', - 'socializing', 'studying', 'sunbathing', 'swimming', - 'taking_a_bath', 'taking_a_shower', 'thinking', - 'walking', 'walking_the_dog', 'watching_a_movie', - 'watching_tv', 'working_out', 'writing']) - - def set_value(self, value): - self.del_value() - general = value - specific = None - if isinstance(value, tuple) or isinstance(value, list): - general = value[0] - specific = value[1] - - if general in self.general: - gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) - if specific: - spec_xml = ET.Element('{%s}%s' % (self.namespace, specific)) - if specific in self.specific: - gen_xml.append(spec_xml) - else: - raise ValueError('Unknown specific activity') - self.xml.append(gen_xml) - else: - raise ValueError('Unknown general activity') - - def get_value(self): - general = None - specific = None - gen_xml = None - for child in self.xml: - if child.tag.startswith('{%s}' % self.namespace): - elem_name = child.tag.split('}')[-1] - if elem_name in self.general: - general = elem_name - gen_xml = child - if gen_xml is not None: - for child in gen_xml: - if child.tag.startswith('{%s}' % self.namespace): - elem_name = child.tag.split('}')[-1] - if elem_name in self.specific: - specific = elem_name - return (general, specific) - - def del_value(self): - curr_value = self.get_value() - if curr_value[0]: - self._set_sub_text(curr_value[0], '', keep=False) diff --git a/sleekxmpp/plugins/xep_0108/user_activity.py b/sleekxmpp/plugins/xep_0108/user_activity.py deleted file mode 100644 index 3a2f49b8..00000000 --- a/sleekxmpp/plugins/xep_0108/user_activity.py +++ /dev/null @@ -1,88 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0108 import stanza, UserActivity - - -log = logging.getLogger(__name__) - - -class XEP_0108(BasePlugin): - - """ - XEP-0108: User Activity - """ - - name = 'xep_0108' - description = 'XEP-0108: User Activity' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace) - self.xmpp['xep_0163'].remove_interest(UserActivity.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) - - def publish_activity(self, general, specific=None, text=None, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Publish the user's current activity. - - Arguments: - general -- The required general category of the activity. - specific -- Optional specific activity being done as part - of the general category. - text -- Optional natural-language description or reason - for the activity. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - activity = UserActivity() - activity['value'] = (general, specific) - activity['text'] = text - return self.xmpp['xep_0163'].publish(activity, - node=UserActivity.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user activity information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - activity = UserActivity() - return self.xmpp['xep_0163'].publish(activity, - node=UserActivity.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0115/__init__.py b/sleekxmpp/plugins/xep_0115/__init__.py deleted file mode 100644 index 31a2c03a..00000000 --- a/sleekxmpp/plugins/xep_0115/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0115.stanza import Capabilities -from sleekxmpp.plugins.xep_0115.static import StaticCaps -from sleekxmpp.plugins.xep_0115.caps import XEP_0115 - - -register_plugin(XEP_0115) - - -# Retain some backwards compatibility -xep_0115 = XEP_0115 diff --git a/sleekxmpp/plugins/xep_0115/caps.py b/sleekxmpp/plugins/xep_0115/caps.py deleted file mode 100644 index 41b5c52e..00000000 --- a/sleekxmpp/plugins/xep_0115/caps.py +++ /dev/null @@ -1,345 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import hashlib -import base64 -import threading - -from sleekxmpp import __version__ -from sleekxmpp.stanza import StreamFeatures, Presence, Iq -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0115 import stanza, StaticCaps - - -log = logging.getLogger(__name__) - - -class XEP_0115(BasePlugin): - - """ - XEP-0115: Entity Capabalities - """ - - name = 'xep_0115' - description = 'XEP-0115: Entity Capabilities' - dependencies = set(['xep_0030', 'xep_0128', 'xep_0004']) - stanza = stanza - default_config = { - 'hash': 'sha-1', - 'caps_node': None, - 'broadcast': True - } - - def plugin_init(self): - self.hashes = {'sha-1': hashlib.sha1, - 'sha1': hashlib.sha1, - 'md5': hashlib.md5} - - if self.caps_node is None: - self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__ - - register_stanza_plugin(Presence, stanza.Capabilities) - register_stanza_plugin(StreamFeatures, stanza.Capabilities) - - self._disco_ops = ['cache_caps', - 'get_caps', - 'assign_verstring', - 'get_verstring', - 'supports', - 'has_identity'] - - self.xmpp.register_handler( - Callback('Entity Capabilites', - StanzaPath('presence/caps'), - self._handle_caps)) - - self.xmpp.add_filter('out', self._filter_add_caps) - - self.xmpp.add_event_handler('entity_caps', self._process_caps, - threaded=True) - - if not self.xmpp.is_component: - self.xmpp.register_feature('caps', - self._handle_caps_feature, - restart=False, - order=10010) - - disco = self.xmpp['xep_0030'] - self.static = StaticCaps(self.xmpp, disco.static) - - for op in self._disco_ops: - self.api.register(getattr(self.static, op), op, default=True) - - for op in ('supports', 'has_identity'): - self.xmpp['xep_0030'].api.register(getattr(self.static, op), op) - - self._run_node_handler = disco._run_node_handler - - disco.cache_caps = self.cache_caps - disco.update_caps = self.update_caps - disco.assign_verstring = self.assign_verstring - disco.get_verstring = self.get_verstring - - self._processing_lock = threading.Lock() - self._processing = set() - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) - self.xmpp.del_filter('out', self._filter_add_caps) - self.xmpp.del_event_handler('entity_caps', self._process_caps) - self.xmpp.remove_handler('Entity Capabilities') - if not self.xmpp.is_component: - self.xmpp.unregister_feature('caps', 10010) - for op in ('supports', 'has_identity'): - self.xmpp['xep_0030'].restore_defaults(op) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) - - def _filter_add_caps(self, stanza): - if not isinstance(stanza, Presence) or not self.broadcast: - return stanza - - if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'): - return stanza - - ver = self.get_verstring(stanza['from']) - if ver: - stanza['caps']['node'] = self.caps_node - stanza['caps']['hash'] = self.hash - stanza['caps']['ver'] = ver - return stanza - - def _handle_caps(self, presence): - if not self.xmpp.is_component: - if presence['from'] == self.xmpp.boundjid: - return - self.xmpp.event('entity_caps', presence) - - def _handle_caps_feature(self, features): - # We already have a method to process presence with - # caps, so wrap things up and use that. - p = Presence() - p['from'] = self.xmpp.boundjid.domain - p.append(features['caps']) - self.xmpp.features.add('caps') - - self.xmpp.event('entity_caps', p) - - def _process_caps(self, pres): - if not pres['caps']['hash']: - log.debug("Received unsupported legacy caps: %s, %s, %s", - pres['caps']['node'], - pres['caps']['ver'], - pres['caps']['ext']) - self.xmpp.event('entity_caps_legacy', pres) - return - - ver = pres['caps']['ver'] - - existing_verstring = self.get_verstring(pres['from'].full) - if str(existing_verstring) == str(ver): - return - - existing_caps = self.get_caps(verstring=ver) - if existing_caps is not None: - self.assign_verstring(pres['from'], ver) - return - - if pres['caps']['hash'] not in self.hashes: - try: - log.debug("Unknown caps hash: %s", pres['caps']['hash']) - self.xmpp['xep_0030'].get_info(jid=pres['from']) - return - except XMPPError: - return - - # Only lookup the same caps once at a time. - with self._processing_lock: - if ver in self._processing: - log.debug('Already processing verstring %s' % ver) - return - self._processing.add(ver) - - log.debug("New caps verification string: %s", ver) - try: - node = '%s#%s' % (pres['caps']['node'], ver) - caps = self.xmpp['xep_0030'].get_info(pres['from'], node) - - if isinstance(caps, Iq): - caps = caps['disco_info'] - - if self._validate_caps(caps, pres['caps']['hash'], - pres['caps']['ver']): - self.assign_verstring(pres['from'], pres['caps']['ver']) - except XMPPError: - log.debug("Could not retrieve disco#info results for caps for %s", node) - - with self._processing_lock: - self._processing.remove(ver) - - def _validate_caps(self, caps, hash, check_verstring): - # Check Identities - full_ids = caps.get_identities(dedupe=False) - deduped_ids = caps.get_identities() - if len(full_ids) != len(deduped_ids): - log.debug("Duplicate disco identities found, invalid for caps") - return False - - # Check Features - full_features = caps.get_features(dedupe=False) - deduped_features = caps.get_features() - if len(full_features) != len(deduped_features): - log.debug("Duplicate disco features found, invalid for caps") - return False - - # Check Forms - form_types = [] - deduped_form_types = set() - for stanza in caps['substanzas']: - if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): - log.debug("Non form extension found, ignoring for caps") - caps.xml.remove(stanza.xml) - continue - if 'FORM_TYPE' in stanza['fields']: - f_type = tuple(stanza['fields']['FORM_TYPE']['value']) - form_types.append(f_type) - deduped_form_types.add(f_type) - if len(form_types) != len(deduped_form_types): - log.debug("Duplicated FORM_TYPE values, " + \ - "invalid for caps") - return False - - if len(f_type) > 1: - deduped_type = set(f_type) - if len(f_type) != len(deduped_type): - log.debug("Extra FORM_TYPE data, invalid for caps") - return False - - if stanza['fields']['FORM_TYPE']['type'] != 'hidden': - log.debug("Field FORM_TYPE type not 'hidden', " + \ - "ignoring form for caps") - caps.xml.remove(stanza.xml) - else: - log.debug("No FORM_TYPE found, ignoring form for caps") - caps.xml.remove(stanza.xml) - - verstring = self.generate_verstring(caps, hash) - if verstring != check_verstring: - log.debug("Verification strings do not match: %s, %s" % ( - verstring, check_verstring)) - return False - - self.cache_caps(verstring, caps) - return True - - def generate_verstring(self, info, hash): - hash = self.hashes.get(hash, None) - if hash is None: - return None - - S = '' - - # Convert None to '' in the identities - def clean_identity(id): - return map(lambda i: i or '', id) - identities = map(clean_identity, info['identities']) - - identities = sorted(('/'.join(i) for i in identities)) - features = sorted(info['features']) - - S += '<'.join(identities) + '<' - S += '<'.join(features) + '<' - - form_types = {} - - for stanza in info['substanzas']: - if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): - if 'FORM_TYPE' in stanza['fields']: - f_type = stanza['values']['FORM_TYPE'] - if len(f_type): - f_type = f_type[0] - if f_type not in form_types: - form_types[f_type] = [] - form_types[f_type].append(stanza) - - sorted_forms = sorted(form_types.keys()) - for f_type in sorted_forms: - for form in form_types[f_type]: - S += '%s<' % f_type - fields = sorted(form['fields'].keys()) - fields.remove('FORM_TYPE') - for field in fields: - S += '%s<' % field - vals = form['fields'][field].get_value(convert=False) - if vals is None: - S += '<' - else: - if not isinstance(vals, list): - vals = [vals] - S += '<'.join(sorted(vals)) + '<' - - binary = hash(S.encode('utf8')).digest() - return base64.b64encode(binary).decode('utf-8') - - def update_caps(self, jid=None, node=None, preserve=False): - try: - info = self.xmpp['xep_0030'].get_info(jid, node, local=True) - if isinstance(info, Iq): - info = info['disco_info'] - ver = self.generate_verstring(info, self.hash) - self.xmpp['xep_0030'].set_info( - jid=jid, - node='%s#%s' % (self.caps_node, ver), - info=info) - self.cache_caps(ver, info) - self.assign_verstring(jid, ver) - - if self.xmpp.session_started_event.is_set() and self.broadcast: - if self.xmpp.is_component or preserve: - for contact in self.xmpp.roster[jid]: - self.xmpp.roster[jid][contact].send_last_presence() - else: - self.xmpp.roster[jid].send_last_presence() - except XMPPError: - return - - def get_verstring(self, jid=None): - if jid in ('', None): - jid = self.xmpp.boundjid.full - if isinstance(jid, JID): - jid = jid.full - return self.api['get_verstring'](jid) - - def assign_verstring(self, jid=None, verstring=None): - if jid in (None, ''): - jid = self.xmpp.boundjid.full - if isinstance(jid, JID): - jid = jid.full - return self.api['assign_verstring'](jid, args={ - 'verstring': verstring}) - - def cache_caps(self, verstring=None, info=None): - data = {'verstring': verstring, 'info': info} - return self.api['cache_caps'](args=data) - - def get_caps(self, jid=None, verstring=None): - if verstring is None: - if jid is not None: - verstring = self.get_verstring(jid) - else: - return None - if isinstance(jid, JID): - jid = jid.full - data = {'verstring': verstring} - return self.api['get_caps'](jid, args=data) diff --git a/sleekxmpp/plugins/xep_0115/stanza.py b/sleekxmpp/plugins/xep_0115/stanza.py deleted file mode 100644 index 3e80b5cf..00000000 --- a/sleekxmpp/plugins/xep_0115/stanza.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import unicode_literals - -from sleekxmpp.xmlstream import ElementBase - - -class Capabilities(ElementBase): - - namespace = 'http://jabber.org/protocol/caps' - name = 'c' - plugin_attrib = 'caps' - interfaces = set(('hash', 'node', 'ver', 'ext')) diff --git a/sleekxmpp/plugins/xep_0115/static.py b/sleekxmpp/plugins/xep_0115/static.py deleted file mode 100644 index f83c244c..00000000 --- a/sleekxmpp/plugins/xep_0115/static.py +++ /dev/null @@ -1,146 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import JID -from sleekxmpp.exceptions import IqError, IqTimeout - - -log = logging.getLogger(__name__) - - -class StaticCaps(object): - - """ - Extend the default StaticDisco implementation to provide - support for extended identity information. - """ - - def __init__(self, xmpp, static): - """ - Augment the default XEP-0030 static handler object. - - Arguments: - static -- The default static XEP-0030 handler object. - """ - self.xmpp = xmpp - self.disco = self.xmpp['xep_0030'] - self.caps = self.xmpp['xep_0115'] - self.static = static - self.ver_cache = {} - self.jid_vers = {} - - def supports(self, jid, node, ifrom, data): - """ - Check if a JID supports a given feature. - - The data parameter may provide: - feature -- The feature to check for support. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - """ - feature = data.get('feature', None) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - if not feature: - return False - - if node in (None, ''): - info = self.caps.get_caps(jid) - if info and feature in info['features']: - return True - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - return feature in info['disco_info']['features'] - except IqError: - return False - except IqTimeout: - return None - - def has_identity(self, jid, node, ifrom, data): - """ - Check if a JID has a given identity. - - The data parameter may provide: - category -- The category of the identity to check. - itype -- The type of the identity to check. - lang -- The language of the identity to check. - local -- If true, then the query is for a JID/node - combination handled by this Sleek instance and - no stanzas need to be sent. - Otherwise, a disco stanza must be sent to the - remove JID to retrieve the info. - cached -- If true, then look for the disco info data from - the local cache system. If no results are found, - send the query as usual. The self.use_cache - setting must be set to true for this option to - be useful. If set to false, then the cache will - be skipped, even if a result has already been - cached. Defaults to false. - """ - identity = (data.get('category', None), - data.get('itype', None), - data.get('lang', None)) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - trunc = lambda i: (i[0], i[1], i[2]) - - if node in (None, ''): - info = self.caps.get_caps(jid) - if info and identity in map(trunc, info['identities']): - return True - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - return identity in map(trunc, info['disco_info']['identities']) - except IqError: - return False - except IqTimeout: - return None - - def cache_caps(self, jid, node, ifrom, data): - with self.static.lock: - verstring = data.get('verstring', None) - info = data.get('info', None) - if not verstring or not info: - return - self.ver_cache[verstring] = info - - def assign_verstring(self, jid, node, ifrom, data): - with self.static.lock: - if isinstance(jid, JID): - jid = jid.full - self.jid_vers[jid] = data.get('verstring', None) - - def get_verstring(self, jid, node, ifrom, data): - with self.static.lock: - return self.jid_vers.get(jid, None) - - def get_caps(self, jid, node, ifrom, data): - with self.static.lock: - return self.ver_cache.get(data.get('verstring', None), None) diff --git a/sleekxmpp/plugins/xep_0118/__init__.py b/sleekxmpp/plugins/xep_0118/__init__.py deleted file mode 100644 index 565f7844..00000000 --- a/sleekxmpp/plugins/xep_0118/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0118 import stanza -from sleekxmpp.plugins.xep_0118.stanza import UserTune -from sleekxmpp.plugins.xep_0118.user_tune import XEP_0118 - - -register_plugin(XEP_0118) diff --git a/sleekxmpp/plugins/xep_0118/stanza.py b/sleekxmpp/plugins/xep_0118/stanza.py deleted file mode 100644 index 3fdab284..00000000 --- a/sleekxmpp/plugins/xep_0118/stanza.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class UserTune(ElementBase): - - name = 'tune' - namespace = 'http://jabber.org/protocol/tune' - plugin_attrib = 'tune' - interfaces = set(['artist', 'length', 'rating', 'source', - 'title', 'track', 'uri']) - sub_interfaces = interfaces - - def set_length(self, value): - self._set_sub_text('length', str(value)) - - def set_rating(self, value): - self._set_sub_text('rating', str(value)) diff --git a/sleekxmpp/plugins/xep_0118/user_tune.py b/sleekxmpp/plugins/xep_0118/user_tune.py deleted file mode 100644 index 1bb00122..00000000 --- a/sleekxmpp/plugins/xep_0118/user_tune.py +++ /dev/null @@ -1,96 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0118 import stanza, UserTune - - -log = logging.getLogger(__name__) - - -class XEP_0118(BasePlugin): - - """ - XEP-0118: User Tune - """ - - name = 'xep_0118' - description = 'XEP-0118: User Tune' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace) - self.xmpp['xep_0163'].remove_interest(UserTune.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_tune', UserTune) - - def publish_tune(self, artist=None, length=None, rating=None, source=None, - title=None, track=None, uri=None, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Publish the user's current tune. - - Arguments: - artist -- The artist or performer of the song. - length -- The length of the song in seconds. - rating -- The user's rating of the song (from 1 to 10) - source -- The album name, website, or other source of the song. - title -- The title of the song. - track -- The song's track number, or other unique identifier. - uri -- A URL to more information about the song. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - tune = UserTune() - tune['artist'] = artist - tune['length'] = length - tune['rating'] = rating - tune['source'] = source - tune['title'] = title - tune['track'] = track - tune['uri'] = uri - return self.xmpp['xep_0163'].publish(tune, - node=UserTune.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user tune information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - tune = UserTune() - return self.xmpp['xep_0163'].publish(tune, - node=UserTune.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0128/__init__.py b/sleekxmpp/plugins/xep_0128/__init__.py deleted file mode 100644 index 27c2cc33..00000000 --- a/sleekxmpp/plugins/xep_0128/__init__.py +++ /dev/null @@ -1,19 +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.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco -from sleekxmpp.plugins.xep_0128.extended_disco import XEP_0128 - - -register_plugin(XEP_0128) - - -# Retain some backwards compatibility -xep_0128 = XEP_0128 diff --git a/sleekxmpp/plugins/xep_0128/extended_disco.py b/sleekxmpp/plugins/xep_0128/extended_disco.py deleted file mode 100644 index d785affe..00000000 --- a/sleekxmpp/plugins/xep_0128/extended_disco.py +++ /dev/null @@ -1,99 +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 sleekxmpp -from sleekxmpp import Iq -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0004 import Form -from sleekxmpp.plugins.xep_0030 import DiscoInfo -from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco - - -class XEP_0128(BasePlugin): - - """ - XEP-0128: Service Discovery Extensions - - Allow the use of data forms to add additional identity - information to disco#info results. - - Also see <http://www.xmpp.org/extensions/xep-0128.html>. - - Attributes: - disco -- A reference to the XEP-0030 plugin. - static -- Object containing the default set of static - node handlers. - xmpp -- The main SleekXMPP object. - - Methods: - set_extended_info -- Set extensions to a disco#info result. - add_extended_info -- Add an extension to a disco#info result. - del_extended_info -- Remove all extensions from a disco#info result. - """ - - name = 'xep_0128' - description = 'XEP-0128: Service Discovery Extensions' - dependencies = set(['xep_0030', 'xep_0004']) - - def plugin_init(self): - """Start the XEP-0128 plugin.""" - self._disco_ops = ['set_extended_info', - 'add_extended_info', - 'del_extended_info'] - - register_stanza_plugin(DiscoInfo, Form, iterable=True) - - self.disco = self.xmpp['xep_0030'] - self.static = StaticExtendedDisco(self.disco.static) - - self.disco.set_extended_info = self.set_extended_info - self.disco.add_extended_info = self.add_extended_info - self.disco.del_extended_info = self.del_extended_info - - for op in self._disco_ops: - self.api.register(getattr(self.static, op), op, default=True) - - def set_extended_info(self, jid=None, node=None, **kwargs): - """ - Set additional, extended identity information to a node. - - Replaces any existing extended information. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - data -- Either a form, or a list of forms to use - as extended information, replacing any - existing extensions. - """ - self.api['set_extended_info'](jid, node, None, kwargs) - - def add_extended_info(self, jid=None, node=None, **kwargs): - """ - Add additional, extended identity information to a node. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - data -- Either a form, or a list of forms to add - as extended information. - """ - self.api['add_extended_info'](jid, node, None, kwargs) - - def del_extended_info(self, jid=None, node=None, **kwargs): - """ - Remove all extended identity information to a node. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - """ - self.api['del_extended_info'](jid, node, None, kwargs) diff --git a/sleekxmpp/plugins/xep_0128/static.py b/sleekxmpp/plugins/xep_0128/static.py deleted file mode 100644 index 427011c0..00000000 --- a/sleekxmpp/plugins/xep_0128/static.py +++ /dev/null @@ -1,73 +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 sleekxmpp -from sleekxmpp.plugins.xep_0030 import StaticDisco - - -log = logging.getLogger(__name__) - - -class StaticExtendedDisco(object): - - """ - Extend the default StaticDisco implementation to provide - support for extended identity information. - """ - - def __init__(self, static): - """ - Augment the default XEP-0030 static handler object. - - Arguments: - static -- The default static XEP-0030 handler object. - """ - self.static = static - - def set_extended_info(self, jid, node, ifrom, data): - """ - Replace the extended identity data for a JID/node combination. - - The data parameter may provide: - data -- Either a single data form, or a list of data forms. - """ - with self.static.lock: - self.del_extended_info(jid, node, ifrom, data) - self.add_extended_info(jid, node, ifrom, data) - - def add_extended_info(self, jid, node, ifrom, data): - """ - Add additional extended identity data for a JID/node combination. - - The data parameter may provide: - data -- Either a single data form, or a list of data forms. - """ - with self.static.lock: - self.static.add_node(jid, node) - - forms = data.get('data', []) - if not isinstance(forms, list): - forms = [forms] - - info = self.static.get_node(jid, node)['info'] - for form in forms: - info.append(form) - - def del_extended_info(self, jid, node, ifrom, data): - """ - Replace the extended identity data for a JID/node combination. - - The data parameter is not used. - """ - with self.static.lock: - if self.static.node_exists(jid, node): - info = self.static.get_node(jid, node)['info'] - for form in info['substanza']: - info.xml.remove(form.xml) diff --git a/sleekxmpp/plugins/xep_0131/__init__.py b/sleekxmpp/plugins/xep_0131/__init__.py deleted file mode 100644 index ec71c98d..00000000 --- a/sleekxmpp/plugins/xep_0131/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0131 import stanza -from sleekxmpp.plugins.xep_0131.stanza import Headers -from sleekxmpp.plugins.xep_0131.headers import XEP_0131 - - -register_plugin(XEP_0131) diff --git a/sleekxmpp/plugins/xep_0131/headers.py b/sleekxmpp/plugins/xep_0131/headers.py deleted file mode 100644 index 3e47541a..00000000 --- a/sleekxmpp/plugins/xep_0131/headers.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Message, Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0131 import stanza -from sleekxmpp.plugins.xep_0131.stanza import Headers - - -class XEP_0131(BasePlugin): - - name = 'xep_0131' - description = 'XEP-0131: Stanza Headers and Internet Metadata' - dependencies = set(['xep_0030']) - stanza = stanza - default_config = { - 'supported_headers': set() - } - - def plugin_init(self): - register_stanza_plugin(Message, Headers) - register_stanza_plugin(Presence, Headers) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Headers.namespace) - for header in self.supported_headers: - self.xmpp['xep_0030'].del_feature( - feature='%s#%s' % (Headers.namespace, header)) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Headers.namespace) - for header in self.supported_headers: - self.xmpp['xep_0030'].add_feature('%s#%s' % ( - Headers.namespace, - header)) diff --git a/sleekxmpp/plugins/xep_0131/stanza.py b/sleekxmpp/plugins/xep_0131/stanza.py deleted file mode 100644 index 347adf96..00000000 --- a/sleekxmpp/plugins/xep_0131/stanza.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.thirdparty import OrderedDict -from sleekxmpp.xmlstream import ET, ElementBase - - -class Headers(ElementBase): - name = 'headers' - namespace = 'http://jabber.org/protocol/shim' - plugin_attrib = 'headers' - interfaces = set(['headers']) - is_extension = True - - def get_headers(self): - result = OrderedDict() - headers = self.xml.findall('{%s}header' % self.namespace) - for header in headers: - name = header.attrib.get('name', '') - value = header.text - if name in result: - if not isinstance(result[name], set): - result[name] = [result[name]] - else: - result[name] = [] - result[name].add(value) - else: - result[name] = value - return result - - def set_headers(self, values): - self.del_headers() - for name in values: - vals = values[name] - if not isinstance(vals, (list, set)): - vals = [values[name]] - for value in vals: - header = ET.Element('{%s}header' % self.namespace) - header.attrib['name'] = name - header.text = value - self.xml.append(header) - - def del_headers(self): - headers = self.xml.findall('{%s}header' % self.namespace) - for header in headers: - self.xml.remove(header) diff --git a/sleekxmpp/plugins/xep_0133.py b/sleekxmpp/plugins/xep_0133.py deleted file mode 100644 index 7bbe4c3c..00000000 --- a/sleekxmpp/plugins/xep_0133.py +++ /dev/null @@ -1,54 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.plugins import BasePlugin, register_plugin - - -class XEP_0133(BasePlugin): - - name = 'xep_0133' - description = 'XEP-0133: Service Administration' - dependencies = set(['xep_0030', 'xep_0004', 'xep_0050']) - commands = set(['add-user', 'delete-user', 'disable-user', - 'reenable-user', 'end-user-session', 'get-user-password', - 'change-user-password', 'get-user-roster', - 'get-user-lastlogin', 'user-stats', 'edit-blacklist', - 'edit-whitelist', 'get-registered-users-num', - 'get-disabled-users-num', 'get-online-users-num', - 'get-active-users-num', 'get-idle-users-num', - 'get-registered-users-list', 'get-disabled-users-list', - 'get-online-users-list', 'get-online-users', - 'get-active-users', 'get-idle-userslist', 'announce', - 'set-motd', 'edit-motd', 'delete-motd', 'set-welcome', - 'delete-welcome', 'edit-admin', 'restart', 'shutdown']) - - def get_commands(self, jid=None, **kwargs): - if jid is None: - jid = self.xmpp.boundjid.server - return self.xmpp['xep_0050'].get_commands(jid, **kwargs) - - -def create_command(name): - def admin_command(self, jid=None, session=None, ifrom=None, block=False): - if jid is None: - jid = self.xmpp.boundjid.server - self.xmpp['xep_0050'].start_command( - jid=jid, - node='http://jabber.org/protocol/admin#%s' % name, - session=session, - ifrom=ifrom, - block=block) - return admin_command - - -for cmd in XEP_0133.commands: - setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd)) - - -register_plugin(XEP_0133) diff --git a/sleekxmpp/plugins/xep_0152/__init__.py b/sleekxmpp/plugins/xep_0152/__init__.py deleted file mode 100644 index 7de031b7..00000000 --- a/sleekxmpp/plugins/xep_0152/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0152 import stanza -from sleekxmpp.plugins.xep_0152.stanza import Reachability -from sleekxmpp.plugins.xep_0152.reachability import XEP_0152 - - -register_plugin(XEP_0152) diff --git a/sleekxmpp/plugins/xep_0152/reachability.py b/sleekxmpp/plugins/xep_0152/reachability.py deleted file mode 100644 index 4cf81739..00000000 --- a/sleekxmpp/plugins/xep_0152/reachability.py +++ /dev/null @@ -1,93 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0152 import stanza, Reachability - - -log = logging.getLogger(__name__) - - -class XEP_0152(BasePlugin): - - """ - XEP-0152: Reachability Addresses - """ - - name = 'xep_0152' - description = 'XEP-0152: Reachability Addresses' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace) - self.xmpp['xep_0163'].remove_interest(Reachability.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('reachability', Reachability) - - def publish_reachability(self, addresses, options=None, - ifrom=None, block=True, callback=None, timeout=None): - """ - Publish alternative addresses where the user can be reached. - - Arguments: - addresses -- A list of dictionaries containing the URI and - optional description for each address. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if not isinstance(addresses, (list, tuple)): - addresses = [addresses] - reach = Reachability() - for address in addresses: - if not hasattr(address, 'items'): - address = {'uri': address} - - addr = stanza.Address() - for key, val in address.items(): - addr[key] = val - reach.append(addr) - return self.xmpp['xep_0163'].publish(reach, - node=Reachability.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user activity information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - reach = Reachability() - return self.xmpp['xep_0163'].publish(reach, - node=Reachability.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0152/stanza.py b/sleekxmpp/plugins/xep_0152/stanza.py deleted file mode 100644 index bd173ce1..00000000 --- a/sleekxmpp/plugins/xep_0152/stanza.py +++ /dev/null @@ -1,29 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class Reachability(ElementBase): - name = 'reach' - namespace = 'urn:xmpp:reach:0' - plugin_attrib = 'reach' - interfaces = set() - - -class Address(ElementBase): - name = 'addr' - namespace = 'urn:xmpp:reach:0' - plugin_attrib = 'address' - plugin_multi_attrib = 'addresses' - interfaces = set(['uri', 'desc']) - lang_interfaces = set(['desc']) - sub_interfaces = set(['desc']) - - -register_stanza_plugin(Reachability, Address, iterable=True) diff --git a/sleekxmpp/plugins/xep_0153/__init__.py b/sleekxmpp/plugins/xep_0153/__init__.py deleted file mode 100644 index f52b7712..00000000 --- a/sleekxmpp/plugins/xep_0153/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0153.stanza import VCardTempUpdate -from sleekxmpp.plugins.xep_0153.vcard_avatar import XEP_0153 - - -register_plugin(XEP_0153) diff --git a/sleekxmpp/plugins/xep_0153/stanza.py b/sleekxmpp/plugins/xep_0153/stanza.py deleted file mode 100644 index 4e6a660f..00000000 --- a/sleekxmpp/plugins/xep_0153/stanza.py +++ /dev/null @@ -1,29 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class VCardTempUpdate(ElementBase): - name = 'x' - namespace = 'vcard-temp:x:update' - plugin_attrib = 'vcard_temp_update' - interfaces = set(['photo']) - sub_interfaces = interfaces - - def set_photo(self, value): - if value is not None: - self._set_sub_text('photo', value, keep=True) - else: - self._del_sub('photo') - - def get_photo(self): - photo = self.xml.find('{%s}photo' % self.namespace) - if photo is None: - return None - return photo.text diff --git a/sleekxmpp/plugins/xep_0153/vcard_avatar.py b/sleekxmpp/plugins/xep_0153/vcard_avatar.py deleted file mode 100644 index ec1ae782..00000000 --- a/sleekxmpp/plugins/xep_0153/vcard_avatar.py +++ /dev/null @@ -1,152 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import hashlib -import logging -import threading - -from sleekxmpp.stanza import Presence -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate - - -log = logging.getLogger(__name__) - - -class XEP_0153(BasePlugin): - - name = 'xep_0153' - description = 'XEP-0153: vCard-Based Avatars' - dependencies = set(['xep_0054']) - stanza = stanza - - def plugin_init(self): - self._hashes = {} - - self._allow_advertising = threading.Event() - - register_stanza_plugin(Presence, VCardTempUpdate) - - self.xmpp.add_filter('out', self._update_presence) - - self.xmpp.add_event_handler('session_start', self._start) - self.xmpp.add_event_handler('session_end', self._end) - - self.xmpp.add_event_handler('presence_available', self._recv_presence) - self.xmpp.add_event_handler('presence_dnd', self._recv_presence) - self.xmpp.add_event_handler('presence_xa', self._recv_presence) - self.xmpp.add_event_handler('presence_chat', self._recv_presence) - self.xmpp.add_event_handler('presence_away', self._recv_presence) - - self.api.register(self._set_hash, 'set_hash', default=True) - self.api.register(self._get_hash, 'get_hash', default=True) - self.api.register(self._reset_hash, 'reset_hash', default=True) - - def plugin_end(self): - self.xmpp.del_filter('out', self._update_presence) - self.xmpp.del_event_handler('session_start', self._start) - self.xmpp.del_event_handler('session_end', self._end) - self.xmpp.del_event_handler('presence_available', self._recv_presence) - self.xmpp.del_event_handler('presence_dnd', self._recv_presence) - self.xmpp.del_event_handler('presence_xa', self._recv_presence) - self.xmpp.del_event_handler('presence_chat', self._recv_presence) - self.xmpp.del_event_handler('presence_away', self._recv_presence) - - def set_avatar(self, jid=None, avatar=None, mtype=None, block=True, - timeout=None, callback=None): - if jid is None: - jid = self.xmpp.boundjid.bare - - vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) - vcard = vcard['vcard_temp'] - vcard['PHOTO']['TYPE'] = mtype - vcard['PHOTO']['BINVAL'] = avatar - - self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard) - - self.api['reset_hash'](jid) - self.xmpp.roster[jid].send_last_presence() - - def _start(self, event): - try: - vcard = self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare) - data = vcard['vcard_temp']['PHOTO']['BINVAL'] - if not data: - new_hash = '' - else: - new_hash = hashlib.sha1(data).hexdigest() - self.api['set_hash'](self.xmpp.boundjid, args=new_hash) - self._allow_advertising.set() - except XMPPError: - log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare) - - def _end(self, event): - self._allow_advertising.clear() - - def _update_presence(self, stanza): - if not isinstance(stanza, Presence): - return stanza - - if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'): - return stanza - - current_hash = self.api['get_hash'](stanza['from']) - stanza['vcard_temp_update']['photo'] = current_hash - return stanza - - def _reset_hash(self, jid, node, ifrom, args): - own_jid = (jid.bare == self.xmpp.boundjid.bare) - if self.xmpp.is_component: - own_jid = (jid.domain == self.xmpp.boundjid.domain) - - self.api['set_hash'](jid, args=None) - if own_jid: - self.xmpp.roster[jid].send_last_presence() - - try: - iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom) - - data = iq['vcard_temp']['PHOTO']['BINVAL'] - if not data: - new_hash = '' - else: - new_hash = hashlib.sha1(data).hexdigest() - - self.api['set_hash'](jid, args=new_hash) - except XMPPError: - log.debug('Could not retrieve vCard for %s' % jid) - - def _recv_presence(self, pres): - try: - if pres['muc']['affiliation']: - # Don't process vCard avatars for MUC occupants - # since they all share the same bare JID. - return - except: pass - - if not pres.match('presence/vcard_temp_update'): - self.api['set_hash'](pres['from'], args=None) - return - - data = pres['vcard_temp_update']['photo'] - if data is None: - return - elif data == '' or data != self.api['get_hash'](pres['from']): - ifrom = pres['to'] if self.xmpp.is_component else None - self.api['reset_hash'](pres['from'], ifrom=ifrom) - self.xmpp.event('vcard_avatar_update', pres) - - # ================================================================= - - def _get_hash(self, jid, node, ifrom, args): - return self._hashes.get(jid.bare, None) - - def _set_hash(self, jid, node, ifrom, args): - self._hashes[jid.bare] = args diff --git a/sleekxmpp/plugins/xep_0163.py b/sleekxmpp/plugins/xep_0163.py deleted file mode 100644 index 2d1a63b7..00000000 --- a/sleekxmpp/plugins/xep_0163.py +++ /dev/null @@ -1,123 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin, register_plugin - - -log = logging.getLogger(__name__) - - -class XEP_0163(BasePlugin): - - """ - XEP-0163: Personal Eventing Protocol (PEP) - """ - - name = 'xep_0163' - description = 'XEP-0163: Personal Eventing Protocol (PEP)' - dependencies = set(['xep_0030', 'xep_0060', 'xep_0115']) - - def register_pep(self, name, stanza): - """ - Setup and configure events and stanza registration for - the given PEP stanza: - - - Add disco feature for the PEP content. - - Register disco interest in the PEP content. - - Map events from the PEP content's namespace to the given name. - - :param str name: The event name prefix to use for PEP events. - :param stanza: The stanza class for the PEP content. - """ - pubsub_stanza = self.xmpp['xep_0060'].stanza - register_stanza_plugin(pubsub_stanza.EventItem, stanza) - - self.add_interest(stanza.namespace) - self.xmpp['xep_0030'].add_feature(stanza.namespace) - self.xmpp['xep_0060'].map_node_event(stanza.namespace, name) - - def add_interest(self, namespace, jid=None): - """ - Mark an interest in a PEP subscription by including a disco - feature with the '+notify' extension. - - Arguments: - namespace -- The base namespace to register as an interest, such - as 'http://jabber.org/protocol/tune'. This may also - be a list of such namespaces. - jid -- Optionally specify the JID. - """ - if not isinstance(namespace, set) and not isinstance(namespace, list): - namespace = [namespace] - - for ns in namespace: - self.xmpp['xep_0030'].add_feature('%s+notify' % ns, - jid=jid) - self.xmpp['xep_0115'].update_caps(jid) - - def remove_interest(self, namespace, jid=None): - """ - Mark an interest in a PEP subscription by including a disco - feature with the '+notify' extension. - - Arguments: - namespace -- The base namespace to remove as an interest, such - as 'http://jabber.org/protocol/tune'. This may also - be a list of such namespaces. - jid -- Optionally specify the JID. - """ - if not isinstance(namespace, (set, list)): - namespace = [namespace] - - for ns in namespace: - self.xmpp['xep_0030'].del_feature(jid=jid, - feature='%s+notify' % namespace) - self.xmpp['xep_0115'].update_caps(jid) - - def publish(self, stanza, node=None, id=None, options=None, ifrom=None, - block=True, callback=None, timeout=None): - """ - Publish a PEP update. - - This is just a (very) thin wrapper around the XEP-0060 publish() - method to set the defaults expected by PEP. - - Arguments: - stanza -- The PEP update stanza to publish. - node -- The node to publish the item to. If not specified, - the stanza's namespace will be used. - id -- Optionally specify the ID of the item. - options -- A form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if node is None: - node = stanza.namespace - if id is None: - id = 'current' - - return self.xmpp['xep_0060'].publish(ifrom, node, - id=id, - payload=stanza.xml, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - -register_plugin(XEP_0163) diff --git a/sleekxmpp/plugins/xep_0172/__init__.py b/sleekxmpp/plugins/xep_0172/__init__.py deleted file mode 100644 index aa7b9f72..00000000 --- a/sleekxmpp/plugins/xep_0172/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0172 import stanza -from sleekxmpp.plugins.xep_0172.stanza import UserNick -from sleekxmpp.plugins.xep_0172.user_nick import XEP_0172 - - -register_plugin(XEP_0172) diff --git a/sleekxmpp/plugins/xep_0172/stanza.py b/sleekxmpp/plugins/xep_0172/stanza.py deleted file mode 100644 index 110c237b..00000000 --- a/sleekxmpp/plugins/xep_0172/stanza.py +++ /dev/null @@ -1,67 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class UserNick(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/protocol/nick' - name = 'nick' - plugin_attrib = name - interfaces = set(('nick',)) - - 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) diff --git a/sleekxmpp/plugins/xep_0172/user_nick.py b/sleekxmpp/plugins/xep_0172/user_nick.py deleted file mode 100644 index cab13c15..00000000 --- a/sleekxmpp/plugins/xep_0172/user_nick.py +++ /dev/null @@ -1,92 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza.message import Message -from sleekxmpp.stanza.presence import Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import MatchXPath -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0172 import stanza, UserNick - - -log = logging.getLogger(__name__) - - -class XEP_0172(BasePlugin): - - """ - XEP-0172: User Nickname - """ - - name = 'xep_0172' - description = 'XEP-0172: User Nickname' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, UserNick) - register_stanza_plugin(Presence, UserNick) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace) - self.xmpp['xep_0163'].remove_interest(UserNick.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_nick', UserNick) - - def publish_nick(self, nick=None, options=None, ifrom=None, block=True, - callback=None, timeout=None): - """ - Publish the user's current nick. - - Arguments: - nick -- The user nickname to publish. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - nickname = UserNick() - nickname['nick'] = nick - return self.xmpp['xep_0163'].publish(nickname, - node=UserNick.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user nick information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - nick = UserNick() - return self.xmpp['xep_0163'].publish(nick, - node=UserNick.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0184/__init__.py b/sleekxmpp/plugins/xep_0184/__init__.py deleted file mode 100644 index 4b129b6b..00000000 --- a/sleekxmpp/plugins/xep_0184/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0184.stanza import Request, Received -from sleekxmpp.plugins.xep_0184.receipt import XEP_0184 - - -register_plugin(XEP_0184) - - -# Retain some backwards compatibility -xep_0184 = XEP_0184 diff --git a/sleekxmpp/plugins/xep_0184/receipt.py b/sleekxmpp/plugins/xep_0184/receipt.py deleted file mode 100644 index 3e97d8db..00000000 --- a/sleekxmpp/plugins/xep_0184/receipt.py +++ /dev/null @@ -1,131 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Message -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0184 import stanza, Request, Received - - -class XEP_0184(BasePlugin): - - """ - XEP-0184: Message Delivery Receipts - """ - - name = 'xep_0184' - description = 'XEP-0184: Message Delivery Receipts' - dependencies = set(['xep_0030']) - stanza = stanza - default_config = { - 'auto_ack': True, - 'auto_request': False - } - - ack_types = ('normal', 'chat', 'headline') - - def plugin_init(self): - register_stanza_plugin(Message, Request) - register_stanza_plugin(Message, Received) - - self.xmpp.add_filter('out', self._filter_add_receipt_request) - - self.xmpp.register_handler( - Callback('Message Receipt', - StanzaPath('message/receipt'), - self._handle_receipt_received)) - - self.xmpp.register_handler( - Callback('Message Receipt Request', - StanzaPath('message/request_receipt'), - self._handle_receipt_request)) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts') - self.xmpp.del_filter('out', self._filter_add_receipt_request) - self.xmpp.remove_handler('Message Receipt') - self.xmpp.remove_handler('Message Receipt Request') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts') - - def ack(self, msg): - """ - Acknowledge a message by sending a receipt. - - Arguments: - msg -- The message to acknowledge. - """ - ack = self.xmpp.Message() - ack['to'] = msg['from'] - ack['from'] = msg['to'] - ack['receipt'] = msg['id'] - ack['id'] = msg['id'] - ack.send() - - def _handle_receipt_received(self, msg): - self.xmpp.event('receipt_received', msg) - - def _handle_receipt_request(self, msg): - """ - Auto-ack message receipt requests if ``self.auto_ack`` is ``True``. - - Arguments: - msg -- The incoming message requesting a receipt. - """ - if self.auto_ack: - if msg['type'] in self.ack_types: - if not msg['receipt']: - self.ack(msg) - - def _filter_add_receipt_request(self, stanza): - """ - Auto add receipt requests to outgoing messages, if: - - - ``self.auto_request`` is set to ``True`` - - The message is not for groupchat - - The message does not contain a receipt acknowledgment - - The recipient is a bare JID or, if a full JID, one - that has the ``urn:xmpp:receipts`` feature enabled - - The disco cache is checked if a full JID is specified in - the outgoing message, which may mean a round-trip disco#info - delay for the first message sent to the JID if entity caps - are not used. - """ - - if not self.auto_request: - return stanza - - if not isinstance(stanza, Message): - return stanza - - if stanza['request_receipt']: - return stanza - - if not stanza['type'] in self.ack_types: - return stanza - - if stanza['receipt']: - return stanza - - if not stanza['body']: - return stanza - - if stanza['to'].resource: - if not self.xmpp['xep_0030'].supports(stanza['to'], - feature='urn:xmpp:receipts', - cached=True): - return stanza - - stanza['request_receipt'] = True - return stanza diff --git a/sleekxmpp/plugins/xep_0184/stanza.py b/sleekxmpp/plugins/xep_0184/stanza.py deleted file mode 100644 index a7607035..00000000 --- a/sleekxmpp/plugins/xep_0184/stanza.py +++ /dev/null @@ -1,72 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.stanzabase import ElementBase, ET - - -class Request(ElementBase): - namespace = 'urn:xmpp:receipts' - name = 'request' - plugin_attrib = 'request_receipt' - interfaces = set(('request_receipt',)) - sub_interfaces = interfaces - is_extension = True - - def setup(self, xml=None): - self.xml = ET.Element('') - return True - - def set_request_receipt(self, val): - self.del_request_receipt() - if val: - parent = self.parent() - parent._set_sub_text("{%s}request" % self.namespace, keep=True) - if not parent['id']: - if parent.stream: - parent['id'] = parent.stream.new_id() - - def get_request_receipt(self): - parent = self.parent() - if parent.find("{%s}request" % self.namespace) is not None: - return True - else: - return False - - def del_request_receipt(self): - self.parent()._del_sub("{%s}request" % self.namespace) - - -class Received(ElementBase): - namespace = 'urn:xmpp:receipts' - name = 'received' - plugin_attrib = 'receipt' - interfaces = set(['receipt']) - sub_interfaces = interfaces - is_extension = True - - def setup(self, xml=None): - self.xml = ET.Element('') - return True - - def set_receipt(self, value): - self.del_receipt() - if value: - parent = self.parent() - xml = ET.Element("{%s}received" % self.namespace) - xml.attrib['id'] = value - parent.append(xml) - - def get_receipt(self): - parent = self.parent() - xml = parent.find("{%s}received" % self.namespace) - if xml is not None: - return xml.attrib.get('id', '') - return '' - - def del_receipt(self): - self.parent()._del_sub('{%s}received' % self.namespace) diff --git a/sleekxmpp/plugins/xep_0186/__init__.py b/sleekxmpp/plugins/xep_0186/__init__.py deleted file mode 100644 index c9b8c6b9..00000000 --- a/sleekxmpp/plugins/xep_0186/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0186 import stanza -from sleekxmpp.plugins.xep_0186.stanza import Invisible, Visible -from sleekxmpp.plugins.xep_0186.invisible_command import XEP_0186 - - -register_plugin(XEP_0186) diff --git a/sleekxmpp/plugins/xep_0186/invisible_command.py b/sleekxmpp/plugins/xep_0186/invisible_command.py deleted file mode 100644 index 15f63b2d..00000000 --- a/sleekxmpp/plugins/xep_0186/invisible_command.py +++ /dev/null @@ -1,44 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0186 import stanza, Visible, Invisible - - -log = logging.getLogger(__name__) - - -class XEP_0186(BasePlugin): - - name = 'xep_0186' - description = 'XEP-0186: Invisible Command' - dependencies = set(['xep_0030']) - - def plugin_init(self): - register_stanza_plugin(Iq, Visible) - register_stanza_plugin(Iq, Invisible) - - def set_invisible(self, ifrom=None, block=True, callback=None, - timeout=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq.enable('invisible') - iq.send(block=block, callback=callback, timeout=timeout) - - def set_visible(self, ifrom=None, block=True, callback=None, - timeout=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq.enable('visible') - iq.send(block=block, callback=callback, timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0186/stanza.py b/sleekxmpp/plugins/xep_0186/stanza.py deleted file mode 100644 index aadbaa16..00000000 --- a/sleekxmpp/plugins/xep_0186/stanza.py +++ /dev/null @@ -1,23 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class Invisible(ElementBase): - name = 'invisible' - namespace = 'urn:xmpp:invisible:0' - plugin_attrib = 'invisible' - interfaces = set() - - -class Visible(ElementBase): - name = 'visible' - namespace = 'urn:xmpp:visible:0' - plugin_attrib = 'visible' - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0191/__init__.py b/sleekxmpp/plugins/xep_0191/__init__.py deleted file mode 100644 index 934ac631..00000000 --- a/sleekxmpp/plugins/xep_0191/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList -from sleekxmpp.plugins.xep_0191.blocking import XEP_0191 - - -register_plugin(XEP_0191) diff --git a/sleekxmpp/plugins/xep_0191/blocking.py b/sleekxmpp/plugins/xep_0191/blocking.py deleted file mode 100644 index 57632319..00000000 --- a/sleekxmpp/plugins/xep_0191/blocking.py +++ /dev/null @@ -1,83 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin, JID -from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList - - -log = logging.getLogger(__name__) - - -class XEP_0191(BasePlugin): - - name = 'xep_0191' - description = 'XEP-0191: Blocking Command' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, BlockList) - register_stanza_plugin(Iq, Block) - register_stanza_plugin(Iq, Unblock) - - self.xmpp.register_handler( - Callback('Blocked Contact', - StanzaPath('iq@type=set/block'), - self._handle_blocked)) - - self.xmpp.register_handler( - Callback('Unblocked Contact', - StanzaPath('iq@type=set/unblock'), - self._handle_unblocked)) - - def plugin_end(self): - self.xmpp.remove_handler('Blocked Contact') - self.xmpp.remove_handler('Unblocked Contact') - - def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['from'] = ifrom - iq.enable('blocklist') - return iq.send(block=block, timeout=timeout, callback=callback) - - def block(self, jids, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - - if not isinstance(jids, (set, list)): - jids = [jids] - - iq['block']['items'] = jids - return iq.send(block=block, timeout=timeout, callback=callback) - - def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - - if jids is None: - jids = [] - if not isinstance(jids, (set, list)): - jids = [jids] - - iq['unblock']['items'] = jids - return iq.send(block=block, timeout=timeout, callback=callback) - - def _handle_blocked(self, iq): - self.xmpp.event('blocked', iq) - - def _handle_unblocked(self, iq): - self.xmpp.event('unblocked', iq) diff --git a/sleekxmpp/plugins/xep_0191/stanza.py b/sleekxmpp/plugins/xep_0191/stanza.py deleted file mode 100644 index c5a284bd..00000000 --- a/sleekxmpp/plugins/xep_0191/stanza.py +++ /dev/null @@ -1,50 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET, ElementBase, JID - - -class BlockList(ElementBase): - name = 'blocklist' - namespace = 'urn:xmpp:blocking' - plugin_attrib = 'blocklist' - interfaces = set(['items']) - - def get_items(self): - result = set() - items = self.xml.findall('{%s}item' % self.namespace) - if items is not None: - for item in items: - jid = JID(item.attrib.get('jid', '')) - if jid: - result.add(jid) - return result - - def set_items(self, values): - self.del_items() - for jid in values: - if jid: - item = ET.Element('{%s}item' % self.namespace) - item.attrib['jid'] = JID(jid).full - self.xml.append(item) - - def del_items(self): - items = self.xml.findall('{%s}item' % self.namespace) - if items is not None: - for item in items: - self.xml.remove(item) - - -class Block(BlockList): - name = 'block' - plugin_attrib = 'block' - - -class Unblock(BlockList): - name = 'unblock' - plugin_attrib = 'unblock' diff --git a/sleekxmpp/plugins/xep_0196/__init__.py b/sleekxmpp/plugins/xep_0196/__init__.py deleted file mode 100644 index 7aeaf6c9..00000000 --- a/sleekxmpp/plugins/xep_0196/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0196 import stanza -from sleekxmpp.plugins.xep_0196.stanza import UserGaming -from sleekxmpp.plugins.xep_0196.user_gaming import XEP_0196 - - -register_plugin(XEP_0196) diff --git a/sleekxmpp/plugins/xep_0196/stanza.py b/sleekxmpp/plugins/xep_0196/stanza.py deleted file mode 100644 index 571c89d7..00000000 --- a/sleekxmpp/plugins/xep_0196/stanza.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class UserGaming(ElementBase): - - name = 'gaming' - namespace = 'urn:xmpp:gaming:0' - plugin_attrib = 'gaming' - interfaces = set(['character_name', 'character_profile', 'name', - 'level', 'server_address', 'server_name', 'uri']) - sub_interfaces = interfaces - diff --git a/sleekxmpp/plugins/xep_0196/user_gaming.py b/sleekxmpp/plugins/xep_0196/user_gaming.py deleted file mode 100644 index e78f1acc..00000000 --- a/sleekxmpp/plugins/xep_0196/user_gaming.py +++ /dev/null @@ -1,97 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0196 import stanza, UserGaming - - -log = logging.getLogger(__name__) - - -class XEP_0196(BasePlugin): - - """ - XEP-0196: User Gaming - """ - - name = 'xep_0196' - description = 'XEP-0196: User Gaming' - dependencies = set(['xep_0163']) - stanza = stanza - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=UserGaming.namespace) - self.xmpp['xep_0163'].remove_interest(UserGaming.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0163'].register_pep('user_gaming', UserGaming) - - def publish_gaming(self, name=None, level=None, server_name=None, uri=None, - character_name=None, character_profile=None, server_address=None, - options=None, ifrom=None, block=True, callback=None, timeout=None): - """ - Publish the user's current gaming status. - - Arguments: - name -- The name of the game. - level -- The user's level in the game. - uri -- A URI for the game or relevant gaming service - server_name -- The name of the server where the user is playing. - server_address -- The hostname or IP address of the server where the - user is playing. - character_name -- The name of the user's character in the game. - character_profile -- A URI for a profile of the user's character. - options -- Optional form of publish options. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - gaming = UserGaming() - gaming['name'] = name - gaming['level'] = level - gaming['uri'] = uri - gaming['character_name'] = character_name - gaming['character_profile'] = character_profile - gaming['server_name'] = server_name - gaming['server_address'] = server_address - return self.xmpp['xep_0163'].publish(gaming, - node=UserGaming.namespace, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def stop(self, ifrom=None, block=True, callback=None, timeout=None): - """ - Clear existing user gaming information to stop notifications. - - Arguments: - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - gaming = UserGaming() - return self.xmpp['xep_0163'].publish(gaming, - node=UserGaming.namespace, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0198/__init__.py b/sleekxmpp/plugins/xep_0198/__init__.py deleted file mode 100644 index db930347..00000000 --- a/sleekxmpp/plugins/xep_0198/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0198.stanza import Enable, Enabled -from sleekxmpp.plugins.xep_0198.stanza import Resume, Resumed -from sleekxmpp.plugins.xep_0198.stanza import Failed -from sleekxmpp.plugins.xep_0198.stanza import StreamManagement -from sleekxmpp.plugins.xep_0198.stanza import Ack, RequestAck - -from sleekxmpp.plugins.xep_0198.stream_management import XEP_0198 - - -register_plugin(XEP_0198) diff --git a/sleekxmpp/plugins/xep_0198/stanza.py b/sleekxmpp/plugins/xep_0198/stanza.py deleted file mode 100644 index 6461d766..00000000 --- a/sleekxmpp/plugins/xep_0198/stanza.py +++ /dev/null @@ -1,150 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Error -from sleekxmpp.xmlstream import ElementBase, StanzaBase - - -class Enable(StanzaBase): - name = 'enable' - namespace = 'urn:xmpp:sm:3' - interfaces = set(['max', 'resume']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_resume(self): - return self._get_attr('resume', 'false').lower() in ('true', '1') - - def set_resume(self, val): - self._del_attr('resume') - self._set_attr('resume', 'true' if val else 'false') - - -class Enabled(StanzaBase): - name = 'enabled' - namespace = 'urn:xmpp:sm:3' - interfaces = set(['id', 'location', 'max', 'resume']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_resume(self): - return self._get_attr('resume', 'false').lower() in ('true', '1') - - def set_resume(self, val): - self._del_attr('resume') - self._set_attr('resume', 'true' if val else 'false') - - -class Resume(StanzaBase): - name = 'resume' - namespace = 'urn:xmpp:sm:3' - interfaces = set(['h', 'previd']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_h(self): - h = self._get_attr('h', None) - if h: - return int(h) - return None - - def set_h(self, val): - self._set_attr('h', str(val)) - - -class Resumed(StanzaBase): - name = 'resumed' - namespace = 'urn:xmpp:sm:3' - interfaces = set(['h', 'previd']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_h(self): - h = self._get_attr('h', None) - if h: - return int(h) - return None - - def set_h(self, val): - self._set_attr('h', str(val)) - - -class Failed(StanzaBase, Error): - name = 'failed' - namespace = 'urn:xmpp:sm:3' - interfaces = set() - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - -class StreamManagement(ElementBase): - name = 'sm' - namespace = 'urn:xmpp:sm:3' - plugin_attrib = name - interfaces = set(['required', 'optional']) - - def get_required(self): - return self.find('{%s}required' % self.namespace) is not None - - def set_required(self, val): - self.del_required() - if val: - self._set_sub_text('required', '', keep=True) - - def del_required(self): - self._del_sub('required') - - def get_optional(self): - return self.find('{%s}optional' % self.namespace) is not None - - def set_optional(self, val): - self.del_optional() - if val: - self._set_sub_text('optional', '', keep=True) - - def del_optional(self): - self._del_sub('optional') - - -class RequestAck(StanzaBase): - name = 'r' - namespace = 'urn:xmpp:sm:3' - interfaces = set() - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - -class Ack(StanzaBase): - name = 'a' - namespace = 'urn:xmpp:sm:3' - interfaces = set(['h']) - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.xml.tag = self.tag_name() - - def get_h(self): - h = self._get_attr('h', None) - if h: - return int(h) - return None - - def set_h(self, val): - self._set_attr('h', str(val)) diff --git a/sleekxmpp/plugins/xep_0198/stream_management.py b/sleekxmpp/plugins/xep_0198/stream_management.py deleted file mode 100644 index 48029913..00000000 --- a/sleekxmpp/plugins/xep_0198/stream_management.py +++ /dev/null @@ -1,314 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import threading -import collections - -from sleekxmpp.stanza import Message, Presence, Iq, StreamFeatures -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback, Waiter -from sleekxmpp.xmlstream.matcher import MatchXPath, MatchMany -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0198 import stanza - - -log = logging.getLogger(__name__) - - -MAX_SEQ = 2 ** 32 - - -class XEP_0198(BasePlugin): - - """ - XEP-0198: Stream Management - """ - - name = 'xep_0198' - description = 'XEP-0198: Stream Management' - dependencies = set() - stanza = stanza - default_config = { - #: The last ack number received from the server. - 'last_ack': 0, - - #: The number of stanzas to wait between sending ack requests to - #: the server. Setting this to ``1`` will send an ack request after - #: every sent stanza. Defaults to ``5``. - 'window': 5, - - #: The stream management ID for the stream. Knowing this value is - #: required in order to do stream resumption. - 'sm_id': None, - - #: A counter of handled incoming stanzas, mod 2^32. - 'handled': 0, - - #: A counter of unacked outgoing stanzas, mod 2^32. - 'seq': 0, - - #: Control whether or not the ability to resume the stream will be - #: requested when enabling stream management. Defaults to ``True``. - 'allow_resume': True, - - 'order': 10100, - 'resume_order': 9000 - } - - def plugin_init(self): - """Start the XEP-0198 plugin.""" - - # Only enable stream management for non-components, - # since components do not yet perform feature negotiation. - if self.xmpp.is_component: - return - - self.window_counter = self.window - self.window_counter_lock = threading.Lock() - - self.enabled = threading.Event() - self.unacked_queue = collections.deque() - - self.seq_lock = threading.Lock() - self.handled_lock = threading.Lock() - self.ack_lock = threading.Lock() - - register_stanza_plugin(StreamFeatures, stanza.StreamManagement) - self.xmpp.register_stanza(stanza.Enable) - self.xmpp.register_stanza(stanza.Enabled) - self.xmpp.register_stanza(stanza.Resume) - self.xmpp.register_stanza(stanza.Resumed) - self.xmpp.register_stanza(stanza.Ack) - self.xmpp.register_stanza(stanza.RequestAck) - - # Only end the session when a </stream> element is sent, - # not just because the connection has died. - self.xmpp.end_session_on_disconnect = False - - # Register the feature twice because it may be ordered two - # different ways: enabling after binding and resumption - # before binding. - self.xmpp.register_feature('sm', - self._handle_sm_feature, - restart=True, - order=self.order) - self.xmpp.register_feature('sm', - self._handle_sm_feature, - restart=True, - order=self.resume_order) - - self.xmpp.register_handler( - Callback('Stream Management Enabled', - MatchXPath(stanza.Enabled.tag_name()), - self._handle_enabled, - instream=True)) - - self.xmpp.register_handler( - Callback('Stream Management Resumed', - MatchXPath(stanza.Resumed.tag_name()), - self._handle_resumed, - instream=True)) - - self.xmpp.register_handler( - Callback('Stream Management Failed', - MatchXPath(stanza.Failed.tag_name()), - self._handle_failed, - instream=True)) - - self.xmpp.register_handler( - Callback('Stream Management Ack', - MatchXPath(stanza.Ack.tag_name()), - self._handle_ack, - instream=True)) - - self.xmpp.register_handler( - Callback('Stream Management Request Ack', - MatchXPath(stanza.RequestAck.tag_name()), - self._handle_request_ack, - instream=True)) - - self.xmpp.add_filter('in', self._handle_incoming) - self.xmpp.add_filter('out_sync', self._handle_outgoing) - - self.xmpp.add_event_handler('session_end', self.session_end) - - def plugin_end(self): - if self.xmpp.is_component: - return - - self.xmpp.unregister_feature('sm', self.order) - self.xmpp.unregister_feature('sm', self.resume_order) - self.xmpp.del_event_handler('session_end', self.session_end) - self.xmpp.del_filter('in', self._handle_incoming) - self.xmpp.del_filter('out_sync', self._handle_outgoing) - self.xmpp.remove_handler('Stream Management Enabled') - self.xmpp.remove_handler('Stream Management Resumed') - self.xmpp.remove_handler('Stream Management Failed') - self.xmpp.remove_handler('Stream Management Ack') - self.xmpp.remove_handler('Stream Management Request Ack') - self.xmpp.remove_stanza(stanza.Enable) - self.xmpp.remove_stanza(stanza.Enabled) - self.xmpp.remove_stanza(stanza.Resume) - self.xmpp.remove_stanza(stanza.Resumed) - self.xmpp.remove_stanza(stanza.Ack) - self.xmpp.remove_stanza(stanza.RequestAck) - - def session_end(self, event): - """Reset stream management state.""" - self.enabled.clear() - self.unacked_queue.clear() - self.sm_id = None - self.handled = 0 - self.seq = 0 - self.last_ack = 0 - - def send_ack(self): - """Send the current ack count to the server.""" - ack = stanza.Ack(self.xmpp) - with self.handled_lock: - ack['h'] = self.handled - self.xmpp.send_raw(str(ack), now=True) - - def request_ack(self, e=None): - """Request an ack from the server.""" - req = stanza.RequestAck(self.xmpp) - self.xmpp.send_queue.put(str(req)) - - def _handle_sm_feature(self, features): - """ - Enable or resume stream management. - - If no SM-ID is stored, and resource binding has taken place, - stream management will be enabled. - - If an SM-ID is known, and the server allows resumption, the - previous stream will be resumed. - """ - if 'stream_management' in self.xmpp.features: - # We've already negotiated stream management, - # so no need to do it again. - return False - if not self.sm_id: - if 'bind' in self.xmpp.features: - self.enabled.set() - enable = stanza.Enable(self.xmpp) - enable['resume'] = self.allow_resume - enable.send(now=True) - self.handled = 0 - elif self.sm_id and self.allow_resume: - self.enabled.set() - resume = stanza.Resume(self.xmpp) - resume['h'] = self.handled - resume['previd'] = self.sm_id - resume.send(now=True) - - # Wait for a response before allowing stream feature processing - # to continue. The actual result processing will be done in the - # _handle_resumed() or _handle_failed() methods. - waiter = Waiter('resumed_or_failed', - MatchMany([ - MatchXPath(stanza.Resumed.tag_name()), - MatchXPath(stanza.Failed.tag_name())])) - self.xmpp.register_handler(waiter) - result = waiter.wait() - if result is not None and result.name == 'resumed': - return True - return False - - def _handle_enabled(self, stanza): - """Save the SM-ID, if provided. - - Raises an :term:`sm_enabled` event. - """ - self.xmpp.features.add('stream_management') - if stanza['id']: - self.sm_id = stanza['id'] - self.xmpp.event('sm_enabled', stanza) - - def _handle_resumed(self, stanza): - """Finish resuming a stream by resending unacked stanzas. - - Raises a :term:`session_resumed` event. - """ - self.xmpp.features.add('stream_management') - self._handle_ack(stanza) - for id, stanza in self.unacked_queue: - self.xmpp.send(stanza, now=True, use_filters=False) - self.xmpp.session_started_event.set() - self.xmpp.event('session_resumed', stanza) - - def _handle_failed(self, stanza): - """ - Disable and reset any features used since stream management was - requested (tracked stanzas may have been sent during the interval - between the enable request and the enabled response). - - Raises an :term:`sm_failed` event. - """ - self.enabled.clear() - self.unacked_queue.clear() - self.xmpp.event('sm_failed', stanza) - - def _handle_ack(self, ack): - """Process a server ack by freeing acked stanzas from the queue. - - Raises a :term:`stanza_acked` event for each acked stanza. - """ - if ack['h'] == self.last_ack: - return - - with self.ack_lock: - num_acked = (ack['h'] - self.last_ack) % MAX_SEQ - num_unacked = len(self.unacked_queue) - log.debug("Ack: %s, Last Ack: %s, " + \ - "Unacked: %s, Num Acked: %s, " + \ - "Remaining: %s", - ack['h'], - self.last_ack, - num_unacked, - num_acked, - num_unacked - num_acked) - for x in range(num_acked): - seq, stanza = self.unacked_queue.popleft() - self.xmpp.event('stanza_acked', stanza) - self.last_ack = ack['h'] - - def _handle_request_ack(self, req): - """Handle an ack request by sending an ack.""" - self.send_ack() - - def _handle_incoming(self, stanza): - """Increment the handled counter for each inbound stanza.""" - if not self.enabled.is_set(): - return stanza - - if isinstance(stanza, (Message, Presence, Iq)): - with self.handled_lock: - # Sequence numbers are mod 2^32 - self.handled = (self.handled + 1) % MAX_SEQ - return stanza - - def _handle_outgoing(self, stanza): - """Store outgoing stanzas in a queue to be acked.""" - if not self.enabled.is_set(): - return stanza - - if isinstance(stanza, (Message, Presence, Iq)): - seq = None - with self.seq_lock: - # Sequence numbers are mod 2^32 - self.seq = (self.seq + 1) % MAX_SEQ - seq = self.seq - self.unacked_queue.append((seq, stanza)) - with self.window_counter_lock: - self.window_counter -= 1 - if self.window_counter == 0: - self.window_counter = self.window - self.request_ack() - return stanza diff --git a/sleekxmpp/plugins/xep_0199/__init__.py b/sleekxmpp/plugins/xep_0199/__init__.py deleted file mode 100644 index 5231a5b5..00000000 --- a/sleekxmpp/plugins/xep_0199/__init__.py +++ /dev/null @@ -1,20 +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.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0199.stanza import Ping -from sleekxmpp.plugins.xep_0199.ping import XEP_0199 - - -register_plugin(XEP_0199) - - -# Backwards compatibility for names -xep_0199 = XEP_0199 -xep_0199.sendPing = xep_0199.send_ping diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py deleted file mode 100644 index 836ff4ae..00000000 --- a/sleekxmpp/plugins/xep_0199/ping.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. -""" - -import time -import logging - -from sleekxmpp.jid import JID -from sleekxmpp.stanza import Iq -from sleekxmpp.exceptions import IqError, IqTimeout -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0199 import stanza, Ping - - -log = logging.getLogger(__name__) - - -class XEP_0199(BasePlugin): - - """ - XEP-0199: XMPP Ping - - Given that XMPP is based on TCP connections, it is possible for the - underlying connection to be terminated without the application's - awareness. Ping stanzas provide an alternative to whitespace based - keepalive methods for detecting lost connections. - - Also see <http://www.xmpp.org/extensions/xep-0199.html>. - - Attributes: - keepalive -- If True, periodically send ping requests - to the server. If a ping is not answered, - the connection will be reset. - interval -- Time in seconds between keepalive pings. - Defaults to 300 seconds. - timeout -- Time in seconds to wait for a ping response. - Defaults to 30 seconds. - Methods: - send_ping -- Send a ping to a given JID, returning the - round trip time. - """ - - name = 'xep_0199' - description = 'XEP-0199: XMPP Ping' - dependencies = set(['xep_0030']) - stanza = stanza - default_config = { - 'keepalive': False, - 'interval': 300, - 'timeout': 30 - } - - def plugin_init(self): - """ - Start the XEP-0199 plugin. - """ - - register_stanza_plugin(Iq, Ping) - - self.xmpp.register_handler( - Callback('Ping', - StanzaPath('iq@type=get/ping'), - self._handle_ping)) - - if self.keepalive: - self.xmpp.add_event_handler('session_start', - self.enable_keepalive, - threaded=True) - self.xmpp.add_event_handler('session_end', - self.disable_keepalive) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Ping.namespace) - self.xmpp.remove_handler('Ping') - if self.keepalive: - self.xmpp.del_event_handler('session_start', - self.enable_keepalive) - self.xmpp.del_event_handler('session_end', - self.disable_keepalive) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Ping.namespace) - - def enable_keepalive(self, interval=None, timeout=None): - if interval: - self.interval = interval - if timeout: - self.timeout = timeout - - self.keepalive = True - self.xmpp.schedule('Ping keepalive', - self.interval, - self._keepalive, - repeat=True) - - def disable_keepalive(self, event=None): - self.xmpp.scheduler.remove('Ping keepalive') - - def _keepalive(self, event=None): - log.debug("Keepalive ping...") - try: - rtt = self.ping(self.xmpp.boundjid.host, timeout=self.timeout) - except IqTimeout: - log.debug("Did not recieve ping back in time." + \ - "Requesting Reconnect.") - self.xmpp.reconnect() - else: - log.debug('Keepalive RTT: %s' % rtt) - - def _handle_ping(self, iq): - """Automatically reply to ping requests.""" - log.debug("Pinged by %s", iq['from']) - iq.reply().send() - - def send_ping(self, jid, ifrom=None, block=True, timeout=None, callback=None): - """Send a ping request. - - Arguments: - jid -- The JID that will receive the ping. - ifrom -- Specifiy the sender JID. - block -- Indicate if execution should block until - a pong response is received. Defaults - to True. - timeout -- Time in seconds to wait for a response. - Defaults to self.timeout. - callback -- Optional handler to execute when a pong - is received. Useful in conjunction with - the option block=False. - """ - if not timeout: - timeout = self.timeout - - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = ifrom - iq.enable('ping') - - return iq.send(block=block, timeout=timeout, callback=callback) - - def ping(self, jid=None, ifrom=None, timeout=None): - """Send a ping request and calculate RTT. - - Arguments: - jid -- The JID that will receive the ping. - ifrom -- Specifiy the sender JID. - timeout -- Time in seconds to wait for a response. - Defaults to self.timeout. - """ - own_host = False - if not jid: - if self.xmpp.is_component: - jid = self.xmpp.server - else: - jid = self.xmpp.boundjid.host - jid = JID(jid) - if jid == self.xmpp.boundjid.host or \ - self.xmpp.is_component and jid == self.xmpp.server: - own_host = True - - if not timeout: - timeout = self.timeout - - start = time.time() - - log.debug('Pinging %s' % jid) - try: - self.send_ping(jid, ifrom=ifrom, timeout=timeout) - except IqError as e: - if own_host: - rtt = time.time() - start - log.debug('Pinged %s, RTT: %s', jid, rtt) - return rtt - else: - raise e - else: - rtt = time.time() - start - log.debug('Pinged %s, RTT: %s', jid, rtt) - return rtt diff --git a/sleekxmpp/plugins/xep_0199/stanza.py b/sleekxmpp/plugins/xep_0199/stanza.py deleted file mode 100644 index 6586a763..00000000 --- a/sleekxmpp/plugins/xep_0199/stanza.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. -""" - -import sleekxmpp -from sleekxmpp.xmlstream import ElementBase - - -class Ping(ElementBase): - - """ - Given that XMPP is based on TCP connections, it is possible for the - underlying connection to be terminated without the application's - awareness. Ping stanzas provide an alternative to whitespace based - keepalive methods for detecting lost connections. - - Example ping stanza: - <iq type="get"> - <ping xmlns="urn:xmpp:ping" /> - </iq> - - Stanza Interface: - None - - Methods: - None - """ - - name = 'ping' - namespace = 'urn:xmpp:ping' - plugin_attrib = 'ping' - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py deleted file mode 100644 index cdab3665..00000000 --- a/sleekxmpp/plugins/xep_0202/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0202 import stanza -from sleekxmpp.plugins.xep_0202.stanza import EntityTime -from sleekxmpp.plugins.xep_0202.time import XEP_0202 - - -register_plugin(XEP_0202) - - -# Retain some backwards compatibility -xep_0202 = XEP_0202 diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py deleted file mode 100644 index b6ccc960..00000000 --- a/sleekxmpp/plugins/xep_0202/stanza.py +++ /dev/null @@ -1,127 +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 datetime as dt - -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.plugins import xep_0082 -from sleekxmpp.thirdparty import tzutc, tzoffset - - -class EntityTime(ElementBase): - - """ - The <time> element represents the local time for an XMPP agent. - The time is expressed in UTC to make synchronization easier - between entities, but the offset for the local timezone is also - included. - - Example <time> stanzas: - <iq type="result"> - <time xmlns="urn:xmpp:time"> - <utc>2011-07-03T11:37:12.234569</utc> - <tzo>-07:00</tzo> - </time> - </iq> - - Stanza Interface: - time -- The local time for the entity (updates utc and tzo). - utc -- The UTC equivalent to local time. - tzo -- The local timezone offset from UTC. - - Methods: - get_time -- Return local time datetime object. - set_time -- Set UTC and TZO fields. - del_time -- Remove both UTC and TZO fields. - get_utc -- Return datetime object of UTC time. - set_utc -- Set the UTC time. - get_tzo -- Return tzinfo object. - set_tzo -- Set the local timezone offset. - """ - - name = 'time' - namespace = 'urn:xmpp:time' - plugin_attrib = 'entity_time' - interfaces = set(('tzo', 'utc', 'time')) - sub_interfaces = interfaces - - def set_time(self, value): - """ - Set both the UTC and TZO fields given a time object. - - Arguments: - value -- A datetime object or properly formatted - string equivalent. - """ - date = value - if not isinstance(value, dt.datetime): - date = xep_0082.parse(value) - self['utc'] = date - self['tzo'] = date.tzinfo - - def get_time(self): - """ - Return the entity's local time based on the UTC and TZO data. - """ - date = self['utc'] - tz = self['tzo'] - return date.astimezone(tz) - - def del_time(self): - """Remove both the UTC and TZO fields.""" - del self['utc'] - del self['tzo'] - - def get_tzo(self): - """ - Return the timezone offset from UTC as a tzinfo object. - """ - tzo = self._get_sub_text('tzo') - if tzo == '': - tzo = 'Z' - time = xep_0082.parse('00:00:00%s' % tzo) - return time.tzinfo - - def set_tzo(self, value): - """ - Set the timezone offset from UTC. - - Arguments: - value -- Either a tzinfo object or the number of - seconds (positive or negative) to offset. - """ - time = xep_0082.time(offset=value) - if xep_0082.parse(time).tzinfo == tzutc(): - self._set_sub_text('tzo', 'Z') - else: - self._set_sub_text('tzo', time[-6:]) - - def get_utc(self): - """ - Return the time in UTC as a datetime object. - """ - value = self._get_sub_text('utc') - if value == '': - return xep_0082.parse(xep_0082.datetime()) - return xep_0082.parse('%sZ' % value) - - def set_utc(self, value): - """ - Set the time in UTC. - - Arguments: - value -- A datetime object or properly formatted - string equivalent. - """ - date = value - if not isinstance(value, dt.datetime): - date = xep_0082.parse(value) - date = date.astimezone(tzutc()) - value = xep_0082.format_datetime(date)[:-1] - self._set_sub_text('utc', value) diff --git a/sleekxmpp/plugins/xep_0202/time.py b/sleekxmpp/plugins/xep_0202/time.py deleted file mode 100644 index d5b3af37..00000000 --- a/sleekxmpp/plugins/xep_0202/time.py +++ /dev/null @@ -1,98 +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 sleekxmpp.stanza.iq import Iq
-from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.xmlstream.handler import Callback
-from sleekxmpp.xmlstream.matcher import StanzaPath
-from sleekxmpp.plugins import BasePlugin
-from sleekxmpp.plugins import xep_0082
-from sleekxmpp.plugins.xep_0202 import stanza
-
-
-log = logging.getLogger(__name__)
-
-
-class XEP_0202(BasePlugin):
-
- """
- XEP-0202: Entity Time
- """
-
- name = 'xep_0202'
- description = 'XEP-0202: Entity Time'
- dependencies = set(['xep_0030', 'xep_0082'])
- stanza = stanza
- default_config = {
- #: As a default, respond to time requests with the
- #: local time returned by XEP-0082. However, a
- #: custom function can be supplied which accepts
- #: the JID of the entity to query for the time.
- 'local_time': None,
- 'tz_offset': 0
- }
-
- def plugin_init(self):
- """Start the XEP-0203 plugin."""
-
- if not self.local_time:
- def default_local_time(jid):
- return xep_0082.datetime(offset=self.tz_offset)
-
- self.local_time = default_local_time
-
- self.xmpp.register_handler(
- Callback('Entity Time',
- StanzaPath('iq/entity_time'),
- self._handle_time_request))
- register_stanza_plugin(Iq, stanza.EntityTime)
-
- def plugin_end(self):
- self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time')
- self.xmpp.remove_handler('Entity Time')
-
- def session_bind(self, jid):
- self.xmpp['xep_0030'].add_feature('urn:xmpp:time')
-
- def _handle_time_request(self, iq):
- """
- Respond to a request for the local time.
-
- The time is taken from self.local_time(), which may be replaced
- during plugin configuration with a function that maps JIDs to
- times.
-
- Arguments:
- iq -- The Iq time request stanza.
- """
- iq.reply()
- iq['entity_time']['time'] = self.local_time(iq['to'])
- iq.send()
-
- def get_entity_time(self, to, ifrom=None, **iqargs):
- """
- Request the time from another entity.
-
- Arguments:
- to -- JID of the entity to query.
- ifrom -- Specifiy the sender's JID.
- block -- If true, block and wait for the stanzas' reply.
- timeout -- The time in seconds to block while waiting for
- a reply. If None, then wait indefinitely.
- callback -- Optional callback to execute when a reply is
- received instead of blocking and waiting for
- the reply.
- """
- iq = self.xmpp.Iq()
- iq['type'] = 'get'
- iq['to'] = to
- iq['from'] = ifrom
- iq.enable('entity_time')
- return iq.send(**iqargs)
diff --git a/sleekxmpp/plugins/xep_0203/__init__.py b/sleekxmpp/plugins/xep_0203/__init__.py deleted file mode 100644 index a95ead7e..00000000 --- a/sleekxmpp/plugins/xep_0203/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0203 import stanza -from sleekxmpp.plugins.xep_0203.stanza import Delay -from sleekxmpp.plugins.xep_0203.delay import XEP_0203 - - -register_plugin(XEP_0203) - -# Retain some backwards compatibility -xep_0203 = XEP_0203 diff --git a/sleekxmpp/plugins/xep_0203/delay.py b/sleekxmpp/plugins/xep_0203/delay.py deleted file mode 100644 index 31f31ce3..00000000 --- a/sleekxmpp/plugins/xep_0203/delay.py +++ /dev/null @@ -1,37 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -from sleekxmpp.stanza import Message, Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0203 import stanza - - -class XEP_0203(BasePlugin): - - """ - XEP-0203: Delayed Delivery - - XMPP stanzas are sometimes withheld for delivery due to the recipient - being offline, or are resent in order to establish recent history as - is the case with MUCS. In any case, it is important to know when the - stanza was originally sent, not just when it was last received. - - Also see <http://www.xmpp.org/extensions/xep-0203.html>. - """ - - name = 'xep_0203' - description = 'XEP-0203: Delayed Delivery' - dependencies = set() - stanza = stanza - - def plugin_init(self): - """Start the XEP-0203 plugin.""" - register_stanza_plugin(Message, stanza.Delay) - register_stanza_plugin(Presence, stanza.Delay) diff --git a/sleekxmpp/plugins/xep_0203/stanza.py b/sleekxmpp/plugins/xep_0203/stanza.py deleted file mode 100644 index e147e975..00000000 --- a/sleekxmpp/plugins/xep_0203/stanza.py +++ /dev/null @@ -1,46 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.plugins import xep_0082 - - -class Delay(ElementBase): - - name = 'delay' - namespace = 'urn:xmpp:delay' - plugin_attrib = 'delay' - interfaces = set(('from', 'stamp', 'text')) - - def get_from(self): - from_ = self._get_attr('from') - return JID(from_) if from_ else None - - def set_from(self, value): - self._set_attr('from', str(value)) - - def get_stamp(self): - timestamp = self._get_attr('stamp') - return xep_0082.parse(timestamp) if timestamp else None - - def set_stamp(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_attr('stamp', value) - - def get_text(self): - return self.xml.text - - def set_text(self, value): - self.xml.text = value - - def del_text(self): - self.xml.text = '' diff --git a/sleekxmpp/plugins/xep_0221/__init__.py b/sleekxmpp/plugins/xep_0221/__init__.py deleted file mode 100644 index 91341f5b..00000000 --- a/sleekxmpp/plugins/xep_0221/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0221 import stanza -from sleekxmpp.plugins.xep_0221.stanza import Media, URI -from sleekxmpp.plugins.xep_0221.media import XEP_0221 - - -register_plugin(XEP_0221) diff --git a/sleekxmpp/plugins/xep_0221/media.py b/sleekxmpp/plugins/xep_0221/media.py deleted file mode 100644 index c4b4a90f..00000000 --- a/sleekxmpp/plugins/xep_0221/media.py +++ /dev/null @@ -1,27 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0221 import stanza, Media, URI -from sleekxmpp.plugins.xep_0004 import FormField - - -log = logging.getLogger(__name__) - - -class XEP_0221(BasePlugin): - - name = 'xep_0221' - description = 'XEP-0221: Data Forms Media Element' - dependencies = set(['xep_0004']) - - def plugin_init(self): - register_stanza_plugin(FormField, Media) diff --git a/sleekxmpp/plugins/xep_0221/stanza.py b/sleekxmpp/plugins/xep_0221/stanza.py deleted file mode 100644 index 915ab380..00000000 --- a/sleekxmpp/plugins/xep_0221/stanza.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class Media(ElementBase): - name = 'media' - namespace = 'urn:xmpp:media-element' - plugin_attrib = 'media' - interfaces = set(['height', 'width', 'alt']) - - def add_uri(self, value, itype): - uri = URI() - uri['value'] = value - uri['type'] = itype - self.append(uri) - - -class URI(ElementBase): - name = 'uri' - namespace = 'urn:xmpp:media-element' - plugin_attrib = 'uri' - plugin_multi_attrib = 'uris' - interfaces = set(['type', 'value']) - - def get_value(self): - return self.xml.text - - def set_value(self, value): - self.xml.text = value - - def del_value(self): - sel.xml.text = '' - - -register_stanza_plugin(Media, URI, iterable=True) diff --git a/sleekxmpp/plugins/xep_0222.py b/sleekxmpp/plugins/xep_0222.py deleted file mode 100644 index 2cc7f703..00000000 --- a/sleekxmpp/plugins/xep_0222.py +++ /dev/null @@ -1,127 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin, register_plugin - - -log = logging.getLogger(__name__) - - -class XEP_0222(BasePlugin): - - """ - XEP-0222: Persistent Storage of Public Data via PubSub - """ - - name = 'xep_0222' - description = 'XEP-0222: Persistent Storage of Public Data via PubSub' - dependencies = set(['xep_0163', 'xep_0060', 'xep_0004']) - - profile = {'pubsub#persist_items': True, - 'pubsub#send_last_published_item': 'never'} - - def configure(self, node): - """ - Update a node's configuration to match the public storage profile. - """ - config = self.xmpp['xep_0004'].Form() - config['type'] = 'submit' - - for field, value in self.profile.items(): - config.add_field(var=field, value=value) - - return self.xmpp['xep_0060'].set_node_config(None, node, config, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def store(self, stanza, node=None, id=None, ifrom=None, options=None, - block=True, callback=None, timeout=None): - """ - Store public data via PEP. - - This is just a (very) thin wrapper around the XEP-0060 publish() - method to set the defaults expected by PEP. - - Arguments: - stanza -- The private content to store. - node -- The node to publish the content to. If not specified, - the stanza's namespace will be used. - id -- Optionally specify the ID of the item. - options -- Publish options to use, which will be modified to - fit the persistent storage option profile. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if not options: - options = self.xmpp['xep_0004'].stanza.Form() - options['type'] = 'submit' - options.add_field( - var='FORM_TYPE', - ftype='hidden', - value='http://jabber.org/protocol/pubsub#publish-options') - - fields = options['fields'] - for field, value in self.profile.items(): - if field not in fields: - options.add_field(var=field) - options['fields'][field]['value'] = value - - return self.xmpp['xep_0163'].publish(stanza, node, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def retrieve(self, node, id=None, item_ids=None, ifrom=None, - block=True, callback=None, timeout=None): - """ - Retrieve public data via PEP. - - This is just a (very) thin wrapper around the XEP-0060 publish() - method to set the defaults expected by PEP. - - Arguments: - node -- The node to retrieve content from. - id -- Optionally specify the ID of the item. - item_ids -- Specify a group of IDs. If id is also specified, it - will be included in item_ids. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if item_ids is None: - item_ids = [] - if id is not None: - item_ids.append(id) - - return self.xmpp['xep_0060'].get_items(None, node, - item_ids=item_ids, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - -register_plugin(XEP_0222) diff --git a/sleekxmpp/plugins/xep_0223.py b/sleekxmpp/plugins/xep_0223.py deleted file mode 100644 index abbecfc7..00000000 --- a/sleekxmpp/plugins/xep_0223.py +++ /dev/null @@ -1,127 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin, register_plugin - - -log = logging.getLogger(__name__) - - -class XEP_0223(BasePlugin): - - """ - XEP-0223: Persistent Storage of Private Data via PubSub - """ - - name = 'xep_0223' - description = 'XEP-0223: Persistent Storage of Private Data via PubSub' - dependencies = set(['xep_0163', 'xep_0060', 'xep_0004']) - - profile = {'pubsub#persist_items': True, - 'pubsub#send_last_published_item': 'never'} - - def configure(self, node): - """ - Update a node's configuration to match the public storage profile. - """ - config = self.xmpp['xep_0004'].Form() - config['type'] = 'submit' - - for field, value in self.profile.items(): - config.add_field(var=field, value=value) - - return self.xmpp['xep_0060'].set_node_config(None, node, config, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def store(self, stanza, node=None, id=None, ifrom=None, options=None, - block=True, callback=None, timeout=None): - """ - Store private data via PEP. - - This is just a (very) thin wrapper around the XEP-0060 publish() - method to set the defaults expected by PEP. - - Arguments: - stanza -- The private content to store. - node -- The node to publish the content to. If not specified, - the stanza's namespace will be used. - id -- Optionally specify the ID of the item. - options -- Publish options to use, which will be modified to - fit the persistent storage option profile. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if not options: - options = self.xmpp['xep_0004'].stanza.Form() - options['type'] = 'submit' - options.add_field( - var='FORM_TYPE', - ftype='hidden', - value='http://jabber.org/protocol/pubsub#publish-options') - - fields = options['fields'] - for field, value in self.profile.items(): - if field not in fields: - options.add_field(var=field) - options['fields'][field]['value'] = value - - return self.xmpp['xep_0163'].publish(stanza, node, - options=options, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - def retrieve(self, node, id=None, item_ids=None, ifrom=None, - block=True, callback=None, timeout=None): - """ - Retrieve private data via PEP. - - This is just a (very) thin wrapper around the XEP-0060 publish() - method to set the defaults expected by PEP. - - Arguments: - node -- The node to retrieve content from. - id -- Optionally specify the ID of the item. - item_ids -- Specify a group of IDs. If id is also specified, it - will be included in item_ids. - ifrom -- Specify the sender's JID. - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - """ - if item_ids is None: - item_ids = [] - if id is not None: - item_ids.append(id) - - return self.xmpp['xep_0060'].get_items(None, node, - item_ids=item_ids, - ifrom=ifrom, - block=block, - callback=callback, - timeout=timeout) - - -register_plugin(XEP_0223) diff --git a/sleekxmpp/plugins/xep_0224/__init__.py b/sleekxmpp/plugins/xep_0224/__init__.py deleted file mode 100644 index 1a9d2342..00000000 --- a/sleekxmpp/plugins/xep_0224/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0224 import stanza -from sleekxmpp.plugins.xep_0224.stanza import Attention -from sleekxmpp.plugins.xep_0224.attention import XEP_0224 - - -register_plugin(XEP_0224) - - -# Retain some backwards compatibility -xep_0224 = XEP_0224 diff --git a/sleekxmpp/plugins/xep_0224/attention.py b/sleekxmpp/plugins/xep_0224/attention.py deleted file mode 100644 index 4e560604..00000000 --- a/sleekxmpp/plugins/xep_0224/attention.py +++ /dev/null @@ -1,75 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp.stanza import Message -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0224 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0224(BasePlugin): - - """ - XEP-0224: Attention - """ - - name = 'xep_0224' - description = 'XEP-0224: Attention' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - """Start the XEP-0224 plugin.""" - register_stanza_plugin(Message, stanza.Attention) - - self.xmpp.register_handler( - Callback('Attention', - StanzaPath('message/attention'), - self._handle_attention)) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace) - self.xmpp.remove_handler('Attention') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace) - - def request_attention(self, to, mfrom=None, mbody=''): - """ - Send an attention message with an optional body. - - Arguments: - to -- The attention request recipient's JID. - mfrom -- Optionally specify the sender of the attention request. - mbody -- An optional message body to include in the request. - """ - m = self.xmpp.Message() - m['to'] = to - m['type'] = 'headline' - m['attention'] = True - if mfrom: - m['from'] = mfrom - m['body'] = mbody - m.send() - - def _handle_attention(self, msg): - """ - Raise an event after receiving a message with an attention request. - - Arguments: - msg -- A message stanza with an attention element. - """ - log.debug("Received attention request from: %s", msg['from']) - self.xmpp.event('attention', msg) diff --git a/sleekxmpp/plugins/xep_0224/stanza.py b/sleekxmpp/plugins/xep_0224/stanza.py deleted file mode 100644 index f15172d9..00000000 --- a/sleekxmpp/plugins/xep_0224/stanza.py +++ /dev/null @@ -1,40 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class Attention(ElementBase): - - """ - """ - - name = 'attention' - namespace = 'urn:xmpp:attention:0' - plugin_attrib = 'attention' - interfaces = set(('attention',)) - is_extension = True - - def setup(self, xml): - return True - - def set_attention(self, value): - if value: - xml = ET.Element(self.tag_name()) - self.parent().xml.append(xml) - else: - self.del_attention() - - def get_attention(self): - xml = self.parent().xml.find(self.tag_name()) - return xml is not None - - def del_attention(self): - xml = self.parent().xml.find(self.tag_name()) - if xml is not None: - self.parent().xml.remove(xml) diff --git a/sleekxmpp/plugins/xep_0231/__init__.py b/sleekxmpp/plugins/xep_0231/__init__.py deleted file mode 100644 index 2861d67b..00000000 --- a/sleekxmpp/plugins/xep_0231/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, - Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0231.stanza import BitsOfBinary -from sleekxmpp.plugins.xep_0231.bob import XEP_0231 - - -register_plugin(XEP_0231) diff --git a/sleekxmpp/plugins/xep_0231/bob.py b/sleekxmpp/plugins/xep_0231/bob.py deleted file mode 100644 index 5e1f590b..00000000 --- a/sleekxmpp/plugins/xep_0231/bob.py +++ /dev/null @@ -1,140 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, - Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import hashlib - -from sleekxmpp.stanza import Iq, Message, Presence -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0231 import stanza, BitsOfBinary - - -log = logging.getLogger(__name__) - - -class XEP_0231(BasePlugin): - - """ - XEP-0231 Bits of Binary - """ - - name = 'xep_0231' - description = 'XEP-0231: Bits of Binary' - dependencies = set(['xep_0030']) - - def plugin_init(self): - self._cids = {} - - register_stanza_plugin(Iq, BitsOfBinary) - register_stanza_plugin(Message, BitsOfBinary) - register_stanza_plugin(Presence, BitsOfBinary) - - self.xmpp.register_handler( - Callback('Bits of Binary - Iq', - StanzaPath('iq/bob'), - self._handle_bob_iq)) - - self.xmpp.register_handler( - Callback('Bits of Binary - Message', - StanzaPath('message/bob'), - self._handle_bob)) - - self.xmpp.register_handler( - Callback('Bits of Binary - Presence', - StanzaPath('presence/bob'), - self._handle_bob)) - - self.api.register(self._get_bob, 'get_bob', default=True) - self.api.register(self._set_bob, 'set_bob', default=True) - self.api.register(self._del_bob, 'del_bob', default=True) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob') - self.xmpp.remove_handler('Bits of Binary - Iq') - self.xmpp.remove_handler('Bits of Binary - Message') - self.xmpp.remove_handler('Bits of Binary - Presence') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:bob') - - def set_bob(self, data, mtype, cid=None, max_age=None): - if cid is None: - cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest() - - bob = BitsOfBinary() - bob['data'] = data - bob['type'] = mtype - bob['cid'] = cid - bob['max_age'] = max_age - - self.api['set_bob'](args=bob) - - return cid - - def get_bob(self, jid=None, cid=None, cached=True, ifrom=None, - block=True, timeout=None, callback=None): - if cached: - data = self.api['get_bob'](None, None, ifrom, args=cid) - if data is not None: - if not isinstance(data, Iq): - iq = self.xmpp.Iq() - iq.append(data) - return iq - return data - - iq = self.xmpp.Iq() - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'get' - iq['bob']['cid'] = cid - return iq.send(block=block, timeout=timeout, callback=callback) - - def del_bob(self, cid): - self.api['del_bob'](args=cid) - - def _handle_bob_iq(self, iq): - cid = iq['bob']['cid'] - - if iq['type'] == 'result': - self.api['set_bob'](iq['from'], None, iq['to'], args=iq['bob']) - self.xmpp.event('bob', iq) - elif iq['type'] == 'get': - data = self.api['get_bob'](iq['to'], None, iq['from'], args=cid) - if isinstance(data, Iq): - data['id'] = iq['id'] - data.send() - return - - iq.reply() - iq.append(data) - iq.send() - - def _handle_bob(self, stanza): - self.api['set_bob'](stanza['from'], None, - stanza['to'], args=stanza['bob']) - self.xmpp.event('bob', stanza) - - # ================================================================= - - def _set_bob(self, jid, node, ifrom, bob): - self._cids[bob['cid']] = bob - - def _get_bob(self, jid, node, ifrom, cid): - if cid in self._cids: - return self._cids[cid] - else: - raise XMPPError('item-not-found') - - def _del_bob(self, jid, node, ifrom, cid): - if cid in self._cids: - del self._cids[cid] diff --git a/sleekxmpp/plugins/xep_0231/stanza.py b/sleekxmpp/plugins/xep_0231/stanza.py deleted file mode 100644 index 8bf0d6ee..00000000 --- a/sleekxmpp/plugins/xep_0231/stanza.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, - Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import base64 - - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import ElementBase - - -class BitsOfBinary(ElementBase): - name = 'data' - namespace = 'urn:xmpp:bob' - plugin_attrib = 'bob' - interfaces = set(('cid', 'max_age', 'type', 'data')) - - def get_max_age(self): - return self._get_attr('max-age') - - def set_max_age(self, value): - self._set_attr('max-age', value) - - def get_data(self): - return base64.b64decode(bytes(self.xml.text)) - - def set_data(self, value): - self.xml.text = bytes(base64.b64encode(value)).decode('utf-8') - - def del_data(self): - self.xml.text = '' diff --git a/sleekxmpp/plugins/xep_0235/__init__.py b/sleekxmpp/plugins/xep_0235/__init__.py deleted file mode 100644 index 29d4408a..00000000 --- a/sleekxmpp/plugins/xep_0235/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0235 import stanza -from sleekxmpp.plugins.xep_0235.stanza import OAuth -from sleekxmpp.plugins.xep_0235.oauth import XEP_0235 - - -register_plugin(XEP_0235) diff --git a/sleekxmpp/plugins/xep_0235/oauth.py b/sleekxmpp/plugins/xep_0235/oauth.py deleted file mode 100644 index df0e2ebf..00000000 --- a/sleekxmpp/plugins/xep_0235/oauth.py +++ /dev/null @@ -1,32 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -import logging - -from sleekxmpp import Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0235 import stanza, OAuth - - -class XEP_0235(BasePlugin): - - name = 'xep_0235' - description = 'XEP-0235: OAuth Over XMPP' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, OAuth) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:oauth:0') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:oauth:0') diff --git a/sleekxmpp/plugins/xep_0235/stanza.py b/sleekxmpp/plugins/xep_0235/stanza.py deleted file mode 100644 index 0050d583..00000000 --- a/sleekxmpp/plugins/xep_0235/stanza.py +++ /dev/null @@ -1,80 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import hmac -import hashlib -import urllib -import base64 - -from sleekxmpp.xmlstream import ET, ElementBase, JID - - -class OAuth(ElementBase): - - name = 'oauth' - namespace = 'urn:xmpp:oauth:0' - plugin_attrib = 'oauth' - interfaces = set(['oauth_consumer_key', 'oauth_nonce', 'oauth_signature', - 'oauth_signature_method', 'oauth_timestamp', - 'oauth_token', 'oauth_version']) - sub_interfaces = interfaces - - def generate_signature(self, stanza, sfrom, sto, consumer_secret, - token_secret, method='HMAC-SHA1'): - self['oauth_signature_method'] = method - - request = urllib.quote('%s&%s' % (sfrom, sto), '') - parameters = urllib.quote('&'.join([ - 'oauth_consumer_key=%s' % self['oauth_consumer_key'], - 'oauth_nonce=%s' % self['oauth_nonce'], - 'oauth_signature_method=%s' % self['oauth_signature_method'], - 'oauth_timestamp=%s' % self['oauth_timestamp'], - 'oauth_token=%s' % self['oauth_token'], - 'oauth_version=%s' % self['oauth_version'] - ]), '') - - sigbase = '%s&%s&%s' % (stanza, request, parameters) - - consumer_secret = urllib.quote(consumer_secret, '') - token_secret = urllib.quote(token_secret, '') - key = '%s&%s' % (consumer_secret, token_secret) - - if method == 'HMAC-SHA1': - sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest()) - elif method == 'PLAINTEXT': - sig = key - - self['oauth_signature'] = sig - return sig - - def verify_signature(self, stanza, sfrom, sto, consumer_secret, - token_secret): - method = self['oauth_signature_method'] - - request = urllib.quote('%s&%s' % (sfrom, sto), '') - parameters = urllib.quote('&'.join([ - 'oauth_consumer_key=%s' % self['oauth_consumer_key'], - 'oauth_nonce=%s' % self['oauth_nonce'], - 'oauth_signature_method=%s' % self['oauth_signature_method'], - 'oauth_timestamp=%s' % self['oauth_timestamp'], - 'oauth_token=%s' % self['oauth_token'], - 'oauth_version=%s' % self['oauth_version'] - ]), '') - - sigbase = '%s&%s&%s' % (stanza, request, parameters) - - consumer_secret = urllib.quote(consumer_secret, '') - token_secret = urllib.quote(token_secret, '') - key = '%s&%s' % (consumer_secret, token_secret) - - if method == 'HMAC-SHA1': - sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest()) - elif method == 'PLAINTEXT': - sig = key - - return self['oauth_signature'] == sig diff --git a/sleekxmpp/plugins/xep_0242.py b/sleekxmpp/plugins/xep_0242.py deleted file mode 100644 index c1bada27..00000000 --- a/sleekxmpp/plugins/xep_0242.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins import BasePlugin, register_plugin - - -class XEP_0242(BasePlugin): - - name = 'xep_0242' - description = 'XEP-0242: XMPP Client Compliance 2009' - dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', - 'xep_0045', 'xep_0085', 'xep_0016', - 'xep_0191']) - - -register_plugin(XEP_0242) diff --git a/sleekxmpp/plugins/xep_0249/__init__.py b/sleekxmpp/plugins/xep_0249/__init__.py deleted file mode 100644 index b85f55ce..00000000 --- a/sleekxmpp/plugins/xep_0249/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dalek - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0249.stanza import Invite -from sleekxmpp.plugins.xep_0249.invite import XEP_0249 - - -register_plugin(XEP_0249) - - -# Retain some backwards compatibility -xep_0249 = XEP_0249 diff --git a/sleekxmpp/plugins/xep_0249/invite.py b/sleekxmpp/plugins/xep_0249/invite.py deleted file mode 100644 index 4b7abd4a..00000000 --- a/sleekxmpp/plugins/xep_0249/invite.py +++ /dev/null @@ -1,83 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dalek - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -import sleekxmpp -from sleekxmpp import Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.xep_0249 import Invite, stanza - - -log = logging.getLogger(__name__) - - -class XEP_0249(BasePlugin): - - """ - XEP-0249: Direct MUC Invitations - """ - - name = 'xep_0249' - description = 'XEP-0249: Direct MUC Invitations' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('Direct MUC Invitations', - StanzaPath('message/groupchat_invite'), - self._handle_invite)) - - register_stanza_plugin(Message, Invite) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=Invite.namespace) - self.xmpp.remove_handler('Direct MUC Invitations') - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(Invite.namespace) - - def _handle_invite(self, msg): - """ - Raise an event for all invitations received. - """ - log.debug("Received direct muc invitation from %s to room %s", - msg['from'], msg['groupchat_invite']['jid']) - - self.xmpp.event('groupchat_direct_invite', msg) - - def send_invitation(self, jid, roomjid, password=None, - reason=None, ifrom=None): - """ - Send a direct MUC invitation to an XMPP entity. - - Arguments: - jid -- The JID of the entity that will receive - the invitation - roomjid -- the address of the groupchat room to be joined - password -- a password needed for entry into a - password-protected room (OPTIONAL). - reason -- a human-readable purpose for the invitation - (OPTIONAL). - """ - - msg = self.xmpp.Message() - msg['to'] = jid - if ifrom is not None: - msg['from'] = ifrom - msg['groupchat_invite']['jid'] = roomjid - if password is not None: - msg['groupchat_invite']['password'] = password - if reason is not None: - msg['groupchat_invite']['reason'] = reason - - return msg.send() diff --git a/sleekxmpp/plugins/xep_0249/stanza.py b/sleekxmpp/plugins/xep_0249/stanza.py deleted file mode 100644 index ba4060d7..00000000 --- a/sleekxmpp/plugins/xep_0249/stanza.py +++ /dev/null @@ -1,39 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2011 Nathanael C. Fritz, Dalek - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class Invite(ElementBase): - - """ - XMPP allows for an agent in an MUC room to directly invite another - user to join the chat room (as opposed to a mediated invitation - done through the server). - - Example invite stanza: - <message from='crone1@shakespeare.lit/desktop' - to='hecate@shakespeare.lit'> - <x xmlns='jabber:x:conference' - jid='darkcave@macbeth.shakespeare.lit' - password='cauldronburn' - reason='Hey Hecate, this is the place for all good witches!'/> - </message> - - Stanza Interface: - jid -- The JID of the groupchat room - password -- The password used to gain entry in the room - (optional) - reason -- The reason for the invitation (optional) - - """ - - name = "x" - namespace = "jabber:x:conference" - plugin_attrib = "groupchat_invite" - interfaces = ("jid", "password", "reason") diff --git a/sleekxmpp/plugins/xep_0256.py b/sleekxmpp/plugins/xep_0256.py deleted file mode 100644 index 0db8ea3b..00000000 --- a/sleekxmpp/plugins/xep_0256.py +++ /dev/null @@ -1,73 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Presence -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.plugins import BasePlugin, register_plugin -from sleekxmpp.xmlstream import register_stanza_plugin - -from sleekxmpp.plugins.xep_0012 import stanza, LastActivity - - -log = logging.getLogger(__name__) - - -class XEP_0256(BasePlugin): - - name = 'xep_0256' - description = 'XEP-0256: Last Activity in Presence' - dependencies = set(['xep_0012']) - stanza = stanza - default_config = { - 'auto_last_activity': False - } - - def plugin_init(self): - register_stanza_plugin(Presence, LastActivity) - - self.xmpp.add_filter('out', self._initial_presence_activity) - self.xmpp.add_event_handler('connected', self._reset_presence_activity) - - self._initial_presence = set() - - def plugin_end(self): - self.xmpp.del_filter('out', self._initial_presence_activity) - self.xmpp.del_event_handler('connected', self._reset_presence_activity) - - def _reset_presence_activity(self, e): - self._initial_presence = set() - - def _initial_presence_activity(self, stanza): - if isinstance(stanza, Presence): - use_last_activity = False - - if self.auto_last_activity and stanza['show'] in ('xa', 'away'): - use_last_activity = True - - if stanza['from'] not in self._initial_presence: - self._initial_presence.add(stanza['from']) - use_last_activity = True - - if use_last_activity: - plugin = self.xmpp['xep_0012'] - try: - result = plugin.api['get_last_activity'](stanza['from'], - None, - stanza['to']) - seconds = result['last_activity']['seconds'] - except XMPPError: - seconds = None - - if seconds is not None: - stanza['last_activity']['seconds'] = seconds - return stanza - - -register_plugin(XEP_0256) diff --git a/sleekxmpp/plugins/xep_0257/__init__.py b/sleekxmpp/plugins/xep_0257/__init__.py deleted file mode 100644 index 8c5311fd..00000000 --- a/sleekxmpp/plugins/xep_0257/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0257 import stanza -from sleekxmpp.plugins.xep_0257.stanza import Certs, AppendCert -from sleekxmpp.plugins.xep_0257.stanza import DisableCert, RevokeCert -from sleekxmpp.plugins.xep_0257.client_cert_management import XEP_0257 - - -register_plugin(XEP_0257) diff --git a/sleekxmpp/plugins/xep_0257/client_cert_management.py b/sleekxmpp/plugins/xep_0257/client_cert_management.py deleted file mode 100644 index 49317843..00000000 --- a/sleekxmpp/plugins/xep_0257/client_cert_management.py +++ /dev/null @@ -1,65 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0257 import stanza, Certs -from sleekxmpp.plugins.xep_0257 import AppendCert, DisableCert, RevokeCert - - -log = logging.getLogger(__name__) - - -class XEP_0257(BasePlugin): - - name = 'xep_0257' - description = 'XEP-0258: Client Certificate Management for SASL EXTERNAL' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, Certs) - register_stanza_plugin(Iq, AppendCert) - register_stanza_plugin(Iq, DisableCert) - register_stanza_plugin(Iq, RevokeCert) - - def get_certs(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['from'] = ifrom - iq.enable('sasl_certs') - return iq.send(block=block, timeout=timeout, callback=callback) - - def add_cert(self, name, cert, allow_management=True, ifrom=None, - block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq['sasl_cert_append']['name'] = name - iq['sasl_cert_append']['x509cert'] = cert - iq['sasl_cert_append']['cert_management'] = allow_management - return iq.send(block=block, timeout=timeout, callback=callback) - - def disable_cert(self, name, ifrom=None, block=True, - timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq['sasl_cert_disable']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) - - def revoke_cert(self, name, ifrom=None, block=True, - timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq['sasl_cert_revoke']['name'] = name - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0257/stanza.py b/sleekxmpp/plugins/xep_0257/stanza.py deleted file mode 100644 index 17e20136..00000000 --- a/sleekxmpp/plugins/xep_0257/stanza.py +++ /dev/null @@ -1,87 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class Certs(ElementBase): - name = 'query' - namespace = 'urn:xmpp:saslcert:1' - plugin_attrib = 'sasl_certs' - interfaces = set() - - -class CertItem(ElementBase): - name = 'item' - namespace = 'urn:xmpp:saslcert:1' - plugin_attrib = 'item' - plugin_multi_attrib = 'items' - interfaces = set(['name', 'x509cert', 'users']) - sub_interfaces = set(['name', 'x509cert']) - - def get_users(self): - resources = self.xml.findall('{%s}users/{%s}resource' % ( - self.namespace, self.namespace)) - return set([res.text for res in resources]) - - def set_users(self, values): - users = self.xml.find('{%s}users' % self.namespace) - if users is None: - users = ET.Element('{%s}users' % self.namespace) - self.xml.append(users) - for resource in values: - res = ET.Element('{%s}resource' % self.namespace) - res.text = resource - users.append(res) - - def del_users(self): - users = self.xml.find('{%s}users' % self.namespace) - if users is not None: - self.xml.remove(users) - - -class AppendCert(ElementBase): - name = 'append' - namespace = 'urn:xmpp:saslcert:1' - plugin_attrib = 'sasl_cert_append' - interfaces = set(['name', 'x509cert', 'cert_management']) - sub_interfaces = set(['name', 'x509cert']) - - def get_cert_management(self): - manage = self.xml.find('{%s}no-cert-management' % self.namespace) - return manage is None - - def set_cert_management(self, value): - self.del_cert_management() - if not value: - manage = ET.Element('{%s}no-cert-management' % self.namespace) - self.xml.append(manage) - - def del_cert_management(self): - manage = self.xml.find('{%s}no-cert-management' % self.namespace) - if manage is not None: - self.xml.remove(manage) - - -class DisableCert(ElementBase): - name = 'disable' - namespace = 'urn:xmpp:saslcert:1' - plugin_attrib = 'sasl_cert_disable' - interfaces = set(['name']) - sub_interfaces = interfaces - - -class RevokeCert(ElementBase): - name = 'revoke' - namespace = 'urn:xmpp:saslcert:1' - plugin_attrib = 'sasl_cert_revoke' - interfaces = set(['name']) - sub_interfaces = interfaces - - -register_stanza_plugin(Certs, CertItem, iterable=True) diff --git a/sleekxmpp/plugins/xep_0258/__init__.py b/sleekxmpp/plugins/xep_0258/__init__.py deleted file mode 100644 index 516a3706..00000000 --- a/sleekxmpp/plugins/xep_0258/__init__.py +++ /dev/null @@ -1,18 +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.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0258 import stanza -from sleekxmpp.plugins.xep_0258.stanza import SecurityLabel, Label -from sleekxmpp.plugins.xep_0258.stanza import DisplayMarking, EquivalentLabel -from sleekxmpp.plugins.xep_0258.stanza import ESSLabel, Catalog, CatalogItem -from sleekxmpp.plugins.xep_0258.security_labels import XEP_0258 - - -register_plugin(XEP_0258) diff --git a/sleekxmpp/plugins/xep_0258/security_labels.py b/sleekxmpp/plugins/xep_0258/security_labels.py deleted file mode 100644 index 439143c1..00000000 --- a/sleekxmpp/plugins/xep_0258/security_labels.py +++ /dev/null @@ -1,44 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from sleekxmpp import Iq, Message -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0258 import stanza, SecurityLabel, Catalog - - -log = logging.getLogger(__name__) - - -class XEP_0258(BasePlugin): - - name = 'xep_0258' - description = 'XEP-0258: Security Labels in XMPP' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, SecurityLabel) - register_stanza_plugin(Iq, Catalog) - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature=SecurityLabel.namespace) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace) - - def get_catalog(self, jid, ifrom=None, block=True, - callback=None, timeout=None): - iq = self.xmpp.Iq() - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'get' - iq.enable('security_label_catalog') - return iq.send(block=block, callback=callback, timeout=timeout) diff --git a/sleekxmpp/plugins/xep_0258/stanza.py b/sleekxmpp/plugins/xep_0258/stanza.py deleted file mode 100644 index a506064b..00000000 --- a/sleekxmpp/plugins/xep_0258/stanza.py +++ /dev/null @@ -1,141 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from base64 import b64encode, b64decode - -from sleekxmpp.util import bytes -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin - - -class SecurityLabel(ElementBase): - name = 'securitylabel' - namespace = 'urn:xmpp:sec-label:0' - plugin_attrib = 'security_label' - - def add_equivalent(self, label): - equiv = EquivalentLabel(parent=self) - equiv.append(label) - return equiv - - -class Label(ElementBase): - name = 'label' - namespace = 'urn:xmpp:sec-label:0' - plugin_attrib = 'label' - - -class DisplayMarking(ElementBase): - name = 'displaymarking' - namespace = 'urn:xmpp:sec-label:0' - plugin_attrib = 'display_marking' - interfaces = set(['fgcolor', 'bgcolor', 'value']) - - def get_fgcolor(self): - return self._get_attr('fgcolor', 'black') - - def get_bgcolor(self): - return self._get_attr('fgcolor', 'white') - - def get_value(self): - return self.xml.text - - def set_value(self, value): - self.xml.text = value - - def del_value(self): - self.xml.text = '' - - -class EquivalentLabel(ElementBase): - name = 'equivalentlabel' - namespace = 'urn:xmpp:sec-label:0' - plugin_attrib = 'equivalent_label' - plugin_multi_attrib = 'equivalent_labels' - - -class Catalog(ElementBase): - name = 'catalog' - namespace = 'urn:xmpp:sec-label:catalog:2' - plugin_attrib = 'security_label_catalog' - interfaces = set(['to', 'from', 'name', 'desc', 'id', 'size', 'restrict']) - - def get_to(self): - return JID(self._get_attr('to')) - pass - - def set_to(self, value): - return self._set_attr('to', str(value)) - - def get_from(self): - return JID(self._get_attr('from')) - - def set_from(self, value): - return self._set_attr('from', str(value)) - - def get_restrict(self): - value = self._get_attr('restrict', '') - if value and value.lower() in ('true', '1'): - return True - return False - - def set_restrict(self, value): - self._del_attr('restrict') - if value: - self._set_attr('restrict', 'true') - elif value is False: - self._set_attr('restrict', 'false') - - -class CatalogItem(ElementBase): - name = 'catalog' - namespace = 'urn:xmpp:sec-label:catalog:2' - plugin_attrib = 'item' - plugin_multi_attrib = 'items' - interfaces = set(['selector', 'default']) - - def get_default(self): - value = self._get_attr('default', '') - if value.lower() in ('true', '1'): - return True - return False - - def set_default(self, value): - self._del_attr('default') - if value: - self._set_attr('default', 'true') - elif value is False: - self._set_attr('default', 'false') - - -class ESSLabel(ElementBase): - name = 'esssecuritylabel' - namespace = 'urn:xmpp:sec-label:ess:0' - plugin_attrib = 'ess' - interfaces = set(['value']) - - def get_value(self): - if self.xml.text: - return b64decode(bytes(self.xml.text)) - return '' - - def set_value(self, value): - self.xml.text = '' - if value: - self.xml.text = b64encode(bytes(value)) - - def del_value(self): - self.xml.text = '' - - -register_stanza_plugin(Catalog, CatalogItem, iterable=True) -register_stanza_plugin(CatalogItem, SecurityLabel) -register_stanza_plugin(EquivalentLabel, ESSLabel) -register_stanza_plugin(Label, ESSLabel) -register_stanza_plugin(SecurityLabel, DisplayMarking) -register_stanza_plugin(SecurityLabel, EquivalentLabel, iterable=True) -register_stanza_plugin(SecurityLabel, Label) diff --git a/sleekxmpp/plugins/xep_0270.py b/sleekxmpp/plugins/xep_0270.py deleted file mode 100644 index eadd45ad..00000000 --- a/sleekxmpp/plugins/xep_0270.py +++ /dev/null @@ -1,20 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins import BasePlugin, register_plugin - - -class XEP_0270(BasePlugin): - - name = 'xep_0270' - description = 'XEP-0270: XMPP Compliance Suites 2010' - dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', - 'xep_0163', 'xep_0045', 'xep_0085']) - - -register_plugin(XEP_0270) diff --git a/sleekxmpp/plugins/xep_0279/__init__.py b/sleekxmpp/plugins/xep_0279/__init__.py deleted file mode 100644 index 93db9e7c..00000000 --- a/sleekxmpp/plugins/xep_0279/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0279 import stanza -from sleekxmpp.plugins.xep_0279.stanza import IPCheck -from sleekxmpp.plugins.xep_0279.ipcheck import XEP_0279 - - -register_plugin(XEP_0279) diff --git a/sleekxmpp/plugins/xep_0279/ipcheck.py b/sleekxmpp/plugins/xep_0279/ipcheck.py deleted file mode 100644 index f8c167c7..00000000 --- a/sleekxmpp/plugins/xep_0279/ipcheck.py +++ /dev/null @@ -1,39 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -import logging - -from sleekxmpp import Iq -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0279 import stanza, IPCheck - - -class XEP_0279(BasePlugin): - - name = 'xep_0279' - description = 'XEP-0279: Server IP Check' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, IPCheck) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:sic:0') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0') - - def check_ip(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['from'] = ifrom - iq.enable('ip_check') - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0279/stanza.py b/sleekxmpp/plugins/xep_0279/stanza.py deleted file mode 100644 index 181b5957..00000000 --- a/sleekxmpp/plugins/xep_0279/stanza.py +++ /dev/null @@ -1,30 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ElementBase - - -class IPCheck(ElementBase): - - name = 'ip' - namespace = 'urn:xmpp:sic:0' - plugin_attrib = 'ip_check' - interfaces = set(['ip_check']) - is_extension = True - - def get_ip_check(self): - return self.xml.text - - def set_ip_check(self, value): - if value: - self.xml.text = value - else: - self.xml.text = '' - - def del_ip_check(self): - self.xml.text = '' diff --git a/sleekxmpp/plugins/xep_0280/__init__.py b/sleekxmpp/plugins/xep_0280/__init__.py deleted file mode 100644 index 929321af..00000000 --- a/sleekxmpp/plugins/xep_0280/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0280.stanza import ReceivedCarbon, SentCarbon -from sleekxmpp.plugins.xep_0280.stanza import PrivateCarbon -from sleekxmpp.plugins.xep_0280.stanza import CarbonEnable, CarbonDisable -from sleekxmpp.plugins.xep_0280.carbons import XEP_0280 - - -register_plugin(XEP_0280) diff --git a/sleekxmpp/plugins/xep_0280/carbons.py b/sleekxmpp/plugins/xep_0280/carbons.py deleted file mode 100644 index 482d046a..00000000 --- a/sleekxmpp/plugins/xep_0280/carbons.py +++ /dev/null @@ -1,81 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -import sleekxmpp -from sleekxmpp.stanza import Message, Iq -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0280 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0280(BasePlugin): - - """ - XEP-0280 Message Carbons - """ - - name = 'xep_0280' - description = 'XEP-0280: Message Carbons' - dependencies = set(['xep_0030', 'xep_0297']) - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('Carbon Received', - StanzaPath('message/carbon_received'), - self._handle_carbon_received)) - self.xmpp.register_handler( - Callback('Carbon Sent', - StanzaPath('message/carbon_sent'), - self._handle_carbon_sent)) - - register_stanza_plugin(Message, stanza.ReceivedCarbon) - register_stanza_plugin(Message, stanza.SentCarbon) - register_stanza_plugin(Message, stanza.PrivateCarbon) - register_stanza_plugin(Iq, stanza.CarbonEnable) - register_stanza_plugin(Iq, stanza.CarbonDisable) - - register_stanza_plugin(stanza.ReceivedCarbon, - self.xmpp['xep_0297'].stanza.Forwarded) - register_stanza_plugin(stanza.SentCarbon, - self.xmpp['xep_0297'].stanza.Forwarded) - - def plugin_end(self): - self.xmpp.remove_handler('Carbon Received') - self.xmpp.remove_handler('Carbon Sent') - self.xmpp.plugin['xep_0030'].del_feature(feature='urn:xmpp:carbons:2') - - def session_bind(self, jid): - self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:carbons:2') - - def _handle_carbon_received(self, msg): - self.xmpp.event('carbon_received', msg) - - def _handle_carbon_sent(self, msg): - self.xmpp.event('carbon_sent', msg) - - def enable(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq.enable('carbon_enable') - return iq.send(block=block, timeout=timeout, callback=callback) - - def disable(self, ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['from'] = ifrom - iq.enable('carbon_disable') - return iq.send(block=block, timeout=timeout, callback=callback) diff --git a/sleekxmpp/plugins/xep_0280/stanza.py b/sleekxmpp/plugins/xep_0280/stanza.py deleted file mode 100644 index 2f3aad86..00000000 --- a/sleekxmpp/plugins/xep_0280/stanza.py +++ /dev/null @@ -1,64 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.xmlstream import ElementBase - - -class ReceivedCarbon(ElementBase): - name = 'received' - namespace = 'urn:xmpp:carbons:2' - plugin_attrib = 'carbon_received' - interfaces = set(['carbon_received']) - is_extension = True - - def get_carbon_received(self): - return self['forwarded']['stanza'] - - def del_carbon_received(self): - del self['forwarded']['stanza'] - - def set_carbon_received(self, stanza): - self['forwarded']['stanza'] = stanza - - -class SentCarbon(ElementBase): - name = 'sent' - namespace = 'urn:xmpp:carbons:2' - plugin_attrib = 'carbon_sent' - interfaces = set(['carbon_sent']) - is_extension = True - - def get_carbon_sent(self): - return self['forwarded']['stanza'] - - def del_carbon_sent(self): - del self['forwarded']['stanza'] - - def set_carbon_sent(self, stanza): - self['forwarded']['stanza'] = stanza - - -class PrivateCarbon(ElementBase): - name = 'private' - namespace = 'urn:xmpp:carbons:2' - plugin_attrib = 'carbon_private' - interfaces = set() - - -class CarbonEnable(ElementBase): - name = 'enable' - namespace = 'urn:xmpp:carbons:2' - plugin_attrib = 'carbon_enable' - interfaces = set() - - -class CarbonDisable(ElementBase): - name = 'disable' - namespace = 'urn:xmpp:carbons:2' - plugin_attrib = 'carbon_disable' - interfaces = set() diff --git a/sleekxmpp/plugins/xep_0297/__init__.py b/sleekxmpp/plugins/xep_0297/__init__.py deleted file mode 100644 index 551d9420..00000000 --- a/sleekxmpp/plugins/xep_0297/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0297 import stanza -from sleekxmpp.plugins.xep_0297.stanza import Forwarded -from sleekxmpp.plugins.xep_0297.forwarded import XEP_0297 - - -register_plugin(XEP_0297) diff --git a/sleekxmpp/plugins/xep_0297/forwarded.py b/sleekxmpp/plugins/xep_0297/forwarded.py deleted file mode 100644 index 95703a2d..00000000 --- a/sleekxmpp/plugins/xep_0297/forwarded.py +++ /dev/null @@ -1,64 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -import logging - -from sleekxmpp import Iq, Message, Presence -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.xep_0297 import stanza, Forwarded - - -class XEP_0297(BasePlugin): - - name = 'xep_0297' - description = 'XEP-0297: Stanza Forwarding' - dependencies = set(['xep_0030', 'xep_0203']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Message, Forwarded) - - # While these are marked as iterable, that is just for - # making it easier to extract the forwarded stanza. There - # still can be only a single forwarded stanza. - register_stanza_plugin(Forwarded, Message, iterable=True) - register_stanza_plugin(Forwarded, Presence, iterable=True) - register_stanza_plugin(Forwarded, Iq, iterable=True) - - register_stanza_plugin(Forwarded, self.xmpp['xep_0203'].stanza.Delay) - - self.xmpp.register_handler( - Callback('Forwarded Stanza', - StanzaPath('message/forwarded'), - self._handle_forwarded)) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:forward:0') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:forward:0') - self.xmpp.remove_handler('Forwarded Stanza') - - def forward(self, stanza=None, mto=None, mbody=None, mfrom=None, delay=None): - stanza.stream = None - - msg = self.xmpp.Message() - msg['to'] = mto - msg['from'] = mfrom - msg['body'] = mbody - msg['forwarded']['stanza'] = stanza - if delay is not None: - msg['forwarded']['delay']['stamp'] = delay - msg.send() - - def _handle_forwarded(self, msg): - self.xmpp.event('forwarded_stanza', msg) diff --git a/sleekxmpp/plugins/xep_0297/stanza.py b/sleekxmpp/plugins/xep_0297/stanza.py deleted file mode 100644 index 8b97accc..00000000 --- a/sleekxmpp/plugins/xep_0297/stanza.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.stanza import Message, Presence, Iq -from sleekxmpp.xmlstream import ElementBase - - -class Forwarded(ElementBase): - name = 'forwarded' - namespace = 'urn:xmpp:forward:0' - plugin_attrib = 'forwarded' - interfaces = set(['stanza']) - - def get_stanza(self): - for stanza in self: - if isinstance(stanza, (Message, Presence, Iq)): - return stanza - return '' - - def set_stanza(self, value): - self.del_stanza() - self.append(value) - - def del_stanza(self): - found_stanzas = [] - for stanza in self: - if isinstance(stanza, (Message, Presence, Iq)): - found_stanzas.append(stanza) - for stanza in found_stanzas: - self.iterables.remove(stanza) - self.xml.remove(stanza.xml) diff --git a/sleekxmpp/plugins/xep_0302.py b/sleekxmpp/plugins/xep_0302.py deleted file mode 100644 index dee60f91..00000000 --- a/sleekxmpp/plugins/xep_0302.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins import BasePlugin, register_plugin - - -class XEP_0302(BasePlugin): - - name = 'xep_0302' - description = 'XEP-0302: XMPP Compliance Suites 2012' - dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', - 'xep_0163', 'xep_0045', 'xep_0085', - 'xep_0184', 'xep_0198']) - - -register_plugin(XEP_0302) diff --git a/sleekxmpp/plugins/xep_0308/__init__.py b/sleekxmpp/plugins/xep_0308/__init__.py deleted file mode 100644 index a6a100ee..00000000 --- a/sleekxmpp/plugins/xep_0308/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0308.stanza import Replace -from sleekxmpp.plugins.xep_0308.correction import XEP_0308 - - -register_plugin(XEP_0308) diff --git a/sleekxmpp/plugins/xep_0308/correction.py b/sleekxmpp/plugins/xep_0308/correction.py deleted file mode 100644 index d32b4bc4..00000000 --- a/sleekxmpp/plugins/xep_0308/correction.py +++ /dev/null @@ -1,52 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -import sleekxmpp -from sleekxmpp.stanza import Message -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0308 import stanza, Replace - - -log = logging.getLogger(__name__) - - -class XEP_0308(BasePlugin): - - """ - XEP-0308 Last Message Correction - """ - - name = 'xep_0308' - description = 'XEP-0308: Last Message Correction' - dependencies = set(['xep_0030']) - stanza = stanza - - def plugin_init(self): - self.xmpp.register_handler( - Callback('Message Correction', - StanzaPath('message/replace'), - self._handle_correction)) - - register_stanza_plugin(Message, Replace) - - self.xmpp.use_message_ids = True - - def plugin_end(self): - self.xmpp.remove_handler('Message Correction') - self.xmpp.plugin['xep_0030'].del_feature(feature=Replace.namespace) - - def session_bind(self, jid): - self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace) - - def _handle_correction(self, msg): - self.xmpp.event('message_correction', msg) diff --git a/sleekxmpp/plugins/xep_0308/stanza.py b/sleekxmpp/plugins/xep_0308/stanza.py deleted file mode 100644 index 8f88cbc0..00000000 --- a/sleekxmpp/plugins/xep_0308/stanza.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.xmlstream import ElementBase - - -class Replace(ElementBase): - name = 'replace' - namespace = 'urn:xmpp:message-correct:0' - plugin_attrib = 'replace' - interfaces = set(['id']) diff --git a/sleekxmpp/plugins/xep_0313/__init__.py b/sleekxmpp/plugins/xep_0313/__init__.py deleted file mode 100644 index 8b6ed97d..00000000 --- a/sleekxmpp/plugins/xep_0313/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0313.stanza import Result, MAM, Preferences -from sleekxmpp.plugins.xep_0313.mam import XEP_0313 - - -register_plugin(XEP_0313) diff --git a/sleekxmpp/plugins/xep_0313/mam.py b/sleekxmpp/plugins/xep_0313/mam.py deleted file mode 100644 index 4b82ca03..00000000 --- a/sleekxmpp/plugins/xep_0313/mam.py +++ /dev/null @@ -1,94 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging - -import sleekxmpp -from sleekxmpp.stanza import Message, Iq -from sleekxmpp.exceptions import XMPPError -from sleekxmpp.xmlstream.handler import Collector -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.plugins.xep_0313 import stanza - - -log = logging.getLogger(__name__) - - -class XEP_0313(BasePlugin): - - """ - XEP-0313 Message Archive Management - """ - - name = 'xep_0313' - description = 'XEP-0313: Message Archive Management' - dependencies = set(['xep_0030', 'xep_0050', 'xep_0059', 'xep_0297']) - stanza = stanza - - def plugin_init(self): - register_stanza_plugin(Iq, stanza.MAM) - register_stanza_plugin(Iq, stanza.Preferences) - register_stanza_plugin(Message, stanza.Result) - register_stanza_plugin(Message, stanza.Archived, iterable=True) - register_stanza_plugin(stanza.Result, self.xmpp['xep_0297'].stanza.Forwarded) - register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set) - - def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None, - block=True, timeout=None, callback=None, iterator=False): - iq = self.xmpp.Iq() - query_id = iq['id'] - - iq['to'] = jid - iq['from'] = ifrom - iq['type'] = 'get' - iq['mam']['queryid'] = query_id - iq['mam']['start'] = start - iq['mam']['end'] = end - iq['mam']['with'] = with_jid - - collector = Collector( - 'MAM_Results_%s' % query_id, - StanzaPath('message/mam_result@queryid=%s' % query_id)) - self.xmpp.register_handler(collector) - - if iterator: - return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results') - elif not block and callback is not None: - def wrapped_cb(iq): - results = collector.stop() - if iq['type'] == 'result': - iq['mam']['results'] = results - callback(iq) - return iq.send(block=block, timeout=timeout, callback=wrapped_cb) - else: - try: - resp = iq.send(block=block, timeout=timeout, callback=callback) - resp['mam']['results'] = collector.stop() - return resp - except XMPPError as e: - collector.stop() - raise e - - def set_preferences(self, jid=None, default=None, always=None, never=None, - ifrom=None, block=True, timeout=None, callback=None): - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['to'] = jid - iq['from'] = ifrom - iq['mam_prefs']['default'] = default - iq['mam_prefs']['always'] = always - iq['mam_prefs']['never'] = never - return iq.send(block=block, timeout=timeout, callback=callback) - - def get_configuration_commands(self, jid, **kwargs): - return self.xmpp['xep_0030'].get_items( - jid=jid, - node='urn:xmpp:mam#configure', - **kwargs) diff --git a/sleekxmpp/plugins/xep_0313/stanza.py b/sleekxmpp/plugins/xep_0313/stanza.py deleted file mode 100644 index 81576cd4..00000000 --- a/sleekxmpp/plugins/xep_0313/stanza.py +++ /dev/null @@ -1,139 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import datetime as dt - -from sleekxmpp.jid import JID -from sleekxmpp.xmlstream import ElementBase, ET -from sleekxmpp.plugins import xep_0082 - - -class MAM(ElementBase): - name = 'query' - namespace = 'urn:xmpp:mam:tmp' - plugin_attrib = 'mam' - interfaces = set(['queryid', 'start', 'end', 'with', 'results']) - sub_interfaces = set(['start', 'end', 'with']) - - def setup(self, xml=None): - ElementBase.setup(self, xml) - self._results = [] - - def get_start(self): - timestamp = self._get_sub_text('start') - return xep_0082.parse(timestamp) - - def set_start(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_sub_text('start', value) - - def get_end(self): - timestamp = self._get_sub_text('end') - return xep_0082.parse(timestamp) - - def set_end(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_sub_text('end', value) - - def get_with(self): - return JID(self._get_sub_text('with')) - - def set_with(self, value): - self._set_sub_text('with', str(value)) - - # The results interface is meant only as an easy - # way to access the set of collected message responses - # from the query. - - def get_results(self): - return self._results - - def set_results(self, values): - self._results = values - - def del_results(self): - self._results = [] - - -class Preferences(ElementBase): - name = 'prefs' - namespace = 'urn:xmpp:mam:tmp' - plugin_attrib = 'mam_prefs' - interfaces = set(['default', 'always', 'never']) - sub_interfaces = set(['always', 'never']) - - def get_always(self): - results = set() - - jids = self.xml.findall('{%s}always/{%s}jid' % ( - self.namespace, self.namespace)) - - for jid in jids: - results.add(JID(jid.text)) - - return results - - def set_always(self, value): - self._set_sub_text('always', '', keep=True) - always = self.xml.find('{%s}always' % self.namespace) - always.clear() - - if not isinstance(value, (list, set)): - value = [value] - - for jid in value: - jid_xml = ET.Element('{%s}jid' % self.namespace) - jid_xml.text = str(jid) - always.append(jid_xml) - - def get_never(self): - results = set() - - jids = self.xml.findall('{%s}never/{%s}jid' % ( - self.namespace, self.namespace)) - - for jid in jids: - results.add(JID(jid.text)) - - return results - - def set_never(self, value): - self._set_sub_text('never', '', keep=True) - never = self.xml.find('{%s}never' % self.namespace) - never.clear() - - if not isinstance(value, (list, set)): - value = [value] - - for jid in value: - jid_xml = ET.Element('{%s}jid' % self.namespace) - jid_xml.text = str(jid) - never.append(jid_xml) - - -class Result(ElementBase): - name = 'result' - namespace = 'urn:xmpp:mam:tmp' - plugin_attrib = 'mam_result' - interfaces = set(['queryid', 'id']) - - -class Archived(ElementBase): - name = 'archived' - namespace = 'urn:xmpp:mam:tmp' - plugin_attrib = 'mam_archived' - plugin_multi_attrib = 'mam_archives' - interfaces = set(['by', 'id']) - - def get_by(self): - return JID(self._get_attr('by')) - - def set_by(self): - return self._set_attr('by', str(value)) diff --git a/sleekxmpp/plugins/xep_0319/__init__.py b/sleekxmpp/plugins/xep_0319/__init__.py deleted file mode 100644 index 4756e63e..00000000 --- a/sleekxmpp/plugins/xep_0319/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0319 import stanza -from sleekxmpp.plugins.xep_0319.stanza import Idle -from sleekxmpp.plugins.xep_0319.idle import XEP_0319 - - -register_plugin(XEP_0319) diff --git a/sleekxmpp/plugins/xep_0319/idle.py b/sleekxmpp/plugins/xep_0319/idle.py deleted file mode 100644 index 90456f9f..00000000 --- a/sleekxmpp/plugins/xep_0319/idle.py +++ /dev/null @@ -1,75 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from datetime import datetime, timedelta - -from sleekxmpp.stanza import Presence -from sleekxmpp.plugins import BasePlugin -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.xep_0319 import stanza - - -class XEP_0319(BasePlugin): - name = 'xep_0319' - description = 'XEP-0319: Last User Interaction in Presence' - dependencies = set(['xep_0012']) - stanza = stanza - - def plugin_init(self): - self._idle_stamps = {} - register_stanza_plugin(Presence, stanza.Idle) - self.api.register(self._set_idle, - 'set_idle', - default=True) - self.api.register(self._get_idle, - 'get_idle', - default=True) - self.xmpp.register_handler( - Callback('Idle Presence', - StanzaPath('presence/idle'), - self._idle_presence)) - self.xmpp.add_filter('out', self._stamp_idle_presence) - - def session_bind(self, jid): - self.xmpp['xep_0030'].add_feature('urn:xmpp:idle:1') - - def plugin_end(self): - self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:idle:1') - self.xmpp.del_filter('out', self._stamp_idle_presence) - self.xmpp.remove_handler('Idle Presence') - - def idle(self, jid=None, since=None): - seconds = None - if since is None: - since = datetime.now() - else: - seconds = datetime.now() - since - self.api['set_idle'](jid, None, None, since) - self.xmpp['xep_0012'].set_last_activity(jid=jid, seconds=seconds) - - def active(self, jid=None): - self.api['set_idle'](jid, None, None, None) - self.xmpp['xep_0012'].del_last_activity(jid) - - def _set_idle(self, jid, node, ifrom, data): - self._idle_stamps[jid] = data - - def _get_idle(self, jid, node, ifrom, data): - return self._idle_stamps.get(jid, None) - - def _idle_presence(self, pres): - self.xmpp.event('presence_idle', pres) - - def _stamp_idle_presence(self, stanza): - if isinstance(stanza, Presence): - since = self.api['get_idle'](stanza['from'] or self.xmpp.boundjid) - if since: - stanza['idle']['since'] = since - return stanza diff --git a/sleekxmpp/plugins/xep_0319/stanza.py b/sleekxmpp/plugins/xep_0319/stanza.py deleted file mode 100644 index abfb4f41..00000000 --- a/sleekxmpp/plugins/xep_0319/stanza.py +++ /dev/null @@ -1,28 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime as dt - -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.plugins import xep_0082 - - -class Idle(ElementBase): - name = 'idle' - namespace = 'urn:xmpp:idle:1' - plugin_attrib = 'idle' - interfaces = set(['since']) - - def get_since(self): - timestamp = self._get_attr('since') - return xep_0082.parse(timestamp) - - def set_since(self, value): - if isinstance(value, dt.datetime): - value = xep_0082.format_datetime(value) - self._set_attr('since', value) diff --git a/sleekxmpp/plugins/xep_0323/__init__.py b/sleekxmpp/plugins/xep_0323/__init__.py deleted file mode 100644 index 10779ada..00000000 --- a/sleekxmpp/plugins/xep_0323/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0323.sensordata import XEP_0323 -from sleekxmpp.plugins.xep_0323 import stanza - -register_plugin(XEP_0323) - -xep_0323=XEP_0323 diff --git a/sleekxmpp/plugins/xep_0323/device.py b/sleekxmpp/plugins/xep_0323/device.py deleted file mode 100644 index 0bc20327..00000000 --- a/sleekxmpp/plugins/xep_0323/device.py +++ /dev/null @@ -1,255 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime -import logging - -class Device(object): - """ - Example implementation of a device readout object. - Is registered in the XEP_0323.register_node call - The device object may be any custom implementation to support - specific devices, but it must implement the functions: - has_field - request_fields - """ - - def __init__(self, nodeId, fields={}): - self.nodeId = nodeId - self.fields = fields # see fields described below - # {'type':'numeric', - # 'name':'myname', - # 'value': 42, - # 'unit':'Z'}]; - self.timestamp_data = {} - self.momentary_data = {} - self.momentary_timestamp = "" - logging.debug("Device object started nodeId %s",nodeId) - - def has_field(self, field): - """ - Returns true if the supplied field name exists in this device. - - Arguments: - field -- The field name - """ - if field in self.fields.keys(): - return True; - return False; - - def refresh(self, fields): - """ - override method to do the refresh work - refresh values from hardware or other - """ - pass - - - def request_fields(self, fields, flags, session, callback): - """ - Starts a data readout. Verifies the requested fields, - refreshes the data (if needed) and calls the callback - with requested data. - - - Arguments: - fields -- List of field names to readout - flags -- [optional] data classifier flags for the field, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - session -- Session id, only used in the callback as identifier - callback -- Callback function to call when data is available. - - The callback function must support the following arguments: - - session -- Session id, as supplied in the request_fields call - nodeId -- Identifier for this device - result -- The current result status of the readout. Valid values are: - "error" - Readout failed. - "fields" - Contains readout data. - "done" - Indicates that the readout is complete. May contain - readout data. - timestamp_block -- [optional] Only applies when result != "error" - The readout data. Structured as a dictionary: - { - timestamp: timestamp for this datablock, - fields: list of field dictionary (one per readout field). - readout field dictionary format: - { - type: The field type (numeric, boolean, dateTime, timeSpan, string, enum) - name: The field name - value: The field value - unit: The unit of the field. Only applies to type numeric. - dataType: The datatype of the field. Only applies to type enum. - flags: [optional] data classifier flags for the field, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - } - } - error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. - - """ - logging.debug("request_fields called looking for fields %s",fields) - if len(fields) > 0: - # Check availiability - for f in fields: - if f not in self.fields.keys(): - self._send_reject(session, callback) - return False; - else: - # Request all fields - fields = self.fields.keys(); - - - # Refresh data from device - # ... - logging.debug("about to refresh device fields %s",fields) - self.refresh(fields) - - if "momentary" in flags and flags['momentary'] == "true" or \ - "all" in flags and flags['all'] == "true": - ts_block = {}; - timestamp = ""; - - if len(self.momentary_timestamp) > 0: - timestamp = self.momentary_timestamp; - else: - timestamp = self._get_timestamp(); - - field_block = []; - for f in self.momentary_data: - if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], - "unit": self.fields[f]["unit"], - "dataType": self.fields[f]["dataType"], - "value": self.momentary_data[f]["value"], - "flags": self.momentary_data[f]["flags"]}); - ts_block["timestamp"] = timestamp; - ts_block["fields"] = field_block; - - callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block); - return - - from_flag = self._datetime_flag_parser(flags, 'from') - to_flag = self._datetime_flag_parser(flags, 'to') - - for ts in sorted(self.timestamp_data.keys()): - tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S") - if not from_flag is None: - if tsdt < from_flag: - #print (str(tsdt) + " < " + str(from_flag)) - continue - if not to_flag is None: - if tsdt > to_flag: - #print (str(tsdt) + " > " + str(to_flag)) - continue - - ts_block = {}; - field_block = []; - - for f in self.timestamp_data[ts]: - if f in fields: - field_block.append({"name": f, - "type": self.fields[f]["type"], - "unit": self.fields[f]["unit"], - "dataType": self.fields[f]["dataType"], - "value": self.timestamp_data[ts][f]["value"], - "flags": self.timestamp_data[ts][f]["flags"]}); - - ts_block["timestamp"] = ts; - ts_block["fields"] = field_block; - callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block); - callback(session, result="done", nodeId=self.nodeId, timestamp_block=None); - - def _datetime_flag_parser(self, flags, flagname): - if not flagname in flags: - return None - - dt = None - try: - dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S") - except ValueError: - # Badly formatted datetime, ignore it - pass - return dt - - - def _get_timestamp(self): - """ - Generates a properly formatted timestamp of current time - """ - return datetime.datetime.now().replace(microsecond=0).isoformat() - - def _send_reject(self, session, callback): - """ - Sends a reject to the caller - - Arguments: - session -- Session id, see definition in request_fields function - callback -- Callback function, see definition in request_fields function - """ - callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject"); - - def _add_field(self, name, typename, unit=None, dataType=None): - """ - Adds a field to the device - - Arguments: - name -- Name of the field - typename -- Type of the field (numeric, boolean, dateTime, timeSpan, string, enum) - unit -- [optional] only applies to "numeric". Unit for the field. - dataType -- [optional] only applies to "enum". Datatype for the field. - """ - self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}; - - def _add_field_timestamp_data(self, name, timestamp, value, flags=None): - """ - Adds timestamped data to a field - - Arguments: - name -- Name of the field - timestamp -- Timestamp for the data (string) - value -- Field value at the timestamp - flags -- [optional] data classifier flags for the field, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - """ - if not name in self.fields.keys(): - return False; - if not timestamp in self.timestamp_data: - self.timestamp_data[timestamp] = {}; - - self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}; - return True; - - def _add_field_momentary_data(self, name, value, flags=None): - """ - Sets momentary data to a field - - Arguments: - name -- Name of the field - value -- Field value at the timestamp - flags -- [optional] data classifier flags for the field, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - """ - if name not in self.fields: - return False; - if flags is None: - flags = {}; - - flags["momentary"] = "true" - self.momentary_data[name] = {"value": value, "flags": flags}; - return True; - - def _set_momentary_timestamp(self, timestamp): - """ - This function is only for unit testing to produce predictable results. - """ - self.momentary_timestamp = timestamp; - diff --git a/sleekxmpp/plugins/xep_0323/sensordata.py b/sleekxmpp/plugins/xep_0323/sensordata.py deleted file mode 100644 index 2e2f2470..00000000 --- a/sleekxmpp/plugins/xep_0323/sensordata.py +++ /dev/null @@ -1,723 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import time -import datetime -from threading import Thread, Lock, Timer - -from sleekxmpp.plugins.xep_0323.timerreset import TimerReset - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0323 import stanza -from sleekxmpp.plugins.xep_0323.stanza import Sensordata - - -log = logging.getLogger(__name__) - - -class XEP_0323(BasePlugin): - - """ - XEP-0323: IoT Sensor Data - - - This XEP provides the underlying architecture, basic operations and data - structures for sensor data communication over XMPP networks. It includes - a hardware abstraction model, removing any technical detail implemented - in underlying technologies. - - Also see <http://xmpp.org/extensions/xep-0323.html> - - Configuration Values: - threaded -- Indicates if communication with sensors should be threaded. - Defaults to True. - - Events: - Sensor side - ----------- - Sensordata Event:Req -- Received a request for data - Sensordata Event:Cancel -- Received a cancellation for a request - - Client side - ----------- - Sensordata Event:Accepted -- Received a accept from sensor for a request - Sensordata Event:Rejected -- Received a reject from sensor for a request - Sensordata Event:Cancelled -- Received a cancel confirm from sensor - Sensordata Event:Fields -- Received fields from sensor for a request - This may be triggered multiple times since - the sensor can split up its response in - multiple messages. - Sensordata Event:Failure -- Received a failure indication from sensor - for a request. Typically a comm timeout. - - Attributes: - threaded -- Indicates if command events should be threaded. - Defaults to True. - sessions -- A dictionary or equivalent backend mapping - session IDs to dictionaries containing data - relevant to a request's session. This dictionary is used - both by the client and sensor side. On client side, seqnr - is used as key, while on sensor side, a session_id is used - as key. This ensures that the two will not collide, so - one instance can be both client and sensor. - Sensor side - ----------- - nodes -- A dictionary mapping sensor nodes that are serviced through - this XMPP instance to their device handlers ("drivers"). - Client side - ----------- - last_seqnr -- The last used sequence number (integer). One sequence of - communication (e.g. -->request, <--accept, <--fields) - between client and sensor is identified by a unique - sequence number (unique between the client/sensor pair) - - Methods: - plugin_init -- Overrides base_plugin.plugin_init - post_init -- Overrides base_plugin.post_init - plugin_end -- Overrides base_plugin.plugin_end - - Sensor side - ----------- - register_node -- Register a sensor as available from this XMPP - instance. - - Client side - ----------- - request_data -- Initiates a request for data from one or more - sensors. Non-blocking, a callback function will - be called when data is available. - - """ - - name = 'xep_0323' - description = 'XEP-0323 Internet of Things - Sensor Data' - dependencies = set(['xep_0030']) - stanza = stanza - - - default_config = { - 'threaded': True -# 'session_db': None - } - - def plugin_init(self): - """ Start the XEP-0323 plugin """ - - self.xmpp.register_handler( - Callback('Sensordata Event:Req', - StanzaPath('iq@type=get/req'), - self._handle_event_req)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Accepted', - StanzaPath('iq@type=result/accepted'), - self._handle_event_accepted)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Rejected', - StanzaPath('iq@type=error/rejected'), - self._handle_event_rejected)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Cancel', - StanzaPath('iq@type=get/cancel'), - self._handle_event_cancel)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Cancelled', - StanzaPath('iq@type=result/cancelled'), - self._handle_event_cancelled)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Fields', - StanzaPath('message/fields'), - self._handle_event_fields)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Failure', - StanzaPath('message/failure'), - self._handle_event_failure)) - - self.xmpp.register_handler( - Callback('Sensordata Event:Started', - StanzaPath('message/started'), - self._handle_event_started)) - - # Server side dicts - self.nodes = {}; - self.sessions = {}; - - self.last_seqnr = 0; - self.seqnr_lock = Lock(); - - ## For testning only - self.test_authenticated_from = "" - - def post_init(self): - """ Init complete. Register our features in Serivce discovery. """ - BasePlugin.post_init(self) - self.xmpp['xep_0030'].add_feature(Sensordata.namespace) - self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple()) - - def _new_session(self): - """ Return a new session ID. """ - return str(time.time()) + '-' + self.xmpp.new_id() - - def session_bind(self, jid): - logging.debug("setting the Disco discovery for %s" % Sensordata.namespace) - self.xmpp['xep_0030'].add_feature(Sensordata.namespace) - self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple()) - - - def plugin_end(self): - """ Stop the XEP-0323 plugin """ - self.sessions.clear(); - self.xmpp.remove_handler('Sensordata Event:Req') - self.xmpp.remove_handler('Sensordata Event:Accepted') - self.xmpp.remove_handler('Sensordata Event:Rejected') - self.xmpp.remove_handler('Sensordata Event:Cancel') - self.xmpp.remove_handler('Sensordata Event:Cancelled') - self.xmpp.remove_handler('Sensordata Event:Fields') - self.xmpp['xep_0030'].del_feature(feature=Sensordata.namespace) - - - # ================================================================= - # Sensor side (data provider) API - - def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None): - """ - Register a sensor/device as available for serving of data through this XMPP - instance. - - The device object may by any custom implementation to support - specific devices, but it must implement the functions: - has_field - request_fields - according to the interfaces shown in the example device.py file. - - Arguments: - nodeId -- The identifier for the device - device -- The device object - commTimeout -- Time in seconds to wait between each callback from device during - a data readout. Float. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - self.nodes[nodeId] = {"device": device, - "commTimeout": commTimeout, - "sourceId": sourceId, - "cacheType": cacheType}; - - def _set_authenticated(self, auth=''): - """ Internal testing function """ - self.test_authenticated_from = auth; - - - def _handle_event_req(self, iq): - """ - Event handler for reception of an Iq with req - this is a request. - - Verifies that - - all the requested nodes are available - - at least one of the requested fields is available from at least - one of the nodes - - If the request passes verification, an accept response is sent, and - the readout process is started in a separate thread. - If the verification fails, a reject message is sent. - """ - - seqnr = iq['req']['seqnr']; - error_msg = ''; - req_ok = True; - - # Authentication - if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from: - # Invalid authentication - req_ok = False; - error_msg = "Access denied"; - - # Nodes - process_nodes = []; - if len(iq['req']['nodes']) > 0: - for n in iq['req']['nodes']: - if not n['nodeId'] in self.nodes: - req_ok = False; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in iq['req']['nodes']]; - else: - process_nodes = self.nodes.keys(); - - # Fields - if we just find one we are happy, otherwise we reject - process_fields = []; - if len(iq['req']['fields']) > 0: - found = False - for f in iq['req']['fields']: - for node in self.nodes: - if self.nodes[node]["device"].has_field(f['name']): - found = True; - break; - if not found: - req_ok = False; - error_msg = "Invalid field " + f['name']; - process_fields = [f['name'] for n in iq['req']['fields']]; - - req_flags = iq['req']._get_flags(); - - request_delay_sec = None - if 'when' in req_flags: - # Timed request - requires datetime string in iso format - # ex. 2013-04-05T15:00:03 - dt = None - try: - dt = datetime.datetime.strptime(req_flags['when'], "%Y-%m-%dT%H:%M:%S") - except ValueError: - req_ok = False; - error_msg = "Invalid datetime in 'when' flag, please use ISO format (i.e. 2013-04-05T15:00:03)." - - if not dt is None: - # Datetime properly formatted - dtnow = datetime.datetime.now() - dtdiff = dt - dtnow - request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600 - if request_delay_sec <= 0: - req_ok = False; - error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat(); - - if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; - - #print("added session: " + str(self.sessions)) - - iq.reply(); - iq['accepted']['seqnr'] = seqnr; - if not request_delay_sec is None: - iq['accepted']['queued'] = "true" - iq.send(block=False); - - self.sessions[session]["node_list"] = process_nodes; - - if not request_delay_sec is None: - # Delay request to requested time - timer = Timer(request_delay_sec, self._event_delayed_req, args=(session, process_fields, req_flags)) - self.sessions[session]["commTimers"]["delaytimer"] = timer; - timer.start(); - return - - if self.threaded: - #print("starting thread") - tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags)) - tr_req.start() - #print("started thread") - else: - self._threaded_node_request(session, process_fields, req_flags); - - else: - iq.reply(); - iq['type'] = 'error'; - iq['rejected']['seqnr'] = seqnr; - iq['rejected']['error'] = error_msg; - iq.send(block=False); - - def _threaded_node_request(self, session, process_fields, flags): - """ - Helper function to handle the device readouts in a separate thread. - - Arguments: - session -- The request session id - process_fields -- The fields to request from the devices - flags -- [optional] flags to pass to the devices, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - """ - for node in self.sessions[session]["node_list"]: - self.sessions[session]["nodeDone"][node] = False; - - for node in self.sessions[session]["node_list"]: - timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)); - self.sessions[session]["commTimers"][node] = timer; - #print("Starting timer " + str(timer) + ", timeout: " + str(self.nodes[node]['commTimeout'])) - timer.start(); - self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback); - - def _event_comm_timeout(self, session, nodeId): - """ - Triggered if any of the readout operations timeout. - Sends a failure message back to the client, stops communicating - with the failing device. - - Arguments: - session -- The request session id - nodeId -- The id of the device which timed out - """ - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['failure']['seqnr'] = self.sessions[session]['seqnr']; - msg['failure']['error']['text'] = "Timeout"; - msg['failure']['error']['nodeId'] = nodeId; - msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat(); - - # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; - if (self._all_nodes_done(session)): - msg['failure']['done'] = 'true'; - msg.send(); - # The session is complete, delete it - #print("del session " + session + " due to timeout") - del self.sessions[session]; - - def _event_delayed_req(self, session, process_fields, req_flags): - """ - Triggered when the timer from a delayed request fires. - - Arguments: - session -- The request session id - process_fields -- The fields to request from the devices - flags -- [optional] flags to pass to the devices, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - """ - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['started']['seqnr'] = self.sessions[session]['seqnr']; - msg.send(); - - if self.threaded: - tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags)) - tr_req.start() - else: - self._threaded_node_request(session, process_fields, req_flags); - - def _all_nodes_done(self, session): - """ - Checks wheter all devices are done replying to the readout. - - Arguments: - session -- The request session id - """ - for n in self.sessions[session]["nodeDone"]: - if not self.sessions[session]["nodeDone"][n]: - return False; - return True; - - def _device_field_request_callback(self, session, nodeId, result, timestamp_block, error_msg=None): - """ - Callback function called by the devices when they have any additional data. - Composes a message with the data and sends it back to the client, and resets - the timeout timer for the device. - - Arguments: - session -- The request session id - nodeId -- The device id which initiated the callback - result -- The current result status of the readout. Valid values are: - "error" - Readout failed. - "fields" - Contains readout data. - "done" - Indicates that the readout is complete. May contain - readout data. - timestamp_block -- [optional] Only applies when result != "error" - The readout data. Structured as a dictionary: - { - timestamp: timestamp for this datablock, - fields: list of field dictionary (one per readout field). - readout field dictionary format: - { - type: The field type (numeric, boolean, dateTime, timeSpan, string, enum) - name: The field name - value: The field value - unit: The unit of the field. Only applies to type numeric. - dataType: The datatype of the field. Only applies to type enum. - flags: [optional] data classifier flags for the field, e.g. momentary - Formatted as a dictionary like { "flag name": "flag value" ... } - } - } - error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. - """ - if not session in self.sessions: - # This can happend if a session was deleted, like in a cancellation. Just drop the data. - return - - if result == "error": - self.sessions[session]["commTimers"][nodeId].cancel(); - - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['failure']['seqnr'] = self.sessions[session]['seqnr']; - msg['failure']['error']['text'] = error_msg; - msg['failure']['error']['nodeId'] = nodeId; - msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat(); - - # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; - if (self._all_nodes_done(session)): - msg['failure']['done'] = 'true'; - # The session is complete, delete it - # print("del session " + session + " due to error") - del self.sessions[session]; - msg.send(); - else: - msg = self.xmpp.Message(); - msg['from'] = self.sessions[session]['to']; - msg['to'] = self.sessions[session]['from']; - msg['fields']['seqnr'] = self.sessions[session]['seqnr']; - - if timestamp_block is not None and len(timestamp_block) > 0: - node = msg['fields'].add_node(nodeId); - ts = node.add_timestamp(timestamp_block["timestamp"]); - - for f in timestamp_block["fields"]: - data = ts.add_data( typename=f['type'], - name=f['name'], - value=f['value'], - unit=f['unit'], - dataType=f['dataType'], - flags=f['flags']); - - if result == "done": - self.sessions[session]["commTimers"][nodeId].cancel(); - self.sessions[session]["nodeDone"][nodeId] = True; - msg['fields']['done'] = 'true'; - if (self._all_nodes_done(session)): - # The session is complete, delete it - # print("del session " + session + " due to complete") - del self.sessions[session]; - else: - # Restart comm timer - self.sessions[session]["commTimers"][nodeId].reset(); - - msg.send(); - - def _handle_event_cancel(self, iq): - """ Received Iq with cancel - this is a cancel request. - Delete the session and confirm. """ - - seqnr = iq['cancel']['seqnr']; - # Find the session - for s in self.sessions: - if self.sessions[s]['from'] == iq['from'] and self.sessions[s]['to'] == iq['to'] and self.sessions[s]['seqnr'] == seqnr: - # found it. Cancel all timers - for n in self.sessions[s]["commTimers"]: - self.sessions[s]["commTimers"][n].cancel(); - - # Confirm - iq.reply(); - iq['type'] = 'result'; - iq['cancelled']['seqnr'] = seqnr; - iq.send(block=False); - - # Delete session - del self.sessions[s] - return - - # Could not find session, send reject - iq.reply(); - iq['type'] = 'error'; - iq['rejected']['seqnr'] = seqnr; - iq['rejected']['error'] = "Cancel request received, no matching request is active."; - iq.send(block=False); - - # ================================================================= - # Client side (data retriever) API - - def request_data(self, from_jid, to_jid, callback, nodeIds=None, fields=None, flags=None): - """ - Called on the client side to initiade a data readout. - Composes a message with the request and sends it to the device(s). - Does not block, the callback will be called when data is available. - - Arguments: - from_jid -- The jid of the requester - to_jid -- The jid of the device(s) - callback -- The callback function to call when data is availble. - - The callback function must support the following arguments: - - from_jid -- The jid of the responding device(s) - result -- The current result status of the readout. Valid values are: - "accepted" - Readout request accepted - "queued" - Readout request accepted and queued - "rejected" - Readout request rejected - "failure" - Readout failed. - "cancelled" - Confirmation of request cancellation. - "started" - Previously queued request is now started - "fields" - Contains readout data. - "done" - Indicates that the readout is complete. - - nodeId -- [optional] Mandatory when result == "fields" or "failure". - The node Id of the responding device. One callback will only - contain data from one device. - timestamp -- [optional] Mandatory when result == "fields". - The timestamp of data in this callback. One callback will only - contain data from one timestamp. - fields -- [optional] Mandatory when result == "fields". - List of field dictionaries representing the readout data. - Dictionary format: - { - typename: The field type (numeric, boolean, dateTime, timeSpan, string, enum) - name: The field name - value: The field value - unit: The unit of the field. Only applies to type numeric. - dataType: The datatype of the field. Only applies to type enum. - flags: [optional] data classifier flags for the field, e.g. momentary. - Formatted as a dictionary like { "flag name": "flag value" ... } - } - - error_msg -- [optional] Mandatory when result == "rejected" or "failure". - Details about why the request is rejected or failed. - "rejected" means that the request is stopped, but note that the - request will continue even after a "failure". "failure" only means - that communication was stopped to that specific device, other - device(s) (if any) will continue their readout. - - nodeIds -- [optional] Limits the request to the node Ids in this list. - fields -- [optional] Limits the request to the field names in this list. - flags -- [optional] Limits the request according to the flags, or sets - readout conditions such as timing. - - Return value: - session -- Session identifier. Client can use this as a reference to cancel - the request. - """ - iq = self.xmpp.Iq(); - iq['from'] = from_jid; - iq['to'] = to_jid; - iq['type'] = "get"; - seqnr = self._get_new_seqnr(); - iq['id'] = seqnr; - iq['req']['seqnr'] = seqnr; - if nodeIds is not None: - for nodeId in nodeIds: - iq['req'].add_node(nodeId); - if fields is not None: - for field in fields: - iq['req'].add_field(field); - - iq['req']._set_flags(flags); - - self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback}; - iq.send(block=False); - - return seqnr; - - def cancel_request(self, session): - """ - Called on the client side to cancel a request for data readout. - Composes a message with the cancellation and sends it to the device(s). - Does not block, the callback will be called when cancellation is - confirmed. - - Arguments: - session -- The session id of the request to cancel - """ - seqnr = session - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[seqnr]['from'] - iq['to'] = self.sessions[seqnr]['to']; - iq['type'] = "get"; - iq['id'] = seqnr; - iq['cancel']['seqnr'] = seqnr; - iq.send(block=False); - - def _get_new_seqnr(self): - """ Returns a unique sequence number (unique across threads) """ - self.seqnr_lock.acquire(); - self.last_seqnr = self.last_seqnr + 1; - self.seqnr_lock.release(); - return str(self.last_seqnr); - - def _handle_event_accepted(self, iq): - """ Received Iq with accepted - request was accepted """ - seqnr = iq['accepted']['seqnr']; - result = "accepted" - if iq['accepted']['queued'] == 'true': - result = "queued" - - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=iq['from'], result=result); - - def _handle_event_rejected(self, iq): - """ Received Iq with rejected - this is a reject. - Delete the session. """ - seqnr = iq['rejected']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error']); - # Session terminated - del self.sessions[seqnr]; - - def _handle_event_cancelled(self, iq): - """ - Received Iq with cancelled - this is a cancel confirm. - Delete the session. - """ - #print("Got cancelled") - seqnr = iq['cancelled']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=iq['from'], result="cancelled"); - # Session cancelled - del self.sessions[seqnr]; - - def _handle_event_fields(self, msg): - """ - Received Msg with fields - this is a data reponse to a request. - If this is the last data block, issue a "done" callback. - """ - seqnr = msg['fields']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - for node in msg['fields']['nodes']: - for ts in node['timestamps']: - fields = []; - for d in ts['datas']: - field_block = {}; - field_block["name"] = d['name']; - field_block["typename"] = d._get_typename(); - field_block["value"] = d['value']; - if not d['unit'] == "": field_block["unit"] = d['unit']; - if not d['dataType'] == "": field_block["dataType"] = d['dataType']; - flags = d._get_flags(); - if not len(flags) == 0: - field_block["flags"] = flags; - fields.append(field_block); - - callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields); - - if msg['fields']['done'] == "true": - callback(from_jid=msg['from'], result="done"); - # Session done - del self.sessions[seqnr]; - - def _handle_event_failure(self, msg): - """ - Received Msg with failure - our request failed - Delete the session. - """ - seqnr = msg['failure']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text']); - - # Session failed - del self.sessions[seqnr]; - - def _handle_event_started(self, msg): - """ - Received Msg with started - our request was queued and is now started. - """ - seqnr = msg['started']['seqnr']; - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=msg['from'], result="started"); - - diff --git a/sleekxmpp/plugins/xep_0323/stanza/__init__.py b/sleekxmpp/plugins/xep_0323/stanza/__init__.py deleted file mode 100644 index c039cefa..00000000 --- a/sleekxmpp/plugins/xep_0323/stanza/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.xep_0323.stanza.sensordata import * - diff --git a/sleekxmpp/plugins/xep_0323/stanza/base.py b/sleekxmpp/plugins/xep_0323/stanza/base.py deleted file mode 100644 index 1dadcf46..00000000 --- a/sleekxmpp/plugins/xep_0323/stanza/base.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET - -pass diff --git a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py b/sleekxmpp/plugins/xep_0323/stanza/sensordata.py deleted file mode 100644 index a11c3e94..00000000 --- a/sleekxmpp/plugins/xep_0323/stanza/sensordata.py +++ /dev/null @@ -1,792 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Iq, Message -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID -from re import match - -class Sensordata(ElementBase): - """ Placeholder for the namespace, not used as a stanza """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'sensordata' - plugin_attrib = name - interfaces = set(tuple()) - -class FieldTypes(): - """ - All field types are optional booleans that default to False - """ - field_types = set([ 'momentary','peak','status','computed','identity','historicalSecond','historicalMinute','historicalHour', \ - 'historicalDay','historicalWeek','historicalMonth','historicalQuarter','historicalYear','historicalOther']) - -class FieldStatus(): - """ - All field statuses are optional booleans that default to False - """ - field_status = set([ 'missing','automaticEstimate','manualEstimate','manualReadout','automaticReadout','timeOffset','warning','error', \ - 'signed','invoiced','endOfSeries','powerFailure','invoiceConfirmed']) - -class Request(ElementBase): - namespace = 'urn:xmpp:iot:sensordata' - name = 'req' - plugin_attrib = name - interfaces = set(['seqnr','nodes','fields','serviceToken','deviceToken','userToken','from','to','when','historical','all']) - interfaces.update(FieldTypes.field_types); - _flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all']); - _flags.update(FieldTypes.field_types); - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._nodes = set() - self._fields = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._nodes = set([node['nodeId'] for node in self['nodes']]) - self._fields = set([field['name'] for field in self['fields']]) - - def _get_flags(self): - """ - Helper function for getting of flags. Returns all flags in - dictionary format: { "flag name": "flag value" ... } - """ - flags = {}; - for f in self._flags: - if not self[f] == "": - flags[f] = self[f]; - return flags; - - def _set_flags(self, flags): - """ - Helper function for setting of flags. - - Arguments: - flags -- Flags in dictionary format: { "flag name": "flag value" ... } - """ - for f in self._flags: - if flags is not None and f in flags: - self[f] = flags[f]; - else: - self[f] = None; - - def add_node(self, nodeId, sourceId=None, cacheType=None): - """ - Add a new node element. Each item is required to have a - nodeId, but may also specify a sourceId value and cacheType. - - Arguments: - nodeId -- The ID for the node. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - if nodeId not in self._nodes: - self._nodes.add((nodeId)) - node = RequestNode(parent=self) - node['nodeId'] = nodeId - node['sourceId'] = sourceId - node['cacheType'] = cacheType - self.iterables.append(node) - return node - return None - - def del_node(self, nodeId): - """ - Remove a single node. - - Arguments: - nodeId -- Node ID of the item to remove. - """ - if nodeId in self._nodes: - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - if node['nodeId'] == nodeId: - self.xml.remove(node.xml) - self.iterables.remove(node) - return True - return False - - def get_nodes(self): - """Return all nodes.""" - nodes = [] - for node in self['substanzas']: - if isinstance(node, RequestNode): - nodes.append(node) - return nodes - - def set_nodes(self, nodes): - """ - Set or replace all nodes. The given nodes must be in a - list or set where each item is a tuple of the form: - (nodeId, sourceId, cacheType) - - Arguments: - nodes -- A series of nodes in tuple format. - """ - self.del_nodes() - for node in nodes: - if isinstance(node, RequestNode): - self.add_node(node['nodeId'], node['sourceId'], node['cacheType']) - else: - nodeId, sourceId, cacheType = node - self.add_node(nodeId, sourceId, cacheType) - - def del_nodes(self): - """Remove all nodes.""" - self._nodes = set() - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - self.xml.remove(node.xml) - self.iterables.remove(node) - - - def add_field(self, name): - """ - Add a new field element. Each item is required to have a - name. - - Arguments: - name -- The name of the field. - """ - if name not in self._fields: - self._fields.add((name)) - field = RequestField(parent=self) - field['name'] = name - self.iterables.append(field) - return field - return None - - def del_field(self, name): - """ - Remove a single field. - - Arguments: - name -- name of field to remove. - """ - if name in self._fields: - fields = [i for i in self.iterables if isinstance(i, RequestField)] - for field in fields: - if field['name'] == name: - self.xml.remove(field.xml) - self.iterables.remove(field) - return True - return False - - def get_fields(self): - """Return all fields.""" - fields = [] - for field in self['substanzas']: - if isinstance(field, RequestField): - fields.append(field) - return fields - - def set_fields(self, fields): - """ - Set or replace all fields. The given fields must be in a - list or set where each item is RequestField or string - - Arguments: - fields -- A series of fields in RequestField or string format. - """ - self.del_fields() - for field in fields: - if isinstance(field, RequestField): - self.add_field(field['name']) - else: - self.add_field(field) - - def del_fields(self): - """Remove all fields.""" - self._fields = set() - fields = [i for i in self.iterables if isinstance(i, RequestField)] - for field in fields: - self.xml.remove(field.xml) - self.iterables.remove(field) - - -class RequestNode(ElementBase): - """ Node element in a request """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'node' - plugin_attrib = name - interfaces = set(['nodeId','sourceId','cacheType']) - -class RequestField(ElementBase): - """ Field element in a request """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'field' - plugin_attrib = name - interfaces = set(['name']) - -class Accepted(ElementBase): - namespace = 'urn:xmpp:iot:sensordata' - name = 'accepted' - plugin_attrib = name - interfaces = set(['seqnr','queued']) - -class Started(ElementBase): - namespace = 'urn:xmpp:iot:sensordata' - name = 'started' - plugin_attrib = name - interfaces = set(['seqnr']) - -class Failure(ElementBase): - namespace = 'urn:xmpp:iot:sensordata' - name = 'failure' - plugin_attrib = name - interfaces = set(['seqnr','done']) - -class Error(ElementBase): - """ Error element in a request failure """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'error' - plugin_attrib = name - interfaces = set(['nodeId','timestamp','sourceId','cacheType','text']) - - def get_text(self): - """Return then contents inside the XML tag.""" - return self.xml.text - - def set_text(self, value): - """Set then contents inside the XML tag. - - :param value: string - """ - - self.xml.text = value; - return self - - def del_text(self): - """Remove the contents inside the XML tag.""" - self.xml.text = "" - return self - -class Rejected(ElementBase): - namespace = 'urn:xmpp:iot:sensordata' - name = 'rejected' - plugin_attrib = name - interfaces = set(['seqnr','error']) - sub_interfaces = set(['error']) - -class Fields(ElementBase): - """ Fields element, top level in a response message with data """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'fields' - plugin_attrib = name - interfaces = set(['seqnr','done','nodes']) - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._nodes = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._nodes = set([node['nodeId'] for node in self['nodes']]) - - - def add_node(self, nodeId, sourceId=None, cacheType=None, substanzas=None): - """ - Add a new node element. Each item is required to have a - nodeId, but may also specify a sourceId value and cacheType. - - Arguments: - nodeId -- The ID for the node. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - if nodeId not in self._nodes: - self._nodes.add((nodeId)) - node = FieldsNode(parent=self) - node['nodeId'] = nodeId - node['sourceId'] = sourceId - node['cacheType'] = cacheType - if substanzas is not None: - node.set_timestamps(substanzas) - - self.iterables.append(node) - return node - return None - - def del_node(self, nodeId): - """ - Remove a single node. - - Arguments: - nodeId -- Node ID of the item to remove. - """ - if nodeId in self._nodes: - nodes = [i for i in self.iterables if isinstance(i, FieldsNode)] - for node in nodes: - if node['nodeId'] == nodeId: - self.xml.remove(node.xml) - self.iterables.remove(node) - return True - return False - - def get_nodes(self): - """Return all nodes.""" - nodes = [] - for node in self['substanzas']: - if isinstance(node, FieldsNode): - nodes.append(node) - return nodes - - def set_nodes(self, nodes): - """ - Set or replace all nodes. The given nodes must be in a - list or set where each item is a tuple of the form: - (nodeId, sourceId, cacheType) - - Arguments: - nodes -- A series of nodes in tuple format. - """ - #print(str(id(self)) + " set_nodes: got " + str(nodes)) - self.del_nodes() - for node in nodes: - if isinstance(node, FieldsNode): - self.add_node(node['nodeId'], node['sourceId'], node['cacheType'], substanzas=node['substanzas']) - else: - nodeId, sourceId, cacheType = node - self.add_node(nodeId, sourceId, cacheType) - - def del_nodes(self): - """Remove all nodes.""" - self._nodes = set() - nodes = [i for i in self.iterables if isinstance(i, FieldsNode)] - for node in nodes: - self.xml.remove(node.xml) - self.iterables.remove(node) - - -class FieldsNode(ElementBase): - """ Node element in response fields """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'node' - plugin_attrib = name - interfaces = set(['nodeId','sourceId','cacheType','timestamps']) - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._timestamps = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._timestamps = set([ts['value'] for ts in self['timestamps']]) - - def add_timestamp(self, timestamp, substanzas=None): - """ - Add a new timestamp element. - - Arguments: - timestamp -- The timestamp in ISO format. - """ - #print(str(id(self)) + " add_timestamp: " + str(timestamp)) - - if timestamp not in self._timestamps: - self._timestamps.add((timestamp)) - ts = Timestamp(parent=self) - ts['value'] = timestamp - if not substanzas is None: - ts.set_datas(substanzas); - #print("add_timestamp with substanzas: " + str(substanzas)) - self.iterables.append(ts) - #print(str(id(self)) + " added_timestamp: " + str(id(ts))) - return ts - return None - - def del_timestamp(self, timestamp): - """ - Remove a single timestamp. - - Arguments: - timestamp -- timestamp (in ISO format) of the item to remove. - """ - #print("del_timestamp: ") - if timestamp in self._timestamps: - timestamps = [i for i in self.iterables if isinstance(i, Timestamp)] - for ts in timestamps: - if ts['value'] == timestamp: - self.xml.remove(ts.xml) - self.iterables.remove(ts) - return True - return False - - def get_timestamps(self): - """Return all timestamps.""" - #print(str(id(self)) + " get_timestamps: ") - timestamps = [] - for timestamp in self['substanzas']: - if isinstance(timestamp, Timestamp): - timestamps.append(timestamp) - return timestamps - - def set_timestamps(self, timestamps): - """ - Set or replace all timestamps. The given timestamps must be in a - list or set where each item is a timestamp - - Arguments: - timestamps -- A series of timestamps. - """ - #print(str(id(self)) + " set_timestamps: got " + str(timestamps)) - self.del_timestamps() - for timestamp in timestamps: - #print("set_timestamps: subset " + str(timestamp)) - #print("set_timestamps: subset.substanzas " + str(timestamp['substanzas'])) - if isinstance(timestamp, Timestamp): - self.add_timestamp(timestamp['value'], substanzas=timestamp['substanzas']) - else: - #print("set_timestamps: got " + str(timestamp)) - self.add_timestamp(timestamp) - - def del_timestamps(self): - """Remove all timestamps.""" - #print(str(id(self)) + " del_timestamps: ") - self._timestamps = set() - timestamps = [i for i in self.iterables if isinstance(i, Timestamp)] - for timestamp in timestamps: - self.xml.remove(timestamp.xml) - self.iterables.remove(timestamp) - -class Field(ElementBase): - """ - Field element in response Timestamp. This is a base class, - all instances of fields added to Timestamp must be of types: - DataNumeric - DataString - DataBoolean - DataDateTime - DataTimeSpan - DataEnum - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'field' - plugin_attrib = name - interfaces = set(['name','module','stringIds']); - interfaces.update(FieldTypes.field_types); - interfaces.update(FieldStatus.field_status); - - _flags = set(); - _flags.update(FieldTypes.field_types); - _flags.update(FieldStatus.field_status); - - def set_stringIds(self, value): - """Verifies stringIds according to regexp from specification XMPP-0323. - - :param value: string - """ - - pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") - if pattern.match(value) is not None: - self.xml.stringIds = value; - else: - # Bad content, add nothing - pass - - return self - - def _get_flags(self): - """ - Helper function for getting of flags. Returns all flags in - dictionary format: { "flag name": "flag value" ... } - """ - flags = {}; - for f in self._flags: - if not self[f] == "": - flags[f] = self[f]; - return flags; - - def _set_flags(self, flags): - """ - Helper function for setting of flags. - - Arguments: - flags -- Flags in dictionary format: { "flag name": "flag value" ... } - """ - for f in self._flags: - if flags is not None and f in flags: - self[f] = flags[f]; - else: - self[f] = None; - - def _get_typename(self): - return "invalid type, use subclasses!"; - - -class Timestamp(ElementBase): - """ Timestamp element in response Node """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'timestamp' - plugin_attrib = name - interfaces = set(['value','datas']) - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._datas = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._datas = set([data['name'] for data in self['datas']]) - - def add_data(self, typename, name, value, module=None, stringIds=None, unit=None, dataType=None, flags=None): - """ - Add a new data element. - - Arguments: - typename -- The type of data element (numeric, string, boolean, dateTime, timeSpan or enum) - value -- The value of the data element - module -- [optional] language module to use for the data element - stringIds -- [optional] The stringIds used to find associated text in the language module - unit -- [optional] The unit. Only applicable for type numeric - dataType -- [optional] The dataType. Only applicable for type enum - """ - if name not in self._datas: - dataObj = None; - if typename == "numeric": - dataObj = DataNumeric(parent=self); - dataObj['unit'] = unit; - elif typename == "string": - dataObj = DataString(parent=self); - elif typename == "boolean": - dataObj = DataBoolean(parent=self); - elif typename == "dateTime": - dataObj = DataDateTime(parent=self); - elif typename == "timeSpan": - dataObj = DataTimeSpan(parent=self); - elif typename == "enum": - dataObj = DataEnum(parent=self); - dataObj['dataType'] = dataType; - - dataObj['name'] = name; - dataObj['value'] = value; - dataObj['module'] = module; - dataObj['stringIds'] = stringIds; - - if flags is not None: - dataObj._set_flags(flags); - - self._datas.add(name) - self.iterables.append(dataObj) - return dataObj - return None - - def del_data(self, name): - """ - Remove a single data element. - - Arguments: - data_name -- The data element name to remove. - """ - if name in self._datas: - datas = [i for i in self.iterables if isinstance(i, Field)] - for data in datas: - if data['name'] == name: - self.xml.remove(data.xml) - self.iterables.remove(data) - return True - return False - - def get_datas(self): - """ Return all data elements. """ - datas = [] - for data in self['substanzas']: - if isinstance(data, Field): - datas.append(data) - return datas - - def set_datas(self, datas): - """ - Set or replace all data elements. The given elements must be in a - list or set where each item is a data element (numeric, string, boolean, dateTime, timeSpan or enum) - - Arguments: - datas -- A series of data elements. - """ - self.del_datas() - for data in datas: - self.add_data(typename=data._get_typename(), name=data['name'], value=data['value'], module=data['module'], stringIds=data['stringIds'], unit=data['unit'], dataType=data['dataType'], flags=data._get_flags()) - - def del_datas(self): - """Remove all data elements.""" - self._datas = set() - datas = [i for i in self.iterables if isinstance(i, Field)] - for data in datas: - self.xml.remove(data.xml) - self.iterables.remove(data) - -class DataNumeric(Field): - """ - Field data of type numeric. - Note that the value is expressed as a string. - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'numeric' - plugin_attrib = name - interfaces = set(['value', 'unit']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "numeric" - -class DataString(Field): - """ - Field data of type string - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'string' - plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "string" - -class DataBoolean(Field): - """ - Field data of type boolean. - Note that the value is expressed as a string. - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'boolean' - plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "boolean" - -class DataDateTime(Field): - """ - Field data of type dateTime. - Note that the value is expressed as a string. - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'dateTime' - plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "dateTime" - -class DataTimeSpan(Field): - """ - Field data of type timeSpan. - Note that the value is expressed as a string. - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'timeSpan' - plugin_attrib = name - interfaces = set(['value']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "timeSpan" - -class DataEnum(Field): - """ - Field data of type enum. - Note that the value is expressed as a string. - """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'enum' - plugin_attrib = name - interfaces = set(['value', 'dataType']); - interfaces.update(Field.interfaces); - - def _get_typename(self): - return "enum" - -class Done(ElementBase): - """ Done element used to signal that all data has been transferred """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'done' - plugin_attrib = name - interfaces = set(['seqnr']) - -class Cancel(ElementBase): - """ Cancel element used to signal that a request shall be cancelled """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'cancel' - plugin_attrib = name - interfaces = set(['seqnr']) - -class Cancelled(ElementBase): - """ Cancelled element used to signal that cancellation is confirmed """ - namespace = 'urn:xmpp:iot:sensordata' - name = 'cancelled' - plugin_attrib = name - interfaces = set(['seqnr']) - - -register_stanza_plugin(Iq, Request) -register_stanza_plugin(Request, RequestNode, iterable=True) -register_stanza_plugin(Request, RequestField, iterable=True) - -register_stanza_plugin(Iq, Accepted) -register_stanza_plugin(Message, Failure) -register_stanza_plugin(Failure, Error) - -register_stanza_plugin(Iq, Rejected) - -register_stanza_plugin(Message, Fields) -register_stanza_plugin(Fields, FieldsNode, iterable=True) -register_stanza_plugin(FieldsNode, Timestamp, iterable=True) -register_stanza_plugin(Timestamp, Field, iterable=True) -register_stanza_plugin(Timestamp, DataNumeric, iterable=True) -register_stanza_plugin(Timestamp, DataString, iterable=True) -register_stanza_plugin(Timestamp, DataBoolean, iterable=True) -register_stanza_plugin(Timestamp, DataDateTime, iterable=True) -register_stanza_plugin(Timestamp, DataTimeSpan, iterable=True) -register_stanza_plugin(Timestamp, DataEnum, iterable=True) - -register_stanza_plugin(Message, Started) - -register_stanza_plugin(Iq, Cancel) -register_stanza_plugin(Iq, Cancelled) diff --git a/sleekxmpp/plugins/xep_0323/timerreset.py b/sleekxmpp/plugins/xep_0323/timerreset.py deleted file mode 100644 index 578f1efe..00000000 --- a/sleekxmpp/plugins/xep_0323/timerreset.py +++ /dev/null @@ -1,64 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" -from threading import Thread, Event, Timer -import time - -def TimerReset(*args, **kwargs): - """ Global function for Timer """ - return _TimerReset(*args, **kwargs) - - -class _TimerReset(Thread): - """Call a function after a specified number of seconds: - - t = TimerReset(30.0, f, args=[], kwargs={}) - t.start() - t.cancel() # stop the timer's action if it's still waiting - """ - - def __init__(self, interval, function, args=[], kwargs={}): - Thread.__init__(self) - self.interval = interval - self.function = function - self.args = args - self.kwargs = kwargs - self.finished = Event() - self.resetted = True - - def cancel(self): - """Stop the timer if it hasn't finished yet""" - self.finished.set() - - def run(self): - #print "Time: %s - timer running..." % time.asctime() - - while self.resetted: - #print "Time: %s - timer waiting for timeout in %.2f..." % (time.asctime(), self.interval) - self.resetted = False - self.finished.wait(self.interval) - - if not self.finished.isSet(): - self.function(*self.args, **self.kwargs) - self.finished.set() - #print "Time: %s - timer finished!" % time.asctime() - - def reset(self, interval=None): - """ Reset the timer """ - - if interval: - #print "Time: %s - timer resetting to %.2f..." % (time.asctime(), interval) - self.interval = interval - else: - #print "Time: %s - timer resetting..." % time.asctime() - pass - - self.resetted = True - self.finished.set() - self.finished.clear() diff --git a/sleekxmpp/plugins/xep_0325/__init__.py b/sleekxmpp/plugins/xep_0325/__init__.py deleted file mode 100644 index 01c38dce..00000000 --- a/sleekxmpp/plugins/xep_0325/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0325.control import XEP_0325 -from sleekxmpp.plugins.xep_0325 import stanza - -register_plugin(XEP_0325) - -xep_0325=XEP_0325 diff --git a/sleekxmpp/plugins/xep_0325/control.py b/sleekxmpp/plugins/xep_0325/control.py deleted file mode 100644 index e34eb2c2..00000000 --- a/sleekxmpp/plugins/xep_0325/control.py +++ /dev/null @@ -1,574 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -import time -from threading import Thread, Timer, Lock - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.handler import Callback -from sleekxmpp.xmlstream.matcher import StanzaPath -from sleekxmpp.plugins.base import BasePlugin -from sleekxmpp.plugins.xep_0325 import stanza -from sleekxmpp.plugins.xep_0325.stanza import Control - - -log = logging.getLogger(__name__) - - -class XEP_0325(BasePlugin): - - """ - XEP-0325: IoT Control - - - Actuators are devices in sensor networks that can be controlled through - the network and act with the outside world. In sensor networks and - Internet of Things applications, actuators make it possible to automate - real-world processes. - This plugin implements a mechanism whereby actuators can be controlled - in XMPP-based sensor networks, making it possible to integrate sensors - and actuators of different brands, makes and models into larger - Internet of Things applications. - - Also see <http://xmpp.org/extensions/xep-0325.html> - - Configuration Values: - threaded -- Indicates if communication with sensors should be threaded. - Defaults to True. - - Events: - Sensor side - ----------- - Control Event:DirectSet -- Received a control message - Control Event:SetReq -- Received a control request - - Client side - ----------- - Control Event:SetResponse -- Received a response to a - control request, type result - Control Event:SetResponseError -- Received a response to a - control request, type error - - Attributes: - threaded -- Indicates if command events should be threaded. - Defaults to True. - sessions -- A dictionary or equivalent backend mapping - session IDs to dictionaries containing data - relevant to a request's session. This dictionary is used - both by the client and sensor side. On client side, seqnr - is used as key, while on sensor side, a session_id is used - as key. This ensures that the two will not collide, so - one instance can be both client and sensor. - Sensor side - ----------- - nodes -- A dictionary mapping sensor nodes that are serviced through - this XMPP instance to their device handlers ("drivers"). - Client side - ----------- - last_seqnr -- The last used sequence number (integer). One sequence of - communication (e.g. -->request, <--accept, <--fields) - between client and sensor is identified by a unique - sequence number (unique between the client/sensor pair) - - Methods: - plugin_init -- Overrides base_plugin.plugin_init - post_init -- Overrides base_plugin.post_init - plugin_end -- Overrides base_plugin.plugin_end - - Sensor side - ----------- - register_node -- Register a sensor as available from this XMPP - instance. - - Client side - ----------- - set_request -- Initiates a control request to modify data in - sensor(s). Non-blocking, a callback function will - be called when the sensor has responded. - set_command -- Initiates a control command to modify data in - sensor(s). Non-blocking. The sensor(s) will not - respond regardless of the result of the command, - so no callback is made. - - """ - - name = 'xep_0325' - description = 'XEP-0325 Internet of Things - Control' - dependencies = set(['xep_0030']) - stanza = stanza - - - default_config = { - 'threaded': True -# 'session_db': None - } - - def plugin_init(self): - """ Start the XEP-0325 plugin """ - - self.xmpp.register_handler( - Callback('Control Event:DirectSet', - StanzaPath('message/set'), - self._handle_direct_set)) - - self.xmpp.register_handler( - Callback('Control Event:SetReq', - StanzaPath('iq@type=set/set'), - self._handle_set_req)) - - self.xmpp.register_handler( - Callback('Control Event:SetResponse', - StanzaPath('iq@type=result/setResponse'), - self._handle_set_response)) - - self.xmpp.register_handler( - Callback('Control Event:SetResponseError', - StanzaPath('iq@type=error/setResponse'), - self._handle_set_response)) - - # Server side dicts - self.nodes = {}; - self.sessions = {}; - - self.last_seqnr = 0; - self.seqnr_lock = Lock(); - - ## For testning only - self.test_authenticated_from = "" - - def post_init(self): - """ Init complete. Register our features in Serivce discovery. """ - BasePlugin.post_init(self) - self.xmpp['xep_0030'].add_feature(Control.namespace) - self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple()) - - def _new_session(self): - """ Return a new session ID. """ - return str(time.time()) + '-' + self.xmpp.new_id() - - def plugin_end(self): - """ Stop the XEP-0325 plugin """ - self.sessions.clear(); - self.xmpp.remove_handler('Control Event:DirectSet') - self.xmpp.remove_handler('Control Event:SetReq') - self.xmpp.remove_handler('Control Event:SetResponse') - self.xmpp.remove_handler('Control Event:SetResponseError') - self.xmpp['xep_0030'].del_feature(feature=Control.namespace) - self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple()); - - - # ================================================================= - # Sensor side (data provider) API - - def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None): - """ - Register a sensor/device as available for control requests/commands - through this XMPP instance. - - The device object may by any custom implementation to support - specific devices, but it must implement the functions: - has_control_field - set_control_fields - according to the interfaces shown in the example device.py file. - - Arguments: - nodeId -- The identifier for the device - device -- The device object - commTimeout -- Time in seconds to wait between each callback from device during - a data readout. Float. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - self.nodes[nodeId] = {"device": device, - "commTimeout": commTimeout, - "sourceId": sourceId, - "cacheType": cacheType}; - - def _set_authenticated(self, auth=''): - """ Internal testing function """ - self.test_authenticated_from = auth; - - def _get_new_seqnr(self): - """ Returns a unique sequence number (unique across threads) """ - self.seqnr_lock.acquire(); - self.last_seqnr = self.last_seqnr + 1; - self.seqnr_lock.release(); - return str(self.last_seqnr); - - def _handle_set_req(self, iq): - """ - Event handler for reception of an Iq with set req - this is a - control request. - - Verifies that - - all the requested nodes are available - (if no nodes are specified in the request, assume all nodes) - - all the control fields are available from all requested nodes - (if no nodes are specified in the request, assume all nodes) - - If the request passes verification, the control request is passed - to the devices (in a separate thread). - If the verification fails, a setResponse with error indication - is sent. - """ - - error_msg = ''; - req_ok = True; - missing_node = None; - missing_field = None; - - # Authentication - if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from: - # Invalid authentication - req_ok = False; - error_msg = "Access denied"; - - # Nodes - process_nodes = []; - if len(iq['set']['nodes']) > 0: - for n in iq['set']['nodes']: - if not n['nodeId'] in self.nodes: - req_ok = False; - missing_node = n['nodeId']; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in iq['set']['nodes']]; - else: - process_nodes = self.nodes.keys(); - - # Fields - for control we need to find all in all devices, otherwise we reject - process_fields = []; - if len(iq['set']['datas']) > 0: - for f in iq['set']['datas']: - for node in self.nodes: - if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()): - req_ok = False; - missing_field = f['name']; - error_msg = "Invalid field " + f['name']; - break; - process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']]; - - if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; - # Flag that a reply is exected when we are done - self.sessions[session]["reply"] = True; - - self.sessions[session]["node_list"] = process_nodes; - if self.threaded: - #print("starting thread") - tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields)) - tr_req.start() - #print("started thread") - else: - self._threaded_node_request(session, process_fields); - - else: - iq.reply(); - iq['type'] = 'error'; - iq['setResponse']['responseCode'] = "NotFound"; - if missing_node is not None: - iq['setResponse'].add_node(missing_node); - if missing_field is not None: - iq['setResponse'].add_data(missing_field); - iq['setResponse']['error']['var'] = "Output"; - iq['setResponse']['error']['text'] = error_msg; - iq.send(block=False); - - def _handle_direct_set(self, msg): - """ - Event handler for reception of a Message with set command - this is a - direct control command. - - Verifies that - - all the requested nodes are available - (if no nodes are specified in the request, assume all nodes) - - all the control fields are available from all requested nodes - (if no nodes are specified in the request, assume all nodes) - - If the request passes verification, the control request is passed - to the devices (in a separate thread). - If the verification fails, do nothing. - """ - req_ok = True; - - # Nodes - process_nodes = []; - if len(msg['set']['nodes']) > 0: - for n in msg['set']['nodes']: - if not n['nodeId'] in self.nodes: - req_ok = False; - error_msg = "Invalid nodeId " + n['nodeId']; - process_nodes = [n['nodeId'] for n in msg['set']['nodes']]; - else: - process_nodes = self.nodes.keys(); - - # Fields - for control we need to find all in all devices, otherwise we reject - process_fields = []; - if len(msg['set']['datas']) > 0: - for f in msg['set']['datas']: - for node in self.nodes: - if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()): - req_ok = False; - missing_field = f['name']; - error_msg = "Invalid field " + f['name']; - break; - process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']]; - - if req_ok: - session = self._new_session(); - self.sessions[session] = {"from": msg['from'], "to": msg['to']}; - self.sessions[session]["commTimers"] = {}; - self.sessions[session]["nodeDone"] = {}; - self.sessions[session]["reply"] = False; - - self.sessions[session]["node_list"] = process_nodes; - if self.threaded: - #print("starting thread") - tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields)) - tr_req.start() - #print("started thread") - else: - self._threaded_node_request(session, process_fields); - - - def _threaded_node_request(self, session, process_fields): - """ - Helper function to handle the device control in a separate thread. - - Arguments: - session -- The request session id - process_fields -- The fields to set in the devices. List of tuple format: - (name, datatype, value) - """ - for node in self.sessions[session]["node_list"]: - self.sessions[session]["nodeDone"][node] = False; - - for node in self.sessions[session]["node_list"]: - timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node)); - self.sessions[session]["commTimers"][node] = timer; - timer.start(); - self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback); - - def _event_comm_timeout(self, session, nodeId): - """ - Triggered if any of the control operations timeout. - Stop communicating with the failing device. - If the control command was an Iq request, sends a failure - message back to the client. - - Arguments: - session -- The request session id - nodeId -- The id of the device which timed out - """ - - if self.sessions[session]["reply"]: - # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "error"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OtherError"; - iq['setResponse'].add_node(nodeId); - iq['setResponse']['error']['var'] = "Output"; - iq['setResponse']['error']['text'] = "Timeout."; - iq.send(block=False); - - ## TODO - should we send one timeout per node?? - - # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; - if (self._all_nodes_done(session)): - # The session is complete, delete it - del self.sessions[session]; - - def _all_nodes_done(self, session): - """ - Checks wheter all devices are done replying to the control command. - - Arguments: - session -- The request session id - """ - for n in self.sessions[session]["nodeDone"]: - if not self.sessions[session]["nodeDone"][n]: - return False; - return True; - - def _device_set_command_callback(self, session, nodeId, result, error_field=None, error_msg=None): - """ - Callback function called by the devices when the control command is - complete or failed. - If needed, composes a message with the result and sends it back to the - client. - - Arguments: - session -- The request session id - nodeId -- The device id which initiated the callback - result -- The current result status of the control command. Valid values are: - "error" - Set fields failed. - "ok" - All fields were set. - error_field -- [optional] Only applies when result == "error" - The field name that failed (usually means it is missing) - error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. - """ - - if not session in self.sessions: - # This can happend if a session was deleted, like in a timeout. Just drop the data. - return - - if result == "error": - self.sessions[session]["commTimers"][nodeId].cancel(); - - if self.sessions[session]["reply"]: - # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "error"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OtherError"; - iq['setResponse'].add_node(nodeId); - if error_field is not None: - iq['setResponse'].add_data(error_field); - iq['setResponse']['error']['var'] = error_field; - iq['setResponse']['error']['text'] = error_msg; - iq.send(block=False); - - # Drop communication with this device and check if we are done - self.sessions[session]["nodeDone"][nodeId] = True; - if (self._all_nodes_done(session)): - # The session is complete, delete it - del self.sessions[session]; - else: - self.sessions[session]["commTimers"][nodeId].cancel(); - - self.sessions[session]["nodeDone"][nodeId] = True; - if (self._all_nodes_done(session)): - if self.sessions[session]["reply"]: - # Reply is exected when we are done - iq = self.xmpp.Iq(); - iq['from'] = self.sessions[session]['to']; - iq['to'] = self.sessions[session]['from']; - iq['type'] = "result"; - iq['id'] = self.sessions[session]['seqnr']; - iq['setResponse']['responseCode'] = "OK"; - iq.send(block=False); - - # The session is complete, delete it - del self.sessions[session]; - - - # ================================================================= - # Client side (data controller) API - - def set_request(self, from_jid, to_jid, callback, fields, nodeIds=None): - """ - Called on the client side to initiade a control request. - Composes a message with the request and sends it to the device(s). - Does not block, the callback will be called when the device(s) - has responded. - - Arguments: - from_jid -- The jid of the requester - to_jid -- The jid of the device(s) - callback -- The callback function to call when data is availble. - - The callback function must support the following arguments: - - from_jid -- The jid of the responding device(s) - result -- The result of the control request. Valid values are: - "OK" - Control request completed successfully - "NotFound" - One or more nodes or fields are missing - "InsufficientPrivileges" - Not authorized. - "Locked" - Field(s) is locked and cannot - be changed at the moment. - "NotImplemented" - Request feature not implemented. - "FormError" - Error while setting with - a form (not implemented). - "OtherError" - Indicates other types of - errors, such as timeout. - Details in the error_msg. - - - nodeId -- [optional] Only applicable when result == "error" - List of node Ids of failing device(s). - - fields -- [optional] Only applicable when result == "error" - List of fields that failed.[optional] Mandatory when result == "rejected" or "failure". - - error_msg -- Details about why the request failed. - - fields -- Fields to set. List of tuple format: (name, typename, value). - nodeIds -- [optional] Limits the request to the node Ids in this list. - """ - iq = self.xmpp.Iq(); - iq['from'] = from_jid; - iq['to'] = to_jid; - seqnr = self._get_new_seqnr(); - iq['id'] = seqnr; - iq['type'] = "set"; - if nodeIds is not None: - for nodeId in nodeIds: - iq['set'].add_node(nodeId); - if fields is not None: - for name, typename, value in fields: - iq['set'].add_data(name=name, typename=typename, value=value); - - self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback}; - iq.send(block=False); - - def set_command(self, from_jid, to_jid, fields, nodeIds=None): - """ - Called on the client side to initiade a control command. - Composes a message with the set commandand sends it to the device(s). - Does not block. Device(s) will not respond, regardless of result. - - Arguments: - from_jid -- The jid of the requester - to_jid -- The jid of the device(s) - - fields -- Fields to set. List of tuple format: (name, typename, value). - nodeIds -- [optional] Limits the request to the node Ids in this list. - """ - msg = self.xmpp.Message(); - msg['from'] = from_jid; - msg['to'] = to_jid; - msg['type'] = "set"; - if nodeIds is not None: - for nodeId in nodeIds: - msg['set'].add_node(nodeId); - if fields is not None: - for name, typename, value in fields: - msg['set'].add_data(name, typename, value); - - # We won't get any reply, so don't create a session - msg.send(); - - def _handle_set_response(self, iq): - """ Received response from device(s) """ - #print("ooh") - seqnr = iq['id']; - from_jid = str(iq['from']); - result = iq['setResponse']['responseCode']; - nodeIds = [n['name'] for n in iq['setResponse']['nodes']]; - fields = [f['name'] for f in iq['setResponse']['datas']]; - error_msg = None; - - if not iq['setResponse'].find('error') is None and not iq['setResponse']['error']['text'] == "": - error_msg = iq['setResponse']['error']['text']; - - callback = self.sessions[seqnr]["callback"]; - callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg); - - diff --git a/sleekxmpp/plugins/xep_0325/device.py b/sleekxmpp/plugins/xep_0325/device.py deleted file mode 100644 index a60d5f9a..00000000 --- a/sleekxmpp/plugins/xep_0325/device.py +++ /dev/null @@ -1,125 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import datetime - -class Device(object): - """ - Example implementation of a device control object. - - The device object may by any custom implementation to support - specific devices, but it must implement the functions: - has_control_field - set_control_fields - """ - - def __init__(self, nodeId): - self.nodeId = nodeId; - self.control_fields = {}; - - def has_control_field(self, field, typename): - """ - Returns true if the supplied field name exists - and the type matches for control in this device. - - Arguments: - field -- The field name - typename -- The expected type - """ - if field in self.control_fields and self.control_fields[field]["type"] == typename: - return True; - return False; - - def set_control_fields(self, fields, session, callback): - """ - Starts a control setting procedure. Verifies the fields, - sets the data and (if needed) and calls the callback. - - Arguments: - fields -- List of control fields in tuple format: - (name, typename, value) - session -- Session id, only used in the callback as identifier - callback -- Callback function to call when control set is complete. - - The callback function must support the following arguments: - - session -- Session id, as supplied in the - request_fields call - nodeId -- Identifier for this device - result -- The current result status of the readout. - Valid values are: - "error" - Set fields failed. - "ok" - All fields were set. - error_field -- [optional] Only applies when result == "error" - The field name that failed - (usually means it is missing) - error_msg -- [optional] Only applies when result == "error". - Error details when a request failed. - """ - - if len(fields) > 0: - # Check availiability - for name, typename, value in fields: - if not self.has_control_field(name, typename): - self._send_control_reject(session, name, "NotFound", callback) - return False; - - for name, typename, value in fields: - self._set_field_value(name, value) - - callback(session, result="ok", nodeId=self.nodeId); - return True - - def _send_control_reject(self, session, field, message, callback): - """ - Sends a reject to the caller - - Arguments: - session -- Session id, see definition in - set_control_fields function - callback -- Callback function, see definition in - set_control_fields function - """ - callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message); - - def _add_control_field(self, name, typename, value): - """ - Adds a control field to the device - - Arguments: - name -- Name of the field - typename -- Type of the field, one of: - (boolean, color, string, date, dateTime, - double, duration, int, long, time) - value -- Field value - """ - self.control_fields[name] = {"type": typename, "value": value}; - - def _set_field_value(self, name, value): - """ - Set the value of a control field - - Arguments: - name -- Name of the field - value -- New value for the field - """ - if name in self.control_fields: - self.control_fields[name]["value"] = value; - - def _get_field_value(self, name): - """ - Get the value of a control field. Only used for unit testing. - - Arguments: - name -- Name of the field - """ - if name in self.control_fields: - return self.control_fields[name]["value"]; - return None; diff --git a/sleekxmpp/plugins/xep_0325/stanza/__init__.py b/sleekxmpp/plugins/xep_0325/stanza/__init__.py deleted file mode 100644 index 746c2033..00000000 --- a/sleekxmpp/plugins/xep_0325/stanza/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.plugins.xep_0325.stanza.control import * - diff --git a/sleekxmpp/plugins/xep_0325/stanza/base.py b/sleekxmpp/plugins/xep_0325/stanza/base.py deleted file mode 100644 index 1dadcf46..00000000 --- a/sleekxmpp/plugins/xep_0325/stanza/base.py +++ /dev/null @@ -1,13 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream import ET - -pass diff --git a/sleekxmpp/plugins/xep_0325/stanza/control.py b/sleekxmpp/plugins/xep_0325/stanza/control.py deleted file mode 100644 index 67107ecb..00000000 --- a/sleekxmpp/plugins/xep_0325/stanza/control.py +++ /dev/null @@ -1,526 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Implementation of xeps for Internet of Things - http://wiki.xmpp.org/web/Tech_pages/IoT_systems - Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp import Iq, Message -from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID -from re import match - -class Control(ElementBase): - """ Placeholder for the namespace, not used as a stanza """ - namespace = 'urn:xmpp:iot:control' - name = 'control' - plugin_attrib = name - interfaces = set(tuple()) - -class ControlSet(ElementBase): - namespace = 'urn:xmpp:iot:control' - name = 'set' - plugin_attrib = name - interfaces = set(['nodes','datas']) - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._nodes = set() - self._datas = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._nodes = set([node['nodeId'] for node in self['nodes']]) - self._datas = set([data['name'] for data in self['datas']]) - - def add_node(self, nodeId, sourceId=None, cacheType=None): - """ - Add a new node element. Each item is required to have a - nodeId, but may also specify a sourceId value and cacheType. - - Arguments: - nodeId -- The ID for the node. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - if nodeId not in self._nodes: - self._nodes.add((nodeId)) - node = RequestNode(parent=self) - node['nodeId'] = nodeId - node['sourceId'] = sourceId - node['cacheType'] = cacheType - self.iterables.append(node) - return node - return None - - def del_node(self, nodeId): - """ - Remove a single node. - - Arguments: - nodeId -- Node ID of the item to remove. - """ - if nodeId in self._nodes: - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - if node['nodeId'] == nodeId: - self.xml.remove(node.xml) - self.iterables.remove(node) - return True - return False - - def get_nodes(self): - """Return all nodes.""" - nodes = [] - for node in self['substanzas']: - if isinstance(node, RequestNode): - nodes.append(node) - return nodes - - def set_nodes(self, nodes): - """ - Set or replace all nodes. The given nodes must be in a - list or set where each item is a tuple of the form: - (nodeId, sourceId, cacheType) - - Arguments: - nodes -- A series of nodes in tuple format. - """ - self.del_nodes() - for node in nodes: - if isinstance(node, RequestNode): - self.add_node(node['nodeId'], node['sourceId'], node['cacheType']) - else: - nodeId, sourceId, cacheType = node - self.add_node(nodeId, sourceId, cacheType) - - def del_nodes(self): - """Remove all nodes.""" - self._nodes = set() - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - self.xml.remove(node.xml) - self.iterables.remove(node) - - - def add_data(self, name, typename, value): - """ - Add a new data element. - - Arguments: - name -- The name of the data element - typename -- The type of data element - (boolean, color, string, date, dateTime, - double, duration, int, long, time) - value -- The value of the data element - """ - if name not in self._datas: - dataObj = None; - if typename == "boolean": - dataObj = BooleanParameter(parent=self); - elif typename == "color": - dataObj = ColorParameter(parent=self); - elif typename == "string": - dataObj = StringParameter(parent=self); - elif typename == "date": - dataObj = DateParameter(parent=self); - elif typename == "dateTime": - dataObj = DateTimeParameter(parent=self); - elif typename == "double": - dataObj = DoubleParameter(parent=self); - elif typename == "duration": - dataObj = DurationParameter(parent=self); - elif typename == "int": - dataObj = IntParameter(parent=self); - elif typename == "long": - dataObj = LongParameter(parent=self); - elif typename == "time": - dataObj = TimeParameter(parent=self); - - dataObj['name'] = name; - dataObj['value'] = value; - - self._datas.add(name) - self.iterables.append(dataObj) - return dataObj - return None - - def del_data(self, name): - """ - Remove a single data element. - - Arguments: - data_name -- The data element name to remove. - """ - if name in self._datas: - datas = [i for i in self.iterables if isinstance(i, BaseParameter)] - for data in datas: - if data['name'] == name: - self.xml.remove(data.xml) - self.iterables.remove(data) - return True - return False - - def get_datas(self): - """ Return all data elements. """ - datas = [] - for data in self['substanzas']: - if isinstance(data, BaseParameter): - datas.append(data) - return datas - - def set_datas(self, datas): - """ - Set or replace all data elements. The given elements must be in a - list or set where each item is a data element (numeric, string, boolean, dateTime, timeSpan or enum) - - Arguments: - datas -- A series of data elements. - """ - self.del_datas() - for data in datas: - self.add_data(name=data['name'], typename=data._get_typename(), value=data['value']) - - def del_datas(self): - """Remove all data elements.""" - self._datas = set() - datas = [i for i in self.iterables if isinstance(i, BaseParameter)] - for data in datas: - self.xml.remove(data.xml) - self.iterables.remove(data) - - -class RequestNode(ElementBase): - """ Node element in a request """ - namespace = 'urn:xmpp:iot:control' - name = 'node' - plugin_attrib = name - interfaces = set(['nodeId','sourceId','cacheType']) - - -class ControlSetResponse(ElementBase): - namespace = 'urn:xmpp:iot:control' - name = 'setResponse' - plugin_attrib = name - interfaces = set(['responseCode']) - - def __init__(self, xml=None, parent=None): - ElementBase.__init__(self, xml, parent); - self._nodes = set() - self._datas = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._nodes = set([node['nodeId'] for node in self['nodes']]) - self._datas = set([data['name'] for data in self['datas']]) - - def add_node(self, nodeId, sourceId=None, cacheType=None): - """ - Add a new node element. Each item is required to have a - nodeId, but may also specify a sourceId value and cacheType. - - Arguments: - nodeId -- The ID for the node. - sourceId -- [optional] identifying the data source controlling the device - cacheType -- [optional] narrowing down the search to a specific kind of node - """ - if nodeId not in self._nodes: - self._nodes.add(nodeId) - node = RequestNode(parent=self) - node['nodeId'] = nodeId - node['sourceId'] = sourceId - node['cacheType'] = cacheType - self.iterables.append(node) - return node - return None - - def del_node(self, nodeId): - """ - Remove a single node. - - Arguments: - nodeId -- Node ID of the item to remove. - """ - if nodeId in self._nodes: - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - if node['nodeId'] == nodeId: - self.xml.remove(node.xml) - self.iterables.remove(node) - return True - return False - - def get_nodes(self): - """Return all nodes.""" - nodes = [] - for node in self['substanzas']: - if isinstance(node, RequestNode): - nodes.append(node) - return nodes - - def set_nodes(self, nodes): - """ - Set or replace all nodes. The given nodes must be in a - list or set where each item is a tuple of the form: - (nodeId, sourceId, cacheType) - - Arguments: - nodes -- A series of nodes in tuple format. - """ - self.del_nodes() - for node in nodes: - if isinstance(node, RequestNode): - self.add_node(node['nodeId'], node['sourceId'], node['cacheType']) - else: - nodeId, sourceId, cacheType = node - self.add_node(nodeId, sourceId, cacheType) - - def del_nodes(self): - """Remove all nodes.""" - self._nodes = set() - nodes = [i for i in self.iterables if isinstance(i, RequestNode)] - for node in nodes: - self.xml.remove(node.xml) - self.iterables.remove(node) - - - def add_data(self, name): - """ - Add a new ResponseParameter element. - - Arguments: - name -- Name of the parameter - """ - if name not in self._datas: - self._datas.add(name) - data = ResponseParameter(parent=self) - data['name'] = name; - self.iterables.append(data) - return data - return None - - def del_data(self, name): - """ - Remove a single ResponseParameter element. - - Arguments: - name -- The data element name to remove. - """ - if name in self._datas: - datas = [i for i in self.iterables if isinstance(i, ResponseParameter)] - for data in datas: - if data['name'] == name: - self.xml.remove(data.xml) - self.iterables.remove(data) - return True - return False - - def get_datas(self): - """ Return all ResponseParameter elements. """ - datas = set() - for data in self['substanzas']: - if isinstance(data, ResponseParameter): - datas.add(data) - return datas - - def set_datas(self, datas): - """ - Set or replace all data elements. The given elements must be in a - list or set of ResponseParameter elements - - Arguments: - datas -- A series of data element names. - """ - self.del_datas() - for data in datas: - self.add_data(name=data['name']) - - def del_datas(self): - """Remove all ResponseParameter elements.""" - self._datas = set() - datas = [i for i in self.iterables if isinstance(i, ResponseParameter)] - for data in datas: - self.xml.remove(data.xml) - self.iterables.remove(data) - - -class Error(ElementBase): - namespace = 'urn:xmpp:iot:control' - name = 'error' - plugin_attrib = name - interfaces = set(['var','text']) - - def get_text(self): - """Return then contents inside the XML tag.""" - return self.xml.text - - def set_text(self, value): - """Set then contents inside the XML tag. - - Arguments: - value -- string - """ - - self.xml.text = value; - return self - - def del_text(self): - """Remove the contents inside the XML tag.""" - self.xml.text = "" - return self - -class ResponseParameter(ElementBase): - """ - Parameter element in ControlSetResponse. - """ - namespace = 'urn:xmpp:iot:control' - name = 'parameter' - plugin_attrib = name - interfaces = set(['name']); - - -class BaseParameter(ElementBase): - """ - Parameter element in SetCommand. This is a base class, - all instances of parameters added to SetCommand must be of types: - BooleanParameter - ColorParameter - StringParameter - DateParameter - DateTimeParameter - DoubleParameter - DurationParameter - IntParameter - LongParameter - TimeParameter - """ - namespace = 'urn:xmpp:iot:control' - name = 'baseParameter' - plugin_attrib = name - interfaces = set(['name','value']); - - def _get_typename(self): - return self.name; - -class BooleanParameter(BaseParameter): - """ - Field data of type boolean. - Note that the value is expressed as a string. - """ - name = 'boolean' - plugin_attrib = name - -class ColorParameter(BaseParameter): - """ - Field data of type color. - Note that the value is expressed as a string. - """ - name = 'color' - plugin_attrib = name - -class StringParameter(BaseParameter): - """ - Field data of type string. - """ - name = 'string' - plugin_attrib = name - -class DateParameter(BaseParameter): - """ - Field data of type date. - Note that the value is expressed as a string. - """ - name = 'date' - plugin_attrib = name - -class DateTimeParameter(BaseParameter): - """ - Field data of type dateTime. - Note that the value is expressed as a string. - """ - name = 'dateTime' - plugin_attrib = name - -class DoubleParameter(BaseParameter): - """ - Field data of type double. - Note that the value is expressed as a string. - """ - name = 'double' - plugin_attrib = name - -class DurationParameter(BaseParameter): - """ - Field data of type duration. - Note that the value is expressed as a string. - """ - name = 'duration' - plugin_attrib = name - -class IntParameter(BaseParameter): - """ - Field data of type int. - Note that the value is expressed as a string. - """ - name = 'int' - plugin_attrib = name - -class LongParameter(BaseParameter): - """ - Field data of type long (64-bit int). - Note that the value is expressed as a string. - """ - name = 'long' - plugin_attrib = name - -class TimeParameter(BaseParameter): - """ - Field data of type time. - Note that the value is expressed as a string. - """ - name = 'time' - plugin_attrib = name - -register_stanza_plugin(Iq, ControlSet) -register_stanza_plugin(Message, ControlSet) - -register_stanza_plugin(ControlSet, RequestNode, iterable=True) - -register_stanza_plugin(ControlSet, BooleanParameter, iterable=True) -register_stanza_plugin(ControlSet, ColorParameter, iterable=True) -register_stanza_plugin(ControlSet, StringParameter, iterable=True) -register_stanza_plugin(ControlSet, DateParameter, iterable=True) -register_stanza_plugin(ControlSet, DateTimeParameter, iterable=True) -register_stanza_plugin(ControlSet, DoubleParameter, iterable=True) -register_stanza_plugin(ControlSet, DurationParameter, iterable=True) -register_stanza_plugin(ControlSet, IntParameter, iterable=True) -register_stanza_plugin(ControlSet, LongParameter, iterable=True) -register_stanza_plugin(ControlSet, TimeParameter, iterable=True) - -register_stanza_plugin(Iq, ControlSetResponse) -register_stanza_plugin(ControlSetResponse, Error) -register_stanza_plugin(ControlSetResponse, RequestNode, iterable=True) -register_stanza_plugin(ControlSetResponse, ResponseParameter, iterable=True) - diff --git a/sleekxmpp/roster/__init__.py b/sleekxmpp/roster/__init__.py deleted file mode 100644 index 18b380c9..00000000 --- a/sleekxmpp/roster/__init__.py +++ /dev/null @@ -1,11 +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.roster.item import RosterItem -from sleekxmpp.roster.single import RosterNode -from sleekxmpp.roster.multi import Roster diff --git a/sleekxmpp/roster/item.py b/sleekxmpp/roster/item.py deleted file mode 100644 index ae194e0a..00000000 --- a/sleekxmpp/roster/item.py +++ /dev/null @@ -1,497 +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 RosterItem(object): - - """ - A RosterItem is a single entry in a roster node, and tracks - the subscription state and user annotations of a single JID. - - Roster items may use an external datastore to persist roster data - across sessions. Client applications will not need to use this - functionality, but is intended for components that do not have their - roster persisted automatically by the XMPP server. - - Roster items provide many methods for handling incoming presence - stanzas that ensure that response stanzas are sent according to - RFC 3921. - - The external datastore is accessed through a provided interface - object which is stored in self.db. The interface object MUST - provide two methods: load and save, both of which are responsible - for working with a single roster item. A private dictionary, - self._db_state, is used to store any metadata needed by the - interface, such as the row ID of a roster item, etc. - - Interface for self.db.load: - load(owner_jid, jid, db_state): - owner_jid -- The JID that owns the roster. - jid -- The JID of the roster item. - db_state -- A dictionary containing any data saved - by the interface object after a save() - call. Will typically have the equivalent - of a 'row_id' value. - - Interface for self.db.save: - save(owner_jid, jid, item_state, db_state): - owner_jid -- The JID that owns the roster. - jid -- The JID of the roster item. - item_state -- A dictionary containing the fields: - 'from', 'to', 'pending_in', 'pending_out', - 'whitelisted', 'subscription', 'name', - and 'groups'. - db_state -- A dictionary provided for persisting - datastore specific information. Typically, - a value equivalent to 'row_id' will be - stored here. - - State Fields: - from -- Indicates if a subscription of type 'from' - has been authorized. - to -- Indicates if a subscription of type 'to' has - been authorized. - pending_in -- Indicates if a subscription request has been - received from this JID and it has not been - authorized yet. - pending_out -- Indicates if a subscription request has been sent - to this JID and it has not been accepted yet. - subscription -- Returns one of: 'to', 'from', 'both', or 'none' - based on the states of from, to, pending_in, - and pending_out. Assignment to this value does - not affect the states of the other values. - whitelisted -- Indicates if a subscription request from this - JID should be automatically accepted. - name -- A user supplied alias for the JID. - groups -- A list of group names for the JID. - - Attributes: - xmpp -- The main SleekXMPP instance. - owner -- The JID that owns the roster. - jid -- The JID for the roster item. - db -- Optional datastore interface object. - last_status -- The last presence sent to this JID. - resources -- A dictionary of online resources for this JID. - Will contain the fields 'show', 'status', - and 'priority'. - - Methods: - load -- Retrieve the roster item from an - external datastore, if one was provided. - save -- Save the roster item to an external - datastore, if one was provided. - remove -- Remove a subscription to the JID and revoke - its whitelisted status. - subscribe -- Subscribe to the JID. - authorize -- Accept a subscription from the JID. - unauthorize -- Deny a subscription from the JID. - unsubscribe -- Unsubscribe from the JID. - send_presence -- Send a directed presence to the JID. - send_last_presence -- Resend the last sent presence. - handle_available -- Update the JID's resource information. - handle_unavailable -- Update the JID's resource information. - handle_subscribe -- Handle a subscription request. - handle_subscribed -- Handle a notice that a subscription request - was authorized by the JID. - handle_unsubscribe -- Handle an unsubscribe request. - handle_unsubscribed -- Handle a notice that a subscription was - removed by the JID. - handle_probe -- Handle a presence probe query. - """ - - def __init__(self, xmpp, jid, owner=None, - state=None, db=None, roster=None): - """ - Create a new roster item. - - Arguments: - xmpp -- The main SleekXMPP instance. - jid -- The item's JID. - owner -- The roster owner's JID. Defaults - so self.xmpp.boundjid.bare. - state -- A dictionary of initial state values. - db -- An optional interface to an external datastore. - roster -- The roster object containing this entry. - """ - self.xmpp = xmpp - self.jid = jid - self.owner = owner or self.xmpp.boundjid.bare - self.last_status = None - self.resources = {} - self.roster = roster - self.db = db - self._state = state or { - 'from': False, - 'to': False, - 'pending_in': False, - 'pending_out': False, - 'whitelisted': False, - 'subscription': 'none', - 'name': '', - 'groups': []} - - self._db_state = {} - self.load() - - def set_backend(self, db=None, save=True): - """ - Set the datastore interface object for the roster item. - - Arguments: - db -- The new datastore interface. - save -- If True, save the existing state to the new - backend datastore. Defaults to True. - """ - self.db = db - if save: - self.save() - self.load() - - def load(self): - """ - Load the item's state information from an external datastore, - if one has been provided. - """ - if self.db: - item = self.db.load(self.owner, self.jid, - self._db_state) - if item: - self['name'] = item['name'] - self['groups'] = item['groups'] - self['from'] = item['from'] - self['to'] = item['to'] - self['whitelisted'] = item['whitelisted'] - self['pending_out'] = item['pending_out'] - self['pending_in'] = item['pending_in'] - self['subscription'] = self._subscription() - return self._state - return None - - def save(self, remove=False): - """ - Save the item's state information to an external datastore, - if one has been provided. - - Arguments: - remove -- If True, expunge the item from the datastore. - """ - self['subscription'] = self._subscription() - if remove: - self._state['removed'] = True - if self.db: - self.db.save(self.owner, self.jid, - self._state, self._db_state) - - # Finally, remove the in-memory copy if needed. - if remove: - del self.xmpp.roster[self.owner][self.jid] - - def __getitem__(self, key): - """Return a state field's value.""" - if key in self._state: - if key == 'subscription': - return self._subscription() - return self._state[key] - else: - raise KeyError - - def __setitem__(self, key, value): - """ - Set the value of a state field. - - For boolean states, the values True, 'true', '1', 'on', - and 'yes' are accepted as True; all others are False. - - Arguments: - key -- The state field to modify. - value -- The new value of the state field. - """ - if key in self._state: - if key in ['name', 'subscription', 'groups']: - self._state[key] = value - else: - value = str(value).lower() - self._state[key] = value in ('true', '1', 'on', 'yes') - else: - raise KeyError - - def _subscription(self): - """Return the proper subscription type based on current state.""" - if self['to'] and self['from']: - return 'both' - elif self['from']: - return 'from' - elif self['to']: - return 'to' - else: - return 'none' - - def remove(self): - """ - Remove a JID's whitelisted status and unsubscribe if a - subscription exists. - """ - if self['to']: - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'unsubscribe' - if self.xmpp.is_component: - p['from'] = self.owner - p.send() - self['to'] = False - self['whitelisted'] = False - self.save() - - def subscribe(self): - """Send a subscription request to the JID.""" - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'subscribe' - if self.xmpp.is_component: - p['from'] = self.owner - self['pending_out'] = True - self.save() - p.send() - - def authorize(self): - """Authorize a received subscription request from the JID.""" - self['from'] = True - self['pending_in'] = False - self.save() - self._subscribed() - self.send_last_presence() - - def unauthorize(self): - """Deny a received subscription request from the JID.""" - self['from'] = False - self['pending_in'] = False - self.save() - self._unsubscribed() - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'unavailable' - if self.xmpp.is_component: - p['from'] = self.owner - p.send() - - def _subscribed(self): - """Handle acknowledging a subscription.""" - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'subscribed' - if self.xmpp.is_component: - p['from'] = self.owner - p.send() - - def unsubscribe(self): - """Unsubscribe from the JID.""" - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'unsubscribe' - if self.xmpp.is_component: - p['from'] = self.owner - self.save() - p.send() - - def _unsubscribed(self): - """Handle acknowledging an unsubscribe request.""" - p = self.xmpp.Presence() - p['to'] = self.jid - p['type'] = 'unsubscribed' - if self.xmpp.is_component: - p['from'] = self.owner - p.send() - - def send_presence(self, **kwargs): - """ - Create, initialize, and send a Presence stanza. - - If no recipient is specified, send the presence immediately. - Otherwise, forward the send request to the recipient's roster - entry for processing. - - Arguments: - pshow -- The presence's show value. - pstatus -- The presence's status message. - ppriority -- This connections' priority. - pto -- The recipient of a directed presence. - pfrom -- The sender of a directed presence, which should - be the owner JID plus resource. - ptype -- The type of presence, such as 'subscribe'. - pnick -- Optional nickname of the presence's sender. - """ - if self.xmpp.is_component and not kwargs.get('pfrom', ''): - kwargs['pfrom'] = self.owner - if not kwargs.get('pto', ''): - kwargs['pto'] = self.jid - self.xmpp.send_presence(**kwargs) - - def send_last_presence(self): - if self.last_status is None: - pres = self.roster.last_status - if pres is None: - self.send_presence() - else: - pres['to'] = self.jid - if self.xmpp.is_component: - pres['from'] = self.owner - else: - del pres['from'] - pres.send() - else: - self.last_status.send() - - def handle_available(self, presence): - resource = presence['from'].resource - data = {'status': presence['status'], - 'show': presence['show'], - 'priority': presence['priority']} - got_online = not self.resources - if resource not in self.resources: - self.resources[resource] = {} - old_status = self.resources[resource].get('status', '') - old_show = self.resources[resource].get('show', None) - self.resources[resource].update(data) - if got_online: - self.xmpp.event('got_online', presence) - if old_show != presence['show'] or old_status != presence['status']: - self.xmpp.event('changed_status', presence) - - def handle_unavailable(self, presence): - resource = presence['from'].resource - if not self.resources: - return - if resource in self.resources: - del self.resources[resource] - self.xmpp.event('changed_status', presence) - if not self.resources: - self.xmpp.event('got_offline', presence) - - def handle_subscribe(self, presence): - """ - +------------------------------------------------------------------+ - | EXISTING STATE | DELIVER? | NEW STATE | - +------------------------------------------------------------------+ - | "None" | yes | "None + Pending In" | - | "None + Pending Out" | yes | "None + Pending Out/In" | - | "None + Pending In" | no | no state change | - | "None + Pending Out/In" | no | no state change | - | "To" | yes | "To + Pending In" | - | "To + Pending In" | no | no state change | - | "From" | no * | no state change | - | "From + Pending Out" | no * | no state change | - | "Both" | no * | no state change | - +------------------------------------------------------------------+ - """ - if self.xmpp.is_component: - if not self['from'] and not self['pending_in']: - self['pending_in'] = True - self.xmpp.event('roster_subscription_request', presence) - elif self['from']: - self._subscribed() - self.save() - else: - #server shouldn't send an invalid subscription request - self.xmpp.event('roster_subscription_request', presence) - - def handle_subscribed(self, presence): - """ - +------------------------------------------------------------------+ - | EXISTING STATE | DELIVER? | NEW STATE | - +------------------------------------------------------------------+ - | "None" | no | no state change | - | "None + Pending Out" | yes | "To" | - | "None + Pending In" | no | no state change | - | "None + Pending Out/In" | yes | "To + Pending In" | - | "To" | no | no state change | - | "To + Pending In" | no | no state change | - | "From" | no | no state change | - | "From + Pending Out" | yes | "Both" | - | "Both" | no | no state change | - +------------------------------------------------------------------+ - """ - if self.xmpp.is_component: - if not self['to'] and self['pending_out']: - self['pending_out'] = False - self['to'] = True - self.xmpp.event('roster_subscription_authorized', presence) - self.save() - else: - self.xmpp.event('roster_subscription_authorized', presence) - - def handle_unsubscribe(self, presence): - """ - +------------------------------------------------------------------+ - | EXISTING STATE | DELIVER? | NEW STATE | - +------------------------------------------------------------------+ - | "None" | no | no state change | - | "None + Pending Out" | no | no state change | - | "None + Pending In" | yes * | "None" | - | "None + Pending Out/In" | yes * | "None + Pending Out" | - | "To" | no | no state change | - | "To + Pending In" | yes * | "To" | - | "From" | yes * | "None" | - | "From + Pending Out" | yes * | "None + Pending Out | - | "Both" | yes * | "To" | - +------------------------------------------------------------------+ - """ - if self.xmpp.is_component: - if not self['from'] and self['pending_in']: - self['pending_in'] = False - self._unsubscribed() - elif self['from']: - self['from'] = False - self._unsubscribed() - self.xmpp.event('roster_subscription_remove', presence) - self.save() - else: - self.xmpp.event('roster_subscription_remove', presence) - - def handle_unsubscribed(self, presence): - """ - +------------------------------------------------------------------+ - | EXISTING STATE | DELIVER? | NEW STATE | - +------------------------------------------------------------------+ - | "None" | no | no state change | - | "None + Pending Out" | yes | "None" | - | "None + Pending In" | no | no state change | - | "None + Pending Out/In" | yes | "None + Pending In" | - | "To" | yes | "None" | - | "To + Pending In" | yes | "None + Pending In" | - | "From" | no | no state change | - | "From + Pending Out" | yes | "From" | - | "Both" | yes | "From" | - +------------------------------------------------------------------ - """ - if self.xmpp.is_component: - if not self['to'] and self['pending_out']: - self['pending_out'] = False - elif self['to'] and not self['pending_out']: - self['to'] = False - self.xmpp.event('roster_subscription_removed', presence) - self.save() - else: - self.xmpp.event('roster_subscription_removed', presence) - - def handle_probe(self, presence): - if self['from']: - self.send_last_presence() - if self['pending_out']: - self.subscribe() - if not self['from']: - self._unsubscribed() - - def reset(self): - """ - Forgot current resource presence information as part of - a roster reset request. - """ - self.resources = {} - - def __repr__(self): - return repr(self._state) diff --git a/sleekxmpp/roster/multi.py b/sleekxmpp/roster/multi.py deleted file mode 100644 index 5d070ec8..00000000 --- a/sleekxmpp/roster/multi.py +++ /dev/null @@ -1,224 +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 Presence -from sleekxmpp.xmlstream import JID -from sleekxmpp.roster import RosterNode - - -class Roster(object): - - """ - SleekXMPP's roster manager. - - The roster is divided into "nodes", where each node is responsible - for a single JID. While the distinction is not strictly necessary - for client connections, it is a necessity for components that use - multiple JIDs. - - Rosters may be stored and persisted in an external datastore. An - interface object to the datastore that loads and saves roster items may - be provided. See the documentation for the RosterItem class for the - methods that the datastore interface object must provide. - - Attributes: - xmpp -- The main SleekXMPP instance. - db -- Optional interface object to an external datastore. - auto_authorize -- Default auto_authorize value for new roster nodes. - Defaults to True. - auto_subscribe -- Default auto_subscribe value for new roster nodes. - Defaults to True. - - Methods: - add -- Create a new roster node for a JID. - send_presence -- Shortcut for sending a presence stanza. - """ - - def __init__(self, xmpp, db=None): - """ - Create a new roster. - - Arguments: - xmpp -- The main SleekXMPP instance. - db -- Optional interface object to a datastore. - """ - self.xmpp = xmpp - self.db = db - self._auto_authorize = True - self._auto_subscribe = True - self._rosters = {} - - if self.db: - for node in self.db.entries(None, {}): - self.add(node) - - self.xmpp.add_filter('out', self._save_last_status) - - def _save_last_status(self, stanza): - - if isinstance(stanza, Presence): - sfrom = stanza['from'].full - sto = stanza['to'].full - - if not sfrom: - sfrom = self.xmpp.boundjid - - if stanza['type'] in stanza.showtypes or \ - stanza['type'] in ('available', 'unavailable'): - if sto: - self[sfrom][sto].last_status = stanza - else: - self[sfrom].last_status = stanza - with self[sfrom]._last_status_lock: - for jid in self[sfrom]: - self[sfrom][jid].last_status = None - - if not self.xmpp.sentpresence: - self.xmpp.event('sent_presence') - self.xmpp.sentpresence = True - - return stanza - - def __getitem__(self, key): - """ - Return the roster node for a JID. - - A new roster node will be created if one - does not already exist. - - Arguments: - key -- Return the roster for this JID. - """ - if key is None: - key = self.xmpp.boundjid - if not isinstance(key, JID): - key = JID(key) - key = key.bare - - if key not in self._rosters: - self.add(key) - self._rosters[key].auto_authorize = self.auto_authorize - self._rosters[key].auto_subscribe = self.auto_subscribe - return self._rosters[key] - - def keys(self): - """Return the JIDs managed by the roster.""" - return self._rosters.keys() - - def __iter__(self): - """Iterate over the roster nodes.""" - return self._rosters.__iter__() - - def add(self, node): - """ - Add a new roster node for the given JID. - - Arguments: - node -- The JID for the new roster node. - """ - if not isinstance(node, JID): - node = JID(node) - - node = node.bare - if node not in self._rosters: - self._rosters[node] = RosterNode(self.xmpp, node, self.db) - - def set_backend(self, db=None, save=True): - """ - Set the datastore interface object for the roster. - - Arguments: - db -- The new datastore interface. - save -- If True, save the existing state to the new - backend datastore. Defaults to True. - """ - self.db = db - existing_entries = set(self._rosters) - new_entries = set(self.db.entries(None, {})) - - for node in existing_entries: - self._rosters[node].set_backend(db, save) - for node in new_entries - existing_entries: - self.add(node) - - def reset(self): - """ - Reset the state of the roster to forget any current - presence information. Useful after a disconnection occurs. - """ - for node in self: - self[node].reset() - - def send_presence(self, **kwargs): - """ - Create, initialize, and send a Presence stanza. - - If no recipient is specified, send the presence immediately. - Otherwise, forward the send request to the recipient's roster - entry for processing. - - Arguments: - pshow -- The presence's show value. - pstatus -- The presence's status message. - ppriority -- This connections' priority. - pto -- The recipient of a directed presence. - pfrom -- The sender of a directed presence, which should - be the owner JID plus resource. - ptype -- The type of presence, such as 'subscribe'. - pnick -- Optional nickname of the presence's sender. - """ - if self.xmpp.is_component and not kwargs.get('pfrom', ''): - kwargs['pfrom'] = self.jid - self.xmpp.send_presence(**kwargs) - - @property - def auto_authorize(self): - """ - Auto accept or deny subscription requests. - - If True, auto accept subscription requests. - If False, auto deny subscription requests. - If None, don't automatically respond. - """ - return self._auto_authorize - - @auto_authorize.setter - def auto_authorize(self, value): - """ - Auto accept or deny subscription requests. - - If True, auto accept subscription requests. - If False, auto deny subscription requests. - If None, don't automatically respond. - """ - self._auto_authorize = value - for node in self._rosters: - self._rosters[node].auto_authorize = value - - @property - def auto_subscribe(self): - """ - Auto send requests for mutual subscriptions. - - If True, auto send mutual subscription requests. - """ - return self._auto_subscribe - - @auto_subscribe.setter - def auto_subscribe(self, value): - """ - Auto send requests for mutual subscriptions. - - If True, auto send mutual subscription requests. - """ - self._auto_subscribe = value - for node in self._rosters: - self._rosters[node].auto_subscribe = value - - def __repr__(self): - return repr(self._rosters) diff --git a/sleekxmpp/roster/single.py b/sleekxmpp/roster/single.py deleted file mode 100644 index f080ae8a..00000000 --- a/sleekxmpp/roster/single.py +++ /dev/null @@ -1,337 +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 - -from sleekxmpp.xmlstream import JID -from sleekxmpp.roster import RosterItem - - -class RosterNode(object): - - """ - A roster node is a roster for a single JID. - - Attributes: - xmpp -- The main SleekXMPP instance. - jid -- The JID that owns the roster node. - db -- Optional interface to an external datastore. - auto_authorize -- Determines how authorizations are handled: - True -- Accept all subscriptions. - False -- Reject all subscriptions. - None -- Subscriptions must be - manually authorized. - Defaults to True. - auto_subscribe -- Determines if bi-directional subscriptions - are created after automatically authrorizing - a subscription request. - Defaults to True - last_status -- The last sent presence status that was broadcast - to all contact JIDs. - - Methods: - add -- Add a JID to the roster. - update -- Update a JID's subscription information. - subscribe -- Subscribe to a JID. - unsubscribe -- Unsubscribe from a JID. - remove -- Remove a JID from the roster. - presence -- Return presence information for a JID's resources. - send_presence -- Shortcut for sending a presence stanza. - """ - - def __init__(self, xmpp, jid, db=None): - """ - Create a roster node for a JID. - - Arguments: - xmpp -- The main SleekXMPP instance. - jid -- The JID that owns the roster. - db -- Optional interface to an external datastore. - """ - self.xmpp = xmpp - self.jid = jid - self.db = db - self.auto_authorize = True - self.auto_subscribe = True - self.last_status = None - self._version = '' - self._jids = {} - self._last_status_lock = threading.Lock() - - if self.db: - if hasattr(self.db, 'version'): - self._version = self.db.version(self.jid) - for jid in self.db.entries(self.jid): - self.add(jid) - - @property - def version(self): - """Retrieve the roster's version ID.""" - if self.db and hasattr(self.db, 'version'): - self._version = self.db.version(self.jid) - return self._version - - @version.setter - def version(self, version): - """Set the roster's version ID.""" - self._version = version - if self.db and hasattr(self.db, 'set_version'): - self.db.set_version(self.jid, version) - - def __getitem__(self, key): - """ - Return the roster item for a subscribed JID. - - A new item entry will be created if one does not already exist. - """ - if key is None: - key = JID('') - if not isinstance(key, JID): - key = JID(key) - key = key.bare - if key not in self._jids: - self.add(key, save=True) - return self._jids[key] - - def __delitem__(self, key): - """ - Remove a roster item from the local storage. - - To remove an item from the server, use the remove() method. - """ - if key is None: - key = JID('') - if not isinstance(key, JID): - key = JID(key) - key = key.bare - if key in self._jids: - del self._jids[key] - - def __len__(self): - """Return the number of JIDs referenced by the roster.""" - return len(self._jids) - - def keys(self): - """Return a list of all subscribed JIDs.""" - return self._jids.keys() - - def has_jid(self, jid): - """Returns whether the roster has a JID.""" - return jid in self._jids - - def groups(self): - """Return a dictionary mapping group names to JIDs.""" - result = {} - for jid in self._jids: - groups = self._jids[jid]['groups'] - if not groups: - if '' not in result: - result[''] = [] - result[''].append(jid) - for group in groups: - if group not in result: - result[group] = [] - result[group].append(jid) - return result - - def __iter__(self): - """Iterate over the roster items.""" - return self._jids.__iter__() - - def set_backend(self, db=None, save=True): - """ - Set the datastore interface object for the roster node. - - Arguments: - db -- The new datastore interface. - save -- If True, save the existing state to the new - backend datastore. Defaults to True. - """ - self.db = db - existing_entries = set(self._jids) - new_entries = set(self.db.entries(self.jid, {})) - - for jid in existing_entries: - self._jids[jid].set_backend(db, save) - for jid in new_entries - existing_entries: - self.add(jid) - - def add(self, jid, name='', groups=None, afrom=False, ato=False, - pending_in=False, pending_out=False, whitelisted=False, - save=False): - """ - Add a new roster item entry. - - Arguments: - jid -- The JID for the roster item. - name -- An alias for the JID. - groups -- A list of group names. - afrom -- Indicates if the JID has a subscription state - of 'from'. Defaults to False. - ato -- Indicates if the JID has a subscription state - of 'to'. Defaults to False. - pending_in -- Indicates if the JID has sent a subscription - request to this connection's JID. - Defaults to False. - pending_out -- Indicates if a subscription request has been sent - to this JID. - Defaults to False. - whitelisted -- Indicates if a subscription request from this JID - should be automatically authorized. - Defaults to False. - save -- Indicates if the item should be persisted - immediately to an external datastore, - if one is used. - Defaults to False. - """ - if isinstance(jid, JID): - key = jid.bare - else: - key = jid - - state = {'name': name, - 'groups': groups or [], - 'from': afrom, - 'to': ato, - 'pending_in': pending_in, - 'pending_out': pending_out, - 'whitelisted': whitelisted, - 'subscription': 'none'} - self._jids[key] = RosterItem(self.xmpp, jid, self.jid, - state=state, db=self.db, - roster=self) - if save: - self._jids[key].save() - - def subscribe(self, jid): - """ - Subscribe to the given JID. - - Arguments: - jid -- The JID to subscribe to. - """ - self[jid].subscribe() - - def unsubscribe(self, jid): - """ - Unsubscribe from the given JID. - - Arguments: - jid -- The JID to unsubscribe from. - """ - self[jid].unsubscribe() - - def remove(self, jid): - """ - Remove a JID from the roster. - - Arguments: - jid -- The JID to remove. - """ - self[jid].remove() - if not self.xmpp.is_component: - return self.update(jid, subscription='remove') - - def update(self, jid, name=None, subscription=None, groups=[], - block=True, timeout=None, callback=None): - """ - Update a JID's subscription information. - - Arguments: - jid -- The JID to update. - name -- Optional alias for the JID. - subscription -- The subscription state. May be one of: 'to', - 'from', 'both', 'none', or 'remove'. - groups -- A list of group names. - block -- Specify if the roster request 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 continuing if blocking - is used. Defaults to self.response_timeout. - callback -- Optional reference to a stream handler function. - Will be executed when the roster is received. - Implies block=False. - """ - self[jid]['name'] = name - self[jid]['groups'] = groups - self[jid].save() - - if not self.xmpp.is_component: - iq = self.xmpp.Iq() - iq['type'] = 'set' - iq['roster']['items'] = {jid: {'name': name, - 'subscription': subscription, - 'groups': groups}} - - return iq.send(block, timeout, callback) - - def presence(self, jid, resource=None): - """ - Retrieve the presence information of a JID. - - May return either all online resources' status, or - a single resource's status. - - Arguments: - jid -- The JID to lookup. - resource -- Optional resource for returning - only the status of a single connection. - """ - if resource is None: - return self[jid].resources - - default_presence = {'status': '', - 'priority': 0, - 'show': ''} - return self[jid].resources.get(resource, - default_presence) - - def reset(self): - """ - Reset the state of the roster to forget any current - presence information. Useful after a disconnection occurs. - """ - for jid in self: - self[jid].reset() - - def send_presence(self, **kwargs): - """ - Create, initialize, and send a Presence stanza. - - If no recipient is specified, send the presence immediately. - Otherwise, forward the send request to the recipient's roster - entry for processing. - - Arguments: - pshow -- The presence's show value. - pstatus -- The presence's status message. - ppriority -- This connections' priority. - pto -- The recipient of a directed presence. - pfrom -- The sender of a directed presence, which should - be the owner JID plus resource. - ptype -- The type of presence, such as 'subscribe'. - pnick -- Optional nickname of the presence's sender. - """ - if self.xmpp.is_component and not kwargs.get('pfrom', ''): - kwargs['pfrom'] = self.jid - self.xmpp.send_presence(**kwargs) - - def send_last_presence(self): - if self.last_status is None: - self.send_presence() - else: - pres = self.last_status - if self.xmpp.is_component: - pres['from'] = self.jid - else: - del pres['from'] - pres.send() - - def __repr__(self): - return repr(self._jids) diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py deleted file mode 100644 index 4bd37dc5..00000000 --- a/sleekxmpp/stanza/__init__.py +++ /dev/null @@ -1,15 +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 -from sleekxmpp.stanza.stream_features import StreamFeatures -from sleekxmpp.stanza.stream_error import StreamError diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py deleted file mode 100644 index 244ef315..00000000 --- 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 56558ba8..00000000 --- a/sleekxmpp/stanza/error.py +++ /dev/null @@ -1,174 +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 - - -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', - 'gone', 'redirect', 'by')) - sub_interfaces = set(('text',)) - plugin_attrib_map = {} - plugin_tag_map = {} - 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. - """ - 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: - if "{%s}" % self.condition_ns in child.tag: - cond = child.tag.split('}', 1)[-1] - if cond in self.conditions: - return cond - 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: - 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 - - def get_gone(self): - return self._get_sub_text('{%s}gone' % self.condition_ns, '') - - def get_redirect(self): - return self._get_sub_text('{%s}redirect' % self.condition_ns, '') - - def set_gone(self, value): - if value: - del self['condition'] - return self._set_sub_text('{%s}gone' % self.condition_ns, value) - elif self['condition'] == 'gone': - del self['condition'] - - def set_redirect(self, value): - if value: - del self['condition'] - ns = self.condition_ns - return self._set_sub_text('{%s}redirect' % ns, value) - elif self['condition'] == 'redirect': - del self['condition'] - - def del_gone(self): - self._del_sub('{%s}gone' % self.condition_ns) - - def del_redirect(self): - self._del_sub('{%s}redirect' % self.condition_ns) - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Error.getCondition = Error.get_condition -Error.setCondition = Error.set_condition -Error.delCondition = Error.del_condition -Error.getText = Error.get_text -Error.setText = Error.set_text -Error.delText = Error.del_text diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py deleted file mode 100644 index c43178f2..00000000 --- a/sleekxmpp/stanza/htmlim.py +++ /dev/null @@ -1,21 +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 register_stanza_plugin -from sleekxmpp.plugins.xep_0071 import XHTML_IM as HTMLIM - - -register_stanza_plugin(Message, HTMLIM) - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -HTMLIM.setBody = HTMLIM.set_body -HTMLIM.getBody = HTMLIM.get_body -HTMLIM.delBody = HTMLIM.del_body diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py deleted file mode 100644 index 088de4c0..00000000 --- a/sleekxmpp/stanza/iq.py +++ /dev/null @@ -1,281 +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.rootstanza import RootStanza -from sleekxmpp.xmlstream import StanzaBase, ET -from sleekxmpp.xmlstream.handler import Waiter, Callback -from sleekxmpp.xmlstream.matcher import MatchIDSender, MatcherId -from sleekxmpp.exceptions import IqTimeout, IqError - - -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) - if self['id'] == '': - if self.stream is not None: - self['id'] = self.stream.new_id() - 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: - plugin = self.plugin_tag_map.get('{%s}query' % value, None) - if plugin: - self.enable(plugin.plugin_attrib) - else: - 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: - 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: - if child.tag.endswith('query'): - self.xml.remove(child) - return self - - def reply(self, clear=True): - """ - Send a reply <iq> stanza. - - Overrides StanzaBase.reply - - Sets the 'type' to 'result' in addition to the default - StanzaBase.reply behavior. - - Arguments: - clear -- Indicates if existing content should be - removed before replying. Defaults to True. - """ - self['type'] = 'result' - StanzaBase.reply(self, clear) - return self - - def send(self, block=True, timeout=None, callback=None, now=False, timeout_callback=None): - """ - 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. Otherwise, a callback - handler can be provided that will be executed when the Iq stanza's - result reply is received. Be aware though that that the callback - handler will not be executed in its own thread. - - Using both block and callback is not recommended, and only the - callback argument will be used in that case. - - 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 - callback -- Optional reference to a stream handler function. Will - be executed when a reply stanza is received. - now -- Indicates if the send queue should be skipped and send - the stanza immediately. Used during stream - initialization. Defaults to False. - timeout_callback -- Optional reference to a stream handler function. - Will be executed when the timeout expires before a - response has been received with the originally-sent IQ - stanza. Only called if there is a callback parameter - (and therefore are in async mode). - """ - if timeout is None: - timeout = self.stream.response_timeout - - if self.stream.session_bind_event.is_set(): - matcher = MatchIDSender({ - 'id': self['id'], - 'self': self.stream.boundjid, - 'peer': self['to'] - }) - else: - matcher = MatcherId(self['id']) - - if callback is not None and self['type'] in ('get', 'set'): - handler_name = 'IqCallback_%s' % self['id'] - if timeout_callback: - self.callback = callback - self.timeout_callback = timeout_callback - self.stream.schedule('IqTimeout_%s' % self['id'], - timeout, - self._fire_timeout, - repeat=False) - handler = Callback(handler_name, - matcher, - self._handle_result, - once=True) - else: - handler = Callback(handler_name, - matcher, - callback, - once=True) - self.stream.register_handler(handler) - StanzaBase.send(self, now=now) - return handler_name - elif block and self['type'] in ('get', 'set'): - waitfor = Waiter('IqWait_%s' % self['id'], matcher) - self.stream.register_handler(waitfor) - StanzaBase.send(self, now=now) - result = waitfor.wait(timeout) - if not result: - raise IqTimeout(self) - if result['type'] == 'error': - raise IqError(result) - return result - else: - return StanzaBase.send(self, now=now) - - def _handle_result(self, iq): - # we got the IQ, so don't fire the timeout - self.stream.scheduler.remove('IqTimeout_%s' % self['id']) - self.callback(iq) - - def _fire_timeout(self): - # don't fire the handler for the IQ, if it finally does come in - self.stream.remove_handler('IqCallback_%s' % self['id']) - self.timeout_callback(self) - - def _set_stanza_values(self, values): - """ - Set multiple stanza interface values using a dictionary. - - Stanza plugin values may be set usind nested dictionaries. - - If the interface 'query' is given, then it will be set - last to avoid duplication of the <query /> element. - - Overrides ElementBase._set_stanza_values. - - Arguments: - values -- A dictionary mapping stanza interface with values. - Plugin interfaces may accept a nested dictionary that - will be used recursively. - """ - query = values.get('query', '') - if query: - del values['query'] - StanzaBase._set_stanza_values(self, values) - self['query'] = query - else: - StanzaBase._set_stanza_values(self, values) - return self - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Iq.setPayload = Iq.set_payload -Iq.getQuery = Iq.get_query -Iq.setQuery = Iq.set_query -Iq.delQuery = Iq.del_query diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py deleted file mode 100644 index 0bb6e587..00000000 --- a/sleekxmpp/stanza/message.py +++ /dev/null @@ -1,199 +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.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. - """ - - name = 'message' - namespace = 'jabber:client' - plugin_attrib = name - interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject', - 'thread', 'parent_thread', 'mucroom', 'mucnick']) - sub_interfaces = set(['body', 'subject', 'thread']) - lang_interfaces = sub_interfaces - types = set(['normal', 'chat', 'headline', 'error', 'groupchat']) - - def __init__(self, *args, **kwargs): - """ - Initialize a new <message /> stanza with an optional 'id' value. - - Overrides StanzaBase.__init__. - """ - StanzaBase.__init__(self, *args, **kwargs) - if self['id'] == '': - if self.stream is not None and self.stream.use_message_ids: - self['id'] = self.stream.new_id() - - 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 get_parent_thread(self): - """Return the message thread's parent thread.""" - thread = self.xml.find('{%s}thread' % self.namespace) - if thread is not None: - return thread.attrib.get('parent', '') - return '' - - def set_parent_thread(self, value): - """Add or change the message thread's parent thread.""" - thread = self.xml.find('{%s}thread' % self.namespace) - if value: - if thread is None: - thread = ET.Element('{%s}thread' % self.namespace) - self.xml.append(thread) - thread.attrib['parent'] = value - else: - if thread is not None and 'parent' in thread.attrib: - del thread.attrib['parent'] - - def del_parent_thread(self): - """Delete the message thread's parent reference.""" - thread = self.xml.find('{%s}thread' % self.namespace) - if thread is not None and 'parent' in thread.attrib: - del thread.attrib['parent'] - - def chat(self): - """Set the message type to 'chat'.""" - self['type'] = 'chat' - return self - - def normal(self): - """Set the message type to 'normal'.""" - self['type'] = 'normal' - return self - - def reply(self, body=None, clear=True): - """ - 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. - clear -- Indicates if existing content should be removed - before replying. Defaults to True. - """ - thread = self['thread'] - parent = self['parent_thread'] - - StanzaBase.reply(self, clear) - if self['type'] == 'groupchat': - self['to'] = self['to'].bare - - self['thread'] = thread - self['parent_thread'] = parent - - 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 - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Message.getType = Message.get_type -Message.getMucroom = Message.get_mucroom -Message.setMucroom = Message.set_mucroom -Message.delMucroom = Message.del_mucroom -Message.getMucnick = Message.get_mucnick -Message.setMucnick = Message.set_mucnick -Message.delMucnick = Message.del_mucnick diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py deleted file mode 100644 index 0e9a5c2b..00000000 --- a/sleekxmpp/stanza/nick.py +++ /dev/null @@ -1,23 +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. -""" - -# The nickname stanza has been moved to its own plugin, but the existing -# references are kept for backwards compatibility. - -from sleekxmpp.stanza import Message, Presence -from sleekxmpp.xmlstream import register_stanza_plugin -from sleekxmpp.plugins.xep_0172 import UserNick as Nick - -register_stanza_plugin(Message, Nick) -register_stanza_plugin(Presence, Nick) - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Nick.setNick = Nick.set_nick -Nick.getNick = Nick.get_nick -Nick.delNick = Nick.del_nick diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py deleted file mode 100644 index 84bcd122..00000000 --- a/sleekxmpp/stanza/presence.py +++ /dev/null @@ -1,191 +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.rootstanza import RootStanza -from sleekxmpp.xmlstream import StanzaBase - - -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. - """ - - name = 'presence' - namespace = 'jabber:client' - plugin_attrib = name - interfaces = set(['type', 'to', 'from', 'id', 'show', - 'status', 'priority']) - sub_interfaces = set(['show', 'status', 'priority']) - lang_interfaces = set(['status']) - - types = set(['available', 'unavailable', 'error', 'probe', 'subscribe', - 'subscribed', 'unsubscribe', 'unsubscribed']) - showtypes = set(['dnd', 'chat', 'xa', 'away']) - - def __init__(self, *args, **kwargs): - """ - Initialize a new <presence /> stanza with an optional 'id' value. - - Overrides StanzaBase.__init__. - """ - StanzaBase.__init__(self, *args, **kwargs) - if self['id'] == '': - if self.stream is not None and self.stream.use_presence_ids: - self['id'] = self.stream.new_id() - - 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, clear=True): - """ - Set the appropriate presence reply type. - - Overrides StanzaBase.reply. - - Arguments: - clear -- Indicates if the stanza contents should be removed - before replying. Defaults to True. - """ - if self['type'] == 'unsubscribe': - self['type'] = 'unsubscribed' - elif self['type'] == 'subscribe': - self['type'] = 'subscribed' - return StanzaBase.reply(self, clear) - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Presence.setShow = Presence.set_show -Presence.getType = Presence.get_type -Presence.setType = Presence.set_type -Presence.delType = Presence.get_type -Presence.getPriority = Presence.get_priority -Presence.setPriority = Presence.set_priority diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py deleted file mode 100644 index a7c2b218..00000000 --- a/sleekxmpp/stanza/rootstanza.py +++ /dev/null @@ -1,86 +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 sleekxmpp.exceptions import XMPPError, IqError, IqTimeout -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 - """ - if isinstance(e, IqError): - # We received an Iq error reply, but it wasn't caught - # locally. Using the condition/text from that error - # response could leak too much information, so we'll - # only use a generic error here. - self.reply() - self['error']['condition'] = 'undefined-condition' - self['error']['text'] = 'External error' - self['error']['type'] = 'cancel' - log.warning('You should catch IqError exceptions') - self.send() - elif isinstance(e, IqTimeout): - self.reply() - self['error']['condition'] = 'remote-server-timeout' - self['error']['type'] = 'wait' - log.warning('You should catch IqTimeout exceptions') - self.send() - elif isinstance(e, XMPPError): - # We raised this deliberately - self.reply(clear=e.clear) - self['error']['condition'] = e.condition - self['error']['text'] = e.text - self['error']['type'] = e.etype - 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.send() - else: - # We probably didn't raise this on purpose, so send an error stanza - self.reply() - self['error']['condition'] = 'undefined-condition' - self['error']['text'] = "SleekXMPP got into trouble." - self['error']['type'] = 'cancel' - self.send() - # log the error - log.exception('Error handling {%s}%s stanza', - self.namespace, self.name) - # Finally raise the exception to a global exception handler - self.stream.exception(e) - -register_stanza_plugin(RootStanza, Error) diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py deleted file mode 100644 index 681efd4f..00000000 --- a/sleekxmpp/stanza/roster.py +++ /dev/null @@ -1,158 +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', 'ver')) - - def get_ver(self): - """ - Ensure handling an empty ver attribute propery. - - The ver attribute is special in that the presence of the - attribute with an empty value is important for boostrapping - roster versioning. - """ - return self.xml.attrib.get('ver', None) - - def set_ver(self, ver): - """ - Ensure handling an empty ver attribute propery. - - The ver attribute is special in that the presence of the - attribute with an empty value is important for boostrapping - roster versioning. - """ - if ver is not None: - self.xml.attrib['ver'] = ver - else: - del self.xml.attrib['ver'] - - 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: - item = RosterItem() - item.values = items[jid] - item['jid'] = jid - self.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 = {} - for item in self['substanzas']: - if isinstance(item, RosterItem): - items[item['jid']] = item.values - # Remove extra JID reference to keep everything - # backward compatible - del items[item['jid']]['jid'] - del items[item['jid']]['lang'] - return items - - def del_items(self): - """ - Remove all <item> elements from the roster stanza. - """ - for item in self['substanzas']: - if isinstance(item, RosterItem): - self.xml.remove(item.xml) - - -class RosterItem(ElementBase): - namespace = 'jabber:iq:roster' - name = 'item' - plugin_attrib = 'item' - interfaces = set(('jid', 'name', 'subscription', 'ask', - 'approved', 'groups')) - - def get_jid(self): - return JID(self._get_attr('jid', '')) - - def set_jid(self, jid): - self._set_attr('jid', str(jid)) - - def get_groups(self): - groups = [] - for group in self.xml.findall('{%s}group' % self.namespace): - if group.text: - groups.append(group.text) - else: - groups.append('') - return groups - - def set_groups(self, values): - self.del_groups() - for group in values: - group_xml = ET.Element('{%s}group' % self.namespace) - group_xml.text = group - self.xml.append(group_xml) - - def del_groups(self): - for group in self.xml.findall('{%s}group' % self.namespace): - self.xml.remove(group) - - -register_stanza_plugin(Iq, Roster) -register_stanza_plugin(Roster, RosterItem, iterable=True) - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -Roster.setItems = Roster.set_items -Roster.getItems = Roster.get_items -Roster.delItems = Roster.del_items diff --git a/sleekxmpp/stanza/stream_error.py b/sleekxmpp/stanza/stream_error.py deleted file mode 100644 index ed0078c9..00000000 --- a/sleekxmpp/stanza/stream_error.py +++ /dev/null @@ -1,83 +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.xmlstream import StanzaBase - - -class StreamError(Error, StanzaBase): - - """ - 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. - - The stream:error stanza is used to provide more information for - error that occur with the underlying XML stream itself, and not - a particular stanza. - - Note: The StreamError stanza is mostly the same as the normal - Error stanza, but with different namespaces and - condition names. - - Example error stanza: - <stream:error> - <not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams" /> - <text xmlns="urn:ietf:params:xml:ns:xmpp-streams"> - XML was not well-formed. - </text> - </stream:error> - - Stanza Interface: - condition -- The name of the condition element. - text -- Human readable description of the error. - - Attributes: - conditions -- The set of allowable error condition elements. - condition_ns -- The namespace for the condition element. - - 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 = 'http://etherx.jabber.org/streams' - interfaces = set(('condition', 'text', 'see_other_host')) - conditions = set(( - 'bad-format', 'bad-namespace-prefix', 'conflict', - 'connection-timeout', 'host-gone', 'host-unknown', - 'improper-addressing', 'internal-server-error', 'invalid-from', - 'invalid-namespace', 'invalid-xml', 'not-authorized', - 'not-well-formed', 'policy-violation', 'remote-connection-failed', - 'reset', 'resource-constraint', 'restricted-xml', 'see-other-host', - 'system-shutdown', 'undefined-condition', 'unsupported-encoding', - 'unsupported-feature', 'unsupported-stanza-type', - 'unsupported-version')) - condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams' - - def get_see_other_host(self): - ns = self.condition_ns - return self._get_sub_text('{%s}see-other-host' % ns, '') - - def set_see_other_host(self, value): - if value: - del self['condition'] - ns = self.condition_ns - return self._set_sub_text('{%s}see-other-host' % ns, value) - elif self['condition'] == 'see-other-host': - del self['condition'] - - def del_see_other_host(self): - self._del_sub('{%s}see-other-host' % self.condition_ns) diff --git a/sleekxmpp/stanza/stream_features.py b/sleekxmpp/stanza/stream_features.py deleted file mode 100644 index e487721e..00000000 --- a/sleekxmpp/stanza/stream_features.py +++ /dev/null @@ -1,57 +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.thirdparty import OrderedDict -from sleekxmpp.xmlstream import StanzaBase - - -class StreamFeatures(StanzaBase): - - """ - """ - - name = 'features' - namespace = 'http://etherx.jabber.org/streams' - interfaces = set(('features', 'required', 'optional')) - sub_interfaces = interfaces - plugin_tag_map = {} - plugin_attrib_map = {} - - def setup(self, xml): - StanzaBase.setup(self, xml) - self.values = self.values - - def get_features(self): - """ - """ - features = OrderedDict() - for (name, lang), plugin in self.plugins.items(): - features[name] = plugin - return features - - def set_features(self, value): - """ - """ - pass - - def del_features(self): - """ - """ - pass - - def get_required(self): - """ - """ - features = self['features'] - return [f for n, f in features.items() if f['required']] - - def get_optional(self): - """ - """ - features = self['features'] - return [f for n, f in features.items() if not f['required']] diff --git a/sleekxmpp/test/__init__.py b/sleekxmpp/test/__init__.py deleted file mode 100644 index 54d4dc57..00000000 --- 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 d70ee4eb..00000000 --- a/sleekxmpp/test/livesocket.py +++ /dev/null @@ -1,172 +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 -import threading - -from sleekxmpp.util import 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() - self.send_queue = Queue() - self.send_queue_lock = threading.Lock() - self.recv_queue_lock = threading.Lock() - 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 disconnect_errror(self): - """ - Used to simulate a socket disconnection error. - - Not used by live sockets. - """ - try: - self.socket.shutdown() - self.socket.close() - except: - pass - - 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) - with self.recv_queue_lock: - 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. - """ - with self.send_queue_lock: - self.send_queue.put(data) - return 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) - - def clear(self): - """ - Empty the send queue, typically done once the session has started to - remove the feature negotiation and log in stanzas. - """ - with self.send_queue_lock: - for i in range(0, self.send_queue.qsize()): - self.send_queue.get(block=False) - with self.recv_queue_lock: - for i in range(0, self.recv_queue.qsize()): - self.recv_queue.get(block=False) diff --git a/sleekxmpp/test/mocksocket.py b/sleekxmpp/test/mocksocket.py deleted file mode 100644 index 4c9d1699..00000000 --- a/sleekxmpp/test/mocksocket.py +++ /dev/null @@ -1,153 +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 - -from sleekxmpp.util import 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() - self.send_queue = Queue() - self.is_live = False - self.disconnected = 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) - - def disconnect_error(self): - """ - Simulate a disconnect error by raising a socket.error exception - for any current or further socket operations. - """ - self.disconnected = True - - # ------------------------------------------------------------------ - # Socket Interface - - def recv(self, *args, **kwargs): - """ - Read a value from the received queue. - - Arguments: - Placeholders. Same as for socket.Socket.recv. - """ - if self.disconnected: - raise socket.error - return self.read(block=True) - - def send(self, data): - """ - Send data by placing it in the send queue. - - Arguments: - data -- String value to write. - """ - if self.disconnected: - raise socket.error - self.send_queue.put(data) - return len(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 self.disconnected: - raise socket.error - 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 d28f77e2..00000000 --- a/sleekxmpp/test/sleektest.py +++ /dev/null @@ -1,772 +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 -from xml.parsers.expat import ExpatError - -from sleekxmpp import ClientXMPP, ComponentXMPP -from sleekxmpp.util import Queue -from sleekxmpp.stanza import Message, Iq, Presence -from sleekxmpp.test import TestSocket, TestLiveSocket -from sleekxmpp.xmlstream import ET -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId, MatchIDSender -from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath - - -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 __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - self.xmpp = None - - def parse_xml(self, xml_string): - try: - xml = ET.fromstring(xml_string) - return xml - except (SyntaxError, ExpatError) as e: - msg = e.msg if hasattr(e, 'msg') else e.message - if 'unbound' in 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 = list(xml)[0] - return xml - else: - self.fail("XML data was mal-formed:\n%s" % xml_string) - - # ------------------------------------------------------------------ - # 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(self.xmpp, *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(self.xmpp, *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(self.xmpp, *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)) - - def check_roster(self, owner, jid, name=None, subscription=None, - afrom=None, ato=None, pending_out=None, pending_in=None, - groups=None): - roster = self.xmpp.roster[owner][jid] - if name is not None: - self.assertEqual(roster['name'], name, - "Incorrect name value: %s" % roster['name']) - if subscription is not None: - self.assertEqual(roster['subscription'], subscription, - "Incorrect subscription: %s" % roster['subscription']) - if afrom is not None: - self.assertEqual(roster['from'], afrom, - "Incorrect from state: %s" % roster['from']) - if ato is not None: - self.assertEqual(roster['to'], ato, - "Incorrect to state: %s" % roster['to']) - if pending_out is not None: - self.assertEqual(roster['pending_out'], pending_out, - "Incorrect pending_out state: %s" % roster['pending_out']) - if pending_in is not None: - self.assertEqual(roster['pending_in'], pending_out, - "Incorrect pending_in state: %s" % roster['pending_in']) - if groups is not None: - self.assertEqual(roster['groups'], groups, - "Incorrect groups: %s" % roster['groups']) - - # ------------------------------------------------------------------ - # Methods for comparing stanza objects to XML strings - - def check(self, stanza, criteria, method='exact', - defaults=None, use_values=True): - """ - Create and compare several stanza objects to a correct XML string. - - If use_values is False, tests using stanza.values 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. - criteria -- An expression the stanza must match against. - method -- The type of matching to use; one of: - 'exact', 'mask', 'id', 'xpath', and 'stanzapath'. - Defaults to the value of self.match_method. - 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 stanza.values should - be used. Defaults to True. - """ - if method is None and hasattr(self, 'match_method'): - method = getattr(self, 'match_method') - - if method != 'exact': - matchers = {'stanzapath': StanzaPath, - 'xpath': MatchXPath, - 'mask': MatchXMLMask, - 'idsender': MatchIDSender, - 'id': MatcherId} - Matcher = matchers.get(method, None) - if Matcher is None: - raise ValueError("Unknown matching method.") - test = Matcher(criteria) - self.failUnless(test.match(stanza), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % str(criteria) + \ - "Stanza:\n%s" % str(stanza)) - else: - stanza_class = stanza.__class__ - if not isinstance(criteria, ElementBase): - xml = self.parse_xml(criteria) - else: - xml = criteria.xml - - # 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 stanza.values 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.values - stanza3 = stanza_class() - stanza3.values = 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_disconnect(self): - """ - Simulate a stream disconnection. - """ - if self.xmpp: - self.xmpp.socket.disconnect_error() - - def stream_start(self, mode='client', skip=True, header=None, - socket='mock', jid='tester@localhost', - password='test', server='localhost', - port=5222, sasl_mech=None, - plugins=None, plugin_config={}): - """ - 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. - plugins -- List of plugins to register. By default, all plugins - are loaded. - """ - if mode == 'client': - self.xmpp = ClientXMPP(jid, password, - sasl_mech=sasl_mech, - plugin_config=plugin_config) - elif mode == 'component': - self.xmpp = ComponentXMPP(jid, password, - server, port, - plugin_config=plugin_config) - else: - raise ValueError("Unknown XMPP connection mode.") - - # Remove unique ID prefix to make it easier to test - self.xmpp._id_prefix = '' - self.xmpp._disconnect_wait_for_threads = False - self.xmpp.default_lang = None - self.xmpp.peer_default_lang = None - - # We will use this to wait for the session_start event - # for live connections. - skip_queue = Queue() - - if socket == 'mock': - self.xmpp.set_socket(TestSocket()) - - # Simulate connecting for mock sockets. - self.xmpp.auto_reconnect = False - 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 - - def wait_for_session(x): - self.xmpp.socket.clear() - skip_queue.put('started') - - self.xmpp.add_event_handler('session_start', wait_for_session) - if server is not None: - self.xmpp.connect((server, port)) - else: - self.xmpp.connect() - else: - raise ValueError("Unknown socket type.") - - if plugins is None: - self.xmpp.register_plugins() - else: - for plugin in plugins: - self.xmpp.register_plugin(plugin) - - # Some plugins require messages to have ID values. Set - # this to True in tests related to those plugins. - self.xmpp.use_message_ids = False - - self.xmpp.process(threaded=True) - if skip: - if socket != 'live': - # Mark send queue as usable - self.xmpp.session_bind_event.set() - self.xmpp.session_started_event.set() - # Clear startup stanzas - self.xmpp.socket.next_sent(timeout=1) - if mode == 'component': - self.xmpp.socket.next_sent(timeout=1) - else: - skip_queue.get(block=True, timeout=10) - - def make_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - default_lang="en", - 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) - if default_lang: - parts.append('xml:lang="%s"' % default_lang) - 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, defaults=[], method='exact', - 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 -- If a dummy socket is being used, the XML that is to - be received next. Otherwise it is the criteria used - to match against live data that is received. - defaults -- A list of stanza interfaces with default values that - may interfere with comparisons. - method -- Select the type of comparison to use for - verifying the received stanza. Options are 'exact', - 'id', 'stanzapath', 'xpath', and 'mask'. - Defaults to the value of self.match_method. - use_values -- Indicates if stanza comparisons should test using - stanza.values. 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: - self.fail("No stanza was received.") - xml = self.parse_xml(recv_data) - self.fix_namespaces(xml, 'jabber:client') - stanza = self.xmpp._build_stanza(xml, 'jabber:client') - self.check(stanza, data, - method=method, - 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 list(recv_xml): - # We received more than just the header - for xml in recv_xml: - 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, method='mask', use_values=True, timeout=1): - """ - """ - if method is None and hasattr(self, 'match_method'): - method = getattr(self, 'match_method') - - 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) - xml = self.parse_xml(data) - recv_xml = self.parse_xml(recv_data) - if recv_data is None: - self.fail("No stanza was received.") - if method == 'exact': - self.failUnless(self.compare(xml, recv_xml), - "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( - tostring(xml), tostring(recv_xml))) - elif method == 'mask': - matcher = MatchXMLMask(xml) - self.failUnless(matcher.match(recv_xml), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % tostring(xml) + \ - "Stanza:\n%s" % tostring(recv_xml)) - else: - raise ValueError("Uknown matching method: %s" % method) - 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", - default_lang="en", - 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, - default_lang=default_lang, - 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, method='mask', use_values=True, timeout=1): - """ - """ - sent_data = self.xmpp.socket.next_sent(timeout) - xml = self.parse_xml(data) - sent_xml = self.parse_xml(sent_data) - if sent_data is None: - self.fail("No stanza was sent.") - if method == 'exact': - self.failUnless(self.compare(xml, sent_xml), - "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( - tostring(xml), tostring(sent_xml))) - elif method == 'mask': - matcher = MatchXMLMask(xml) - self.failUnless(matcher.match(sent_xml), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % tostring(xml) + \ - "Stanza:\n%s" % tostring(sent_xml)) - else: - raise ValueError("Uknown matching method: %s" % method) - - def send(self, data, defaults=None, use_values=True, - timeout=.5, method='exact'): - """ - 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. - method -- Select the type of comparison to use for - verifying the sent stanza. Options are 'exact', - 'id', 'stanzapath', 'xpath', and 'mask'. - Defaults to the value of self.match_method. - """ - sent = self.xmpp.socket.next_sent(timeout) - if data is None and sent is None: - return - if data is None and sent is not None: - self.fail("Stanza data was sent: %s" % sent) - if sent is None: - self.fail("No stanza was sent.") - - xml = self.parse_xml(sent) - self.fix_namespaces(xml, 'jabber:client') - sent = self.xmpp._build_stanza(xml, 'jabber:client') - self.check(sent, data, - method=method, - 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: - 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(list(xml)) != len(list(other)): - 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 2a1db717..00000000 --- a/sleekxmpp/thirdparty/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -try: - from collections import OrderedDict -except: - from sleekxmpp.thirdparty.ordereddict import OrderedDict - -try: - from gnupg import GPG -except: - from sleekxmpp.thirdparty.gnupg import GPG - -from sleekxmpp.thirdparty import socks -from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso diff --git a/sleekxmpp/thirdparty/gnupg.py b/sleekxmpp/thirdparty/gnupg.py deleted file mode 100644 index a89289fd..00000000 --- a/sleekxmpp/thirdparty/gnupg.py +++ /dev/null @@ -1,1017 +0,0 @@ -""" A wrapper for the 'gpg' command:: - -Portions of this module are derived from A.M. Kuchling's well-designed -GPG.py, using Richard Jones' updated version 1.3, which can be found -in the pycrypto CVS repository on Sourceforge: - -http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py - -This module is *not* forward-compatible with amk's; some of the -old interface has changed. For instance, since I've added decrypt -functionality, I elected to initialize with a 'gnupghome' argument -instead of 'keyring', so that gpg can find both the public and secret -keyrings. I've also altered some of the returned objects in order for -the caller to not have to know as much about the internals of the -result classes. - -While the rest of ISconf is released under the GPL, I am releasing -this single file under the same terms that A.M. Kuchling used for -pycrypto. - -Steve Traugott, stevegt@terraluna.org -Thu Jun 23 21:27:20 PDT 2005 - -This version of the module has been modified from Steve Traugott's version -(see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by -Vinay Sajip to make use of the subprocess module (Steve's version uses os.fork() -and so does not work on Windows). Renamed to gnupg.py to avoid confusion with -the previous versions. - -Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved. - -A unittest harness (test_gnupg.py) has also been added. -""" -import locale - -__version__ = "0.2.9" -__author__ = "Vinay Sajip" -__date__ = "$29-Mar-2012 21:12:58$" - -try: - from io import StringIO -except ImportError: - from cStringIO import StringIO - -import codecs -import locale -import logging -import os -import socket -from subprocess import Popen -from subprocess import PIPE -import sys -import threading - -try: - import logging.NullHandler as NullHandler -except ImportError: - class NullHandler(logging.Handler): - def handle(self, record): - pass -try: - unicode - _py3k = False -except NameError: - _py3k = True - -logger = logging.getLogger(__name__) -if not logger.handlers: - logger.addHandler(NullHandler()) - -def _copy_data(instream, outstream): - # Copy one stream to another - sent = 0 - if hasattr(sys.stdin, 'encoding'): - enc = sys.stdin.encoding - else: - enc = 'ascii' - while True: - data = instream.read(1024) - if len(data) == 0: - break - sent += len(data) - logger.debug("sending chunk (%d): %r", sent, data[:256]) - try: - outstream.write(data) - except UnicodeError: - outstream.write(data.encode(enc)) - except: - # Can sometimes get 'broken pipe' errors even when the data has all - # been sent - logger.exception('Error sending data') - break - try: - outstream.close() - except IOError: - logger.warning('Exception occurred while closing: ignored', exc_info=1) - logger.debug("closed output, %d bytes sent", sent) - -def _threaded_copy_data(instream, outstream): - wr = threading.Thread(target=_copy_data, args=(instream, outstream)) - wr.setDaemon(True) - logger.debug('data copier: %r, %r, %r', wr, instream, outstream) - wr.start() - return wr - -def _write_passphrase(stream, passphrase, encoding): - passphrase = '%s\n' % passphrase - passphrase = passphrase.encode(encoding) - stream.write(passphrase) - logger.debug("Wrote passphrase: %r", passphrase) - -def _is_sequence(instance): - return isinstance(instance,list) or isinstance(instance,tuple) - -def _make_binary_stream(s, encoding): - try: - if _py3k: - if isinstance(s, str): - s = s.encode(encoding) - else: - if type(s) is not str: - s = s.encode(encoding) - from io import BytesIO - rv = BytesIO(s) - except ImportError: - rv = StringIO(s) - return rv - -class Verify(object): - "Handle status messages for --verify" - - def __init__(self, gpg): - self.gpg = gpg - self.valid = False - self.fingerprint = self.creation_date = self.timestamp = None - self.signature_id = self.key_id = None - self.username = None - - def __nonzero__(self): - return self.valid - - __bool__ = __nonzero__ - - def handle_status(self, key, value): - if key in ("TRUST_UNDEFINED", "TRUST_NEVER", "TRUST_MARGINAL", - "TRUST_FULLY", "TRUST_ULTIMATE", "RSA_OR_IDEA", "NODATA", - "IMPORT_RES", "PLAINTEXT", "PLAINTEXT_LENGTH", - "POLICY_URL", "DECRYPTION_INFO", "DECRYPTION_OKAY", "IMPORTED"): - pass - elif key == "BADSIG": - self.valid = False - self.status = 'signature bad' - self.key_id, self.username = value.split(None, 1) - elif key == "GOODSIG": - self.valid = True - self.status = 'signature good' - self.key_id, self.username = value.split(None, 1) - elif key == "VALIDSIG": - (self.fingerprint, - self.creation_date, - self.sig_timestamp, - self.expire_timestamp) = value.split()[:4] - # may be different if signature is made with a subkey - self.pubkey_fingerprint = value.split()[-1] - self.status = 'signature valid' - elif key == "SIG_ID": - (self.signature_id, - self.creation_date, self.timestamp) = value.split() - elif key == "ERRSIG": - self.valid = False - (self.key_id, - algo, hash_algo, - cls, - self.timestamp) = value.split()[:5] - self.status = 'signature error' - elif key == "DECRYPTION_FAILED": - self.valid = False - self.key_id = value - self.status = 'decryption failed' - elif key == "NO_PUBKEY": - self.valid = False - self.key_id = value - self.status = 'no public key' - elif key in ("KEYEXPIRED", "SIGEXPIRED"): - # these are useless in verify, since they are spit out for any - # pub/subkeys on the key, not just the one doing the signing. - # if we want to check for signatures with expired key, - # the relevant flag is EXPKEYSIG. - pass - elif key in ("EXPKEYSIG", "REVKEYSIG"): - # signed with expired or revoked key - self.valid = False - self.key_id = value.split()[0] - self.status = (('%s %s') % (key[:3], key[3:])).lower() - else: - raise ValueError("Unknown status message: %r" % key) - -class ImportResult(object): - "Handle status messages for --import" - - counts = '''count no_user_id imported imported_rsa unchanged - n_uids n_subk n_sigs n_revoc sec_read sec_imported - sec_dups not_imported'''.split() - def __init__(self, gpg): - self.gpg = gpg - self.imported = [] - self.results = [] - self.fingerprints = [] - for result in self.counts: - setattr(self, result, None) - - def __nonzero__(self): - if self.not_imported: return False - if not self.fingerprints: return False - return True - - __bool__ = __nonzero__ - - ok_reason = { - '0': 'Not actually changed', - '1': 'Entirely new key', - '2': 'New user IDs', - '4': 'New signatures', - '8': 'New subkeys', - '16': 'Contains private key', - } - - problem_reason = { - '0': 'No specific reason given', - '1': 'Invalid Certificate', - '2': 'Issuer Certificate missing', - '3': 'Certificate Chain too long', - '4': 'Error storing certificate', - } - - def handle_status(self, key, value): - if key == "IMPORTED": - # this duplicates info we already see in import_ok & import_problem - pass - elif key == "NODATA": - self.results.append({'fingerprint': None, - 'problem': '0', 'text': 'No valid data found'}) - elif key == "IMPORT_OK": - reason, fingerprint = value.split() - reasons = [] - for code, text in list(self.ok_reason.items()): - if int(reason) | int(code) == int(reason): - reasons.append(text) - reasontext = '\n'.join(reasons) + "\n" - self.results.append({'fingerprint': fingerprint, - 'ok': reason, 'text': reasontext}) - self.fingerprints.append(fingerprint) - elif key == "IMPORT_PROBLEM": - try: - reason, fingerprint = value.split() - except: - reason = value - fingerprint = '<unknown>' - self.results.append({'fingerprint': fingerprint, - 'problem': reason, 'text': self.problem_reason[reason]}) - elif key == "IMPORT_RES": - import_res = value.split() - for i in range(len(self.counts)): - setattr(self, self.counts[i], int(import_res[i])) - elif key == "KEYEXPIRED": - self.results.append({'fingerprint': None, - 'problem': '0', 'text': 'Key expired'}) - elif key == "SIGEXPIRED": - self.results.append({'fingerprint': None, - 'problem': '0', 'text': 'Signature expired'}) - else: - raise ValueError("Unknown status message: %r" % key) - - def summary(self): - l = [] - l.append('%d imported'%self.imported) - if self.not_imported: - l.append('%d not imported'%self.not_imported) - return ', '.join(l) - -class ListKeys(list): - ''' Handle status messages for --list-keys. - - Handle pub and uid (relating the latter to the former). - - Don't care about (info from src/DETAILS): - - crt = X.509 certificate - crs = X.509 certificate and private key available - sub = subkey (secondary key) - ssb = secret subkey (secondary key) - uat = user attribute (same as user id except for field 10). - sig = signature - rev = revocation signature - pkd = public key data (special field format, see below) - grp = reserved for gpgsm - rvk = revocation key - ''' - def __init__(self, gpg): - self.gpg = gpg - self.curkey = None - self.fingerprints = [] - self.uids = [] - - def key(self, args): - vars = (""" - type trust length algo keyid date expires dummy ownertrust uid - """).split() - self.curkey = {} - for i in range(len(vars)): - self.curkey[vars[i]] = args[i] - self.curkey['uids'] = [] - if self.curkey['uid']: - self.curkey['uids'].append(self.curkey['uid']) - del self.curkey['uid'] - self.append(self.curkey) - - pub = sec = key - - def fpr(self, args): - self.curkey['fingerprint'] = args[9] - self.fingerprints.append(args[9]) - - def uid(self, args): - self.curkey['uids'].append(args[9]) - self.uids.append(args[9]) - - def handle_status(self, key, value): - pass - -class Crypt(Verify): - "Handle status messages for --encrypt and --decrypt" - def __init__(self, gpg): - Verify.__init__(self, gpg) - self.data = '' - self.ok = False - self.status = '' - - def __nonzero__(self): - if self.ok: return True - return False - - __bool__ = __nonzero__ - - def __str__(self): - return self.data.decode(self.gpg.encoding, self.gpg.decode_errors) - - def handle_status(self, key, value): - if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION", - "BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA", - "CARDCTRL"): - # in the case of ERROR, this is because a more specific error - # message will have come first - pass - elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE", - "MISSING_PASSPHRASE", "DECRYPTION_FAILED", - "KEY_NOT_CREATED"): - self.status = key.replace("_", " ").lower() - elif key == "NEED_PASSPHRASE_SYM": - self.status = 'need symmetric passphrase' - elif key == "BEGIN_DECRYPTION": - self.status = 'decryption incomplete' - elif key == "BEGIN_ENCRYPTION": - self.status = 'encryption incomplete' - elif key == "DECRYPTION_OKAY": - self.status = 'decryption ok' - self.ok = True - elif key == "END_ENCRYPTION": - self.status = 'encryption ok' - self.ok = True - elif key == "INV_RECP": - self.status = 'invalid recipient' - elif key == "KEYEXPIRED": - self.status = 'key expired' - elif key == "SIG_CREATED": - self.status = 'sig created' - elif key == "SIGEXPIRED": - self.status = 'sig expired' - else: - Verify.handle_status(self, key, value) - -class GenKey(object): - "Handle status messages for --gen-key" - def __init__(self, gpg): - self.gpg = gpg - self.type = None - self.fingerprint = None - - def __nonzero__(self): - if self.fingerprint: return True - return False - - __bool__ = __nonzero__ - - def __str__(self): - return self.fingerprint or '' - - def handle_status(self, key, value): - if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA"): - pass - elif key == "KEY_CREATED": - (self.type,self.fingerprint) = value.split() - else: - raise ValueError("Unknown status message: %r" % key) - -class DeleteResult(object): - "Handle status messages for --delete-key and --delete-secret-key" - def __init__(self, gpg): - self.gpg = gpg - self.status = 'ok' - - def __str__(self): - return self.status - - problem_reason = { - '1': 'No such key', - '2': 'Must delete secret key first', - '3': 'Ambigious specification', - } - - def handle_status(self, key, value): - if key == "DELETE_PROBLEM": - self.status = self.problem_reason.get(value, - "Unknown error: %r" % value) - else: - raise ValueError("Unknown status message: %r" % key) - -class Sign(object): - "Handle status messages for --sign" - def __init__(self, gpg): - self.gpg = gpg - self.type = None - self.fingerprint = None - - def __nonzero__(self): - return self.fingerprint is not None - - __bool__ = __nonzero__ - - def __str__(self): - return self.data.decode(self.gpg.encoding, self.gpg.decode_errors) - - def handle_status(self, key, value): - if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE", - "GOOD_PASSPHRASE", "BEGIN_SIGNING", "CARDCTRL"): - pass - elif key == "SIG_CREATED": - (self.type, - algo, hashalgo, cls, - self.timestamp, self.fingerprint - ) = value.split() - else: - raise ValueError("Unknown status message: %r" % key) - - -class GPG(object): - - decode_errors = 'strict' - - result_map = { - 'crypt': Crypt, - 'delete': DeleteResult, - 'generate': GenKey, - 'import': ImportResult, - 'list': ListKeys, - 'sign': Sign, - 'verify': Verify, - } - - "Encapsulate access to the gpg executable" - def __init__(self, gpgbinary='gpg', gnupghome=None, verbose=False, - use_agent=False, keyring=None): - """Initialize a GPG process wrapper. Options are: - - gpgbinary -- full pathname for GPG binary. - - gnupghome -- full pathname to where we can find the public and - private keyrings. Default is whatever gpg defaults to. - keyring -- name of alternative keyring file to use. If specified, - the default keyring is not used. - """ - self.gpgbinary = gpgbinary - self.gnupghome = gnupghome - self.keyring = keyring - self.verbose = verbose - self.use_agent = use_agent - self.encoding = locale.getpreferredencoding() - if self.encoding is None: # This happens on Jython! - self.encoding = sys.stdin.encoding - if gnupghome and not os.path.isdir(self.gnupghome): - os.makedirs(self.gnupghome,0x1C0) - p = self._open_subprocess(["--version"]) - result = self.result_map['verify'](self) # any result will do for this - self._collect_output(p, result, stdin=p.stdin) - if p.returncode != 0: - raise ValueError("Error invoking gpg: %s: %s" % (p.returncode, - result.stderr)) - - def _open_subprocess(self, args, passphrase=False): - # Internal method: open a pipe to a GPG subprocess and return - # the file objects for communicating with it. - cmd = [self.gpgbinary, '--status-fd 2 --no-tty'] - if self.gnupghome: - cmd.append('--homedir "%s" ' % self.gnupghome) - if self.keyring: - cmd.append('--no-default-keyring --keyring "%s" ' % self.keyring) - if passphrase: - cmd.append('--batch --passphrase-fd 0') - if self.use_agent: - cmd.append('--use-agent') - cmd.extend(args) - cmd = ' '.join(cmd) - if self.verbose: - print(cmd) - logger.debug("%s", cmd) - return Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) - - def _read_response(self, stream, result): - # Internal method: reads all the stderr output from GPG, taking notice - # only of lines that begin with the magic [GNUPG:] prefix. - # - # Calls methods on the response object for each valid token found, - # with the arg being the remainder of the status line. - lines = [] - while True: - line = stream.readline() - if len(line) == 0: - break - lines.append(line) - line = line.rstrip() - if self.verbose: - print(line) - logger.debug("%s", line) - if line[0:9] == '[GNUPG:] ': - # Chop off the prefix - line = line[9:] - L = line.split(None, 1) - keyword = L[0] - if len(L) > 1: - value = L[1] - else: - value = "" - result.handle_status(keyword, value) - result.stderr = ''.join(lines) - - def _read_data(self, stream, result): - # Read the contents of the file from GPG's stdout - chunks = [] - while True: - data = stream.read(1024) - if len(data) == 0: - break - logger.debug("chunk: %r" % data[:256]) - chunks.append(data) - if _py3k: - # Join using b'' or '', as appropriate - result.data = type(data)().join(chunks) - else: - result.data = ''.join(chunks) - - def _collect_output(self, process, result, writer=None, stdin=None): - """ - Drain the subprocesses output streams, writing the collected output - to the result. If a writer thread (writing to the subprocess) is given, - make sure it's joined before returning. If a stdin stream is given, - close it before returning. - """ - stderr = codecs.getreader(self.encoding)(process.stderr) - rr = threading.Thread(target=self._read_response, args=(stderr, result)) - rr.setDaemon(True) - logger.debug('stderr reader: %r', rr) - rr.start() - - stdout = process.stdout - dr = threading.Thread(target=self._read_data, args=(stdout, result)) - dr.setDaemon(True) - logger.debug('stdout reader: %r', dr) - dr.start() - - dr.join() - rr.join() - if writer is not None: - writer.join() - process.wait() - if stdin is not None: - try: - stdin.close() - except IOError: - pass - stderr.close() - stdout.close() - - def _handle_io(self, args, file, result, passphrase=None, binary=False): - "Handle a call to GPG - pass input data, collect output data" - # Handle a basic data call - pass data to GPG, handle the output - # including status information. Garbage In, Garbage Out :) - p = self._open_subprocess(args, passphrase is not None) - if not binary: - stdin = codecs.getwriter(self.encoding)(p.stdin) - else: - stdin = p.stdin - if passphrase: - _write_passphrase(stdin, passphrase, self.encoding) - writer = _threaded_copy_data(file, stdin) - self._collect_output(p, result, writer, stdin) - return result - - # - # SIGNATURE METHODS - # - def sign(self, message, **kwargs): - """sign message""" - f = _make_binary_stream(message, self.encoding) - result = self.sign_file(f, **kwargs) - f.close() - return result - - def sign_file(self, file, keyid=None, passphrase=None, clearsign=True, - detach=False, binary=False): - """sign file""" - logger.debug("sign_file: %s", file) - if binary: - args = ['-s'] - else: - args = ['-sa'] - # You can't specify detach-sign and clearsign together: gpg ignores - # the detach-sign in that case. - if detach: - args.append("--detach-sign") - elif clearsign: - args.append("--clearsign") - if keyid: - args.append('--default-key "%s"' % keyid) - args.extend(['--no-version', "--comment ''"]) - result = self.result_map['sign'](self) - #We could use _handle_io here except for the fact that if the - #passphrase is bad, gpg bails and you can't write the message. - p = self._open_subprocess(args, passphrase is not None) - try: - stdin = p.stdin - if passphrase: - _write_passphrase(stdin, passphrase, self.encoding) - writer = _threaded_copy_data(file, stdin) - except IOError: - logging.exception("error writing message") - writer = None - self._collect_output(p, result, writer, stdin) - return result - - def verify(self, data): - """Verify the signature on the contents of the string 'data' - - >>> gpg = GPG(gnupghome="keys") - >>> input = gpg.gen_key_input(Passphrase='foo') - >>> key = gpg.gen_key(input) - >>> assert key - >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar') - >>> assert not sig - >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='foo') - >>> assert sig - >>> verify = gpg.verify(sig.data) - >>> assert verify - - """ - f = _make_binary_stream(data, self.encoding) - result = self.verify_file(f) - f.close() - return result - - def verify_file(self, file, data_filename=None): - "Verify the signature on the contents of the file-like object 'file'" - logger.debug('verify_file: %r, %r', file, data_filename) - result = self.result_map['verify'](self) - args = ['--verify'] - if data_filename is None: - self._handle_io(args, file, result, binary=True) - else: - logger.debug('Handling detached verification') - import tempfile - fd, fn = tempfile.mkstemp(prefix='pygpg') - s = file.read() - file.close() - logger.debug('Wrote to temp file: %r', s) - os.write(fd, s) - os.close(fd) - args.append(fn) - args.append('"%s"' % data_filename) - try: - p = self._open_subprocess(args) - self._collect_output(p, result, stdin=p.stdin) - finally: - os.unlink(fn) - return result - - # - # KEY MANAGEMENT - # - - def import_keys(self, key_data): - """ import the key_data into our keyring - - >>> import shutil - >>> shutil.rmtree("keys") - >>> gpg = GPG(gnupghome="keys") - >>> input = gpg.gen_key_input() - >>> result = gpg.gen_key(input) - >>> print1 = result.fingerprint - >>> result = gpg.gen_key(input) - >>> print2 = result.fingerprint - >>> pubkey1 = gpg.export_keys(print1) - >>> seckey1 = gpg.export_keys(print1,secret=True) - >>> seckeys = gpg.list_keys(secret=True) - >>> pubkeys = gpg.list_keys() - >>> assert print1 in seckeys.fingerprints - >>> assert print1 in pubkeys.fingerprints - >>> str(gpg.delete_keys(print1)) - 'Must delete secret key first' - >>> str(gpg.delete_keys(print1,secret=True)) - 'ok' - >>> str(gpg.delete_keys(print1)) - 'ok' - >>> str(gpg.delete_keys("nosuchkey")) - 'No such key' - >>> seckeys = gpg.list_keys(secret=True) - >>> pubkeys = gpg.list_keys() - >>> assert not print1 in seckeys.fingerprints - >>> assert not print1 in pubkeys.fingerprints - >>> result = gpg.import_keys('foo') - >>> assert not result - >>> result = gpg.import_keys(pubkey1) - >>> pubkeys = gpg.list_keys() - >>> seckeys = gpg.list_keys(secret=True) - >>> assert not print1 in seckeys.fingerprints - >>> assert print1 in pubkeys.fingerprints - >>> result = gpg.import_keys(seckey1) - >>> assert result - >>> seckeys = gpg.list_keys(secret=True) - >>> pubkeys = gpg.list_keys() - >>> assert print1 in seckeys.fingerprints - >>> assert print1 in pubkeys.fingerprints - >>> assert print2 in pubkeys.fingerprints - - """ - result = self.result_map['import'](self) - logger.debug('import_keys: %r', key_data[:256]) - data = _make_binary_stream(key_data, self.encoding) - self._handle_io(['--import'], data, result, binary=True) - logger.debug('import_keys result: %r', result.__dict__) - data.close() - return result - - def recv_keys(self, keyserver, *keyids): - """Import a key from a keyserver - - >>> import shutil - >>> shutil.rmtree("keys") - >>> gpg = GPG(gnupghome="keys") - >>> result = gpg.recv_keys('pgp.mit.edu', '3FF0DB166A7476EA') - >>> assert result - - """ - result = self.result_map['import'](self) - logger.debug('recv_keys: %r', keyids) - data = _make_binary_stream("", self.encoding) - #data = "" - args = ['--keyserver', keyserver, '--recv-keys'] - args.extend(keyids) - self._handle_io(args, data, result, binary=True) - logger.debug('recv_keys result: %r', result.__dict__) - data.close() - return result - - def delete_keys(self, fingerprints, secret=False): - which='key' - if secret: - which='secret-key' - if _is_sequence(fingerprints): - fingerprints = ' '.join(fingerprints) - args = ['--batch --delete-%s "%s"' % (which, fingerprints)] - result = self.result_map['delete'](self) - p = self._open_subprocess(args) - self._collect_output(p, result, stdin=p.stdin) - return result - - def export_keys(self, keyids, secret=False): - "export the indicated keys. 'keyid' is anything gpg accepts" - which='' - if secret: - which='-secret-key' - if _is_sequence(keyids): - keyids = ' '.join(['"%s"' % k for k in keyids]) - args = ["--armor --export%s %s" % (which, keyids)] - p = self._open_subprocess(args) - # gpg --export produces no status-fd output; stdout will be - # empty in case of failure - #stdout, stderr = p.communicate() - result = self.result_map['delete'](self) # any result will do - self._collect_output(p, result, stdin=p.stdin) - logger.debug('export_keys result: %r', result.data) - return result.data.decode(self.encoding, self.decode_errors) - - def list_keys(self, secret=False): - """ list the keys currently in the keyring - - >>> import shutil - >>> shutil.rmtree("keys") - >>> gpg = GPG(gnupghome="keys") - >>> input = gpg.gen_key_input() - >>> result = gpg.gen_key(input) - >>> print1 = result.fingerprint - >>> result = gpg.gen_key(input) - >>> print2 = result.fingerprint - >>> pubkeys = gpg.list_keys() - >>> assert print1 in pubkeys.fingerprints - >>> assert print2 in pubkeys.fingerprints - - """ - - which='keys' - if secret: - which='secret-keys' - args = "--list-%s --fixed-list-mode --fingerprint --with-colons" % (which,) - args = [args] - p = self._open_subprocess(args) - - # there might be some status thingumy here I should handle... (amk) - # ...nope, unless you care about expired sigs or keys (stevegt) - - # Get the response information - result = self.result_map['list'](self) - self._collect_output(p, result, stdin=p.stdin) - lines = result.data.decode(self.encoding, - self.decode_errors).splitlines() - valid_keywords = 'pub uid sec fpr'.split() - for line in lines: - if self.verbose: - print(line) - logger.debug("line: %r", line.rstrip()) - if not line: - break - L = line.strip().split(':') - if not L: - continue - keyword = L[0] - if keyword in valid_keywords: - getattr(result, keyword)(L) - return result - - def gen_key(self, input): - """Generate a key; you might use gen_key_input() to create the - control input. - - >>> gpg = GPG(gnupghome="keys") - >>> input = gpg.gen_key_input() - >>> result = gpg.gen_key(input) - >>> assert result - >>> result = gpg.gen_key('foo') - >>> assert not result - - """ - args = ["--gen-key --batch"] - result = self.result_map['generate'](self) - f = _make_binary_stream(input, self.encoding) - self._handle_io(args, f, result, binary=True) - f.close() - return result - - def gen_key_input(self, **kwargs): - """ - Generate --gen-key input per gpg doc/DETAILS - """ - parms = {} - for key, val in list(kwargs.items()): - key = key.replace('_','-').title() - parms[key] = val - parms.setdefault('Key-Type','RSA') - parms.setdefault('Key-Length',1024) - parms.setdefault('Name-Real', "Autogenerated Key") - parms.setdefault('Name-Comment', "Generated by gnupg.py") - try: - logname = os.environ['LOGNAME'] - except KeyError: - logname = os.environ['USERNAME'] - hostname = socket.gethostname() - parms.setdefault('Name-Email', "%s@%s" % (logname.replace(' ', '_'), - hostname)) - out = "Key-Type: %s\n" % parms.pop('Key-Type') - for key, val in list(parms.items()): - out += "%s: %s\n" % (key, val) - out += "%commit\n" - return out - - # Key-Type: RSA - # Key-Length: 1024 - # Name-Real: ISdlink Server on %s - # Name-Comment: Created by %s - # Name-Email: isdlink@%s - # Expire-Date: 0 - # %commit - # - # - # Key-Type: DSA - # Key-Length: 1024 - # Subkey-Type: ELG-E - # Subkey-Length: 1024 - # Name-Real: Joe Tester - # Name-Comment: with stupid passphrase - # Name-Email: joe@foo.bar - # Expire-Date: 0 - # Passphrase: abc - # %pubring foo.pub - # %secring foo.sec - # %commit - - # - # ENCRYPTION - # - def encrypt_file(self, file, recipients, sign=None, - always_trust=False, passphrase=None, - armor=True, output=None, symmetric=False): - "Encrypt the message read from the file-like object 'file'" - args = ['--no-version', "--comment ''"] - if symmetric: - args.append('--symmetric') - else: - args.append('--encrypt') - if not _is_sequence(recipients): - recipients = (recipients,) - for recipient in recipients: - args.append('--recipient "%s"' % recipient) - if armor: # create ascii-armored output - set to False for binary output - args.append('--armor') - if output: # write the output to a file with the specified name - if os.path.exists(output): - os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) - if sign: - args.append('--sign --default-key "%s"' % sign) - if always_trust: - args.append("--always-trust") - result = self.result_map['crypt'](self) - self._handle_io(args, file, result, passphrase=passphrase, binary=True) - logger.debug('encrypt result: %r', result.data) - return result - - def encrypt(self, data, recipients, **kwargs): - """Encrypt the message contained in the string 'data' - - >>> import shutil - >>> if os.path.exists("keys"): - ... shutil.rmtree("keys") - >>> gpg = GPG(gnupghome="keys") - >>> input = gpg.gen_key_input(passphrase='foo') - >>> result = gpg.gen_key(input) - >>> print1 = result.fingerprint - >>> input = gpg.gen_key_input() - >>> result = gpg.gen_key(input) - >>> print2 = result.fingerprint - >>> result = gpg.encrypt("hello",print2) - >>> message = str(result) - >>> assert message != 'hello' - >>> result = gpg.decrypt(message) - >>> assert result - >>> str(result) - 'hello' - >>> result = gpg.encrypt("hello again",print1) - >>> message = str(result) - >>> result = gpg.decrypt(message) - >>> result.status == 'need passphrase' - True - >>> result = gpg.decrypt(message,passphrase='bar') - >>> result.status in ('decryption failed', 'bad passphrase') - True - >>> assert not result - >>> result = gpg.decrypt(message,passphrase='foo') - >>> result.status == 'decryption ok' - True - >>> str(result) - 'hello again' - >>> result = gpg.encrypt("signed hello",print2,sign=print1) - >>> result.status == 'need passphrase' - True - >>> result = gpg.encrypt("signed hello",print2,sign=print1,passphrase='foo') - >>> result.status == 'encryption ok' - True - >>> message = str(result) - >>> result = gpg.decrypt(message) - >>> result.status == 'decryption ok' - True - >>> assert result.fingerprint == print1 - - """ - data = _make_binary_stream(data, self.encoding) - result = self.encrypt_file(data, recipients, **kwargs) - data.close() - return result - - def decrypt(self, message, **kwargs): - data = _make_binary_stream(message, self.encoding) - result = self.decrypt_file(data, **kwargs) - data.close() - return result - - def decrypt_file(self, file, always_trust=False, passphrase=None, - output=None): - args = ["--decrypt"] - if output: # write the output to a file with the specified name - if os.path.exists(output): - os.remove(output) # to avoid overwrite confirmation message - args.append('--output "%s"' % output) - if always_trust: - args.append("--always-trust") - result = self.result_map['crypt'](self) - self._handle_io(args, file, result, passphrase, binary=True) - logger.debug('decrypt result: %r', result.data) - return result - diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py deleted file mode 100644 index e751a448..00000000 --- a/sleekxmpp/thirdparty/mini_dateutil.py +++ /dev/null @@ -1,273 +0,0 @@ -# This module is a very stripped down version of the dateutil -# package for when dateutil has not been installed. As a replacement -# for dateutil.parser.parse, the parsing methods from -# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/ - -#As such, the following copyrights and licenses applies: - - -# dateutil - Extensions to the standard python 2.3+ datetime module. -# -# Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net> -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -# fixed_dateime -# -# Copyright (c) 2008, Red Innovation Ltd., Finland -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of Red Innovation nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY -# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -import re -import math -import datetime - - -ZERO = datetime.timedelta(0) - - -try: - from dateutil.parser import parse as parse_iso - from dateutil.tz import tzoffset, tzutc -except: - # As a stopgap, define the two timezones here based - # on the dateutil code. - - class tzutc(datetime.tzinfo): - - def utcoffset(self, dt): - return ZERO - - def dst(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def __eq__(self, other): - return (isinstance(other, tzutc) or - (isinstance(other, tzoffset) and other._offset == ZERO)) - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return "%s()" % self.__class__.__name__ - - __reduce__ = object.__reduce__ - - class tzoffset(datetime.tzinfo): - - def __init__(self, name, offset): - self._name = name - self._offset = datetime.timedelta(minutes=offset) - - def utcoffset(self, dt): - return self._offset - - def dst(self, dt): - return ZERO - - def tzname(self, dt): - return self._name - - def __eq__(self, other): - return (isinstance(other, tzoffset) and - self._offset == other._offset) - - def __ne__(self, other): - return not self.__eq__(other) - - def __repr__(self): - return "%s(%s, %s)" % (self.__class__.__name__, - repr(self._name), - self._offset.days*86400+self._offset.seconds) - - __reduce__ = object.__reduce__ - - - _fixed_offset_tzs = { } - UTC = tzutc() - - def _get_fixed_offset_tz(offsetmins): - """For internal use only: Returns a tzinfo with - the given fixed offset. This creates only one instance - for each offset; the zones are kept in a dictionary""" - - if offsetmins == 0: - return UTC - - if not offsetmins in _fixed_offset_tzs: - if offsetmins < 0: - sign = '-' - absoff = -offsetmins - else: - sign = '+' - absoff = offsetmins - - name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60) - inst = tzoffset(name,offsetmins) - _fixed_offset_tzs[offsetmins] = inst - - return _fixed_offset_tzs[offsetmins] - - - _iso8601_parser = re.compile(""" - ^ - (?P<year> [0-9]{4})?(?P<ymdsep>-?)? - (?P<month>[0-9]{2})?(?P=ymdsep)? - (?P<day> [0-9]{2})? - - (?P<time> - (?: # time part... optional... at least hour must be specified - (?:T|\s+)? - (?P<hour>[0-9]{2}) - (?: - # minutes, separated with :, or none, from hours - (?P<hmssep>[:]?) - (?P<minute>[0-9]{2}) - (?: - # same for seconds, separated with :, or none, from hours - (?P=hmssep) - (?P<second>[0-9]{2}) - )? - )? - - # fractions - (?: [,.] (?P<frac>[0-9]{1,10}))? - - # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there. - ( - (?P<tzempty>Z) - | - (?P<tzh>[+-][0-9]{2}) - (?: :? # optional separator - (?P<tzm>[0-9]{2}) - )? - )? - ) - )? - $ - """, re.X) # """ - - def parse_iso(timestamp): - """Internal function for parsing a timestamp in - ISO 8601 format""" - - timestamp = timestamp.strip() - - m = _iso8601_parser.match(timestamp) - if not m: - raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp) - - vals = m.groupdict() - def_vals = {'year': 1970, 'month': 1, 'day': 1} - for key in vals: - if vals[key] is None: - vals[key] = def_vals.get(key, 0) - elif key not in ['time', 'ymdsep', 'hmssep', 'tzempty']: - vals[key] = int(vals[key]) - - year = vals['year'] - month = vals['month'] - day = vals['day'] - - if m.group('time') is None: - return datetime.date(year, month, day) - - h, min, s, us = None, None, None, 0 - frac = 0 - if m.group('tzempty') == None and m.group('tzh') == None: - raise ValueError("Not a proper ISO 8601 timestamp: " + - "missing timezone (Z or +hh[:mm])!") - - if m.group('frac'): - frac = m.group('frac') - power = len(frac) - frac = int(frac) / 10.0 ** power - - if m.group('hour'): - h = vals['hour'] - - if m.group('minute'): - min = vals['minute'] - - if m.group('second'): - s = vals['second'] - - if frac != None: - # ok, fractions of hour? - if min == None: - frac, min = math.modf(frac * 60.0) - min = int(min) - - # fractions of second? - if s == None: - frac, s = math.modf(frac * 60.0) - s = int(s) - - # and extract microseconds... - us = int(frac * 1000000) - - if m.group('tzempty') == 'Z': - offsetmins = 0 - else: - # timezone: hour diff with sign - offsetmins = vals['tzh'] * 60 - tzm = m.group('tzm') - - # add optional minutes - if tzm != None: - tzm = int(tzm) - offsetmins += tzm if offsetmins > 0 else -tzm - - tz = _get_fixed_offset_tz(offsetmins) - return datetime.datetime(year, month, day, h, min, s, us, tz) diff --git a/sleekxmpp/thirdparty/ordereddict.py b/sleekxmpp/thirdparty/ordereddict.py deleted file mode 100644 index 5b0303f5..00000000 --- a/sleekxmpp/thirdparty/ordereddict.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-
-from UserDict import DictMixin
-
-class OrderedDict(dict, DictMixin):
-
- def __init__(self, *args, **kwds):
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__end
- except AttributeError:
- self.clear()
- self.update(*args, **kwds)
-
- def clear(self):
- self.__end = end = []
- end += [None, end, end] # sentinel node for doubly linked list
- self.__map = {} # key --> [key, prev, next]
- dict.clear(self)
-
- def __setitem__(self, key, value):
- if key not in self:
- end = self.__end
- curr = end[1]
- curr[2] = end[1] = self.__map[key] = [key, curr, end]
- dict.__setitem__(self, key, value)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- key, prev, next = self.__map.pop(key)
- prev[2] = next
- next[1] = prev
-
- def __iter__(self):
- end = self.__end
- curr = end[2]
- while curr is not end:
- yield curr[0]
- curr = curr[2]
-
- def __reversed__(self):
- end = self.__end
- curr = end[1]
- while curr is not end:
- yield curr[0]
- curr = curr[1]
-
- def popitem(self, last=True):
- if not self:
- raise KeyError('dictionary is empty')
- if last:
- key = reversed(self).next()
- else:
- key = iter(self).next()
- value = self.pop(key)
- return key, value
-
- def __reduce__(self):
- items = [[k, self[k]] for k in self]
- tmp = self.__map, self.__end
- del self.__map, self.__end
- inst_dict = vars(self).copy()
- self.__map, self.__end = tmp
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def keys(self):
- return list(self)
-
- setdefault = DictMixin.setdefault
- update = DictMixin.update
- pop = DictMixin.pop
- values = DictMixin.values
- items = DictMixin.items
- iterkeys = DictMixin.iterkeys
- itervalues = DictMixin.itervalues
- iteritems = DictMixin.iteritems
-
- def __repr__(self):
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
-
- def copy(self):
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- if isinstance(other, OrderedDict):
- if len(self) != len(other):
- return False
- for p, q in zip(self.items(), other.items()):
- if p != q:
- return False
- return True
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
diff --git a/sleekxmpp/thirdparty/socks.py b/sleekxmpp/thirdparty/socks.py deleted file mode 100644 index 9239a7b9..00000000 --- a/sleekxmpp/thirdparty/socks.py +++ /dev/null @@ -1,378 +0,0 @@ -"""SocksiPy - Python SOCKS module. -Version 1.00 - -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. - - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) -for use in PyLoris (http://pyloris.sourceforge.net/) - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge - -""" - -import socket -import struct - -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 - -_defaultproxy = None -_orgsocket = socket.socket - -class ProxyError(Exception): pass -class GeneralProxyError(ProxyError): pass -class Socks5AuthError(ProxyError): pass -class Socks5Error(ProxyError): pass -class Socks4Error(ProxyError): pass -class HTTPError(ProxyError): pass - -_generalerrors = ("success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input") - -_socks5errors = ("succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error") - -_socks5autherrors = ("succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error") - -_socks4errors = ("request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different user-ids", - "unknown error") - -def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): - """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - global _defaultproxy - _defaultproxy = (proxytype, addr, port, rdns, username, password) - -def wrapmodule(module): - """wrapmodule(module) - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using setdefaultproxy(...) first. - This will only work on modules that import socket directly into the namespace; - most of the Python Standard Library falls into this category. - """ - if _defaultproxy != None: - module.socket.socket = socksocket - else: - raise GeneralProxyError((4, "no proxy specified")) - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): - _orgsocket.__init__(self, family, type, proto, _sock) - if _defaultproxy != None: - self.__proxy = _defaultproxy - else: - self.__proxy = (None, None, None, None, None, None) - self.__proxysockname = None - self.__proxypeername = None - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - data = self.recv(count) - while len(data) < count: - d = self.recv(count-len(data)) - if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - return data - - def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): - """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets the proxy to be used. - proxytype - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - addr - The address of the server (IP or DNS). - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - rdns - Should DNS queries be preformed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - username - Username to authenticate with to the server. - The default is no authentication. - password - Password to authenticate with to the server. - Only relevant when username is also provided. - """ - self.__proxy = (proxytype, addr, port, rdns, username, password) - - def __negotiatesocks5(self, destaddr, destport): - """__negotiatesocks5(self,destaddr,destport) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): - # The username/password details were supplied to the - # setproxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - # Now we can request the actual connection - req = struct.pack('BBB', 0x05, 0x01, 0x00) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - ipaddr = socket.inet_aton(destaddr) - req = req + chr(0x01).encode() + ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if self.__proxy[3]: - # Resolve remotely - ipaddr = None - req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2])<=8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def getproxysockname(self): - """getsockname() -> address info - Returns the bound IP address and port number at the proxy. - """ - return self.__proxysockname - - def getproxypeername(self): - """getproxypeername() -> address info - Returns the IP and port number of the proxy. - """ - return _orgsocket.getpeername(self) - - def getpeername(self): - """getpeername() -> address info - Returns the IP address and port number of the destination - machine (note: getproxypeername returns the proxy) - """ - return self.__proxypeername - - def __negotiatesocks4(self,destaddr,destport): - """__negotiatesocks4(self,destaddr,destport) - Negotiates a connection through a SOCKS4 server. - """ - # Check if the destination address provided is an IP address - rmtrslv = False - try: - ipaddr = socket.inet_aton(destaddr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if self.__proxy[3]: - ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: - req = req + self.__proxy[4] - req = req + chr(0x00).encode() - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if rmtrslv: - req = req + destaddr + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1,_generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) - else: - raise Socks4Error((94, _socks4errors[4])) - # Get the bound address/port - self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) - if rmtrslv != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __negotiatehttp(self, destaddr, destport): - """__negotiatehttp(self,destaddr,destport) - Negotiates a connection through an HTTP server. - """ - # If we need to resolve locally, we do this now - if not self.__proxy[3]: - addr = socket.gethostbyname(destaddr) - else: - addr = destaddr - self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) - # We read the response until we get the string "\r\n\r\n" - resp = self.recv(1) - while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - try: - statuscode = int(statusline[1]) - except ValueError: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if statuscode != 200: - self.close() - raise HTTPError((statuscode, statusline[2])) - self.__proxysockname = ("0.0.0.0", 0) - self.__proxypeername = (addr, destport) - - def connect(self, destpair): - """connect(self, despair) - Connects to the specified destination through a proxy. - destpar - A tuple of the IP/DNS address and the port number. - (identical to socket's connect). - To select the proxy server use setproxy(). - """ - # Do a minimal input check first - if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): - raise GeneralProxyError((5, _generalerrors[5])) - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self,(self.__proxy[1], portnum)) - self.__negotiatesocks4(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - _orgsocket.connect(self,(self.__proxy[1], portnum)) - self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == None: - _orgsocket.connect(self, (destpair[0], destpair[1])) - else: - raise GeneralProxyError((4, _generalerrors[4])) diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py deleted file mode 100644 index 113320fa..00000000 --- a/sleekxmpp/thirdparty/statemachine.py +++ /dev/null @@ -1,286 +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=None): - if not states: states = [] - self.lock = threading.Condition() - 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, list, set)): - raise ValueError("from_states should be a list, tuple, or set") - - 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) - - if self.__current_state == to_state: - return True - - 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 in %s sec: %s -> %s ", wait, self.__current_state, to_state) - return False - - while not self.__current_state in from_states: - # detect timeout: - remainder = start + wait - time.time() - if remainder > 0: - self.lock.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.lock.notify_all() - 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. - self.lock.acquire() - start = time.time() - while not self.__current_state in states: - # detect timeout: - remainder = start + wait - time.time() - if remainder > 0: - self.lock.wait(remainder) - else: - self.lock.release() - return False - self.lock.release() - 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.lock.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.lock.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.lock.notify_all() - 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/util/__init__.py b/sleekxmpp/util/__init__.py deleted file mode 100644 index 05286d33..00000000 --- a/sleekxmpp/util/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util - ~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout - :license: MIT, see LICENSE for more details -""" - - -from sleekxmpp.util.misc_ops import bytes, unicode, hashes, hash, \ - num_to_bytes, bytes_to_num, quote, \ - XOR, safedict - - -# ===================================================================== -# Standardize import of Queue class: - -import sys - -def _gevent_threads_enabled(): - if not 'gevent' in sys.modules: - return False - try: - from gevent import thread as green_thread - thread = __import__('thread') - return thread.LockType is green_thread.LockType - except ImportError: - return False - -if _gevent_threads_enabled(): - import gevent.queue as queue - Queue = queue.JoinableQueue -else: - try: - import queue - except ImportError: - import Queue as queue - Queue = queue.Queue - -QueueEmpty = queue.Empty diff --git a/sleekxmpp/util/misc_ops.py b/sleekxmpp/util/misc_ops.py deleted file mode 100644 index 18c919a8..00000000 --- a/sleekxmpp/util/misc_ops.py +++ /dev/null @@ -1,165 +0,0 @@ -import sys -import hashlib - - -def unicode(text): - if sys.version_info < (3, 0): - if isinstance(text, str): - text = text.decode('utf-8') - import __builtin__ - return __builtin__.unicode(text) - elif not isinstance(text, str): - return text.decode('utf-8') - else: - return text - - -def bytes(text): - """ - Convert Unicode text to UTF-8 encoded bytes. - - Since Python 2.6+ and Python 3+ have similar but incompatible - signatures, this function unifies the two to keep code sane. - - :param text: Unicode text to convert to bytes - :rtype: bytes (Python3), str (Python2.6+) - """ - if text is None: - return b'' - - if sys.version_info < (3, 0): - import __builtin__ - return __builtin__.bytes(text) - else: - import builtins - if isinstance(text, builtins.bytes): - # We already have bytes, so do nothing - return text - if isinstance(text, list): - # Convert a list of integers to bytes - return builtins.bytes(text) - else: - # Convert UTF-8 text to bytes - return builtins.bytes(text, encoding='utf-8') - - -def quote(text): - """ - Enclose in quotes and escape internal slashes and double quotes. - - :param text: A Unicode or byte string. - """ - text = bytes(text) - return b'"' + text.replace(b'\\', b'\\\\').replace(b'"', b'\\"') + b'"' - - -def num_to_bytes(num): - """ - Convert an integer into a four byte sequence. - - :param integer num: An integer to convert to its byte representation. - """ - bval = b'' - bval += bytes(chr(0xFF & (num >> 24))) - bval += bytes(chr(0xFF & (num >> 16))) - bval += bytes(chr(0xFF & (num >> 8))) - bval += bytes(chr(0xFF & (num >> 0))) - return bval - - -def bytes_to_num(bval): - """ - Convert a four byte sequence to an integer. - - :param bytes bval: A four byte sequence to turn into an integer. - """ - num = 0 - num += ord(bval[0] << 24) - num += ord(bval[1] << 16) - num += ord(bval[2] << 8) - num += ord(bval[3]) - return num - - -def XOR(x, y): - """ - Return the results of an XOR operation on two equal length byte strings. - - :param bytes x: A byte string - :param bytes y: A byte string - :rtype: bytes - """ - result = b'' - for a, b in zip(x, y): - if sys.version_info < (3, 0): - result += chr((ord(a) ^ ord(b))) - else: - result += bytes([a ^ b]) - return result - - -def hash(name): - """ - Return a hash function implementing the given algorithm. - - :param name: The name of the hashing algorithm to use. - :type name: string - - :rtype: function - """ - name = name.lower() - if name.startswith('sha-'): - name = 'sha' + name[4:] - if name in dir(hashlib): - return getattr(hashlib, name) - return None - - -def hashes(): - """ - Return a list of available hashing algorithms. - - :rtype: list of strings - """ - t = [] - if 'md5' in dir(hashlib): - t = ['MD5'] - if 'md2' in dir(hashlib): - t += ['MD2'] - hashes = ['SHA-' + h[3:] for h in dir(hashlib) if h.startswith('sha')] - return t + hashes - - -def setdefaultencoding(encoding): - """ - Set the current default string encoding used by the Unicode implementation. - - Actually calls sys.setdefaultencoding under the hood - see the docs for that - for more details. This method exists only as a way to call find/call it - even after it has been 'deleted' when the site module is executed. - - :param string encoding: An encoding name, compatible with sys.setdefaultencoding - """ - func = getattr(sys, 'setdefaultencoding', None) - if func is None: - import gc - import types - for obj in gc.get_objects(): - if (isinstance(obj, types.BuiltinFunctionType) - and obj.__name__ == 'setdefaultencoding'): - func = obj - break - if func is None: - raise RuntimeError("Could not find setdefaultencoding") - sys.setdefaultencoding = func - return func(encoding) - - -def safedict(data): - if sys.version_info < (2, 7): - safe = {} - for key in data: - safe[key.encode('utf8')] = data[key] - return safe - else: - return data diff --git a/sleekxmpp/util/sasl/__init__.py b/sleekxmpp/util/sasl/__init__.py deleted file mode 100644 index 2d344e9b..00000000 --- a/sleekxmpp/util/sasl/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl - ~~~~~~~~~~~~~~~~~~~ - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -from sleekxmpp.util.sasl.client import * -from sleekxmpp.util.sasl.mechanisms import * diff --git a/sleekxmpp/util/sasl/client.py b/sleekxmpp/util/sasl/client.py deleted file mode 100644 index fd685547..00000000 --- a/sleekxmpp/util/sasl/client.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl.client - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -import logging -import stringprep - -from sleekxmpp.util import hashes, bytes, stringprep_profiles - - -log = logging.getLogger(__name__) - - -#: Global registry mapping mechanism names to implementation classes. -MECHANISMS = {} - - -#: Global registry mapping mechanism names to security scores. -MECH_SEC_SCORES = {} - - -#: The SASLprep profile of stringprep used to validate simple username -#: and password credentials. -saslprep = stringprep_profiles.create( - nfkc=True, - bidi=True, - mappings=[ - stringprep_profiles.b1_mapping, - stringprep_profiles.c12_mapping], - prohibited=[ - stringprep.in_table_c12, - stringprep.in_table_c21, - stringprep.in_table_c22, - stringprep.in_table_c3, - stringprep.in_table_c4, - stringprep.in_table_c5, - stringprep.in_table_c6, - stringprep.in_table_c7, - stringprep.in_table_c8, - stringprep.in_table_c9], - unassigned=[stringprep.in_table_a1]) - - -def sasl_mech(score): - sec_score = score - def register(mech): - n = 0 - mech.score = sec_score - if mech.use_hashes: - for hashing_alg in hashes(): - n += 1 - score = mech.score + n - name = '%s-%s' % (mech.name, hashing_alg) - MECHANISMS[name] = mech - MECH_SEC_SCORES[name] = score - - if mech.channel_binding: - name += '-PLUS' - score += 10 - MECHANISMS[name] = mech - MECH_SEC_SCORES[name] = score - else: - MECHANISMS[mech.name] = mech - MECH_SEC_SCORES[mech.name] = mech.score - if mech.channel_binding: - MECHANISMS[mech.name + '-PLUS'] = mech - MECH_SEC_SCORES[name] = mech.score + 10 - return mech - return register - - -class SASLNoAppropriateMechanism(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLCancelled(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLFailed(Exception): - def __init__(self, value=''): - self.message = value - - -class SASLMutualAuthFailed(SASLFailed): - def __init__(self, value=''): - self.message = value - - -class Mech(object): - - name = 'GENERIC' - score = -1 - use_hashes = False - channel_binding = False - required_credentials = set() - optional_credentials = set() - security = set() - - def __init__(self, name, credentials, security_settings): - self.credentials = credentials - self.security_settings = security_settings - self.values = {} - self.base_name = self.name - self.name = name - self.setup(name) - - def setup(self, name): - pass - - def process(self, challenge=b''): - return b'' - - -def choose(mech_list, credentials, security_settings, limit=None, min_mech=None): - available_mechs = set(MECHANISMS.keys()) - if limit is None: - limit = set(mech_list) - if not isinstance(limit, set): - limit = set(limit) - if not isinstance(mech_list, set): - mech_list = set(mech_list) - - mech_list = mech_list.intersection(limit) - available_mechs = available_mechs.intersection(mech_list) - - best_score = MECH_SEC_SCORES.get(min_mech, -1) - best_mech = None - for name in available_mechs: - if name in MECH_SEC_SCORES: - if MECH_SEC_SCORES[name] > best_score: - best_score = MECH_SEC_SCORES[name] - best_mech = name - if best_mech is None: - raise SASLNoAppropriateMechanism() - - mech_class = MECHANISMS[best_mech] - - try: - creds = credentials(mech_class.required_credentials, - mech_class.optional_credentials) - for req in mech_class.required_credentials: - if req not in creds: - raise SASLCancelled('Missing credential: %s' % req) - for opt in mech_class.optional_credentials: - if opt not in creds: - creds[opt] = b'' - for cred in creds: - if cred in ('username', 'password', 'authzid'): - creds[cred] = bytes(saslprep(creds[cred])) - else: - creds[cred] = bytes(creds[cred]) - security_opts = security_settings(mech_class.security) - - return mech_class(best_mech, creds, security_opts) - except SASLCancelled as e: - log.info('SASL: %s: %s', best_mech, e.message) - mech_list.remove(best_mech) - return choose(mech_list, credentials, security_settings, - limit=limit, - min_mech=min_mech) diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py deleted file mode 100644 index d341ed3e..00000000 --- a/sleekxmpp/util/sasl/mechanisms.py +++ /dev/null @@ -1,551 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.sasl.mechanisms - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - A collection of supported SASL mechanisms. - - This module was originally based on Dave Cridland's Suelta library. - - Part of SleekXMPP: The Sleek XMPP Library - - :copryight: (c) 2004-2013 David Alan Cridland - :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout - - :license: MIT, see LICENSE for more details -""" - -import sys -import hmac -import random - -from base64 import b64encode, b64decode - -from sleekxmpp.util import bytes, hash, XOR, quote, num_to_bytes -from sleekxmpp.util.sasl.client import sasl_mech, Mech, \ - SASLCancelled, SASLFailed, \ - SASLMutualAuthFailed - - -@sasl_mech(0) -class ANONYMOUS(Mech): - - name = 'ANONYMOUS' - - def process(self, challenge=b''): - return b'Anonymous, Suelta' - - -@sasl_mech(1) -class LOGIN(Mech): - - name = 'LOGIN' - required_credentials = set(['username', 'password']) - - def setup(self, name): - self.step = 0 - - def process(self, challenge=b''): - if not challenge: - return b'' - - if self.step == 0: - self.step = 1 - return self.credentials['username'] - else: - return self.credentials['password'] - - -@sasl_mech(2) -class PLAIN(Mech): - - name = 'PLAIN' - required_credentials = set(['username', 'password']) - optional_credentials = set(['authzid']) - security = set(['encrypted', 'encrypted_plain', 'unencrypted_plain']) - - def setup(self, name): - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_plain']: - raise SASLCancelled('PLAIN without encryption') - else: - if not self.security_settings['encrypted_plain']: - raise SASLCancelled('PLAIN with encryption') - - def process(self, challenge=b''): - authzid = self.credentials['authzid'] - authcid = self.credentials['username'] - password = self.credentials['password'] - return authzid + b'\x00' + authcid + b'\x00' + password - - -@sasl_mech(100) -class EXTERNAL(Mech): - - name = 'EXTERNAL' - optional_credentials = set(['authzid']) - - def process(self, challenge=b''): - return self.credentials['authzid'] - - -@sasl_mech(31) -class X_FACEBOOK_PLATFORM(Mech): - - name = 'X-FACEBOOK-PLATFORM' - required_credentials = set(['api_key', 'access_token']) - - def process(self, challenge=b''): - if challenge: - values = {} - for kv in challenge.split(b'&'): - key, value = kv.split(b'=') - values[key] = value - - resp_data = { - b'method': values[b'method'], - b'v': b'1.0', - b'call_id': b'1.0', - b'nonce': values[b'nonce'], - b'access_token': self.credentials['access_token'], - b'api_key': self.credentials['api_key'] - } - - resp = '&'.join(['%s=%s' % (k.decode("utf-8"), v.decode("utf-8")) for k, v in resp_data.items()]) - return bytes(resp) - return b'' - - -@sasl_mech(10) -class X_MESSENGER_OAUTH2(Mech): - - name = 'X-MESSENGER-OAUTH2' - required_credentials = set(['access_token']) - - def process(self, challenge=b''): - return self.credentials['access_token'] - - -@sasl_mech(10) -class X_OAUTH2(Mech): - - name = 'X-OAUTH2' - required_credentials = set(['username', 'access_token']) - - def process(self, challenge=b''): - return b'\x00' + self.credentials['username'] + \ - b'\x00' + self.credentials['access_token'] - - -@sasl_mech(3) -class X_GOOGLE_TOKEN(Mech): - - name = 'X-GOOGLE-TOKEN' - required_credentials = set(['email', 'access_token']) - - def process(self, challenge=b''): - email = self.credentials['email'] - token = self.credentials['access_token'] - return b'\x00' + email + b'\x00' + token - - -@sasl_mech(20) -class CRAM(Mech): - - name = 'CRAM' - use_hashes = True - required_credentials = set(['username', 'password']) - security = set(['encrypted', 'unencrypted_cram']) - - def setup(self, name): - self.hash_name = name[5:] - self.hash = hash(self.hash_name) - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_cram']: - raise SASLCancelled('Unecrypted CRAM-%s' % self.hash_name) - - def process(self, challenge=b''): - if not challenge: - return None - - username = self.credentials['username'] - password = self.credentials['password'] - - mac = hmac.HMAC(key=password, digestmod=self.hash) - mac.update(challenge) - - return username + b' ' + bytes(mac.hexdigest()) - - -@sasl_mech(60) -class SCRAM(Mech): - - name = 'SCRAM' - use_hashes = True - channel_binding = True - required_credentials = set(['username', 'password']) - optional_credentials = set(['authzid', 'channel_binding']) - security = set(['encrypted', 'unencrypted_scram']) - - def setup(self, name): - self.use_channel_binding = False - if name[-5:] == '-PLUS': - name = name[:-5] - self.use_channel_binding = True - - self.hash_name = name[6:] - self.hash = hash(self.hash_name) - - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_scram']: - raise SASLCancelled('Unencrypted SCRAM') - - self.step = 0 - self._mutual_auth = False - - def HMAC(self, key, msg): - return hmac.HMAC(key=key, msg=msg, digestmod=self.hash).digest() - - def Hi(self, text, salt, iterations): - text = bytes(text) - ui1 = self.HMAC(text, salt + b'\0\0\0\01') - ui = ui1 - for i in range(iterations - 1): - ui1 = self.HMAC(text, ui1) - ui = XOR(ui, ui1) - return ui - - def H(self, text): - return self.hash(text).digest() - - def saslname(self, value): - escaped = b'' - for char in bytes(value): - if char == b',': - escaped += b'=2C' - elif char == b'=': - escaped += b'=3D' - else: - if isinstance(char, int): - char = chr(char) - escaped += bytes(char) - return escaped - - def parse(self, challenge): - items = {} - for key, value in [item.split(b'=', 1) for item in challenge.split(b',')]: - items[key] = value - return items - - def process(self, challenge=b''): - steps = [self.process_1, self.process_2, self.process_3] - return steps[self.step](challenge) - - def process_1(self, challenge): - self.step = 1 - data = {} - - self.cnonce = bytes(('%s' % random.random())[2:]) - - gs2_cbind_flag = b'n' - if self.credentials['channel_binding']: - if self.use_channel_binding: - gs2_cbind_flag = b'p=tls-unique' - else: - gs2_cbind_flag = b'y' - - authzid = b'' - if self.credentials['authzid']: - authzid = b'a=' + self.saslname(self.credentials['authzid']) - - self.gs2_header = gs2_cbind_flag + b',' + authzid + b',' - - nonce = b'r=' + self.cnonce - username = b'n=' + self.saslname(self.credentials['username']) - - self.client_first_message_bare = username + b',' + nonce - self.client_first_message = self.gs2_header + \ - self.client_first_message_bare - - return self.client_first_message - - def process_2(self, challenge): - self.step = 2 - - data = self.parse(challenge) - if b'm' in data: - raise SASLCancelled('Received reserved attribute.') - - salt = b64decode(data[b's']) - iteration_count = int(data[b'i']) - nonce = data[b'r'] - - if nonce[:len(self.cnonce)] != self.cnonce: - raise SASLCancelled('Invalid nonce') - - cbind_data = b'' - if self.use_channel_binding: - cbind_data = self.credentials['channel_binding'] - cbind_input = self.gs2_header + cbind_data - channel_binding = b'c=' + b64encode(cbind_input).replace(b'\n', b'') - - client_final_message_without_proof = channel_binding + b',' + \ - b'r=' + nonce - - salted_password = self.Hi(self.credentials['password'], - salt, - iteration_count) - client_key = self.HMAC(salted_password, b'Client Key') - stored_key = self.H(client_key) - auth_message = self.client_first_message_bare + b',' + \ - challenge + b',' + \ - client_final_message_without_proof - client_signature = self.HMAC(stored_key, auth_message) - client_proof = XOR(client_key, client_signature) - server_key = self.HMAC(salted_password, b'Server Key') - - self.server_signature = self.HMAC(server_key, auth_message) - - client_final_message = client_final_message_without_proof + \ - b',p=' + b64encode(client_proof) - - return client_final_message - - def process_3(self, challenge): - data = self.parse(challenge) - verifier = data.get(b'v', None) - error = data.get(b'e', 'Unknown error') - - if not verifier: - raise SASLFailed(error) - - if b64decode(verifier) != self.server_signature: - raise SASLMutualAuthFailed() - - self._mutual_auth = True - - return b'' - - -@sasl_mech(30) -class DIGEST(Mech): - - name = 'DIGEST' - use_hashes = True - required_credentials = set(['username', 'password', 'realm', 'service', 'host']) - optional_credentials = set(['authzid', 'service-name']) - security = set(['encrypted', 'unencrypted_digest']) - - def setup(self, name): - self.hash_name = name[7:] - self.hash = hash(self.hash_name) - if self.hash is None: - raise SASLCancelled('Unknown hash: %s' % self.hash_name) - if not self.security_settings['encrypted']: - if not self.security_settings['unencrypted_digest']: - raise SASLCancelled('Unencrypted DIGEST') - - self.qops = [b'auth'] - self.qop = b'auth' - self.maxbuf = b'65536' - self.nonce = b'' - self.cnonce = b'' - self.nonce_count = 1 - - def parse(self, challenge=b''): - data = {} - var_name = b'' - var_value = b'' - - # States: var, new_var, end, quote, escaped_quote - state = 'var' - - - for char in challenge: - if sys.version_info >= (3, 0): - char = bytes([char]) - - if state == 'var': - if char.isspace(): - continue - if char == b'=': - state = 'value' - else: - var_name += char - elif state == 'value': - if char == b'"': - state = 'quote' - elif char == b',': - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - else: - var_value += char - elif state == 'escaped': - var_value += char - elif state == 'quote': - if char == b'\\': - state = 'escaped' - elif char == b'"': - state = 'end' - else: - var_value += char - else: - if char == b',': - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - else: - var_value += char - - if var_name: - data[var_name.decode('utf-8')] = var_value - var_name = b'' - var_value = b'' - state = 'var' - return data - - def MAC(self, key, seq, msg): - mac = hmac.HMAC(key=key, digestmod=self.hash) - seqnum = num_to_bytes(seq) - mac.update(seqnum) - mac.update(msg) - return mac.digest()[:10] + b'\x00\x01' + seqnum - - def A1(self): - username = self.credentials['username'] - password = self.credentials['password'] - authzid = self.credentials['authzid'] - realm = self.credentials['realm'] - - a1 = self.hash() - a1.update(username + b':' + realm + b':' + password) - a1 = a1.digest() - a1 += b':' + self.nonce + b':' + self.cnonce - if authzid: - a1 += b':' + authzid - - return bytes(a1) - - def A2(self, prefix=b''): - a2 = prefix + b':' + self.digest_uri() - if self.qop in (b'auth-int', b'auth-conf'): - a2 += b':00000000000000000000000000000000' - return bytes(a2) - - def response(self, prefix=b''): - nc = bytes('%08x' % self.nonce_count) - - a1 = bytes(self.hash(self.A1()).hexdigest().lower()) - a2 = bytes(self.hash(self.A2(prefix)).hexdigest().lower()) - s = self.nonce + b':' + nc + b':' + self.cnonce + \ - b':' + self.qop + b':' + a2 - - return bytes(self.hash(a1 + b':' + s).hexdigest().lower()) - - def digest_uri(self): - serv_type = self.credentials['service'] - serv_name = self.credentials['service-name'] - host = self.credentials['host'] - - uri = serv_type + b'/' + host - if serv_name and host != serv_name: - uri += b'/' + serv_name - return uri - - def respond(self): - data = { - 'username': quote(self.credentials['username']), - 'authzid': quote(self.credentials['authzid']), - 'realm': quote(self.credentials['realm']), - 'nonce': quote(self.nonce), - 'cnonce': quote(self.cnonce), - 'nc': bytes('%08x' % self.nonce_count), - 'qop': self.qop, - 'digest-uri': quote(self.digest_uri()), - 'response': self.response(b'AUTHENTICATE'), - 'maxbuf': self.maxbuf, - 'charset': 'utf-8' - } - resp = b'' - for key, value in data.items(): - if value and value != b'""': - resp += b',' + bytes(key) + b'=' + bytes(value) - return resp[1:] - - def process(self, challenge=b''): - if not challenge: - if self.cnonce and self.nonce and self.nonce_count and self.qop: - self.nonce_count += 1 - return self.respond() - return None - - data = self.parse(challenge) - if 'rspauth' in data: - if data['rspauth'] != self.response(): - raise SASLMutualAuthFailed() - else: - self.nonce_count = 1 - self.cnonce = bytes('%s' % random.random())[2:] - self.qops = data.get('qop', [b'auth']) - self.qop = b'auth' - if 'nonce' in data: - self.nonce = data['nonce'] - if 'realm' in data and not self.credentials['realm']: - self.credentials['realm'] = data['realm'] - - return self.respond() - - -try: - import kerberos -except ImportError: - pass -else: - @sasl_mech(75) - class GSSAPI(Mech): - - name = 'GSSAPI' - required_credentials = set(['username', 'service-name']) - optional_credentials = set(['authzid']) - - def setup(self, name): - authzid = self.credentials['authzid'] - if not authzid: - authzid = 'xmpp@%s' % self.credentials['service-name'] - - _, self.gss = kerberos.authGSSClientInit(authzid) - self.step = 0 - - def process(self, challenge=b''): - b64_challenge = b64encode(challenge) - try: - if self.step == 0: - result = kerberos.authGSSClientStep(self.gss, b64_challenge) - if result != kerberos.AUTH_GSS_CONTINUE: - self.step = 1 - elif not challenge: - kerberos.authGSSClientClean(self.gss) - return b'' - elif self.step == 1: - username = self.credentials['username'] - - kerberos.authGSSClientUnwrap(self.gss, b64_challenge) - resp = kerberos.authGSSClientResponse(self.gss) - kerberos.authGSSClientWrap(self.gss, resp, username) - - resp = kerberos.authGSSClientResponse(self.gss) - except kerberos.GSSError as e: - raise SASLCancelled('Kerberos error: %s' % e) - if not resp: - return b'' - else: - return b64decode(resp) diff --git a/sleekxmpp/util/stringprep_profiles.py b/sleekxmpp/util/stringprep_profiles.py deleted file mode 100644 index 84326bc3..00000000 --- a/sleekxmpp/util/stringprep_profiles.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.util.stringprep_profiles - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module makes it easier to define profiles of stringprep, - such as nodeprep and resourceprep for JID validation, and - SASLprep for SASL. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout - :license: MIT, see LICENSE for more details -""" - - -from __future__ import unicode_literals - -import stringprep -from unicodedata import ucd_3_2_0 as unicodedata - -from sleekxmpp.util import unicode - - -class StringPrepError(UnicodeError): - pass - - -def b1_mapping(char): - """Map characters that are commonly mapped to nothing.""" - return '' if stringprep.in_table_b1(char) else None - - -def c12_mapping(char): - """Map non-ASCII whitespace to spaces.""" - return ' ' if stringprep.in_table_c12(char) else None - - -def map_input(data, tables=None): - """ - Each character in the input stream MUST be checked against - a mapping table. - """ - result = [] - for char in data: - replacement = None - - for mapping in tables: - replacement = mapping(char) - if replacement is not None: - break - - if replacement is None: - replacement = char - result.append(replacement) - return ''.join(result) - - -def normalize(data, nfkc=True): - """ - A profile can specify one of two options for Unicode normalization: - - no normalization - - Unicode normalization with form KC - """ - if nfkc: - data = unicodedata.normalize('NFKC', data) - return data - - -def prohibit_output(data, tables=None): - """ - Before the text can be emitted, it MUST be checked for prohibited - code points. - """ - for char in data: - for check in tables: - if check(char): - raise StringPrepError("Prohibited code point: %s" % char) - - -def check_bidi(data): - """ - 1) The characters in section 5.8 MUST be prohibited. - - 2) If a string contains any RandALCat character, the string MUST NOT - contain any LCat character. - - 3) If a string contains any RandALCat character, a RandALCat - character MUST be the first character of the string, and a - RandALCat character MUST be the last character of the string. - """ - if not data: - return data - - has_lcat = False - has_randal = False - - for c in data: - if stringprep.in_table_c8(c): - raise StringPrepError("BIDI violation: seciton 6 (1)") - if stringprep.in_table_d1(c): - has_randal = True - elif stringprep.in_table_d2(c): - has_lcat = True - - if has_randal and has_lcat: - raise StringPrepError("BIDI violation: section 6 (2)") - - first_randal = stringprep.in_table_d1(data[0]) - last_randal = stringprep.in_table_d1(data[-1]) - if has_randal and not (first_randal and last_randal): - raise StringPrepError("BIDI violation: section 6 (3)") - - -def create(nfkc=True, bidi=True, mappings=None, - prohibited=None, unassigned=None): - """Create a profile of stringprep. - - :param bool nfkc: - If `True`, perform NFKC Unicode normalization. Defaults to `True`. - :param bool bidi: - If `True`, perform bidirectional text checks. Defaults to `True`. - :param list mappings: - Optional list of functions for mapping characters to - suitable replacements. - :param list prohibited: - Optional list of functions which check for the presence of - prohibited characters. - :param list unassigned: - Optional list of functions for detecting the use of unassigned - code points. - - :raises: StringPrepError - :return: Unicode string of the resulting text passing the - profile's requirements. - """ - def profile(data, query=False): - try: - data = unicode(data) - except UnicodeError: - raise StringPrepError - - data = map_input(data, mappings) - data = normalize(data, nfkc) - prohibit_output(data, prohibited) - if bidi: - check_bidi(data) - if query and unassigned: - check_unassigned(data, unassigned) - return data - return profile diff --git a/sleekxmpp/version.py b/sleekxmpp/version.py deleted file mode 100644 index ecf62550..00000000 --- a/sleekxmpp/version.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. -""" - -# We don't want to have to import the entire library -# just to get the version info for setup.py - -__version__ = '1.3.1' -__version_info__ = (1, 3, 1, '', 0) diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py deleted file mode 100644 index 5a1ea1be..00000000 --- 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.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/cert.py b/sleekxmpp/xmlstream/cert.py deleted file mode 100644 index 71146f36..00000000 --- a/sleekxmpp/xmlstream/cert.py +++ /dev/null @@ -1,184 +0,0 @@ -import logging -from datetime import datetime, timedelta - -# Make a call to strptime before starting threads to -# prevent thread safety issues. -datetime.strptime('1970-01-01 12:00:00', "%Y-%m-%d %H:%M:%S") - - -try: - from pyasn1.codec.der import decoder, encoder - from pyasn1.type.univ import Any, ObjectIdentifier, OctetString - from pyasn1.type.char import BMPString, IA5String, UTF8String - from pyasn1.type.useful import GeneralizedTime - from pyasn1_modules.rfc2459 import (Certificate, DirectoryString, - SubjectAltName, GeneralNames, - GeneralName) - from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME - from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME - - XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5') - SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7') - - HAVE_PYASN1 = True -except ImportError: - HAVE_PYASN1 = False - - -log = logging.getLogger(__name__) - - -class CertificateError(Exception): - pass - - -def decode_str(data): - encoding = 'utf-16-be' if isinstance(data, BMPString) else 'utf-8' - return bytes(data).decode(encoding) - - -def extract_names(raw_cert): - results = {'CN': set(), - 'DNS': set(), - 'SRV': set(), - 'URI': set(), - 'XMPPAddr': set()} - - cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0] - tbs = cert.getComponentByName('tbsCertificate') - subject = tbs.getComponentByName('subject') - extensions = tbs.getComponentByName('extensions') or [] - - # Extract the CommonName(s) from the cert. - for rdnss in subject: - for rdns in rdnss: - for name in rdns: - oid = name.getComponentByName('type') - value = name.getComponentByName('value') - - if oid != COMMON_NAME: - continue - - value = decoder.decode(value, asn1Spec=DirectoryString())[0] - value = decode_str(value.getComponent()) - results['CN'].add(value) - - # Extract the Subject Alternate Names (DNS, SRV, URI, XMPPAddr) - for extension in extensions: - oid = extension.getComponentByName('extnID') - if oid != SUBJECT_ALT_NAME: - continue - - value = decoder.decode(extension.getComponentByName('extnValue'), - asn1Spec=OctetString())[0] - sa_names = decoder.decode(value, asn1Spec=SubjectAltName())[0] - for name in sa_names: - name_type = name.getName() - if name_type == 'dNSName': - results['DNS'].add(decode_str(name.getComponent())) - if name_type == 'uniformResourceIdentifier': - value = decode_str(name.getComponent()) - if value.startswith('xmpp:'): - results['URI'].add(value[5:]) - elif name_type == 'otherName': - name = name.getComponent() - - oid = name.getComponentByName('type-id') - value = name.getComponentByName('value') - - if oid == XMPP_ADDR: - value = decoder.decode(value, asn1Spec=UTF8String())[0] - results['XMPPAddr'].add(decode_str(value)) - elif oid == SRV_NAME: - value = decoder.decode(value, asn1Spec=IA5String())[0] - results['SRV'].add(decode_str(value)) - - return results - - -def extract_dates(raw_cert): - if not HAVE_PYASN1: - log.warning("Could not find pyasn1 and pyasn1_modules. " + \ - "SSL certificate expiration COULD NOT BE VERIFIED.") - return None, None - - cert = decoder.decode(raw_cert, asn1Spec=Certificate())[0] - tbs = cert.getComponentByName('tbsCertificate') - validity = tbs.getComponentByName('validity') - - not_before = validity.getComponentByName('notBefore') - not_before = str(not_before.getComponent()) - - not_after = validity.getComponentByName('notAfter') - not_after = str(not_after.getComponent()) - - if isinstance(not_before, GeneralizedTime): - not_before = datetime.strptime(not_before, '%Y%m%d%H%M%SZ') - else: - not_before = datetime.strptime(not_before, '%y%m%d%H%M%SZ') - - if isinstance(not_after, GeneralizedTime): - not_after = datetime.strptime(not_after, '%Y%m%d%H%M%SZ') - else: - not_after = datetime.strptime(not_after, '%y%m%d%H%M%SZ') - - return not_before, not_after - - -def get_ttl(raw_cert): - not_before, not_after = extract_dates(raw_cert) - if not_after is None: - return None - return not_after - datetime.utcnow() - - -def verify(expected, raw_cert): - if not HAVE_PYASN1: - log.warning("Could not find pyasn1 and pyasn1_modules. " + \ - "SSL certificate COULD NOT BE VERIFIED.") - return - - not_before, not_after = extract_dates(raw_cert) - cert_names = extract_names(raw_cert) - - now = datetime.utcnow() - - if not_before > now: - raise CertificateError( - 'Certificate has not entered its valid date range.') - - if not_after <= now: - raise CertificateError( - 'Certificate has expired.') - - if '.' in expected: - expected_wild = expected[expected.index('.'):] - else: - expected_wild = expected - expected_srv = '_xmpp-client.%s' % expected - - for name in cert_names['XMPPAddr']: - if name == expected: - return True - for name in cert_names['SRV']: - if name == expected_srv or name == expected: - return True - for name in cert_names['DNS']: - if name == expected: - return True - if name.startswith('*'): - if '.' in name: - name_wild = name[name.index('.'):] - else: - name_wild = name - if expected_wild == name_wild: - return True - for name in cert_names['URI']: - if name == expected: - return True - for name in cert_names['CN']: - if name == expected: - return True - - raise CertificateError( - 'Could not match certficate against hostname: %s' % expected) diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py deleted file mode 100644 index 53b83bc7..00000000 --- a/sleekxmpp/xmlstream/filesocket.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.filesocket - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module is a shim for correcting deficiencies in the file - socket implementation of Python2.6. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from socket import _fileobject -import errno -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 :class:`~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.""" - if self._sock is None: - return None - while True: - try: - data = self._sock.recv(size) - break - except socket.error as serr: - if serr.errno != errno.EINTR: - raise - if data is not None: - return data - - -class Socket26(socket.socket): - - """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 83c87f01..00000000 --- a/sleekxmpp/xmlstream/handler/__init__.py +++ /dev/null @@ -1,15 +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.collector import Collector -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 01c1991a..00000000 --- a/sleekxmpp/xmlstream/handler/base.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.handler.base - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -import weakref - - -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: during the incoming - stream processing, and in the main event loop. The :meth:`prerun()` - method is executed in the first case, and :meth:`run()` is called - during the second. - - :param string name: The name of the handler. - :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` - derived object that will be used to determine if a - stanza should be accepted by this handler. - :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` - instance that the handle will respond to. - """ - - def __init__(self, name, matcher, stream=None): - #: The name of the handler - self.name = name - - #: The XML stream this handler is assigned to - self.stream = None - if stream is not None: - self.stream = weakref.ref(stream) - stream.register_handler(self) - - self._destroy = False - self._payload = None - self._matcher = matcher - - def match(self, xml): - """Compare a stanza or XML object with the handler's matcher. - - :param xml: An XML or - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object - """ - return self._matcher.match(xml) - - def prerun(self, payload): - """Prepare the handler for execution while the XML - stream is being processed. - - :param payload: A :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - object. - """ - self._payload = payload - - def run(self, payload): - """Execute the handler after XML stream processing and during the - main event loop. - - :param payload: A :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - object. - """ - self._payload = payload - - def check_delete(self): - """Check if the handler should be removed from the list - of stream handlers. - """ - return self._destroy - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -BaseHandler.checkDelete = BaseHandler.check_delete diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py deleted file mode 100644 index 7e3388f1..00000000 --- a/sleekxmpp/xmlstream/handler/callback.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.handler.callback - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -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 :meth:`~sleekxmpp.xmlstream.xmlstream.XMLStream.event()` - method to pass the stanza off to a threaded event handler for further - processing. - - - :param string name: The name of the handler. - :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` - derived object for matching stanza objects. - :param pointer: The function to execute during callback. - :param bool thread: **DEPRECATED.** Remains only for - backwards compatibility. - :param bool once: Indicates if the handler should be used only - once. Defaults to False. - :param bool instream: Indicates if the callback should be executed - during stream processing instead of in the - main event loop. - :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` - instance this handler should monitor. - """ - - def __init__(self, name, matcher, pointer, thread=False, - once=False, instream=False, stream=None): - 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``. - - :param payload: The matched - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object. - """ - if self._once: - self._destroy = True - if self._instream: - self.run(payload, True) - - def run(self, payload, instream=False): - """Execute the callback function with the matched stanza payload. - - :param payload: The matched - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object. - :param bool instream: Force the handler to execute during stream - processing. This should only be used by - :meth:`prerun()`. Defaults to ``False``. - """ - if not self._instream or instream: - self._pointer(payload) - if self._once: - self._destroy = True - del self._pointer diff --git a/sleekxmpp/xmlstream/handler/collector.py b/sleekxmpp/xmlstream/handler/collector.py deleted file mode 100644 index 8f02f8c3..00000000 --- a/sleekxmpp/xmlstream/handler/collector.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.handler.collector - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout - :license: MIT, see LICENSE for more details -""" - -import logging - -from sleekxmpp.util import Queue, QueueEmpty -from sleekxmpp.xmlstream.handler.base import BaseHandler - - -log = logging.getLogger(__name__) - - -class Collector(BaseHandler): - - """ - The Collector handler allows for collecting a set of stanzas - that match a given pattern. Unlike the Waiter handler, a - Collector does not block execution, and will continue to - accumulate matching stanzas until told to stop. - - :param string name: The name of the handler. - :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` - derived object for matching stanza objects. - :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` - instance this handler should monitor. - """ - - def __init__(self, name, matcher, stream=None): - BaseHandler.__init__(self, name, matcher, stream=stream) - self._payload = Queue() - - def prerun(self, payload): - """Store the matched stanza when received during processing. - - :param payload: The matched - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object. - """ - self._payload.put(payload) - - def run(self, payload): - """Do not process this handler during the main event loop.""" - pass - - def stop(self): - """ - Stop collection of matching stanzas, and return the ones that - have been stored so far. - """ - self._destroy = True - results = [] - try: - while True: - results.append(self._payload.get(False)) - except QueueEmpty: - pass - - self.stream().remove_handler(self.name) - return results diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py deleted file mode 100644 index 66e14496..00000000 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.handler.waiter - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -import logging - -from sleekxmpp.util import Queue, QueueEmpty -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. - - :param string name: The name of the handler. - :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` - derived object for matching stanza objects. - :param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` - instance this handler should monitor. - """ - - def __init__(self, name, matcher, stream=None): - BaseHandler.__init__(self, name, matcher, stream=stream) - self._payload = Queue() - - def prerun(self, payload): - """Store the matched stanza when received during processing. - - :param payload: The matched - :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object. - """ - self._payload.put(payload) - - def run(self, payload): - """Do not process this handler during the main event loop.""" - pass - - def wait(self, timeout=None): - """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. - - :param int timeout: The number of seconds to wait for the stanza - to arrive. Defaults to the the stream's - :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout` - value. - """ - if timeout is None: - timeout = self.stream().response_timeout - - elapsed_time = 0 - stanza = False - while elapsed_time < timeout and not self.stream().stop.is_set(): - try: - stanza = self._payload.get(True, 1) - break - except QueueEmpty: - elapsed_time += 1 - if elapsed_time >= timeout: - log.warning("Timed out waiting for %s", self.name) - self.stream().remove_handler(self.name) - return stanza - - def check_delete(self): - """Always remove waiters after use.""" - return True diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py deleted file mode 100644 index 11607ffb..00000000 --- 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 5201caf3..00000000 --- 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 2b59db47..00000000 --- a/sleekxmpp/xmlstream/jid.py +++ /dev/null @@ -1,5 +0,0 @@ -import logging - -logging.warning('Deprecated: sleekxmpp.xmlstream.jid is moving to sleekxmpp.jid') - -from sleekxmpp.jid import JID diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py deleted file mode 100644 index aa74c434..00000000 --- a/sleekxmpp/xmlstream/matcher/__init__.py +++ /dev/null @@ -1,17 +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.idsender import MatchIDSender -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 83c26688..00000000 --- a/sleekxmpp/xmlstream/matcher/base.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.matcher.base - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - - -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. - - :param criteria: Object to compare some aspect of a stanza against. - """ - - def __init__(self, criteria): - 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 11ab70bb..00000000 --- a/sleekxmpp/xmlstream/matcher/id.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.matcher.id - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -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. - """ - - def match(self, xml): - """Compare the given stanza's ``'id'`` attribute to the stored - ``id`` value. - - :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to compare against. - """ - return xml['id'] == self._criteria diff --git a/sleekxmpp/xmlstream/matcher/idsender.py b/sleekxmpp/xmlstream/matcher/idsender.py deleted file mode 100644 index 5c2c1f51..00000000 --- a/sleekxmpp/xmlstream/matcher/idsender.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.matcher.id - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatchIDSender(MatcherBase): - - """ - The IDSender matcher selects stanzas that have the same stanza 'id' - interface value as the desired ID, and that the 'from' value is one - of a set of approved entities that can respond to a request. - """ - - def match(self, xml): - """Compare the given stanza's ``'id'`` attribute to the stored - ``id`` value, and verify the sender's JID. - - :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to compare against. - """ - - selfjid = self._criteria['self'] - peerjid = self._criteria['peer'] - - allowed = {} - allowed[''] = True - allowed[selfjid.bare] = True - allowed[selfjid.host] = True - allowed[peerjid.full] = True - allowed[peerjid.bare] = True - allowed[peerjid.host] = True - - _from = xml['from'] - - try: - return xml['id'] == self._criteria['id'] and allowed[_from] - except KeyError: - return False diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py deleted file mode 100644 index f470ec9c..00000000 --- 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 a4c0fda0..00000000 --- a/sleekxmpp/xmlstream/matcher/stanzapath.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.matcher.stanzapath - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase -from sleekxmpp.xmlstream.stanzabase import fix_ns - - -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. - - :param criteria: Object to compare some aspect of a stanza against. - """ - - def __init__(self, criteria): - self._criteria = fix_ns(criteria, split=True, - propagate_ns=False, - default_ns='jabber:client') - self._raw_criteria = criteria - - 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. See the documentation for the stanza - :meth:`~sleekxmpp.xmlstream.stanzabase.ElementBase.match()` method - for more information. - - :param stanza: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to compare against. - """ - return stanza.match(self._criteria) or stanza.match(self._raw_criteria) diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py deleted file mode 100644 index 56f728e1..00000000 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ /dev/null @@ -1,117 +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 - - -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: - - .. code-block:: xml - - <message xmlns="jabber:client"><body /></message> - - Use of XMLMask is discouraged, and - :class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or - :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` - should be used instead. - - :param criteria: Either an :class:`~xml.etree.ElementTree.Element` XML - object or XML string to use as a mask. - """ - - def __init__(self, criteria, default_ns='jabber:client'): - MatcherBase.__init__(self, criteria) - if isinstance(criteria, str): - self._criteria = ET.fromstring(self._criteria) - self.default_ns = default_ns - - def setDefaultNS(self, ns): - """Set the default namespace to use during comparisons. - - :param 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. - - :param 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. - - :param source: The :class:`~xml.etree.ElementTree.Element` XML object - to compare against the mask. - :param mask: The :class:`~xml.etree.ElementTree.Element` XML object - serving as the mask. - :param 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__"``. - """ - 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) - - 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 and \ - source.text.strip() != mask.text.strip(): - 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. - matched_elements = {} - for subelement in mask: - matched = False - for other in source.findall(subelement.tag): - matched_elements[other] = False - if self._mask_cmp(other, subelement, use_ns): - if not matched_elements.get(other, False): - matched_elements[other] = True - matched = True - if not matched: - return False - - # Everything matches. - return True diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py deleted file mode 100644 index f3d28429..00000000 --- a/sleekxmpp/xmlstream/matcher/xpath.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.matcher.xpath - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from sleekxmpp.xmlstream.stanzabase import ET, fix_ns -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatchXPath(MatcherBase): - - """ - The XPath matcher selects stanzas whose XML contents matches a given - XPath expression. - - .. warning:: - - Using this matcher may not produce expected behavior when using - attribute selectors. For Python 2.6 and 3.1, the ElementTree - :meth:`~xml.etree.ElementTree.Element.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 - :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. - - If the value of :data:`IGNORE_NS` is set to ``True``, then XPath - expressions will be matched without using namespaces. - """ - - def __init__(self, criteria): - self._criteria = fix_ns(criteria) - - def match(self, xml): - """ - Compare a stanza's XML contents to an XPath expression. - - If the value of :data:`IGNORE_NS` is set to ``True``, then XPath - expressions will be matched without using namespaces. - - .. warning:: - - In Python 2.6 and 3.1 the ElementTree - :meth:`~xml.etree.ElementTree.Element.find()` method does not - support attribute selectors in the XPath expression. - - :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to compare against. - """ - if hasattr(xml, 'xml'): - xml = xml.xml - x = ET.Element('x') - x.append(xml) - - return x.find(self._criteria) is not None diff --git a/sleekxmpp/xmlstream/resolver.py b/sleekxmpp/xmlstream/resolver.py deleted file mode 100644 index 188e5ac7..00000000 --- a/sleekxmpp/xmlstream/resolver.py +++ /dev/null @@ -1,333 +0,0 @@ -# -*- encoding: utf-8 -*- - -""" - sleekxmpp.xmlstream.dns - ~~~~~~~~~~~~~~~~~~~~~~~ - - :copyright: (c) 2012 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -import socket -import logging -import random - - -log = logging.getLogger(__name__) - - -#: Global flag indicating the availability of the ``dnspython`` package. -#: Installing ``dnspython`` can be done via: -#: -#: .. code-block:: sh -#: -#: pip install dnspython -#: -#: For Python3, installation may require installing from source using -#: the ``python3`` branch: -#: -#: .. code-block:: sh -#: -#: git clone http://github.com/rthalley/dnspython -#: cd dnspython -#: git checkout python3 -#: python3 setup.py install -DNSPYTHON_AVAILABLE = False -try: - import dns.resolver - DNSPYTHON_AVAILABLE = True -except ImportError as e: - log.debug("Could not find dnspython package. " + \ - "Not all features will be available") - - -def default_resolver(): - """Return a basic DNS resolver object. - - :returns: A :class:`dns.resolver.Resolver` object if dnspython - is available. Otherwise, ``None``. - """ - if DNSPYTHON_AVAILABLE: - return dns.resolver.get_default_resolver() - return None - - -def resolve(host, port=None, service=None, proto='tcp', - resolver=None, use_ipv6=True, use_dnspython=True): - """Peform DNS resolution for a given hostname. - - Resolution may perform SRV record lookups if a service and protocol - are specified. The returned addresses will be sorted according to - the SRV priorities and weights. - - If no resolver is provided, the dnspython resolver will be used if - available. Otherwise the built-in socket facilities will be used, - but those do not provide SRV support. - - If SRV records were used, queries to resolve alternative hosts will - be made as needed instead of all at once. - - :param host: The hostname to resolve. - :param port: A default port to connect with. SRV records may - dictate use of a different port. - :param service: Optional SRV service name without leading underscore. - :param proto: Optional SRV protocol name without leading underscore. - :param resolver: Optionally provide a DNS resolver object that has - been custom configured. - :param use_ipv6: Optionally control the use of IPv6 in situations - where it is either not available, or performance - is degraded. Defaults to ``True``. - :param use_dnspython: Optionally control if dnspython is used to make - the DNS queries instead of the built-in DNS - library. - - :type host: string - :type port: int - :type service: string - :type proto: string - :type resolver: :class:`dns.resolver.Resolver` - :type use_ipv6: bool - :type use_dnspython: bool - - :return: An iterable of IP address, port pairs in the order - dictated by SRV priorities and weights, if applicable. - """ - - if not use_dnspython: - if DNSPYTHON_AVAILABLE: - log.debug("DNS: Not using dnspython, but dnspython is installed.") - else: - log.debug("DNS: Not using dnspython.") - - if not use_ipv6: - log.debug("DNS: Use of IPv6 has been disabled.") - - if resolver is None and DNSPYTHON_AVAILABLE and use_dnspython: - resolver = dns.resolver.get_default_resolver() - - # An IPv6 literal is allowed to be enclosed in square brackets, but - # the brackets must be stripped in order to process the literal; - # otherwise, things break. - host = host.strip('[]') - - try: - # If `host` is an IPv4 literal, we can return it immediately. - ipv4 = socket.inet_aton(host) - yield (host, host, port) - except socket.error: - pass - - if use_ipv6: - try: - # Likewise, If `host` is an IPv6 literal, we can return - # it immediately. - if hasattr(socket, 'inet_pton'): - ipv6 = socket.inet_pton(socket.AF_INET6, host) - yield (host, host, port) - except (socket.error, ValueError): - pass - - # If no service was provided, then we can just do A/AAAA lookups on the - # provided host. Otherwise we need to get an ordered list of hosts to - # resolve based on SRV records. - if not service: - hosts = [(host, port)] - else: - hosts = get_SRV(host, port, service, proto, - resolver=resolver, - use_dnspython=use_dnspython) - - for host, port in hosts: - results = [] - if host == 'localhost': - if use_ipv6: - results.append((host, '::1', port)) - results.append((host, '127.0.0.1', port)) - if use_ipv6: - for address in get_AAAA(host, resolver=resolver, - use_dnspython=use_dnspython): - results.append((host, address, port)) - for address in get_A(host, resolver=resolver, - use_dnspython=use_dnspython): - results.append((host, address, port)) - - for host, address, port in results: - yield host, address, port - - -def get_A(host, resolver=None, use_dnspython=True): - """Lookup DNS A records for a given host. - - If ``resolver`` is not provided, or is ``None``, then resolution will - be performed using the built-in :mod:`socket` module. - - :param host: The hostname to resolve for A record IPv4 addresses. - :param resolver: Optional DNS resolver object to use for the query. - :param use_dnspython: Optionally control if dnspython is used to make - the DNS queries instead of the built-in DNS - library. - - :type host: string - :type resolver: :class:`dns.resolver.Resolver` or ``None`` - :type use_dnspython: bool - - :return: A list of IPv4 literals. - """ - log.debug("DNS: Querying %s for A records." % host) - - # If not using dnspython, attempt lookup using the OS level - # getaddrinfo() method. - if resolver is None or not use_dnspython: - try: - recs = socket.getaddrinfo(host, None, socket.AF_INET, - socket.SOCK_STREAM) - return [rec[4][0] for rec in recs] - except socket.gaierror: - log.debug("DNS: Error retreiving A address info for %s." % host) - return [] - - # Using dnspython: - try: - recs = resolver.query(host, dns.rdatatype.A) - return [rec.to_text() for rec in recs] - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - log.debug("DNS: No A records for %s" % host) - return [] - except dns.exception.Timeout: - log.debug("DNS: A record resolution timed out for %s" % host) - return [] - except dns.exception.DNSException as e: - log.debug("DNS: Error querying A records for %s" % host) - log.exception(e) - return [] - - -def get_AAAA(host, resolver=None, use_dnspython=True): - """Lookup DNS AAAA records for a given host. - - If ``resolver`` is not provided, or is ``None``, then resolution will - be performed using the built-in :mod:`socket` module. - - :param host: The hostname to resolve for AAAA record IPv6 addresses. - :param resolver: Optional DNS resolver object to use for the query. - :param use_dnspython: Optionally control if dnspython is used to make - the DNS queries instead of the built-in DNS - library. - - :type host: string - :type resolver: :class:`dns.resolver.Resolver` or ``None`` - :type use_dnspython: bool - - :return: A list of IPv6 literals. - """ - log.debug("DNS: Querying %s for AAAA records." % host) - - # If not using dnspython, attempt lookup using the OS level - # getaddrinfo() method. - if resolver is None or not use_dnspython: - if not socket.has_ipv6: - log.debug("Unable to query %s for AAAA records: IPv6 is not supported", host) - return [] - try: - recs = socket.getaddrinfo(host, None, socket.AF_INET6, - socket.SOCK_STREAM) - return [rec[4][0] for rec in recs] - except (OSError, socket.gaierror): - log.debug("DNS: Error retreiving AAAA address " + \ - "info for %s." % host) - return [] - - # Using dnspython: - try: - recs = resolver.query(host, dns.rdatatype.AAAA) - return [rec.to_text() for rec in recs] - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - log.debug("DNS: No AAAA records for %s" % host) - return [] - except dns.exception.Timeout: - log.debug("DNS: AAAA record resolution timed out for %s" % host) - return [] - except dns.exception.DNSException as e: - log.debug("DNS: Error querying AAAA records for %s" % host) - log.exception(e) - return [] - - -def get_SRV(host, port, service, proto='tcp', resolver=None, use_dnspython=True): - """Perform SRV record resolution for a given host. - - .. note:: - - This function requires the use of the ``dnspython`` package. Calling - :func:`get_SRV` without ``dnspython`` will return the provided host - and port without performing any DNS queries. - - :param host: The hostname to resolve. - :param port: A default port to connect with. SRV records may - dictate use of a different port. - :param service: Optional SRV service name without leading underscore. - :param proto: Optional SRV protocol name without leading underscore. - :param resolver: Optionally provide a DNS resolver object that has - been custom configured. - - :type host: string - :type port: int - :type service: string - :type proto: string - :type resolver: :class:`dns.resolver.Resolver` - - :return: A list of hostname, port pairs in the order dictacted - by SRV priorities and weights. - """ - if resolver is None or not use_dnspython: - log.warning("DNS: dnspython not found. Can not use SRV lookup.") - return [(host, port)] - - log.debug("DNS: Querying SRV records for %s" % host) - try: - recs = resolver.query('_%s._%s.%s' % (service, proto, host), - dns.rdatatype.SRV) - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - log.debug("DNS: No SRV records for %s." % host) - return [(host, port)] - except dns.exception.Timeout: - log.debug("DNS: SRV record resolution timed out for %s." % host) - return [(host, port)] - except dns.exception.DNSException as e: - log.debug("DNS: Error querying SRV records for %s." % host) - log.exception(e) - return [(host, port)] - - if len(recs) == 1 and recs[0].target == '.': - return [(host, port)] - - answers = {} - for rec in recs: - if rec.priority not in answers: - answers[rec.priority] = [] - if rec.weight == 0: - answers[rec.priority].insert(0, rec) - else: - answers[rec.priority].append(rec) - - sorted_recs = [] - for priority in sorted(answers.keys()): - while answers[priority]: - running_sum = 0 - sums = {} - for rec in answers[priority]: - running_sum += rec.weight - sums[running_sum] = rec - - selected = random.randint(0, running_sum + 1) - for running_sum in sums: - if running_sum >= selected: - rec = sums[running_sum] - host = rec.target.to_text() - if host.endswith('.'): - host = host[:-1] - sorted_recs.append((host, rec.port)) - answers[priority].remove(rec) - break - - return sorted_recs diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py deleted file mode 100644 index e6fae37a..00000000 --- a/sleekxmpp/xmlstream/scheduler.py +++ /dev/null @@ -1,250 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.scheduler - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module provides a task scheduler that works better - with SleekXMPP's threading usage than the stock version. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -import time -import threading -import logging -import itertools - -from sleekxmpp.util import Queue, QueueEmpty - - -#: The time in seconds to wait for events from the event queue, and also the -#: time between checks for the process stop signal. -WAIT_TIMEOUT = 1.0 - - -log = logging.getLogger(__name__) - - -class Task(object): - - """ - A scheduled task that will be executed by the scheduler - after a given time interval has passed. - - :param string name: The name of the task. - :param int seconds: The number of seconds to wait before executing. - :param callback: The function to execute. - :param tuple args: The arguments to pass to the callback. - :param dict kwargs: The keyword arguments to pass to the callback. - :param bool repeat: Indicates if the task should repeat. - Defaults to ``False``. - :param pointer: A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - - def __init__(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - #: The name of the task. - self.name = name - - #: The number of seconds to wait before executing. - self.seconds = seconds - - #: The function to execute once enough time has passed. - self.callback = callback - - #: The arguments to pass to :attr:`callback`. - self.args = args or tuple() - - #: The keyword arguments to pass to :attr:`callback`. - self.kwargs = kwargs or {} - - #: Indicates if the task should repeat after executing, - #: using the same :attr:`seconds` delay. - self.repeat = repeat - - #: The time when the task should execute next. - self.next = time.time() + self.seconds - - #: The main event queue, which allows for callbacks to - #: be queued for execution instead of executing immediately. - 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, self.kwargs, self.name)) - 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. - - Based on: http://docs.python.org/library/sched.html#module-sched - - :param parentstop: An :class:`~threading.Event` to signal stopping - the scheduler. - """ - - def __init__(self, parentstop=None): - #: A queue for storing tasks - self.addq = Queue() - - #: A list of tasks in order of execution time. - self.schedule = [] - - #: If running in threaded mode, this will be the thread processing - #: the schedule. - self.thread = None - - #: A flag indicating that the scheduler is running. - self.run = False - - #: An :class:`~threading.Event` instance for signalling to stop - #: the scheduler. - self.stop = parentstop - - #: Lock for accessing the task queue. - self.schedule_lock = threading.RLock() - - #: The time in seconds to wait for events from the event queue, - #: and also the time between checks for the process stop signal. - self.wait_timeout = WAIT_TIMEOUT - - def process(self, threaded=True, daemon=False): - """Begin accepting and processing scheduled tasks. - - :param bool threaded: Indicates if the scheduler should execute - in its own thread. Defaults to ``True``. - """ - if threaded: - self.thread = threading.Thread(name='scheduler_process', - target=self._process) - self.thread.daemon = daemon - self.thread.start() - else: - self._process() - - def _process(self): - """Process scheduled tasks.""" - self.run = True - try: - while self.run and not self.stop.is_set(): - updated = False - if self.schedule: - wait = self.schedule[0].next - time.time() - else: - wait = self.wait_timeout - try: - if wait <= 0.0: - newtask = self.addq.get(False) - else: - newtask = None - while self.run and \ - not self.stop.is_set() and \ - newtask is None and \ - wait > 0: - try: - newtask = self.addq.get(True, min(wait, self.wait_timeout)) - except QueueEmpty: # Nothing to add, nothing to do. Check run flags and continue waiting. - wait -= self.wait_timeout - except QueueEmpty: # Time to run some tasks, and no new tasks to add. - self.schedule_lock.acquire() - # select only those tasks which are to be executed now - relevant = itertools.takewhile( - lambda task: time.time() >= task.next, self.schedule) - # run the tasks and keep the return value in a tuple - status = map(lambda task: (task, task.run()), relevant) - # remove non-repeating tasks - for task, doRepeat in status: - if not doRepeat: - try: - self.schedule.remove(task) - except ValueError: - pass - else: - # only need to resort tasks if a repeated task has - # been kept in the list. - updated = True - else: # Add new task - self.schedule_lock.acquire() - if newtask is not None: - self.schedule.append(newtask) - updated = True - finally: - if updated: - self.schedule.sort(key=lambda task: task.next) - self.schedule_lock.release() - except KeyboardInterrupt: - self.run = False - except SystemExit: - self.run = False - log.debug("Quitting Scheduler thread") - - def add(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - """Schedule a new task. - - :param string name: The name of the task. - :param int seconds: The number of seconds to wait before executing. - :param callback: The function to execute. - :param tuple args: The arguments to pass to the callback. - :param dict kwargs: The keyword arguments to pass to the callback. - :param bool repeat: Indicates if the task should repeat. - Defaults to ``False``. - :param pointer: A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - try: - self.schedule_lock.acquire() - for task in self.schedule: - if task.name == name: - raise ValueError("Key %s already exists" % name) - - self.addq.put(Task(name, seconds, callback, args, - kwargs, repeat, qpointer)) - except: - raise - finally: - self.schedule_lock.release() - - def remove(self, name): - """Remove a scheduled task ahead of schedule, and without - executing it. - - :param string name: The name of the task to remove. - """ - try: - self.schedule_lock.acquire() - the_task = None - for task in self.schedule: - if task.name == name: - the_task = task - if the_task is not None: - self.schedule.remove(the_task) - except: - raise - finally: - self.schedule_lock.release() - - 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 97107098..00000000 --- a/sleekxmpp/xmlstream/stanzabase.py +++ /dev/null @@ -1,1644 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.stanzabase - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - module implements a wrapper layer for XML objects - that allows them to be treated like dictionaries. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import with_statement, unicode_literals - -import copy -import logging -import weakref -from xml.etree import cElementTree as ET - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.thirdparty import OrderedDict - - -log = logging.getLogger(__name__) - - -# Used to check if an argument is an XML object. -XML_TYPE = type(ET.Element('xml')) - - -XML_NS = 'http://www.w3.org/XML/1998/namespace' - - -def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False): - """ - Associate a stanza object as a plugin for another stanza. - - >>> from sleekxmpp.xmlstream import register_stanza_plugin - >>> register_stanza_plugin(Iq, CustomStanza) - - Plugin stanzas marked as iterable will be included in the list of - substanzas for the parent, using ``parent['substanzas']``. If the - attribute ``plugin_multi_attrib`` was defined for the plugin, then - the substanza set can be filtered to only instances of the plugin - class. For example, given a plugin class ``Foo`` with - ``plugin_multi_attrib = 'foos'`` then:: - - parent['foos'] - - would return a collection of all ``Foo`` substanzas. - - :param class stanza: The class of the parent stanza. - :param class plugin: The class of the plugin stanza. - :param bool iterable: Indicates if the plugin stanza should be - included in the parent stanza's iterable - ``'substanzas'`` interface results. - :param bool overrides: Indicates if the plugin should be allowed - to override the interface handlers for - the parent stanza, based on the plugin's - ``overrides`` field. - - .. versionadded:: 1.0-Beta1 - Made ``register_stanza_plugin`` the default name. The prior - ``registerStanzaPlugin`` function name remains as an alias. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) - - # Prevent weird memory reference gotchas by ensuring - # that the parent stanza class has its own set of - # plugin info maps and is not using the mappings from - # an ancestor class (like ElementBase). - plugin_info = ('plugin_attrib_map', 'plugin_tag_map', - 'plugin_iterables', 'plugin_overrides') - for attr in plugin_info: - info = getattr(stanza, attr) - setattr(stanza, attr, info.copy()) - - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map[tag] = plugin - - if iterable: - stanza.plugin_iterables.add(plugin) - if plugin.plugin_multi_attrib: - multiplugin = multifactory(plugin, plugin.plugin_multi_attrib) - register_stanza_plugin(stanza, multiplugin) - if overrides: - for interface in plugin.overrides: - stanza.plugin_overrides[interface] = plugin.plugin_attrib - - -# To maintain backwards compatibility for now, preserve the camel case name. -registerStanzaPlugin = register_stanza_plugin - - -def multifactory(stanza, plugin_attrib): - """ - Returns a ElementBase class for handling reoccuring child stanzas - """ - - def plugin_filter(self): - return lambda x: isinstance(x, self._multistanza) - - def plugin_lang_filter(self, lang): - return lambda x: isinstance(x, self._multistanza) and \ - x['lang'] == lang - - class Multi(ElementBase): - """ - Template class for multifactory - """ - def setup(self, xml=None): - self.xml = ET.Element('') - - def get_multi(self, lang=None): - parent = self.parent() - if not lang or lang == '*': - res = filter(plugin_filter(self), parent) - else: - res = filter(plugin_filter(self, lang), parent) - return list(res) - - def set_multi(self, val, lang=None): - parent = self.parent() - del_multi = getattr(self, 'del_%s' % plugin_attrib) - del_multi(lang) - for sub in val: - parent.append(sub) - - def del_multi(self, lang=None): - parent = self.parent() - if not lang or lang == '*': - res = filter(plugin_filter(self), parent) - else: - res = filter(plugin_filter(self, lang), parent) - res = list(res) - if not res: - del parent.plugins[(plugin_attrib, None)] - parent.loaded_plugins.remove(plugin_attrib) - try: - parent.xml.remove(self.xml) - except ValueError: - pass - else: - for stanza in list(res): - parent.iterables.remove(stanza) - parent.xml.remove(stanza.xml) - - Multi.is_extension = True - Multi.plugin_attrib = plugin_attrib - Multi._multistanza = stanza - Multi.interfaces = set([plugin_attrib]) - Multi.lang_interfaces = set([plugin_attrib]) - setattr(Multi, "get_%s" % plugin_attrib, get_multi) - setattr(Multi, "set_%s" % plugin_attrib, set_multi) - setattr(Multi, "del_%s" % plugin_attrib, del_multi) - return Multi - - -def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''): - """Apply the stanza's namespace to elements in an XPath expression. - - :param string xpath: The XPath expression to fix with namespaces. - :param bool 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. - :param bool 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 = default_ns - elements = ns_block.split('/') - - for element in elements: - if element: - # Skip empty entry artifacts from splitting. - if propagate_ns and element[0] != '*': - tag = '{%s}%s' % (namespace, element) - else: - tag = element - fixed.append(tag) - if split: - return fixed - return '/'.join(fixed) - - -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 assigned - directly from the parent stanza, as shown below, but retrieving - information will require all interfaces to be used, as so:: - - >>> # Same as using message['custom']['custom'] - >>> message['custom'] = 'bar' - >>> # Must use all interfaces - >>> message['custom']['custom'] - 'bar' - - If the plugin sets :attr:`is_extension` to ``True``, then both setting - and getting an interface value that is the same as the plugin's - plugin_attrib value will work, as so:: - - >>> message['custom'] = 'bar' # Using is_extension=True - >>> message['custom'] - 'bar' - - - :param xml: Initialize the stanza object with an existing XML object. - :param parent: Optionally specify a parent stanza object will - contain this substanza. - """ - - #: The XML tag name of the element, not including any namespace - #: prefixes. For example, an :class:`ElementBase` object for - #: ``<message />`` would use ``name = 'message'``. - name = 'stanza' - - #: The XML namespace for the element. Given ``<foo xmlns="bar" />``, - #: then ``namespace = "bar"`` should be used. The default namespace - #: is ``jabber:client`` since this is being used in an XMPP library. - namespace = 'jabber:client' - - #: For :class:`ElementBase` subclasses which are intended to be used - #: as plugins, the ``plugin_attrib`` value defines the plugin name. - #: Plugins may be accessed by using the ``plugin_attrib`` value as - #: the interface. An example using ``plugin_attrib = 'foo'``:: - #: - #: register_stanza_plugin(Message, FooPlugin) - #: msg = Message() - #: msg['foo']['an_interface_from_the_foo_plugin'] - plugin_attrib = 'plugin' - - #: For :class:`ElementBase` subclasses that are intended to be an - #: iterable group of items, the ``plugin_multi_attrib`` value defines - #: an interface for the parent stanza which returns the entire group - #: of matching substanzas. So the following are equivalent:: - #: - #: # Given stanza class Foo, with plugin_multi_attrib = 'foos' - #: parent['foos'] - #: filter(isinstance(item, Foo), parent['substanzas']) - plugin_multi_attrib = '' - - #: The set of keys that the stanza provides for accessing and - #: manipulating the underlying XML object. This set may be augmented - #: with the :attr:`plugin_attrib` value of any registered - #: stanza plugins. - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - - #: A subset of :attr:`interfaces` which maps interfaces to direct - #: subelements of the underlying XML object. Using this set, the text - #: of these subelements may be set, retrieved, or removed without - #: needing to define custom methods. - sub_interfaces = set() - - #: A subset of :attr:`interfaces` which maps the presence of - #: subelements to boolean values. Using this set allows for quickly - #: checking for the existence of empty subelements like ``<required />``. - #: - #: .. versionadded:: 1.1 - bool_interfaces = set() - - #: .. versionadded:: 1.1.2 - lang_interfaces = set() - - #: In some cases you may wish to override the behaviour of one of the - #: parent stanza's interfaces. The ``overrides`` list specifies the - #: interface name and access method to be overridden. For example, - #: to override setting the parent's ``'condition'`` interface you - #: would use:: - #: - #: overrides = ['set_condition'] - #: - #: Getting and deleting the ``'condition'`` interface would not - #: be affected. - #: - #: .. versionadded:: 1.0-Beta5 - overrides = [] - - #: If you need to add a new interface to an existing stanza, you - #: can create a plugin and set ``is_extension = True``. Be sure - #: to set the :attr:`plugin_attrib` value to the desired interface - #: name, and that it is the only interface listed in - #: :attr:`interfaces`. Requests for the new interface from the - #: parent stanza will be passed to the plugin directly. - #: - #: .. versionadded:: 1.0-Beta5 - is_extension = False - - #: A map of interface operations to the overriding functions. - #: For example, after overriding the ``set`` operation for - #: the interface ``body``, :attr:`plugin_overrides` would be:: - #: - #: {'set_body': <some function>} - #: - #: .. versionadded: 1.0-Beta5 - plugin_overrides = {} - - #: A mapping of the :attr:`plugin_attrib` values of registered - #: plugins to their respective classes. - plugin_attrib_map = {} - - #: A mapping of root element tag names (in ``'{namespace}elementname'`` - #: format) to the plugin classes responsible for them. - plugin_tag_map = {} - - #: The set of stanza classes that can be iterated over using - #: the 'substanzas' interface. Classes are added to this set - #: when registering a plugin with ``iterable=True``:: - #: - #: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True) - #: - #: .. versionadded:: 1.0-Beta5 - plugin_iterables = set() - - #: A deprecated version of :attr:`plugin_iterables` that remains - #: for backward compatibility. It required a parent stanza to - #: know beforehand what stanza classes would be iterable:: - #: - #: class DiscoItem(ElementBase): - #: ... - #: - #: class DiscoInfo(ElementBase): - #: subitem = (DiscoItem, ) - #: ... - #: - #: .. deprecated:: 1.0-Beta5 - subitem = set() - - #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``. - xml_ns = XML_NS - - def __init__(self, xml=None, parent=None): - self._index = 0 - - #: The underlying XML object for the stanza. It is a standard - #: :class:`xml.etree.cElementTree` object. - self.xml = xml - - #: An ordered dictionary of plugin stanzas, mapped by their - #: :attr:`plugin_attrib` value. - self.plugins = OrderedDict() - self.loaded_plugins = set() - - #: A list of child stanzas whose class is included in - #: :attr:`plugin_iterables`. - self.iterables = [] - - #: The name of the tag for the stanza's root element. It is the - #: same as calling :meth:`tag_name()` and is formatted as - #: ``'{namespace}elementname'``. - self.tag = self.tag_name() - - #: A :class:`weakref.weakref` to the parent stanza, if there is one. - #: If not, then :attr:`parent` is ``None``. - self.parent = None - if parent is not None: - if not isinstance(parent, weakref.ReferenceType): - self.parent = weakref.ref(parent) - else: - self.parent = parent - - if self.subitem is not None: - for sub in self.subitem: - self.plugin_iterables.add(sub) - - 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: - if child.tag in self.plugin_tag_map: - plugin_class = self.plugin_tag_map[child.tag] - self.init_plugin(plugin_class.plugin_attrib, - existing_xml=child, - reuse=False) - - def setup(self, xml=None): - """Initialize the stanza's XML contents. - - Will return ``True`` if XML was generated according to the stanza's - definition instead of building a stanza object from an existing - XML object. - - :param xml: An existing XML object to use for the stanza's content - instead of generating new XML. - """ - if self.xml is None: - self.xml = xml - - last_xml = self.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, lang=None): - """Enable and initialize a stanza plugin. - - Alias for :meth:`init_plugin`. - - :param string attrib: The :attr:`plugin_attrib` value of the - plugin to enable. - """ - return self.init_plugin(attrib, lang) - - def _get_plugin(self, name, lang=None, check=False): - if lang is None: - lang = self.get_lang() - - if name not in self.plugin_attrib_map: - return None - - plugin_class = self.plugin_attrib_map[name] - - if plugin_class.is_extension: - if (name, None) in self.plugins: - return self.plugins[(name, None)] - else: - return None if check else self.init_plugin(name, lang) - else: - if (name, lang) in self.plugins: - return self.plugins[(name, lang)] - else: - return None if check else self.init_plugin(name, lang) - - def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True): - """Enable and initialize a stanza plugin. - - :param string attrib: The :attr:`plugin_attrib` value of the - plugin to enable. - """ - default_lang = self.get_lang() - if not lang: - lang = default_lang - - plugin_class = self.plugin_attrib_map[attrib] - - if plugin_class.is_extension and (attrib, None) in self.plugins: - return self.plugins[(attrib, None)] - if reuse and (attrib, lang) in self.plugins: - return self.plugins[(attrib, lang)] - - plugin = plugin_class(parent=self, xml=existing_xml) - - if plugin.is_extension: - self.plugins[(attrib, None)] = plugin - else: - if lang != default_lang: - plugin['lang'] = lang - self.plugins[(attrib, lang)] = plugin - - if plugin_class in self.plugin_iterables: - self.iterables.append(plugin) - if plugin_class.plugin_multi_attrib: - self.init_plugin(plugin_class.plugin_multi_attrib) - - self.loaded_plugins.add(attrib) - - return plugin - - def _get_stanza_values(self): - """Return A JSON/dictionary version of the XML content - exposed through the stanza's interfaces:: - - >>> msg = Message() - >>> msg.values - {'body': '', 'from': , 'mucnick': '', 'mucroom': '', - 'to': , 'type': 'normal', 'id': '', 'subject': ''} - - Likewise, assigning to :attr:`values` will change the XML - content:: - - >>> msg = Message() - >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'} - >>> msg - '<message to="user@example.com"><body>Hi!</body></message>' - - .. versionadded:: 1.0-Beta1 - """ - values = {} - values['lang'] = self['lang'] - for interface in self.interfaces: - values[interface] = self[interface] - if interface in self.lang_interfaces: - values['%s|*' % interface] = self['%s|*' % interface] - for plugin, stanza in self.plugins.items(): - lang = stanza['lang'] - if lang: - values['%s|%s' % (plugin[0], lang)] = stanza.values - else: - values[plugin[0]] = stanza.values - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza.values) - iterables[-1]['__childtag__'] = stanza.tag - 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. - - :param values: A dictionary mapping stanza interface with values. - Plugin interfaces may accept a nested dictionary that - will be used recursively. - - .. versionadded:: 1.0-Beta1 - """ - iterable_interfaces = [p.plugin_attrib for \ - p in self.plugin_iterables] - - if 'lang' in values: - self['lang'] = values['lang'] - - if 'substanzas' in values: - # Remove existing substanzas - for stanza in self.iterables: - try: - self.xml.remove(stanza.xml) - except ValueError: - pass - self.iterables = [] - - # Add new substanzas - for subdict in values['substanzas']: - if '__childtag__' in subdict: - for subclass in self.plugin_iterables: - child_tag = "{%s}%s" % (subclass.namespace, - subclass.name) - if subdict['__childtag__'] == child_tag: - sub = subclass(parent=self) - sub.values = subdict - self.iterables.append(sub) - - for interface, value in values.items(): - full_interface = interface - interface_lang = ('%s|' % interface).split('|') - interface = interface_lang[0] - lang = interface_lang[1] or self.get_lang() - - if interface == 'lang': - continue - elif interface == 'substanzas': - continue - elif interface in self.interfaces: - self[full_interface] = value - elif interface in self.plugin_attrib_map: - if interface not in iterable_interfaces: - plugin = self._get_plugin(interface, lang) - if plugin: - plugin.values = value - return self - - def __getitem__(self, attrib): - """Return the value of a stanza interface using dict-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 (``'substanzas'``) - 2. The result of calling the ``get_foo`` override handler. - 3. The result of calling ``get_foo``. - 4. The result of calling ``getFoo``. - 5. The contents of the ``foo`` subelement, if ``foo`` is listed - in :attr:`sub_interfaces`. - 6. True or False depending on the existence of a ``foo`` - subelement and ``foo`` is in :attr:`bool_interfaces`. - 7. The value of the ``foo`` attribute of the XML object. - 8. The plugin named ``'foo'`` - 9. An empty string. - - :param string attrib: The name of the requested stanza interface. - """ - full_attrib = attrib - attrib_lang = ('%s|' % attrib).split('|') - attrib = attrib_lang[0] - lang = attrib_lang[1] or None - - kwargs = {} - if lang and attrib in self.lang_interfaces: - kwargs['lang'] = lang - - if attrib == 'substanzas': - return self.iterables - elif attrib in self.interfaces or attrib == 'lang': - get_method = "get_%s" % attrib.lower() - get_method2 = "get%s" % attrib.title() - - if self.plugin_overrides: - name = self.plugin_overrides.get(get_method, None) - if name: - plugin = self._get_plugin(name, lang) - if plugin: - handler = getattr(plugin, get_method, None) - if handler: - return handler(**kwargs) - - if hasattr(self, get_method): - return getattr(self, get_method)(**kwargs) - elif hasattr(self, get_method2): - return getattr(self, get_method2)(**kwargs) - else: - if attrib in self.sub_interfaces: - return self._get_sub_text(attrib, lang=lang) - elif attrib in self.bool_interfaces: - elem = self.xml.find('{%s}%s' % (self.namespace, attrib)) - return elem is not None - else: - return self._get_attr(attrib) - elif attrib in self.plugin_attrib_map: - plugin = self._get_plugin(attrib, lang) - if plugin and plugin.is_extension: - return plugin[full_attrib] - return plugin - 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 the ``set_foo`` override handler, if it exists. - 3. Call ``set_foo``, if it exists. - 4. Call ``setFoo``, if it exists. - 5. Set the text of a ``foo`` element, if ``'foo'`` is - in :attr:`sub_interfaces`. - 6. Add or remove an empty subelement ``foo`` - if ``foo`` is in :attr:`bool_interfaces`. - 7. Set the value of a top level XML attribute named ``foo``. - 8. Attempt to pass the value to a plugin named ``'foo'`` using - the plugin's ``'foo'`` interface. - 9. Do nothing. - - :param string attrib: The name of the stanza interface to modify. - :param value: The new value of the stanza interface. - """ - full_attrib = attrib - attrib_lang = ('%s|' % attrib).split('|') - attrib = attrib_lang[0] - lang = attrib_lang[1] or None - - kwargs = {} - if lang and attrib in self.lang_interfaces: - kwargs['lang'] = lang - - if attrib in self.interfaces or attrib == 'lang': - if value is not None: - set_method = "set_%s" % attrib.lower() - set_method2 = "set%s" % attrib.title() - - if self.plugin_overrides: - name = self.plugin_overrides.get(set_method, None) - if name: - plugin = self._get_plugin(name, lang) - if plugin: - handler = getattr(plugin, set_method, None) - if handler: - return handler(value, **kwargs) - - if hasattr(self, set_method): - getattr(self, set_method)(value, **kwargs) - elif hasattr(self, set_method2): - getattr(self, set_method2)(value, **kwargs) - else: - if attrib in self.sub_interfaces: - if lang == '*': - return self._set_all_sub_text(attrib, - value, - lang='*') - return self._set_sub_text(attrib, text=value, - lang=lang) - elif attrib in self.bool_interfaces: - if value: - return self._set_sub_text(attrib, '', - keep=True, - lang=lang) - else: - return self._set_sub_text(attrib, '', - keep=False, - lang=lang) - else: - self._set_attr(attrib, value) - else: - self.__delitem__(attrib) - elif attrib in self.plugin_attrib_map: - plugin = self._get_plugin(attrib, lang) - if plugin: - plugin[full_attrib] = value - return self - - def __delitem__(self, attrib): - """Delete the value of a stanza interface using dict-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`` override handler, if it exists. - 2. Call ``del_foo``, if it exists. - 3. Call ``delFoo``, if it exists. - 4. Delete ``foo`` element, if ``'foo'`` is in - :attr:`sub_interfaces`. - 5. Remove ``foo`` element if ``'foo'`` is in - :attr:`bool_interfaces`. - 6. Delete top level XML attribute named ``foo``. - 7. Remove the ``foo`` plugin, if it was loaded. - 8. Do nothing. - - :param attrib: The name of the affected stanza interface. - """ - full_attrib = attrib - attrib_lang = ('%s|' % attrib).split('|') - attrib = attrib_lang[0] - lang = attrib_lang[1] or None - - kwargs = {} - if lang and attrib in self.lang_interfaces: - kwargs['lang'] = lang - - if attrib in self.interfaces or attrib == 'lang': - del_method = "del_%s" % attrib.lower() - del_method2 = "del%s" % attrib.title() - - if self.plugin_overrides: - name = self.plugin_overrides.get(del_method, None) - if name: - plugin = self._get_plugin(attrib, lang) - if plugin: - handler = getattr(plugin, del_method, None) - if handler: - return handler(**kwargs) - - if hasattr(self, del_method): - getattr(self, del_method)(**kwargs) - elif hasattr(self, del_method2): - getattr(self, del_method2)(**kwargs) - else: - if attrib in self.sub_interfaces: - return self._del_sub(attrib, lang=lang) - elif attrib in self.bool_interfaces: - return self._del_sub(attrib, lang=lang) - else: - self._del_attr(attrib) - elif attrib in self.plugin_attrib_map: - plugin = self._get_plugin(attrib, lang, check=True) - if not plugin: - return self - if plugin.is_extension: - del plugin[full_attrib] - del self.plugins[(attrib, None)] - else: - del self.plugins[(attrib, plugin['lang'])] - self.loaded_plugins.remove(attrib) - try: - self.xml.remove(plugin.xml) - except ValueError: - pass - return self - - def _set_attr(self, name, value): - """Set the value of a top level attribute of the XML object. - - If the new value is None or an empty string, then the attribute will - be removed. - - :param name: The name of the attribute. - :param 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 XML object. - - :param 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 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. - - :param name: The name of the attribute. - :param 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='', lang=None): - """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. - - :param name: The name or XPath expression of the element. - :param default: Optional default to return if the element does - not exists. An empty string is returned otherwise. - """ - name = self._fix_ns(name) - if lang == '*': - return self._get_all_sub_text(name, default, None) - - default_lang = self.get_lang() - if not lang: - lang = default_lang - - stanzas = self.xml.findall(name) - if not stanzas: - return default - for stanza in stanzas: - if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang: - if stanza.text is None: - return default - return stanza.text - return default - - def _get_all_sub_text(self, name, default='', lang=None): - name = self._fix_ns(name) - - default_lang = self.get_lang() - results = OrderedDict() - stanzas = self.xml.findall(name) - if stanzas: - for stanza in stanzas: - stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS, - default_lang) - if not lang or lang == '*' or stanza_lang == lang: - results[stanza_lang] = stanza.text - return results - - def _set_sub_text(self, name, text=None, keep=False, lang=None): - """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. - - :param name: The name or XPath expression of the element. - :param 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. - :param keep: Indicates if the element should be kept if its text is - removed. Defaults to False. - """ - default_lang = self.get_lang() - if lang is None: - lang = default_lang - - if not text and not keep: - return self._del_sub(name, lang=lang) - - path = self._fix_ns(name, split=True) - name = path[-1] - parent = self.xml - - # The first goal is to find the parent of the subelement, or, if - # we can't find that, the closest grandparent element. - missing_path = [] - search_order = path[:-1] - while search_order: - parent = self.xml.find('/'.join(search_order)) - ename = search_order.pop() - if parent is not None: - break - else: - missing_path.append(ename) - missing_path.reverse() - - # Find all existing elements that match the desired - # element path (there may be multiples due to different - # languages values). - if parent is not None: - elements = self.xml.findall('/'.join(path)) - else: - parent = self.xml - elements = [] - - # Insert the remaining grandparent elements that don't exist yet. - for ename in missing_path: - element = ET.Element(ename) - parent.append(element) - parent = element - - # Re-use an existing element with the proper language, if one exists. - for element in elements: - elang = element.attrib.get('{%s}lang' % XML_NS, default_lang) - if not lang and elang == default_lang or lang and lang == elang: - element.text = text - return element - - # No useable element exists, so create a new one. - element = ET.Element(name) - element.text = text - if lang and lang != default_lang: - element.attrib['{%s}lang' % XML_NS] = lang - parent.append(element) - return element - - def _set_all_sub_text(self, name, values, keep=False, lang=None): - self._del_sub(name, lang) - for value_lang, value in values.items(): - if not lang or lang == '*' or value_lang == lang: - self._set_sub_text(name, text=value, - keep=keep, - lang=value_lang) - - def _del_sub(self, name, all=False, lang=None): - """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. - - :param name: The name or XPath expression for the element(s) to remove. - :param bool 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] - - default_lang = self.get_lang() - if not lang: - lang = default_lang - - 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 list(element): - # Only delete the originally requested elements, and - # any parent elements that have become empty. - elem_lang = element.attrib.get('{%s}lang' % XML_NS, - default_lang) - if lang == '*' or elem_lang == lang: - 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-like 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. - - :param string xpath: The XPath expression to check against. It - may be either a string or a list of element - names with attribute checks. - """ - if not isinstance(xpath, list): - 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.loaded_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] - langs = [name[1] for name in self.plugins if name[0] == next_tag] - for lang in langs: - plugin = self._get_plugin(next_tag, lang) - if plugin and plugin.match(xpath[1:]): - return True - 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:: - - Matching on attribute values is not supported in Python 2.6 - or Python 3.1 - - :param string 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:: - - Matching on attribute values is not supported in Python 2.6 - or Python 3.1. - - :param string 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. - - :param string key: The name of the stanza interface to check. - :param 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.loaded_plugins] - out.append('lang') - 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. - - :param 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) - if item.__class__ in self.plugin_iterables: - if item.__class__.plugin_multi_attrib: - self.init_plugin(item.__class__.plugin_multi_attrib) - elif item.__class__ == self.plugin_tag_map.get(item.tag_name(), None): - self.init_plugin(item.plugin_attrib, - existing_xml=item.xml, - reuse=False) - 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. - - :param XML 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. - - :param int 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__() - - def clear(self): - """Remove all XML element contents and plugins. - - Any attribute values will be preserved. - """ - for child in list(self.xml): - self.xml.remove(child) - - for plugin in list(self.plugins.keys()): - del self.plugins[plugin] - return self - - @classmethod - def tag_name(cls): - """Return the namespaced name of the stanza's root element. - - The format for the tag name is:: - - '{namespace}elementname' - - For example, for the stanza ``<foo xmlns="bar" />``, - ``stanza.tag_name()`` would return ``"{bar}foo"``. - """ - return "{%s}%s" % (cls.namespace, cls.name) - - def get_lang(self, lang=None): - result = self.xml.attrib.get('{%s}lang' % XML_NS, '') - if not result and self.parent and self.parent(): - return self.parent()['lang'] - return result - - def set_lang(self, lang): - self.del_lang() - attr = '{%s}lang' % XML_NS - if lang: - self.xml.attrib[attr] = lang - - def del_lang(self): - attr = '{%s}lang' % XML_NS - if attr in self.xml.attrib: - del self.xml.attrib[attr] - - @property - def attrib(self): - """Return the stanza object 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. - - .. deprecated:: 1.0 - """ - return self - - def _fix_ns(self, xpath, split=False, propagate_ns=True): - return fix_ns(xpath, split=split, - propagate_ns=propagate_ns, - default_ns=self.namespace) - - 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. - - :param ElementBase 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.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.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. - - :param ElementBase 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 in this stanza.""" - return len(self.iterables) - - def __iter__(self): - """Return an iterator object for 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, top_level_ns=True): - """Return a string serialization of the underlying XML object. - - .. seealso:: :ref:`tostring` - - :param bool top_level_ns: Display the top-most namespace. - Defaults to True. - """ - return tostring(self.xml, xmlns='', - top_level=True) - - 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 Interfaces:** - - :id: An optional id value that can be used to associate stanzas - :to: A JID object representing the recipient's JID. - :from: A JID object representing the sender's JID. - with their replies. - :type: The type of stanza, typically will be ``'normal'``, - ``'error'``, ``'get'``, or ``'set'``, etc. - :payload: The XML contents of the stanza. - - :param XMLStream stream: Optional :class:`sleekxmpp.xmlstream.XMLStream` - object responsible for sending this stanza. - :param XML xml: Optional XML contents to initialize stanza values. - :param string stype: Optional stanza type value. - :param sto: Optional string or :class:`sleekxmpp.xmlstream.JID` - object of the recipient's JID. - :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID` - object of the sender's JID. - :param string sid: Optional ID value for the stanza. - :param parent: Optionally specify a parent stanza object will - contain this substanza. - """ - - #: The default XMPP client namespace - namespace = 'jabber:client' - - #: There is a small set of attributes which apply to all XMPP stanzas: - #: the stanza type, the to and from JIDs, the stanza ID, and, especially - #: in the case of an Iq stanza, a payload. - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - - #: A basic set of allowed values for the ``'type'`` interface. - types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) - - def __init__(self, stream=None, xml=None, stype=None, - sto=None, sfrom=None, sid=None, parent=None): - self.stream = stream - if stream is not None: - self.namespace = stream.default_ns - ElementBase.__init__(self, xml, parent) - if stype is not None: - self['type'] = stype - if sto is not None: - self['to'] = sto - if sfrom is not None: - self['from'] = sfrom - if sid is not None: - self['id'] = sid - self.tag = "{%s}%s" % (self.namespace, self.name) - - def set_type(self, value): - """Set the stanza's ``'type'`` attribute. - - Only type values contained in :attr:`types` are accepted. - - :param string value: One of the values contained in :attr:`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. - - :param value: A string or :class:`sleekxmpp.xmlstream.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 list(self.xml) - - def set_payload(self, value): - """Add XML content to the stanza. - - :param 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 reply(self, clear=True): - """Prepare the stanza for sending a reply. - - Swaps the ``'from'`` and ``'to'`` attributes. - - If ``clear=True``, then also remove the stanza's - contents to make room for the reply content. - - For client streams, the ``'from'`` attribute is removed. - - :param bool clear: Indicates if the stanza's contents should be - removed. Defaults to ``True``. - """ - # 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'] - if clear: - self.clear() - return self - - def error(self): - """Set the stanza's type to ``'error'``.""" - self['type'] = 'error' - return self - - def unhandled(self): - """Called if 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, now=False): - """Queue the stanza to be sent on the XML stream. - - :param bool now: Indicates if the queue should be skipped and the - stanza sent immediately. Useful for stream - initialization. Defaults to ``False``. - """ - self.stream.send(self, now=now) - - 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, top_level_ns=False): - """Serialize the stanza's XML to a string. - - :param bool top_level_ns: Display the top-most namespace. - Defaults to ``False``. - """ - xmlns = self.stream.default_ns if self.stream else '' - return tostring(self.xml, xmlns=xmlns, - stream=self.stream, - top_level=(self.stream is None)) - - -#: A JSON/dictionary version of the XML content exposed through -#: the stanza interfaces:: -#: -#: >>> msg = Message() -#: >>> msg.values -#: {'body': '', 'from': , 'mucnick': '', 'mucroom': '', -#: 'to': , 'type': 'normal', 'id': '', 'subject': ''} -#: -#: Likewise, assigning to the :attr:`values` will change the XML -#: content:: -#: -#: >>> msg = Message() -#: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'} -#: >>> msg -#: '<message to="user@example.com"><body>Hi!</body></message>' -#: -#: Child stanzas are exposed as nested dictionaries. -ElementBase.values = property(ElementBase._get_stanza_values, - ElementBase._set_stanza_values) - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -ElementBase.initPlugin = ElementBase.init_plugin -ElementBase._getAttr = ElementBase._get_attr -ElementBase._setAttr = ElementBase._set_attr -ElementBase._delAttr = ElementBase._del_attr -ElementBase._getSubText = ElementBase._get_sub_text -ElementBase._setSubText = ElementBase._set_sub_text -ElementBase._delSub = ElementBase._del_sub -ElementBase.getStanzaValues = ElementBase._get_stanza_values -ElementBase.setStanzaValues = ElementBase._set_stanza_values - -StanzaBase.setType = StanzaBase.set_type -StanzaBase.getTo = StanzaBase.get_to -StanzaBase.setTo = StanzaBase.set_to -StanzaBase.getFrom = StanzaBase.get_from -StanzaBase.setFrom = StanzaBase.set_from -StanzaBase.getPayload = StanzaBase.get_payload -StanzaBase.setPayload = StanzaBase.set_payload -StanzaBase.delPayload = StanzaBase.del_payload diff --git a/sleekxmpp/xmlstream/tostring.py b/sleekxmpp/xmlstream/tostring.py deleted file mode 100644 index c49abd3e..00000000 --- a/sleekxmpp/xmlstream/tostring.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sleekxmpp.xmlstream.tostring - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module converts XML objects into Unicode strings and - intelligently includes namespaces only when necessary to - keep the output readable. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import unicode_literals - -import sys - -if sys.version_info < (3, 0): - import types - - -XML_NS = 'http://www.w3.org/XML/1998/namespace' - - -def tostring(xml=None, xmlns='', stream=None, outbuffer='', - top_level=False, open_only=False, namespaces=None): - """Serialize an XML object to a Unicode string. - - If an outer xmlns is provided using ``xmlns``, then the current element's - namespace will not be included if it matches the outer namespace. An - exception is made for elements that have an attached stream, and appear - at the stream root. - - :param XML xml: The XML object to serialize. - :param string xmlns: Optional namespace of an element wrapping the XML - object. - :param stream: The XML stream that generated the XML object. - :param string outbuffer: Optional buffer for storing serializations - during recursive calls. - :param bool top_level: Indicates that the element is the outermost - element. - :param set namespaces: Track which namespaces are in active use so - that new ones can be declared when needed. - - :type xml: :py:class:`~xml.etree.ElementTree.Element` - :type stream: :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` - - :rtype: Unicode string - """ - # 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 = '' - - default_ns = '' - stream_ns = '' - use_cdata = False - - if stream: - default_ns = stream.default_ns - stream_ns = stream.stream_ns - use_cdata = stream.use_cdata - - # Output the tag name and derived namespace of the element. - namespace = '' - if tag_xmlns: - if top_level and tag_xmlns not in [default_ns, xmlns, stream_ns] \ - or not top_level and tag_xmlns != xmlns: - 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. - new_namespaces = set() - for attrib, value in xml.attrib.items(): - value = escape(value, use_cdata) - if '}' not in attrib: - output.append(' %s="%s"' % (attrib, value)) - else: - attrib_ns = attrib.split('}')[0][1:] - attrib = attrib.split('}')[1] - if attrib_ns == XML_NS: - output.append(' xml:%s="%s"' % (attrib, value)) - elif stream and attrib_ns in stream.namespace_map: - mapped_ns = stream.namespace_map[attrib_ns] - if mapped_ns: - if namespaces is None: - namespaces = set() - if attrib_ns not in namespaces: - namespaces.add(attrib_ns) - new_namespaces.add(attrib_ns) - output.append(' xmlns:%s="%s"' % ( - mapped_ns, attrib_ns)) - output.append(' %s:%s="%s"' % ( - mapped_ns, attrib, value)) - - if open_only: - # Only output the opening tag, regardless of content. - output.append(">") - return ''.join(output) - - if len(xml) or xml.text: - # If there are additional child elements to serialize. - output.append(">") - if xml.text: - output.append(escape(xml.text, use_cdata)) - if len(xml): - for child in xml: - output.append(tostring(child, tag_xmlns, stream, - namespaces=namespaces)) - output.append("</%s>" % tag_name) - elif xml.text: - # If we only have text content. - output.append(">%s</%s>" % (escape(xml.text, use_cdata), tag_name)) - else: - # Empty element. - output.append(" />") - if xml.tail: - # If there is additional text after the element. - output.append(escape(xml.tail, use_cdata)) - for ns in new_namespaces: - # Remove namespaces introduced in this context. This is necessary - # because the namespaces object continues to be shared with other - # contexts. - namespaces.remove(ns) - return ''.join(output) - - -def escape(text, use_cdata=False): - """Convert special characters in XML to escape sequences. - - :param string text: The XML text to convert. - :rtype: Unicode string - """ - if sys.version_info < (3, 0): - if type(text) != types.UnicodeType: - text = unicode(text, 'utf-8', 'ignore') - - escapes = {'&': '&', - '<': '<', - '>': '>', - "'": ''', - '"': '"'} - - if not use_cdata: - text = list(text) - for i, c in enumerate(text): - text[i] = escapes.get(c, c) - return ''.join(text) - else: - escape_needed = False - for c in text: - if c in escapes: - escape_needed = True - break - if escape_needed: - escaped = map(lambda x : "<![CDATA[%s]]>" % x, text.split("]]>")) - return "<![CDATA[]]]><![CDATA[]>]]>".join(escaped) - return text diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py deleted file mode 100644 index 66985f3d..00000000 --- a/sleekxmpp/xmlstream/xmlstream.py +++ /dev/null @@ -1,1808 +0,0 @@ -""" - sleekxmpp.xmlstream.xmlstream - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module provides the module for creating and - interacting with generic XML streams, along with - the necessary eventing infrastructure. - - Part of SleekXMPP: The Sleek XMPP Library - - :copyright: (c) 2011 Nathanael C. Fritz - :license: MIT, see LICENSE for more details -""" - -from __future__ import with_statement, unicode_literals - -import base64 -import copy -import logging -import signal -import socket as Socket -import ssl -import sys -import threading -import time -import random -import weakref -import uuid -import errno - -from xml.parsers.expat import ExpatError - -import sleekxmpp -from sleekxmpp.util import Queue, QueueEmpty, safedict -from sleekxmpp.thirdparty.statemachine import StateMachine -from sleekxmpp.xmlstream import Scheduler, tostring, cert -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET, ElementBase -from sleekxmpp.xmlstream.handler import Waiter, XMLCallback -from sleekxmpp.xmlstream.matcher import MatchXMLMask -from sleekxmpp.xmlstream.resolver import resolve, default_resolver - -# 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 = 30 - -#: The time in seconds to wait for events from the event queue, and also the -#: time between checks for the process stop signal. -WAIT_TIMEOUT = 1.0 - -#: The number of threads to use to handle XML stream events. This is not the -#: same as the number of custom event handling threads. -#: :data:`HANDLER_THREADS` must be at least 1. For Python implementations -#: with a GIL, this should be left at 1, but for implemetnations without -#: a GIL increasing this value can provide better performance. -HANDLER_THREADS = 1 - -#: The time in seconds to delay between attempts to resend data -#: after an SSL error. -SSL_RETRY_DELAY = 0.5 - -#: The maximum number of times to attempt resending data due to -#: an SSL error. -SSL_RETRY_MAX = 10 - -#: Maximum time to delay between connection attempts is one hour. -RECONNECT_MAX_DELAY = 600 - -#: Maximum number of attempts to connect to the server before quitting -#: and raising a 'connect_failed' event. Setting this to ``None`` will -#: allow infinite reconnection attempts, and using ``0`` will disable -#: reconnections. Defaults to ``None``. -RECONNECT_MAX_ATTEMPTS = None - - -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. - - :param socket: Use an existing socket for the stream. Defaults to - ``None`` to generate a new socket. - :param string host: The name of the target server. - :param int port: The port to use for the connection. Defaults to 0. - """ - - def __init__(self, socket=None, host='', port=0): - #: Most XMPP servers support TLSv1, but OpenFire in particular - #: does not work well with it. For OpenFire, set - #: :attr:`ssl_version` to use ``SSLv23``:: - #: - #: import ssl - #: xmpp.ssl_version = ssl.PROTOCOL_SSLv23 - self.ssl_version = ssl.PROTOCOL_TLSv1 - - #: The list of accepted ciphers, in OpenSSL Format. - #: It might be useful to override it for improved security - #: over the python defaults. - self.ciphers = None - - #: Path to a file containing certificates for verifying the - #: server SSL certificate. A non-``None`` value will trigger - #: certificate checking. - #: - #: .. note:: - #: - #: On Mac OS X, certificates in the system keyring will - #: be consulted, even if they are not in the provided file. - self.ca_certs = None - - #: Path to a file containing a client certificate to use for - #: authenticating via SASL EXTERNAL. If set, there must also - #: be a corresponding `:attr:keyfile` value. - self.certfile = None - - #: Path to a file containing the private key for the selected - #: client certificate to use for authenticating via SASL EXTERNAL. - self.keyfile = None - - self._der_cert = None - - #: The time in seconds to wait for events from the event queue, - #: and also the time between checks for the process stop signal. - self.wait_timeout = WAIT_TIMEOUT - - #: The time in seconds to wait before timing out waiting - #: for response stanzas. - self.response_timeout = RESPONSE_TIMEOUT - - #: The current amount to time to delay attempting to reconnect. - #: This value doubles (with some jitter) with each failed - #: connection attempt up to :attr:`reconnect_max_delay` seconds. - self.reconnect_delay = None - - #: Maximum time to delay between connection attempts is one hour. - self.reconnect_max_delay = RECONNECT_MAX_DELAY - - #: Maximum number of attempts to connect to the server before - #: quitting and raising a 'connect_failed' event. Setting to - #: ``None`` allows infinite reattempts, while setting it to ``0`` - #: will disable reconnection attempts. Defaults to ``None``. - self.reconnect_max_attempts = RECONNECT_MAX_ATTEMPTS - - #: The time in seconds to delay between attempts to resend data - #: after an SSL error. - self.ssl_retry_max = SSL_RETRY_MAX - - #: The maximum number of times to attempt resending data due to - #: an SSL error. - self.ssl_retry_delay = SSL_RETRY_DELAY - - #: The connection state machine tracks if the stream is - #: ``'connected'`` or ``'disconnected'``. - self.state = StateMachine(('disconnected', 'connected')) - self.state._set_state('disconnected') - - #: The default port to return when querying DNS records. - self.default_port = int(port) - - #: The domain to try when querying DNS records. - self.default_domain = '' - - #: The expected name of the server, for validation. - self._expected_server_name = '' - self._service_name = '' - - #: The desired, or actual, address of the connected server. - self.address = (host, int(port)) - - #: A file-like wrapper for the socket for use with the - #: :mod:`~xml.etree.ElementTree` module. - self.filesocket = None - self.set_socket(socket) - - if sys.version_info < (3, 0): - self.socket_class = Socket26 - else: - self.socket_class = Socket.socket - - #: Enable connecting to the server directly over SSL, in - #: particular when the service provides two ports: one for - #: non-SSL traffic and another for SSL traffic. - self.use_ssl = False - - #: Enable connecting to the service without using SSL - #: immediately, but allow upgrading the connection later - #: to use SSL. - self.use_tls = False - - #: If set to ``True``, attempt to connect through an HTTP - #: proxy based on the settings in :attr:`proxy_config`. - self.use_proxy = False - - #: If set to ``True``, attempt to use IPv6. - self.use_ipv6 = True - - #: If set to ``True``, allow using the ``dnspython`` DNS library - #: if available. If set to ``False``, the builtin DNS resolver - #: will be used, even if ``dnspython`` is installed. - self.use_dnspython = True - - #: Use CDATA for escaping instead of XML entities. Defaults - #: to ``False``. - self.use_cdata = False - - #: An optional dictionary of proxy settings. It may provide: - #: :host: The host offering proxy services. - #: :port: The port for the proxy service. - #: :username: Optional username for accessing the proxy. - #: :password: Optional password for accessing the proxy. - self.proxy_config = {} - - #: The default namespace of the stream content, not of the - #: stream wrapper itself. - self.default_ns = '' - - self.default_lang = None - self.peer_default_lang = None - - #: The namespace of the enveloping stream element. - self.stream_ns = '' - - #: The default opening tag for the stream element. - self.stream_header = "<stream>" - - #: The default closing tag for the stream element. - self.stream_footer = "</stream>" - - #: If ``True``, periodically send a whitespace character over the - #: wire to keep the connection alive. Mainly useful for connections - #: traversing NAT. - self.whitespace_keepalive = True - - #: The default interval between keepalive signals when - #: :attr:`whitespace_keepalive` is enabled. - self.whitespace_keepalive_interval = 300 - - #: An :class:`~threading.Event` to signal that the application - #: is stopping, and that all threads should shutdown. - self.stop = threading.Event() - - #: An :class:`~threading.Event` to signal receiving a closing - #: stream tag from the server. - self.stream_end_event = threading.Event() - self.stream_end_event.set() - - #: An :class:`~threading.Event` to signal the start of a stream - #: session. Until this event fires, the send queue is not used - #: and data is sent immediately over the wire. - self.session_started_event = threading.Event() - - #: The default time in seconds to wait for a session to start - #: after connecting before reconnecting and trying again. - self.session_timeout = 45 - - #: Flag for controlling if the session can be considered ended - #: if the connection is terminated. - self.end_session_on_disconnect = True - - #: A queue of stream, custom, and scheduled events to be processed. - self.event_queue = Queue() - - #: A queue of string data to be sent over the stream. - self.send_queue = Queue() - self.send_queue_lock = threading.Lock() - self.send_lock = threading.RLock() - - #: A :class:`~sleekxmpp.xmlstream.scheduler.Scheduler` instance for - #: executing callbacks in the future based on time delays. - self.scheduler = Scheduler(self.stop) - self.__failed_send_stanza = None - - #: A mapping of XML namespaces to well-known prefixes. - self.namespace_map = {StanzaBase.xml_ns: 'xml'} - - self.__thread = {} - self.__root_stanza = [] - self.__handlers = [] - self.__event_handlers = {} - self.__event_handlers_lock = threading.Lock() - self.__filters = {'in': [], 'out': [], 'out_sync': []} - self.__thread_count = 0 - self.__thread_cond = threading.Condition() - self.__active_threads = set() - self._use_daemons = False - self._disconnect_wait_for_threads = True - - self._id = 0 - self._id_lock = threading.Lock() - - #: We use an ID prefix to ensure that all ID values are unique. - self._id_prefix = '%s-' % uuid.uuid4() - - #: The :attr:`auto_reconnnect` setting controls whether or not - #: the stream will be restarted in the event of an error. - self.auto_reconnect = True - - #: The :attr:`disconnect_wait` setting is the default value - #: for controlling if the system waits for the send queue to - #: empty before ending the stream. This may be overridden by - #: passing ``wait=True`` or ``wait=False`` to :meth:`disconnect`. - #: The default :attr:`disconnect_wait` value is ``False``. - self.disconnect_wait = False - - #: A list of DNS results that have not yet been tried. - self.dns_answers = [] - - #: The service name to check with DNS SRV records. For - #: example, setting this to ``'xmpp-client'`` would query the - #: ``_xmpp-client._tcp`` service. - self.dns_service = None - - self.add_event_handler('connected', self._session_timeout_check) - self.add_event_handler('disconnected', self._remove_schedules) - self.add_event_handler('session_start', self._start_keepalive) - self.add_event_handler('session_start', self._cert_expiration) - - def use_signals(self, signals=None): - """Register signal handlers for ``SIGHUP`` and ``SIGTERM``. - - By using signals, a ``'killed'`` event will be raised when the - application is terminated. - - If a signal handler already existed, it will be executed first, - before the ``'killed'`` event is raised. - - :param list signals: A list of signal names to be monitored. - Defaults to ``['SIGHUP', 'SIGTERM']``. - """ - if signals is None: - signals = ['SIGHUP', 'SIGTERM'] - - existing_handlers = {} - for sig_name in signals: - if hasattr(signal, sig_name): - sig = getattr(signal, sig_name) - handler = signal.getsignal(sig) - if handler: - existing_handlers[sig] = handler - - def handle_kill(signum, frame): - """ - Capture kill event and disconnect cleanly after first - spawning the ``'killed'`` event. - """ - - if signum in existing_handlers and \ - existing_handlers[signum] != handle_kill: - existing_handlers[signum](signum, frame) - - self.event("killed", direct=True) - self.disconnect() - - try: - for sig_name in signals: - if hasattr(signal, sig_name): - sig = getattr(signal, sig_name) - signal.signal(sig, handle_kill) - self.__signals_installed = True - except: - log.debug("Can not set interrupt signal handlers. " + \ - "SleekXMPP is not running from a main thread.") - - 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 "%s%X" % (self._id_prefix, 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 with an exponential backoff delay (max of - :attr:`reconnect_max_delay` which defaults to 10 minute) until a - successful connection is established. - - :param host: The name of the desired server for the connection. - :param port: Port to connect to on the server. - :param use_ssl: Flag indicating if SSL should be used by connecting - directly to a port using SSL. - :param use_tls: Flag indicating if TLS should be used, allowing for - connecting to a port without using SSL immediately and - later upgrading the connection. - :param reattempt: Flag indicating if the socket should reconnect - after disconnections. - """ - self.stop.clear() - - if host and port: - self.address = (host, int(port)) - try: - Socket.inet_aton(self.address[0]) - except (Socket.error, ssl.SSLError): - self.default_domain = self.address[0] - - # 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. - attempts = self.reconnect_max_attempts - connected = self.state.transition('disconnected', 'connected', - func=self._connect, - args=(reattempt,)) - while reattempt and not connected and not self.stop.is_set(): - connected = self.state.transition('disconnected', 'connected', - func=self._connect) - if not connected: - if attempts is not None: - attempts -= 1 - if attempts <= 0: - self.event('connection_failed', direct=True) - return False - return connected - - def _connect(self, reattempt=True): - self.scheduler.remove('Session timeout check') - - if self.reconnect_delay is None or not reattempt: - delay = 1.0 - else: - delay = min(self.reconnect_delay * 2, self.reconnect_max_delay) - delay = random.normalvariate(delay, delay * 0.1) - log.debug('Waiting %s seconds before connecting.', delay) - elapsed = 0 - try: - while elapsed < delay and not self.stop.is_set(): - time.sleep(0.1) - elapsed += 0.1 - except KeyboardInterrupt: - self.set_stop() - return False - except SystemExit: - self.set_stop() - return False - - if self.default_domain: - try: - host, address, port = self.pick_dns_answer(self.default_domain, - self.address[1]) - self.address = (address, port) - self._service_name = host - except StopIteration: - log.debug("No remaining DNS records to try.") - self.dns_answers = None - if reattempt: - self.reconnect_delay = delay - return False - - af = Socket.AF_INET - proto = 'IPv4' - if ':' in self.address[0]: - af = Socket.AF_INET6 - proto = 'IPv6' - try: - self.socket = self.socket_class(af, Socket.SOCK_STREAM) - except Socket.error: - log.debug("Could not connect using %s", proto) - return False - - self.configure_socket() - - if self.use_proxy: - connected = self._connect_proxy() - if not connected: - if reattempt: - self.reconnect_delay = delay - return False - - if self.use_ssl: - log.debug("Socket Wrapped for SSL") - if self.ca_certs is None: - cert_policy = ssl.CERT_NONE - else: - cert_policy = ssl.CERT_REQUIRED - - ssl_args = safedict({ - 'certfile': self.certfile, - 'keyfile': self.keyfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': cert_policy, - 'do_handshake_on_connect': False - }) - - if sys.version_info >= (2, 7): - ssl_args['ciphers'] = self.ciphers - - ssl_socket = ssl.wrap_socket(self.socket, **ssl_args) - - 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: - if not self.use_proxy: - domain = self.address[0] - if ':' in domain: - domain = '[%s]' % domain - log.debug("Connecting to %s:%s", domain, self.address[1]) - self.socket.connect(self.address) - - if self.use_ssl: - try: - self.socket.do_handshake() - except (Socket.error, ssl.SSLError): - log.error('CERT: Invalid certificate trust chain.') - if not self.event_handled('ssl_invalid_chain'): - self.disconnect(self.auto_reconnect, - send_close=False) - else: - self.event('ssl_invalid_chain', direct=True) - return False - - self._der_cert = self.socket.getpeercert(binary_form=True) - pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) - log.debug('CERT: %s', pem_cert) - - self.event('ssl_cert', pem_cert, direct=True) - try: - cert.verify(self._expected_server_name, self._der_cert) - except cert.CertificateError as err: - if not self.event_handled('ssl_invalid_cert'): - log.error(err) - self.disconnect(send_close=False) - else: - self.event('ssl_invalid_cert', - pem_cert, - direct=True) - - 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, ssl.SSLError) as serr: - error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" - self.event('socket_error', serr, direct=True) - domain = self.address[0] - if ':' in domain: - domain = '[%s]' % domain - log.error(error_msg, domain, self.address[1], - serr.errno, serr.strerror) - return False - - def _connect_proxy(self): - """Attempt to connect using an HTTP Proxy.""" - - # Extract the proxy address, and optional credentials - address = (self.proxy_config['host'], int(self.proxy_config['port'])) - cred = None - if self.proxy_config['username']: - username = self.proxy_config['username'] - password = self.proxy_config['password'] - - cred = '%s:%s' % (username, password) - if sys.version_info < (3, 0): - cred = bytes(cred) - else: - cred = bytes(cred, 'utf-8') - cred = base64.b64encode(cred).decode('utf-8') - - # Build the HTTP headers for connecting to the XMPP server - headers = ['CONNECT %s:%s HTTP/1.0' % self.address, - 'Host: %s:%s' % self.address, - 'Proxy-Connection: Keep-Alive', - 'Pragma: no-cache', - 'User-Agent: SleekXMPP/%s' % sleekxmpp.__version__] - if cred: - headers.append('Proxy-Authorization: Basic %s' % cred) - headers = '\r\n'.join(headers) + '\r\n\r\n' - - try: - log.debug("Connecting to proxy: %s:%s", *address) - self.socket.connect(address) - self.send_raw(headers, now=True) - resp = '' - while '\r\n\r\n' not in resp and not self.stop.is_set(): - resp += self.socket.recv(1024).decode('utf-8') - log.debug('RECV: %s', resp) - - lines = resp.split('\r\n') - if '200' not in lines[0]: - self.event('proxy_error', resp) - self.event('connection_failed', direct=True) - log.error('Proxy Error: %s', lines[0]) - return False - - # Proxy connection established, continue connecting - # with the XMPP server. - return True - except (Socket.error, ssl.SSLError) as serr: - error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" - self.event('socket_error', serr, direct=True) - log.error(error_msg, self.address[0], self.address[1], - serr.errno, serr.strerror) - return False - - def _session_timeout_check(self, event=None): - """ - Add check to ensure that a session is established within - a reasonable amount of time. - """ - - def _handle_session_timeout(): - if not self.session_started_event.is_set(): - log.debug("Session start has taken more " + \ - "than %d seconds", self.session_timeout) - self.disconnect(reconnect=self.auto_reconnect) - - self.schedule("Session timeout check", - self.session_timeout, - _handle_session_timeout) - - def disconnect(self, reconnect=False, wait=None, send_close=True): - """Terminate processing and close the XML streams. - - Optionally, the connection may be reconnected and - resume processing afterwards. - - If the disconnect should take place after all items - in the send queue have been sent, use ``wait=True``. - - .. warning:: - - If you are constantly adding items to the queue - such that it is never empty, then the disconnect will - not occur and the call will continue to block. - - :param reconnect: Flag indicating if the connection - and processing should be restarted. - Defaults to ``False``. - :param wait: Flag indicating if the send queue should - be emptied before disconnecting, overriding - :attr:`disconnect_wait`. - :param send_close: Flag indicating if the stream footer - should be sent before terminating the - connection. Setting this to ``False`` - prevents error loops when trying to - disconnect after a socket error. - """ - self.state.transition('connected', 'disconnected', - wait=2.0, - func=self._disconnect, - args=(reconnect, wait, send_close)) - - def _disconnect(self, reconnect=False, wait=None, send_close=True): - if not reconnect: - self.auto_reconnect = False - - if self.end_session_on_disconnect or send_close: - self.event('session_end', direct=True) - - # Wait for the send queue to empty. - if wait is not None: - if wait: - self.send_queue.join() - elif self.disconnect_wait: - self.send_queue.join() - - # Clearing this event will pause the send loop. - self.session_started_event.clear() - - self.__failed_send_stanza = None - - # Send the end of stream marker. - if send_close: - self.send_raw(self.stream_footer, now=True) - - # Wait for confirmation that the stream was - # closed in the other direction. If we didn't - # send a stream footer we don't need to wait - # since the server won't know to respond. - if send_close: - log.info('Waiting for %s from server', self.stream_footer) - self.stream_end_event.wait(4) - else: - self.stream_end_event.set() - - if not self.auto_reconnect: - self.set_stop() - if self._disconnect_wait_for_threads: - self._wait_for_threads() - - try: - self.socket.shutdown(Socket.SHUT_RDWR) - self.socket.close() - self.filesocket.close() - except (Socket.error, ssl.SSLError) as serr: - self.event('socket_error', serr, direct=True) - finally: - #clear your application state - self.event('disconnected', direct=True) - return True - - def abort(self): - self.session_started_event.clear() - self.set_stop() - if self._disconnect_wait_for_threads: - self._wait_for_threads() - try: - self.socket.shutdown(Socket.SHUT_RDWR) - self.socket.close() - self.filesocket.close() - except Socket.error: - pass - self.state.transition_any(['connected', 'disconnected'], 'disconnected', func=lambda: True) - self.event("killed", direct=True) - - def reconnect(self, reattempt=True, wait=False, send_close=True): - """Reset the stream's state and reconnect to the server.""" - log.debug("reconnecting...") - if self.state.ensure('connected'): - self.state.transition('connected', 'disconnected', - wait=2.0, - func=self._disconnect, - args=(True, wait, send_close)) - - attempts = self.reconnect_max_attempts - - log.debug("connecting...") - connected = self.state.transition('disconnected', 'connected', - wait=2.0, - func=self._connect, - args=(reattempt,)) - while reattempt and not connected and not self.stop.is_set(): - connected = self.state.transition('disconnected', 'connected', - wait=2.0, func=self._connect) - connected = connected or self.state.ensure('connected') - if not connected: - if attempts is not None: - attempts -= 1 - if attempts <= 0: - self.event('connection_failed', direct=True) - return False - return connected - - def set_socket(self, socket, ignore=False): - """Set the socket to use for the stream. - - The filesocket will be recreated as well. - - :param socket: The new socket object to use. - :param bool ignore: If ``True``, don't set the connection - state to ``'connected'``. - """ - 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 configure_socket(self): - """Set timeout and other options for self.socket. - - Meant to be overridden. - """ - self.socket.settimeout(None) - - def configure_dns(self, resolver, domain=None, port=None): - """ - Configure and set options for a :class:`~dns.resolver.Resolver` - instance, and other DNS related tasks. For example, you - can also check :meth:`~socket.socket.getaddrinfo` to see - if you need to call out to ``libresolv.so.2`` to - run ``res_init()``. - - Meant to be overridden. - - :param resolver: A :class:`~dns.resolver.Resolver` instance - or ``None`` if ``dnspython`` is not installed. - :param domain: The initial domain under consideration. - :param port: The initial port under consideration. - """ - pass - - def start_tls(self): - """Perform handshakes for TLS. - - If the handshake is successful, the XML stream will need - to be restarted. - """ - log.info("Negotiating TLS") - ssl_versions = {3: 'TLS 1.0', 1: 'SSL 3', 2: 'SSL 2/3'} - log.info("Using SSL version: %s", ssl_versions[self.ssl_version]) - if self.ca_certs is None: - cert_policy = ssl.CERT_NONE - else: - cert_policy = ssl.CERT_REQUIRED - - ssl_args = safedict({ - 'certfile': self.certfile, - 'keyfile': self.keyfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': cert_policy, - 'do_handshake_on_connect': False - }) - - if sys.version_info >= (2, 7): - ssl_args['ciphers'] = self.ciphers - - ssl_socket = ssl.wrap_socket(self.socket, **ssl_args); - - 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: - self.socket.do_handshake() - except (Socket.error, ssl.SSLError): - log.error('CERT: Invalid certificate trust chain.') - if not self.event_handled('ssl_invalid_chain'): - self.disconnect(self.auto_reconnect, send_close=False) - else: - self._der_cert = self.socket.getpeercert(binary_form=True) - self.event('ssl_invalid_chain', direct=True) - return False - - self._der_cert = self.socket.getpeercert(binary_form=True) - pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) - log.debug('CERT: %s', pem_cert) - self.event('ssl_cert', pem_cert, direct=True) - - try: - cert.verify(self._expected_server_name, self._der_cert) - except cert.CertificateError as err: - if not self.event_handled('ssl_invalid_cert'): - log.error(err) - self.disconnect(self.auto_reconnect, send_close=False) - else: - self.event('ssl_invalid_cert', pem_cert, direct=True) - - self.set_socket(self.socket) - return True - - def _cert_expiration(self, event): - """Schedule an event for when the TLS certificate expires.""" - - if not self.use_tls and not self.use_ssl: - return - - if not self._der_cert: - log.warn("TLS or SSL was enabled, but no certificate was found.") - return - - def restart(): - if not self.event_handled('ssl_expired_cert'): - log.warn("The server certificate has expired. Restarting.") - self.reconnect() - else: - pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) - self.event('ssl_expired_cert', pem_cert) - - cert_ttl = cert.get_ttl(self._der_cert) - if cert_ttl is None: - return - - if cert_ttl.days < 0: - log.warn('CERT: Certificate has expired.') - restart() - - try: - total_seconds = cert_ttl.total_seconds() - except AttributeError: - # for Python < 2.7 - total_seconds = (cert_ttl.microseconds + (cert_ttl.seconds + cert_ttl.days * 24 * 3600) * 10**6) / 10**6 - - log.info('CERT: Time until certificate expiration: %s' % cert_ttl) - self.schedule('Certificate Expiration', - total_seconds, - restart) - - def _start_keepalive(self, event): - """Begin sending whitespace periodically to keep the connection alive. - - May be disabled by setting:: - - self.whitespace_keepalive = False - - The keepalive interval can be set using:: - - self.whitespace_keepalive_interval = 300 - """ - self.schedule('Whitespace Keepalive', - self.whitespace_keepalive_interval, - self.send_raw, - args=(' ',), - kwargs={'now': True}, - repeat=True) - - def _remove_schedules(self, event): - """Remove whitespace keepalive and certificate expiration schedules.""" - self.scheduler.remove('Whitespace Keepalive') - self.scheduler.remove('Certificate Expiration') - - 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. - - :param 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. - """ - self.__root_stanza.remove(stanza_class) - - def add_filter(self, mode, handler, order=None): - """Add a filter for incoming or outgoing stanzas. - - These filters are applied before incoming stanzas are - passed to any handlers, and before outgoing stanzas - are put in the send queue. - - Each filter must accept a single stanza, and return - either a stanza or ``None``. If the filter returns - ``None``, then the stanza will be dropped from being - processed for events or from being sent. - - :param mode: One of ``'in'`` or ``'out'``. - :param handler: The filter function. - :param int order: The position to insert the filter in - the list of active filters. - """ - if order: - self.__filters[mode].insert(order, handler) - else: - self.__filters[mode].append(handler) - - def del_filter(self, mode, handler): - """Remove an incoming or outgoing filter.""" - self.__filters[mode].remove(handler) - - 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. - - The use of :meth:`register_handler()` is preferred. - - :param mask: An XML snippet matching the structure of the - stanzas that will be passed to this handler. - :param pointer: The handler function itself. - :parm name: A unique name for the handler. A name will - be generated if one is not provided. - :param disposable: Indicates if the handler should be discarded - after one use. - :param threaded: **DEPRECATED**. - Remains for backwards compatibility. - :param filter: **DEPRECATED**. - Remains for backwards compatibility. - :param 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. - - if name is None: - name = 'add_handler_%s' % self.new_id() - self.register_handler( - XMLCallback(name, - MatchXMLMask(mask, self.default_ns), - 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. - - :param handler: - The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler` - derived object to execute. - """ - if handler.stream is None: - self.__handlers.append(handler) - handler.stream = weakref.ref(self) - - def remove_handler(self, name): - """Remove any stream event handlers with the given name. - - :param 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 get_dns_records(self, domain, port=None): - """Get the DNS records for a domain. - - :param domain: The domain in question. - :param port: If the results don't include a port, use this one. - """ - if port is None: - port = self.default_port - - resolver = default_resolver() - self.configure_dns(resolver, domain=domain, port=port) - - return resolve(domain, port, service=self.dns_service, - resolver=resolver, - use_ipv6=self.use_ipv6, - use_dnspython=self.use_dnspython) - - def pick_dns_answer(self, domain, port=None): - """Pick a server and port from DNS answers. - - Gets DNS answers if none available. - Removes used answer from available answers. - - :param domain: The domain in question. - :param port: If the results don't include a port, use this one. - """ - if not self.dns_answers: - self.dns_answers = self.get_dns_records(domain, port) - - if sys.version_info < (3, 0): - return self.dns_answers.next() - else: - return next(self.dns_answers) - - 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. - - :param name: The name of the event that will trigger - this handler. - :param pointer: The function to execute. - :param threaded: If set to ``True``, the handler will execute - in its own thread. Defaults to ``False``. - :param 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. - - :param name: The name of the event. - :param 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] = list(filter( - filter_pointers, - self.__event_handlers[name])) - - def event_handled(self, name): - """Returns the number of registered handlers for an event. - - :param 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. - - :param name: The name of the event to trigger. - :param data: Data that will be passed to each event handler. - Defaults to an empty dictionary, but is usually - a stanza object. - :param direct: Runs the event directly if True, skipping the - event queue. All event handlers will run in the - same thread. - """ - log.debug("Event triggered: " + name) - - handlers = self.__event_handlers.get(name, []) - for handler in handlers: - #TODO: Data should not be copied, but should be read only, - # but this might break current code so it's left for future. - - out_data = copy.copy(data) if len(handlers) > 1 else data - old_exception = getattr(data, 'exception', None) - if direct: - try: - handler[0](out_data) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg, str(handler[0])) - if old_exception: - old_exception(e) - else: - self.exception(e) - else: - self.event_queue.put(('event', handler, out_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. - - :param name: A unique name for the scheduled callback. - :param seconds: The time in seconds to wait before executing. - :param callback: A pointer to the function to execute. - :param args: A tuple of arguments to pass to the function. - :param kwargs: A dictionary of keyword arguments to pass to - the function. - :param 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=None, now=False, use_filters=True): - """A wrapper for :meth:`send_raw()` for sending stanza objects. - - May optionally block until an expected response is received. - - :param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to send on the stream. - :param mask: **DEPRECATED** - An XML string snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - :param int timeout: Time in seconds to wait for a response before - continuing. Defaults to :attr:`response_timeout`. - :param bool now: Indicates if the send queue should be skipped, - sending the stanza immediately. Useful mainly - for stream initialization stanzas. - Defaults to ``False``. - :param bool use_filters: Indicates if outgoing filters should be - applied to the given stanza data. Disabling - filters is useful when resending stanzas. - Defaults to ``True``. - """ - if timeout is None: - timeout = self.response_timeout - if hasattr(mask, 'xml'): - mask = mask.xml - - if isinstance(data, ElementBase): - if use_filters: - for filter in self.__filters['out']: - data = filter(data) - if data is None: - return - - 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) - - if isinstance(data, ElementBase): - with self.send_queue_lock: - if use_filters: - for filter in self.__filters['out_sync']: - data = filter(data) - if data is None: - return - str_data = tostring(data.xml, xmlns=self.default_ns, - stream=self, - top_level=True) - self.send_raw(str_data, now) - else: - self.send_raw(data, now) - if mask is not None: - return wait_for.wait(timeout) - - def send_xml(self, data, mask=None, timeout=None, now=False): - """Send an XML object on the stream, and optionally wait - for a response. - - :param data: The :class:`~xml.etree.ElementTree.Element` XML object - to send on the stream. - :param mask: **DEPRECATED** - An XML string snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - :param int timeout: Time in seconds to wait for a response before - continuing. Defaults to :attr:`response_timeout`. - :param bool now: Indicates if the send queue should be skipped, - sending the stanza immediately. Useful mainly - for stream initialization stanzas. - Defaults to ``False``. - """ - if timeout is None: - timeout = self.response_timeout - return self.send(tostring(data), mask, timeout, now) - - def send_raw(self, data, now=False, reconnect=None): - """Send raw data across the stream. - - :param string data: Any string value. - :param bool reconnect: Indicates if the stream should be - restarted if there is an error sending - the stanza. Used mainly for testing. - Defaults to :attr:`auto_reconnect`. - """ - if now: - log.debug("SEND (IMMED): %s", data) - try: - data = data.encode('utf-8') - total = len(data) - sent = 0 - count = 0 - tries = 0 - with self.send_lock: - while sent < total and not self.stop.is_set(): - try: - sent += self.socket.send(data[sent:]) - count += 1 - except Socket.error as serr: - if serr.errno != errno.EINTR: - raise - except ssl.SSLError as serr: - if tries >= self.ssl_retry_max: - log.debug('SSL error: max retries reached') - self.exception(serr) - log.warning("Failed to send %s", data) - if reconnect is None: - reconnect = self.auto_reconnect - if not self.stop.is_set(): - self.disconnect(reconnect, - send_close=False) - log.warning('SSL write error: retrying') - if not self.stop.is_set(): - time.sleep(self.ssl_retry_delay) - tries += 1 - if count > 1: - log.debug('SENT: %d chunks', count) - except (Socket.error, ssl.SSLError) as serr: - self.event('socket_error', serr, direct=True) - log.warning("Failed to send %s", data) - if reconnect is None: - reconnect = self.auto_reconnect - if not self.stop.is_set(): - self.disconnect(reconnect, send_close=False) - else: - self.send_queue.put(data) - return True - - def _start_thread(self, name, target, track=True): - self.__thread[name] = threading.Thread(name=name, target=target) - self.__thread[name].daemon = self._use_daemons - self.__thread[name].start() - - if track: - self.__active_threads.add(name) - with self.__thread_cond: - self.__thread_count += 1 - - def _end_thread(self, name, early=False): - with self.__thread_cond: - curr_thread = threading.current_thread().name - if curr_thread in self.__active_threads: - self.__thread_count -= 1 - self.__active_threads.remove(curr_thread) - - if early: - log.debug('Threading deadlock prevention!') - log.debug(("Marked %s thread as ended due to " + \ - "disconnect() call. %s threads remain.") % ( - name, self.__thread_count)) - else: - log.debug("Stopped %s thread. %s threads remain." % ( - name, self.__thread_count)) - - else: - log.debug(("Finished exiting %s thread after early " + \ - "termination from disconnect() call. " + \ - "%s threads remain.") % ( - name, self.__thread_count)) - - if self.__thread_count == 0: - self.__thread_cond.notify() - - def set_stop(self): - self.stop.set() - - # Unlock queues - self.event_queue.put(None) - self.send_queue.put(None) - - def _wait_for_threads(self): - with self.__thread_cond: - if self.__thread_count != 0: - log.debug("Waiting for %s threads to exit." % - self.__thread_count) - name = threading.current_thread().name - if name in self.__thread: - self._end_thread(name, early=True) - self.__thread_cond.wait(4) - if self.__thread_count != 0: - log.error("Hanged threads: %s" % threading.enumerate()) - log.error("This may be due to calling disconnect() " + \ - "from a non-threaded event handler. Be " + \ - "sure that event handlers that call " + \ - "disconnect() are registered using: " + \ - "add_event_handler(..., threaded=True)") - - def process(self, **kwargs): - """Initialize the XML streams and begin processing events. - - The number of threads used for processing stream events is determined - by :data:`HANDLER_THREADS`. - - :param bool block: If ``False``, then event dispatcher will run - in a separate thread, allowing for the stream to be - used in the background for another application. - Otherwise, ``process(block=True)`` blocks the current - thread. Defaults to ``False``. - :param bool threaded: **DEPRECATED** - If ``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``. This does **not** mean that no - threads are used at all if ``threaded=False``. - - Regardless of these threading options, these threads will - always exist: - - - The event queue processor - - The send queue processor - - The scheduler - """ - if 'threaded' in kwargs and 'block' in kwargs: - raise ValueError("process() called with both " + \ - "block and threaded arguments") - elif 'block' in kwargs: - threaded = not(kwargs.get('block', False)) - else: - threaded = kwargs.get('threaded', True) - - for t in range(0, HANDLER_THREADS): - log.debug("Starting HANDLER THREAD") - self._start_thread('event_thread_%s' % t, self._event_runner) - - self._start_thread('send_thread', self._send_thread) - self._start_thread('scheduler_thread', self._scheduler_thread) - - if threaded: - # Run the XML stream in the background for another application. - self._start_thread('read_thread', self._process, track=False) - else: - self._process() - - def _process(self): - """Start processing the XML streams. - - Processing will continue after any recoverable errors - if reconnections are allowed. - """ - - # 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 True: - shutdown = False - try: - # 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.is_set(): - # Only process the stream while connected to the server - if not self.state.ensure('connected', wait=0.1): - break - # Ensure the stream header is sent for any - # new connections. - if not self.session_started_event.is_set(): - self.send_raw(self.stream_header, now=True) - if not self.__read_xml(): - # If the server terminated the stream, end processing - break - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _process") - self.event('killed', direct=True) - shutdown = True - except SystemExit: - log.debug("SystemExit in _process") - shutdown = True - except (SyntaxError, ExpatError) as e: - log.error("Error reading from XML stream.") - self.exception(e) - except (Socket.error, ssl.SSLError) as serr: - self.event('socket_error', serr, direct=True) - log.error('Socket Error #%s: %s', serr.errno, serr.strerror) - except ValueError as e: - msg = e.message if hasattr(e, 'message') else e.args[0] - - if 'I/O operation on closed file' in msg: - log.error('Can not read from closed socket.') - else: - self.exception(e) - except Exception as e: - if not self.stop.is_set(): - log.error('Connection error.') - self.exception(e) - - if not shutdown and not self.stop.is_set() \ - and self.auto_reconnect: - self.reconnect() - else: - self.disconnect() - break - - def __read_xml(self): - """Parse the incoming XML stream - - Stream events are raised 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 - log.debug('RECV: %s', tostring(root, xmlns=self.default_ns, - stream=self, - top_level=True, - open_only=True)) - # Perform any stream initialization actions, such - # as handshakes. - self.stream_end_event.clear() - self.start_stream_handler(root) - - # We have a successful stream connection, so reset - # exponential backoff for new reconnect attempts. - self.reconnect_delay = 1.0 - 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 is not None: - # 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 :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase` - stanza will be returned. - - :param xml: The :class:`~xml.etree.ElementTree.Element` XML object - to convert into a stanza object. - :param 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) or \ - xml.tag == stanza_class.tag_name(): - stanza_type = stanza_class - break - stanza = stanza_type(self, xml) - if stanza['lang'] is None and self.peer_default_lang: - stanza['lang'] = self.peer_default_lang - 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. - - :param xml: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` - stanza to analyze. - """ - # 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 = self._build_stanza(xml) - - for filter in self.__filters['in']: - if stanza is not None: - stanza = filter(stanza) - if stanza is None: - return - - log.debug("RECV: %s", stanza) - - # Match the stanza against registered handlers. Handlers marked - # to run "in stream" will be executed immediately; the rest will - # be queued. - unhandled = True - matched_handlers = [h for h in self.__handlers if h.match(stanza)] - for handler in matched_handlers: - if len(matched_handlers) > 1: - stanza_copy = copy.copy(stanza) - else: - stanza_copy = stanza - handler.prerun(stanza_copy) - self.event_queue.put(('stanza', handler, stanza_copy)) - try: - if handler.check_delete(): - self.__handlers.remove(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. - - :param func: The event handler to execute. - :param args: Arguments to the event handler. - """ - # this is always already copied before this is invoked - orig = args[0] - try: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg, str(func)) - if hasattr(orig, 'exception'): - orig.exception(e) - else: - self.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.is_set(): - event = self.event_queue.get() - if event is None: - continue - - etype, handler = event[0:2] - args = event[2:] - orig = copy.copy(args[0]) - - 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) - orig.exception(e) - elif etype == 'schedule': - name = args[2] - try: - log.debug('Scheduled event: %s: %s', name, args[0]) - handler(*args[0], **args[1]) - except Exception as e: - log.exception('Error processing scheduled task') - self.exception(e) - 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.daemon = self._use_daemons - x.start() - else: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg, str(func)) - if hasattr(orig, 'exception'): - orig.exception(e) - else: - self.exception(e) - elif etype == 'quit': - log.debug("Quitting event runner thread") - break - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _event_runner") - self.event('killed', direct=True) - self.disconnect() - except SystemExit: - self.disconnect() - self.event_queue.put(('quit', None, None)) - - self._end_thread('event runner') - - def _send_thread(self): - """Extract stanzas from the send queue and send them on the stream.""" - try: - while not self.stop.is_set(): - while not self.stop.is_set() and \ - not self.session_started_event.is_set(): - self.session_started_event.wait(timeout=0.1) # Wait for session start - if self.__failed_send_stanza is not None: - data = self.__failed_send_stanza - self.__failed_send_stanza = None - else: - data = self.send_queue.get() # Wait for data to send - if data is None: - continue - log.debug("SEND: %s", data) - enc_data = data.encode('utf-8') - total = len(enc_data) - sent = 0 - count = 0 - tries = 0 - try: - with self.send_lock: - while sent < total and not self.stop.is_set() and \ - self.session_started_event.is_set(): - try: - sent += self.socket.send(enc_data[sent:]) - count += 1 - except Socket.error as serr: - if serr.errno != errno.EINTR: - raise - except ssl.SSLError as serr: - if tries >= self.ssl_retry_max: - log.debug('SSL error: max retries reached') - self.exception(serr) - log.warning("Failed to send %s", data) - if not self.stop.is_set(): - self.disconnect(self.auto_reconnect, - send_close=False) - log.warning('SSL write error: retrying') - if not self.stop.is_set(): - time.sleep(self.ssl_retry_delay) - tries += 1 - if count > 1: - log.debug('SENT: %d chunks', count) - self.send_queue.task_done() - except (Socket.error, ssl.SSLError) as serr: - self.event('socket_error', serr, direct=True) - log.warning("Failed to send %s", data) - if not self.stop.is_set(): - self.__failed_send_stanza = data - self._end_thread('send') - self.disconnect(self.auto_reconnect, send_close=False) - return - except Exception as ex: - log.exception('Unexpected error in send thread: %s', ex) - self.exception(ex) - if not self.stop.is_set(): - self._end_thread('send') - self.disconnect(self.auto_reconnect) - return - - self._end_thread('send') - - def _scheduler_thread(self): - self.scheduler.process(threaded=False) - self._end_thread('scheduler') - - def exception(self, exception): - """Process an unknown exception. - - Meant to be overridden. - - :param exception: An unhandled exception object. - """ - pass - - -# To comply with PEP8, method names now use underscores. -# Deprecated method names are re-mapped for backwards compatibility. -XMLStream.startTLS = XMLStream.start_tls -XMLStream.registerStanza = XMLStream.register_stanza -XMLStream.removeStanza = XMLStream.remove_stanza -XMLStream.registerHandler = XMLStream.register_handler -XMLStream.removeHandler = XMLStream.remove_handler -XMLStream.setSocket = XMLStream.set_socket -XMLStream.sendRaw = XMLStream.send_raw -XMLStream.getId = XMLStream.get_id -XMLStream.getNewId = XMLStream.new_id -XMLStream.sendXML = XMLStream.send_xml |