Merge branch 'master' into develop

Conflicts:
	sleekxmpp/basexmpp.py
This commit is contained in:
Lance Stout 2012-06-19 21:50:33 -07:00
commit 5820d49cd4
78 changed files with 1652 additions and 643 deletions

View File

@ -61,6 +61,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0027', 'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030', 'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza', 'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0033',
'sleekxmpp/plugins/xep_0047', 'sleekxmpp/plugins/xep_0047',
'sleekxmpp/plugins/xep_0050', 'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0054', 'sleekxmpp/plugins/xep_0054',
@ -72,6 +73,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0077', 'sleekxmpp/plugins/xep_0077',
'sleekxmpp/plugins/xep_0078', 'sleekxmpp/plugins/xep_0078',
'sleekxmpp/plugins/xep_0080', 'sleekxmpp/plugins/xep_0080',
'sleekxmpp/plugins/xep_0084',
'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0092',
@ -90,6 +92,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0224', 'sleekxmpp/plugins/xep_0224',
'sleekxmpp/plugins/xep_0231', 'sleekxmpp/plugins/xep_0231',
'sleekxmpp/plugins/xep_0249', 'sleekxmpp/plugins/xep_0249',
'sleekxmpp/plugins/xep_0258',
'sleekxmpp/features', 'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms', 'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza', 'sleekxmpp/features/feature_mechanisms/stanza',

View File

@ -16,24 +16,24 @@ class APIWrapper(object):
elif attr == 'settings': elif attr == 'settings':
return self.api.settings[self.name] return self.api.settings[self.name]
elif attr == 'register': elif attr == 'register':
def curried_handler(handler, op, jid=None, node=None, default=False): def partial(handler, op, jid=None, node=None, default=False):
register = getattr(self.api, attr) register = getattr(self.api, attr)
return register(handler, self.name, op, jid, node, default) return register(handler, self.name, op, jid, node, default)
return curried_handler return partial
elif attr == 'register_default': elif attr == 'register_default':
def curried_handler(handler, op, jid=None, node=None): def partial(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op) return getattr(self.api, attr)(handler, self.name, op)
return curried_handler return partial
elif attr in ('run', 'restore_default', 'unregister'): elif attr in ('run', 'restore_default', 'unregister'):
def curried_handler(*args, **kwargs): def partial(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs) return getattr(self.api, attr)(self.name, *args, **kwargs)
return curried_handler return partial
return None return None
def __getitem__(self, attr): def __getitem__(self, attr):
def curried_handler(jid=None, node=None, ifrom=None, args=None): def partial(jid=None, node=None, ifrom=None, args=None):
return self.api.run(self.name, attr, jid, node, ifrom, args) return self.api.run(self.name, attr, jid, node, ifrom, args)
return curried_handler return partial
class APIRegistry(object): class APIRegistry(object):
@ -42,7 +42,7 @@ class APIRegistry(object):
self._handlers = {} self._handlers = {}
self._handler_defaults = {} self._handler_defaults = {}
self.xmpp = xmpp self.xmpp = xmpp
self.settings = {} self.settings = {}
def _setup(self, ctype, op): def _setup(self, ctype, op):
"""Initialize the API callback dictionaries. """Initialize the API callback dictionaries.
@ -138,8 +138,8 @@ class APIRegistry(object):
"""Register an API callback, with JID+node specificity. """Register an API callback, with JID+node specificity.
The API callback can later be executed based on the The API callback can later be executed based on the
specificity of the provided JID+node combination. specificity of the provided JID+node combination.
See :meth:`~ApiRegistry.run` for more details. See :meth:`~ApiRegistry.run` for more details.
:param string ctype: The name of the API to use. :param string ctype: The name of the API to use.

View File

@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import * from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@ -66,7 +67,7 @@ class BaseXMPP(XMLStream):
#: An identifier for the stream as given by the server. #: An identifier for the stream as given by the server.
self.stream_id = None self.stream_id = None
#: The JabberID (JID) used by this connection. #: The JabberID (JID) used by this connection.
self.boundjid = JID(jid) self.boundjid = JID(jid)
self._expected_server_name = self.boundjid.host self._expected_server_name = self.boundjid.host
@ -102,7 +103,7 @@ class BaseXMPP(XMLStream):
#: The API registry is a way to process callbacks based on #: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is #: JID+node combinations. Each callback in the registry is
#: marked with: #: marked with:
#: #:
#: - An API name, e.g. xep_0030 #: - An API name, e.g. xep_0030
#: - The name of an action, e.g. get_info #: - The name of an action, e.g. get_info
#: - The JID that will be affected #: - The JID that will be affected
@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element. :param xml: The incoming stream's root element.
""" """
self.stream_id = xml.get('id', '') self.stream_id = xml.get('id', '')
self.stream_version = xml.get('version', '')
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
def process(self, *args, **kwargs): def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream. """Initialize plugins and begin processing the XML stream.
@ -199,7 +202,7 @@ class BaseXMPP(XMLStream):
Defaults to ``True``. This does **not** mean that no Defaults to ``True``. This does **not** mean that no
threads are used at all if ``threaded=False``. threads are used at all if ``threaded=False``.
Regardless of these threading options, these threads will Regardless of these threading options, these threads will
always exist: always exist:
- The event queue processor - The event queue processor
@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs): def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream.""" """Create a Message stanza associated with this stream."""
return Message(self, *args, **kwargs) msg = Message(self, *args, **kwargs)
msg['lang'] = self.default_lang
return msg
def Iq(self, *args, **kwargs): def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream.""" """Create an Iq stanza associated with this stream."""
@ -280,18 +285,20 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs): def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream.""" """Create a Presence stanza associated with this stream."""
return Presence(self, *args, **kwargs) 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): 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. """Create a new Iq stanza with a given Id and from JID.
:param id: An ideally unique ID value for this stanza thread. :param id: An ideally unique ID value for this stanza thread.
Defaults to 0. Defaults to 0.
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID` :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
to use for this stanza. to use for this stanza.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza. for this stanza.
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type, :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
one of: ``'get'``, ``'set'``, ``'result'``, one of: ``'get'``, ``'set'``, ``'result'``,
or ``'error'``. or ``'error'``.
:param iquery: Optional namespace for adding a query element. :param iquery: Optional namespace for adding a query element.
@ -329,7 +336,7 @@ class BaseXMPP(XMLStream):
def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
""" """
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
``'result'`` with the given ID value. ``'result'`` with the given ID value.
:param id: An ideally unique ID value. May use :meth:`new_id()`. :param id: An ideally unique ID value. May use :meth:`new_id()`.
@ -359,10 +366,10 @@ class BaseXMPP(XMLStream):
Optionally, a substanza may be given to use as the Optionally, a substanza may be given to use as the
stanza's payload. stanza's payload.
:param sub: Either an :param sub: Either an
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza object or an stanza object or an
:class:`~xml.etree.ElementTree.Element` XML object :class:`~xml.etree.ElementTree.Element` XML object
to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload. to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza. for this stanza.
@ -389,9 +396,9 @@ class BaseXMPP(XMLStream):
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
:param id: An ideally unique ID value. May use :meth:`new_id()`. :param id: An ideally unique ID value. May use :meth:`new_id()`.
:param type: The type of the error, such as ``'cancel'`` or :param type: The type of the error, such as ``'cancel'`` or
``'modify'``. Defaults to ``'cancel'``. ``'modify'``. Defaults to ``'cancel'``.
:param condition: The error condition. Defaults to :param condition: The error condition. Defaults to
``'feature-not-implemented'``. ``'feature-not-implemented'``.
:param text: A message describing the cause of the error. :param text: A message describing the cause of the error.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
@ -415,7 +422,7 @@ class BaseXMPP(XMLStream):
def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
""" """
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
to use the given query namespace. to use the given query namespace.
:param iq: Optionally use an existing stanza instead :param iq: Optionally use an existing stanza instead
@ -448,7 +455,7 @@ class BaseXMPP(XMLStream):
def make_message(self, mto, mbody=None, msubject=None, mtype=None, def make_message(self, mto, mbody=None, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None): mhtml=None, mfrom=None, mnick=None):
""" """
Create and initialize a new Create and initialize a new
:class:`~sleekxmpp.stanza.message.Message` stanza. :class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message. :param mto: The recipient of the message.
@ -474,7 +481,7 @@ class BaseXMPP(XMLStream):
def make_presence(self, pshow=None, pstatus=None, ppriority=None, def make_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, ptype=None, pfrom=None, pnick=None): pto=None, ptype=None, pfrom=None, pnick=None):
""" """
Create and initialize a new Create and initialize a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza. :class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value. :param pshow: The presence's show value.
@ -498,7 +505,7 @@ class BaseXMPP(XMLStream):
def send_message(self, mto, mbody, msubject=None, mtype=None, def send_message(self, mto, mbody, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None): mhtml=None, mfrom=None, mnick=None):
""" """
Create, initialize, and send a new Create, initialize, and send a new
:class:`~sleekxmpp.stanza.message.Message` stanza. :class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message. :param mto: The recipient of the message.
@ -518,7 +525,7 @@ class BaseXMPP(XMLStream):
def send_presence(self, pshow=None, pstatus=None, ppriority=None, def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None): pto=None, pfrom=None, ptype=None, pnick=None):
""" """
Create, initialize, and send a new Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza. :class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value. :param pshow: The presence's show value.
@ -529,26 +536,13 @@ class BaseXMPP(XMLStream):
:param pfrom: The sender of the presence. :param pfrom: The sender of the presence.
:param pnick: Optional nickname of the presence's sender. :param pnick: Optional nickname of the presence's sender.
""" """
# Python2.6 chokes on Unicode strings for dict keys. self.make_presence(pshow, pstatus, ppriority, pto,
args = {str('ptype'): ptype, ptype, pfrom, pnick).send()
str('pshow'): pshow,
str('pstatus'): pstatus,
str('ppriority'): ppriority,
str('pnick'): pnick}
if ptype in ('probe', 'subscribe', 'subscribed', \
'unsubscribe', 'unsubscribed'):
args[str('pto')] = pto.bare
if self.is_component:
self.roster[pfrom].send_presence(**args)
else:
self.client_roster.send_presence(**args)
def send_presence_subscription(self, pto, pfrom=None, def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None): ptype='subscribe', pnick=None):
""" """
Create, initialize, and send a new Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza of :class:`~sleekxmpp.stanza.presence.Presence` stanza of
type ``'subscribe'``. type ``'subscribe'``.
@ -557,10 +551,10 @@ class BaseXMPP(XMLStream):
:param ptype: The type of presence, such as ``'subscribe'``. :param ptype: The type of presence, such as ``'subscribe'``.
:param pnick: Optional nickname of the presence's sender. :param pnick: Optional nickname of the presence's sender.
""" """
self.send_presence(pto=pto, self.make_presence(ptype=ptype,
pfrom=pfrom, pfrom=pfrom,
ptype=ptype, pto=JID(pto).bare,
pnick=pnick) pnick=pnick).send()
@property @property
def jid(self): def jid(self):
@ -749,7 +743,7 @@ class BaseXMPP(XMLStream):
return return
def exception(self, exception): def exception(self, exception):
"""Process any uncaught exceptions, notably """Process any uncaught exceptions, notably
:class:`~sleekxmpp.exceptions.IqError` and :class:`~sleekxmpp.exceptions.IqError` and
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions. :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

View File

@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP):
:param password: The password for the XMPP user account. :param password: The password for the XMPP user account.
:param ssl: **Deprecated.** :param ssl: **Deprecated.**
:param plugin_config: A dictionary of plugin configurations. :param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that :param plugin_whitelist: A list of approved plugins that
will be loaded when calling will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param escape_quotes: **Deprecated.** :param escape_quotes: **Deprecated.**
""" """
def __init__(self, jid, password, ssl=False, plugin_config={}, def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
plugin_whitelist=[], escape_quotes=True, sasl_mech=None): escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client') BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid) self.set_jid(jid)
@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist self.plugin_whitelist = plugin_whitelist
self.default_port = 5222 self.default_port = 5222
self.default_lang = lang
self.credentials = {} self.credentials = {}
self.password = password self.password = password
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
self.boundjid.host, self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns, "xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns) "xmlns='%s'" % self.default_ns,
"xml:lang='%s'" % self.default_lang,
"version='1.0'")
self.stream_footer = "</stream:stream>" self.stream_footer = "</stream:stream>"
self.features = set() self.features = set()
@ -186,7 +189,7 @@ class ClientXMPP(BaseXMPP):
occurs. Defaults to ``True``. occurs. Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait :param timeout: The length of time (in seconds) to wait
for a response before continuing if blocking for a response before continuing if blocking
is used. Defaults to is used. Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function. :param callback: Optional reference to a stream handler function.
Will be executed when the roster is received. Will be executed when the roster is received.
@ -197,7 +200,7 @@ class ClientXMPP(BaseXMPP):
def del_roster_item(self, jid): def del_roster_item(self, jid):
"""Remove an item from the roster. """Remove an item from the roster.
This is done by setting its subscription status to ``'remove'``. This is done by setting its subscription status to ``'remove'``.
:param jid: The JID of the item to remove. :param jid: The JID of the item to remove.
@ -212,7 +215,7 @@ class ClientXMPP(BaseXMPP):
Defaults to ``True``. Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait for a response :param timeout: The length of time (in seconds) to wait for a response
before continuing if blocking is used. before continuing if blocking is used.
Defaults to Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function. Will :param callback: Optional reference to a stream handler function. Will
be executed when the roster is received. be executed when the roster is received.
@ -230,7 +233,7 @@ class ClientXMPP(BaseXMPP):
response = iq.send(block, timeout, callback) response = iq.send(block, timeout, callback)
self.event('roster_received', response) self.event('roster_received', response)
if block: if block:
self._handle_roster(response) self._handle_roster(response)
return response return response
@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP):
roster[jid]['pending_out'] = (item['ask'] == 'subscribe') roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
roster[jid].save(remove=(item['subscription'] == 'remove')) roster[jid].save(remove=(item['subscription'] == 'remove'))
self.event("roster_update", iq) self.event("roster_update", iq)
if iq['type'] == 'set': if iq['type'] == 'set':
resp = self.Iq(stype='result', resp = self.Iq(stype='result',

View File

@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
:param host: The server accepting the component. :param host: The server accepting the component.
:param port: The port used to connect to the server. :param port: The port used to connect to the server.
:param plugin_config: A dictionary of plugin configurations. :param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that :param plugin_whitelist: A list of approved plugins that
will be loaded when calling will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
should be used instead of the standard should be used instead of the standard
@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP):
self.add_event_handler('presence_probe', self.add_event_handler('presence_probe',
self._handle_probe) self._handle_probe)
def connect(self, host=None, port=None, use_ssl=False, def connect(self, host=None, port=None, use_ssl=False,
use_tls=False, reattempt=True): use_tls=False, reattempt=True):
"""Connect to the server. """Connect to the server.

View File

@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
condition='remote-server-timeout', condition='remote-server-timeout',
etype='cancel') etype='cancel')
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: did not arrive before the timeout expired. #: did not arrive before the timeout expired.
self.iq = iq self.iq = iq
class IqError(XMPPError): class IqError(XMPPError):
""" """

View File

@ -7,9 +7,9 @@
""" """
__all__ = [ __all__ = [
'feature_starttls', 'feature_starttls',
'feature_mechanisms', 'feature_mechanisms',
'feature_bind', 'feature_bind',
'feature_session', 'feature_session',
'feature_rosterver' 'feature_rosterver'
] ]

View File

@ -23,7 +23,7 @@ class Auth(StanzaBase):
interfaces = set(('mechanism', 'value')) interfaces = set(('mechanism', 'value'))
plugin_attrib = name plugin_attrib = name
#: Some SASL mechs require sending values as is, #: Some SASL mechs require sending values as is,
#: without converting base64. #: without converting base64.
plain_mechs = set(['X-MESSENGER-OAUTH2']) plain_mechs = set(['X-MESSENGER-OAUTH2'])

View File

@ -47,7 +47,7 @@ class Failure(StanzaBase):
def get_condition(self): def get_condition(self):
"""Return the condition element's name.""" """Return the condition element's name."""
for child in self.xml.getchildren(): for child in self.xml:
if "{%s}" % self.namespace in child.tag: if "{%s}" % self.namespace in child.tag:
cond = child.tag.split('}', 1)[-1] cond = child.tag.split('}', 1)[-1]
if cond in self.conditions: if cond in self.conditions:
@ -68,7 +68,7 @@ class Failure(StanzaBase):
def del_condition(self): def del_condition(self):
"""Remove the condition element.""" """Remove the condition element."""
for child in self.xml.getchildren(): for child in self.xml:
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1] tag = child.tag.split('}', 1)[-1]
if tag in self.conditions: if tag in self.conditions:

View File

@ -33,6 +33,7 @@ __all__ = [
# 'xep_0078', # Non-SASL auth. Don't automatically load # 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0080', # User Location 'xep_0080', # User Location
'xep_0082', # XMPP Date and Time Profiles 'xep_0082', # XMPP Date and Time Profiles
'xep_0084', # User Avatar
'xep_0085', # Chat State Notifications 'xep_0085', # Chat State Notifications
'xep_0086', # Legacy Error Codes 'xep_0086', # Legacy Error Codes
'xep_0092', # Software Version 'xep_0092', # Software Version
@ -49,7 +50,10 @@ __all__ = [
'xep_0199', # Ping 'xep_0199', # Ping
'xep_0202', # Entity Time 'xep_0202', # Entity Time
'xep_0203', # Delayed Delivery 'xep_0203', # Delayed Delivery
'xep_0222', # Persistent Storage of Public Data via Pubsub
'xep_0223', # Persistent Storage of Private Data via Pubsub
'xep_0224', # Attention 'xep_0224', # Attention
'xep_0231', # Bits of Binary 'xep_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations 'xep_0249', # Direct MUC Invitations
'xep_0258', # Security Labels in XMPP
] ]

View File

@ -31,10 +31,10 @@ log = logging.getLogger(__name__)
PLUGIN_REGISTRY = {} PLUGIN_REGISTRY = {}
#: In order to do cascading plugin disabling, reverse dependencies #: In order to do cascading plugin disabling, reverse dependencies
#: must be tracked. #: must be tracked.
PLUGIN_DEPENDENTS = {} PLUGIN_DEPENDENTS = {}
#: Only allow one thread to manipulate the plugin registry at a time. #: Only allow one thread to manipulate the plugin registry at a time.
REGISTRY_LOCK = threading.RLock() REGISTRY_LOCK = threading.RLock()
@ -75,7 +75,7 @@ def load_plugin(name, module=None):
plugins are in packages matching their name, plugins are in packages matching their name,
even though the plugin class name does not even though the plugin class name does not
have to match. have to match.
:param str module: The name of the base module to search :param str module: The name of the base module to search
for the plugin. for the plugin.
""" """
try: try:
@ -84,7 +84,7 @@ def load_plugin(name, module=None):
module = 'sleekxmpp.plugins.%s' % name module = 'sleekxmpp.plugins.%s' % name
__import__(module) __import__(module)
mod = sys.modules[module] mod = sys.modules[module]
except: except ImportError:
module = 'sleekxmpp.features.%s' % name module = 'sleekxmpp.features.%s' % name
__import__(module) __import__(module)
mod = sys.modules[module] mod = sys.modules[module]
@ -103,11 +103,11 @@ def load_plugin(name, module=None):
# we can work around dependency issues. # we can work around dependency issues.
plugin.old_style = True plugin.old_style = True
register_plugin(plugin, name) register_plugin(plugin, name)
except: except ImportError:
log.exception("Unable to load plugin: %s", name) log.exception("Unable to load plugin: %s", name)
class PluginManager(object): class PluginManager(object):
def __init__(self, xmpp, config=None): def __init__(self, xmpp, config=None):
#: We will track all enabled plugins in a set so that we #: We will track all enabled plugins in a set so that we
#: can enable plugins in batches and pull in dependencies #: can enable plugins in batches and pull in dependencies
@ -181,7 +181,7 @@ class PluginManager(object):
def enable_all(self, names=None, config=None): def enable_all(self, names=None, config=None):
"""Enable all registered plugins. """Enable all registered plugins.
:param list names: A list of plugin names to enable. If :param list names: A list of plugin names to enable. If
none are provided, all registered plugins none are provided, all registered plugins
will be enabled. will be enabled.
@ -292,7 +292,7 @@ class BasePlugin(object):
def post_init(self): def post_init(self):
"""Initialize any cross-plugin state. """Initialize any cross-plugin state.
Only needed if the plugin has circular dependencies. Only needed if the plugin has circular dependencies.
""" """
pass pass

View File

@ -81,7 +81,8 @@ class XEP_0027(BasePlugin):
def _sign_presence(self, stanza): def _sign_presence(self, stanza):
if isinstance(stanza, Presence): if isinstance(stanza, Presence):
if stanza['type'] == 'available' or stanza['type'] in Presence.showtypes: if stanza['type'] == 'available' or \
stanza['type'] in Presence.showtypes:
stanza['signed'] = stanza['status'] stanza['signed'] = stanza['status']
return stanza return stanza

View File

@ -51,6 +51,3 @@ class Encrypted(ElementBase):
if self.xml.text: if self.xml.text:
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
return None return None

View File

@ -339,8 +339,8 @@ class XEP_0030(BasePlugin):
if local: if local:
log.debug("Looking up local disco#info data " + \ log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node) "for %s, node %s.", jid, node)
info = self.api['get_info'](jid, node, info = self.api['get_info'](jid, node,
kwargs.get('ifrom', None), kwargs.get('ifrom', None),
kwargs) kwargs)
info = self._fix_default_info(info) info = self._fix_default_info(info)
return self._wrap(kwargs.get('ifrom', None), jid, info) return self._wrap(kwargs.get('ifrom', None), jid, info)
@ -348,8 +348,8 @@ class XEP_0030(BasePlugin):
if cached: if cached:
log.debug("Looking up cached disco#info data " + \ log.debug("Looking up cached disco#info data " + \
"for %s, node %s.", jid, node) "for %s, node %s.", jid, node)
info = self.api['get_cached_info'](jid, node, info = self.api['get_cached_info'](jid, node,
kwargs.get('ifrom', None), kwargs.get('ifrom', None),
kwargs) kwargs)
if info is not None: if info is not None:
return self._wrap(kwargs.get('ifrom', None), jid, info) return self._wrap(kwargs.get('ifrom', None), jid, info)
@ -405,8 +405,8 @@ class XEP_0030(BasePlugin):
Otherwise the parameter is ignored. Otherwise the parameter is ignored.
""" """
if local or jid is None: if local or jid is None:
items = self.api['get_items'](jid, node, items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None), kwargs.get('ifrom', None),
kwargs) kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items) return self._wrap(kwargs.get('ifrom', None), jid, items)

View File

@ -1,167 +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
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
from sleekxmpp.plugins import BasePlugin, register_plugin
class Addresses(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'addresses'
plugin_attrib = 'addresses'
interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False):
address = Address(parent=self)
address['type'] = atype
address['jid'] = jid
address['node'] = node
address['uri'] = uri
address['desc'] = desc
address['delivered'] = delivered
return address
def getAddresses(self, atype=None):
addresses = []
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if atype is None or addrXML.attrib.get('type') == atype:
addresses.append(Address(xml=addrXML, parent=None))
return addresses
def setAddresses(self, addresses, set_type=None):
self.delAddresses(set_type)
for addr in addresses:
addr = dict(addr)
# Remap 'type' to 'atype' to match the add method
if set_type is not None:
addr['type'] = set_type
curr_type = addr.get('type', None)
if curr_type is not None:
del addr['type']
addr['atype'] = curr_type
self.addAddress(**addr)
def delAddresses(self, atype=None):
if atype is None:
return
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if addrXML.attrib.get('type') == atype:
self.xml.remove(addrXML)
# --------------------------------------------------------------
def delBcc(self):
self.delAddresses('bcc')
def delCc(self):
self.delAddresses('cc')
def delNoreply(self):
self.delAddresses('noreply')
def delReplyroom(self):
self.delAddresses('replyroom')
def delReplyto(self):
self.delAddresses('replyto')
def delTo(self):
self.delAddresses('to')
# --------------------------------------------------------------
def getBcc(self):
return self.getAddresses('bcc')
def getCc(self):
return self.getAddresses('cc')
def getNoreply(self):
return self.getAddresses('noreply')
def getReplyroom(self):
return self.getAddresses('replyroom')
def getReplyto(self):
return self.getAddresses('replyto')
def getTo(self):
return self.getAddresses('to')
# --------------------------------------------------------------
def setBcc(self, addresses):
self.setAddresses(addresses, 'bcc')
def setCc(self, addresses):
self.setAddresses(addresses, 'cc')
def setNoreply(self, addresses):
self.setAddresses(addresses, 'noreply')
def setReplyroom(self, addresses):
self.setAddresses(addresses, 'replyroom')
def setReplyto(self, addresses):
self.setAddresses(addresses, 'replyto')
def setTo(self, addresses):
self.setAddresses(addresses, 'to')
class Address(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'address'
plugin_attrib = 'address'
interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri'))
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def getDelivered(self):
return self.xml.attrib.get('delivered', False)
def setDelivered(self, delivered):
if delivered:
self.xml.attrib['delivered'] = "true"
else:
del self['delivered']
def setUri(self, uri):
if uri:
del self['jid']
del self['node']
self.xml.attrib['uri'] = uri
elif 'uri' in self.xml.attrib:
del self.xml.attrib['uri']
class XEP_0033(BasePlugin):
"""
XEP-0033: Extended Stanza Addressing
"""
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0033'])
def plugin_init(self):
self.xep = '0033'
register_stanza_plugin(Message, Addresses)
self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace)
xep_0033 = XEP_0033
register_plugin(XEP_0033)

View File

@ -0,0 +1,20 @@
"""
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

View File

@ -0,0 +1,32 @@
"""
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):
self.xmpp['xep_0030'].add_feature(Addresses.namespace)
register_stanza_plugin(Message, Addresses)
register_stanza_plugin(Presence, Addresses)

View File

@ -0,0 +1,131 @@
"""
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)

View File

@ -110,14 +110,14 @@ class Command(ElementBase):
""" """
Return the set of allowable next actions. Return the set of allowable next actions.
""" """
actions = [] actions = set()
actions_xml = self.find('{%s}actions' % self.namespace) actions_xml = self.find('{%s}actions' % self.namespace)
if actions_xml is not None: if actions_xml is not None:
for action in self.next_actions: for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace, action_xml = actions_xml.find('{%s}%s' % (self.namespace,
action)) action))
if action_xml is not None: if action_xml is not None:
actions.append(action) actions.add(action)
return actions return actions
def del_actions(self): def del_actions(self):

View File

@ -72,6 +72,7 @@ class Nickname(ElementBase):
name = 'NICKNAME' name = 'NICKNAME'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'nicknames'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -94,6 +95,7 @@ class Email(ElementBase):
name = 'EMAIL' name = 'EMAIL'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'emails'
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID']) interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
sub_interfaces = set(['USERID']) sub_interfaces = set(['USERID'])
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400']) bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
@ -103,8 +105,9 @@ class Address(ElementBase):
name = 'ADR' name = 'ADR'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL', plugin_multi_attrib = 'addresses'
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY', interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY']) 'REGION', 'PCODE', 'CTRY'])
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY', sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY']) 'REGION', 'PCODE', 'CTRY'])
@ -115,12 +118,13 @@ class Telephone(ElementBase):
name = 'TEL' name = 'TEL'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'telephone_numbers'
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', 'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
'PREF', 'NUMBER']) 'PREF', 'NUMBER'])
sub_interfaces = set(['NUMBER']) sub_interfaces = set(['NUMBER'])
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', 'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
'ISDN', 'PCS', 'PREF']) 'ISDN', 'PCS', 'PREF'])
def setup(self, xml=None): def setup(self, xml=None):
@ -138,9 +142,10 @@ class Label(ElementBase):
name = 'LABEL' name = 'LABEL'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'labels'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
'PREF', 'lines']) 'PREF', 'lines'])
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
'INT', 'PREF']) 'INT', 'PREF'])
def add_line(self, value): def add_line(self, value):
@ -171,6 +176,7 @@ class Geo(ElementBase):
name = 'GEO' name = 'GEO'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'geolocations'
interfaces = set(['LAT', 'LON']) interfaces = set(['LAT', 'LON'])
sub_interfaces = interfaces sub_interfaces = interfaces
@ -179,6 +185,7 @@ class Org(ElementBase):
name = 'ORG' name = 'ORG'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'organizations'
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits']) interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
sub_interfaces = set(['ORGNAME', 'ORGUNIT']) sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
@ -210,6 +217,7 @@ class Photo(ElementBase):
name = 'PHOTO' name = 'PHOTO'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'photos'
interfaces = set(['TYPE', 'EXTVAL']) interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces sub_interfaces = interfaces
@ -218,14 +226,16 @@ class Logo(ElementBase):
name = 'LOGO' name = 'LOGO'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'logos'
interfaces = set(['TYPE', 'EXTVAL']) interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces sub_interfaces = interfaces
class Sound(ElementBase): class Sound(ElementBase):
name = 'LOGO' name = 'SOUND'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'sounds'
interfaces = set(['PHONETC', 'EXTVAL']) interfaces = set(['PHONETC', 'EXTVAL'])
sub_interfaces = interfaces sub_interfaces = interfaces
@ -264,6 +274,7 @@ class Classification(ElementBase):
name = 'CLASS' name = 'CLASS'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'classifications'
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL']) interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
bool_interfaces = interfaces bool_interfaces = interfaces
@ -272,6 +283,7 @@ class Categories(ElementBase):
name = 'CATEGORIES' name = 'CATEGORIES'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'categories'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -301,6 +313,7 @@ class Birthday(ElementBase):
name = 'BDAY' name = 'BDAY'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'birthdays'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -319,6 +332,7 @@ class Rev(ElementBase):
name = 'REV' name = 'REV'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'revision_dates'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -337,6 +351,7 @@ class Title(ElementBase):
name = 'TITLE' name = 'TITLE'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'titles'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -351,6 +366,7 @@ class Role(ElementBase):
name = 'ROLE' name = 'ROLE'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'roles'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -365,6 +381,7 @@ class Note(ElementBase):
name = 'NOTE' name = 'NOTE'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'notes'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -379,6 +396,7 @@ class Desc(ElementBase):
name = 'DESC' name = 'DESC'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'descriptions'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -393,6 +411,7 @@ class URL(ElementBase):
name = 'URL' name = 'URL'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'urls'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -407,6 +426,7 @@ class UID(ElementBase):
name = 'UID' name = 'UID'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'uids'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -421,6 +441,7 @@ class ProdID(ElementBase):
name = 'PRODID' name = 'PRODID'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'product_ids'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -435,6 +456,7 @@ class Mailer(ElementBase):
name = 'MAILER' name = 'MAILER'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'mailers'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -449,6 +471,7 @@ class SortString(ElementBase):
name = 'SORT-STRING' name = 'SORT-STRING'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = 'SORT_STRING' plugin_attrib = 'SORT_STRING'
plugin_multi_attrib = 'sort_strings'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -463,6 +486,7 @@ class Agent(ElementBase):
name = 'AGENT' name = 'AGENT'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'agents'
interfaces = set(['EXTVAL']) interfaces = set(['EXTVAL'])
sub_interfaces = interfaces sub_interfaces = interfaces
@ -471,6 +495,7 @@ class JabberID(ElementBase):
name = 'JABBERID' name = 'JABBERID'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'jids'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
@ -485,11 +510,12 @@ class TimeZone(ElementBase):
name = 'TZ' name = 'TZ'
namespace = 'vcard-temp' namespace = 'vcard-temp'
plugin_attrib = name plugin_attrib = name
plugin_multi_attrib = 'timezones'
interfaces = set([name]) interfaces = set([name])
is_extension = True is_extension = True
def set_tz(self, value): def set_tz(self, value):
time = xep_0082.time(offset=value) time = xep_0082.time(offset=value)
if time[-1] == 'Z': if time[-1] == 'Z':
self.xml.text = 'Z' self.xml.text = 'Z'
else: else:

View File

@ -53,7 +53,7 @@ class XEP_0054(BasePlugin):
def make_vcard(self): def make_vcard(self):
return VCardTemp() return VCardTemp()
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False, def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
block=True, callback=None, timeout=None): block=True, callback=None, timeout=None):
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain: if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
local = True local = True
@ -84,12 +84,12 @@ class XEP_0054(BasePlugin):
iq.enable('vcard_temp') iq.enable('vcard_temp')
vcard = iq.send(block=block, callback=callback, timeout=timeout) vcard = iq.send(block=block, callback=callback, timeout=timeout)
if block: if block:
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp']) self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
return vcard return vcard
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None, def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
callback=None, timeout=None): callback=None, timeout=None):
if self.xmpp.is_component: if self.xmpp.is_component:
self.api['set_vcard'](jid, None, ifrom, vcard) self.api['set_vcard'](jid, None, ifrom, vcard)
@ -107,7 +107,7 @@ class XEP_0054(BasePlugin):
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return return
elif iq['type'] == 'get': elif iq['type'] == 'get':
vcard = self.api['get_vard'](iq['from'].bare) vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq): if isinstance(vcard, Iq):
vcard.send() vcard.send()
else: else:

View File

@ -74,7 +74,7 @@ class Set(ElementBase):
if fi is not None: if fi is not None:
if val: if val:
fi.attrib['index'] = val fi.attrib['index'] = val
else: elif 'index' in fi.attrib:
del fi.attrib['index'] del fi.attrib['index']
elif val: elif val:
fi = ET.Element("{%s}first" % (self.namespace)) fi = ET.Element("{%s}first" % (self.namespace))
@ -93,7 +93,7 @@ class Set(ElementBase):
def set_before(self, val): def set_before(self, val):
b = self.xml.find("{%s}before" % (self.namespace)) b = self.xml.find("{%s}before" % (self.namespace))
if b is None and val == True: if b is None and val is True:
self._set_sub_text('{%s}before' % self.namespace, '', True) self._set_sub_text('{%s}before' % self.namespace, '', True)
else: else:
self._set_sub_text('{%s}before' % self.namespace, val) self._set_sub_text('{%s}before' % self.namespace, val)

View File

@ -77,12 +77,12 @@ class Item(ElementBase):
self.append(value) self.append(value)
def get_payload(self): def get_payload(self):
childs = self.xml.getchildren() childs = list(self.xml)
if len(childs) > 0: if len(childs) > 0:
return childs[0] return childs[0]
def del_payload(self): def del_payload(self):
for child in self.xml.getchildren(): for child in self.xml:
self.xml.remove(child) self.xml.remove(child)
@ -254,12 +254,12 @@ class PubsubState(ElementBase):
self.xml.append(value) self.xml.append(value)
def get_payload(self): def get_payload(self):
childs = self.xml.getchildren() childs = list(self.xml)
if len(childs) > 0: if len(childs) > 0:
return childs[0] return childs[0]
def del_payload(self): def del_payload(self):
for child in self.xml.getchildren(): for child in self.xml:
self.xml.remove(child) self.xml.remove(child)

View File

@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase):
def get_condition(self): def get_condition(self):
"""Return the condition element's name.""" """Return the condition element's name."""
for child in self.parent().xml.getchildren(): for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1] cond = child.tag.split('}', 1)[-1]
if cond in self.conditions: if cond in self.conditions:
@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase):
def del_condition(self): def del_condition(self):
"""Remove the condition element.""" """Remove the condition element."""
for child in self.parent().xml.getchildren(): for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1] tag = child.tag.split('}', 1)[-1]
if tag in self.conditions: if tag in self.conditions:

View File

@ -31,12 +31,12 @@ class EventItem(ElementBase):
self.xml.append(value) self.xml.append(value)
def get_payload(self): def get_payload(self):
childs = self.xml.getchildren() childs = list(self.xml)
if len(childs) > 0: if len(childs) > 0:
return childs[0] return childs[0]
def del_payload(self): def del_payload(self):
for child in self.xml.getchildren(): for child in self.xml:
self.xml.remove(child) self.xml.remove(child)

View File

@ -39,5 +39,3 @@ class AuthFeature(ElementBase):
interfaces = set() interfaces = set()
plugin_tag_map = {} plugin_tag_map = {}
plugin_attrib_map = {} plugin_attrib_map = {}

View File

@ -40,33 +40,33 @@ class XEP_0080(BasePlugin):
accuracy -- Horizontal GPS error in meters. accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level. alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood. area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in heading to reach its next waypoint), measured in
decimal degrees relative to true north. decimal degrees relative to true north.
building -- A specific building on a street or in an area. building -- A specific building on a street or in an area.
country -- The nation where the user is located. country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code. countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum. datum -- GPS datum.
description -- A natural-language name for or description of description -- A natural-language name for or description of
the location. the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter. the accuracy parameter.
floor -- A particular floor in a building. floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North. lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such locality -- A locality within the administrative region, such
as a town or city. as a town or city.
lon -- Longitude in decimal degrees East. lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery. postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such region -- An administrative region of the nation, such
as a state or province. as a state or province.
room -- A particular room in a building. room -- A particular room in a building.
speed -- The speed at which the entity is moving, speed -- The speed at which the entity is moving,
in meters per second. in meters per second.
street -- A thoroughfare within the locality, or a crossing street -- A thoroughfare within the locality, or a crossing
of two thoroughfares. of two thoroughfares.
text -- A catch-all element that captures any other text -- A catch-all element that captures any other
information about the location. information about the location.
timestamp -- UTC timestamp specifying the moment when the timestamp -- UTC timestamp specifying the moment when the
reading was taken. reading was taken.
uri -- A URI or URL pointing to information about uri -- A URI or URL pointing to information about
the location. the location.
@ -115,7 +115,7 @@ class XEP_0080(BasePlugin):
be executed when a reply stanza is received. be executed when a reply stanza is received.
""" """
geoloc = Geoloc() geoloc = Geoloc()
return self.xmpp['xep_0163'].publish(geoloc, return self.xmpp['xep_0163'].publish(geoloc,
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,
callback=callback, callback=callback,

View File

@ -31,33 +31,33 @@ class Geoloc(ElementBase):
accuracy -- Horizontal GPS error in meters. accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level. alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood. area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in heading to reach its next waypoint), measured in
decimal degrees relative to true north. decimal degrees relative to true north.
building -- A specific building on a street or in an area. building -- A specific building on a street or in an area.
country -- The nation where the user is located. country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code. countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum. datum -- GPS datum.
description -- A natural-language name for or description of description -- A natural-language name for or description of
the location. the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter. the accuracy parameter.
floor -- A particular floor in a building. floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North. lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such locality -- A locality within the administrative region, such
as a town or city. as a town or city.
lon -- Longitude in decimal degrees East. lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery. postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such region -- An administrative region of the nation, such
as a state or province. as a state or province.
room -- A particular room in a building. room -- A particular room in a building.
speed -- The speed at which the entity is moving, speed -- The speed at which the entity is moving,
in meters per second. in meters per second.
street -- A thoroughfare within the locality, or a crossing street -- A thoroughfare within the locality, or a crossing
of two thoroughfares. of two thoroughfares.
text -- A catch-all element that captures any other text -- A catch-all element that captures any other
information about the location. information about the location.
timestamp -- UTC timestamp specifying the moment when the timestamp -- UTC timestamp specifying the moment when the
reading was taken. reading was taken.
uri -- A URI or URL pointing to information about uri -- A URI or URL pointing to information about
the location. the location.
@ -65,10 +65,10 @@ class Geoloc(ElementBase):
namespace = 'http://jabber.org/protocol/geoloc' namespace = 'http://jabber.org/protocol/geoloc'
name = 'geoloc' name = 'geoloc'
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building', interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
'country', 'countrycode', 'datum', 'dscription', 'country', 'countrycode', 'datum', 'dscription',
'error', 'floor', 'lat', 'locality', 'lon', 'error', 'floor', 'lat', 'locality', 'lon',
'postalcode', 'region', 'room', 'speed', 'street', 'postalcode', 'region', 'room', 'speed', 'street',
'text', 'timestamp', 'uri')) 'text', 'timestamp', 'uri'))
sub_interfaces = interfaces sub_interfaces = interfaces
plugin_attrib = name plugin_attrib = name
@ -88,7 +88,7 @@ class Geoloc(ElementBase):
""" """
self._set_sub_text('accuracy', text=str(accuracy)) self._set_sub_text('accuracy', text=str(accuracy))
return self return self
def get_accuracy(self): def get_accuracy(self):
""" """
Return the value of the <accuracy> element as an integer. Return the value of the <accuracy> element as an integer.
@ -111,7 +111,7 @@ class Geoloc(ElementBase):
""" """
self._set_sub_text('alt', text=str(alt)) self._set_sub_text('alt', text=str(alt))
return self return self
def get_alt(self): def get_alt(self):
""" """
Return the value of the <alt> element as an integer. Return the value of the <alt> element as an integer.
@ -130,8 +130,8 @@ class Geoloc(ElementBase):
Set the value of the <bearing> element. Set the value of the <bearing> element.
Arguments: Arguments:
bearing -- GPS bearing (direction in which the entity is heading bearing -- GPS bearing (direction in which the entity is heading
to reach its next waypoint), measured in decimal to reach its next waypoint), measured in decimal
degrees relative to true north degrees relative to true north
""" """
self._set_sub_text('bearing', text=str(bearing)) self._set_sub_text('bearing', text=str(bearing))
@ -155,7 +155,7 @@ class Geoloc(ElementBase):
Set the value of the <error> element. Set the value of the <error> element.
Arguments: Arguments:
error -- Horizontal GPS error in arc minutes; this error -- Horizontal GPS error in arc minutes; this
element is deprecated in favor of <accuracy/> element is deprecated in favor of <accuracy/>
""" """
self._set_sub_text('error', text=str(error)) self._set_sub_text('error', text=str(error))
@ -183,7 +183,7 @@ class Geoloc(ElementBase):
""" """
self._set_sub_text('lat', text=str(lat)) self._set_sub_text('lat', text=str(lat))
return self return self
def get_lat(self): def get_lat(self):
""" """
Return the value of the <lat> element as a float. Return the value of the <lat> element as a float.
@ -196,7 +196,7 @@ class Geoloc(ElementBase):
return float(p) return float(p)
except ValueError: except ValueError:
return None return None
def set_lon(self, lon): def set_lon(self, lon):
""" """
Set the value of the <lon> element. Set the value of the <lon> element.
@ -225,12 +225,12 @@ class Geoloc(ElementBase):
Set the value of the <speed> element. Set the value of the <speed> element.
Arguments: Arguments:
speed -- The speed at which the entity is moving, speed -- The speed at which the entity is moving,
in meters per second in meters per second
""" """
self._set_sub_text('speed', text=str(speed)) self._set_sub_text('speed', text=str(speed))
return self return self
def get_speed(self): def get_speed(self):
""" """
Return the value of the <speed> element as a float. Return the value of the <speed> element as a float.

View File

@ -42,6 +42,7 @@ def format_date(time_obj):
time_obj = time_obj.date() time_obj = time_obj.date()
return time_obj.isoformat() return time_obj.isoformat()
def format_time(time_obj): def format_time(time_obj):
""" """
Return a formatted string version of a time object. Return a formatted string version of a time object.
@ -60,6 +61,7 @@ def format_time(time_obj):
return '%sZ' % timestamp return '%sZ' % timestamp
return timestamp return timestamp
def format_datetime(time_obj): def format_datetime(time_obj):
""" """
Return a formatted string version of a datetime object. Return a formatted string version of a datetime object.
@ -76,6 +78,7 @@ def format_datetime(time_obj):
return '%sZ' % timestamp return '%sZ' % timestamp
return timestamp return timestamp
def date(year=None, month=None, day=None, obj=False): def date(year=None, month=None, day=None, obj=False):
""" """
Create a date only timestamp for the given instant. Create a date only timestamp for the given instant.
@ -98,9 +101,10 @@ def date(year=None, month=None, day=None, obj=False):
day = today.day day = today.day
value = dt.date(year, month, day) value = dt.date(year, month, day)
if obj: if obj:
return value return value
return format_date(value) return format_date(value)
def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
""" """
Create a time only timestamp for the given instant. Create a time only timestamp for the given instant.
@ -136,6 +140,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
return value return value
return format_time(value) return format_time(value)
def datetime(year=None, month=None, day=None, hour=None, def datetime(year=None, month=None, day=None, hour=None,
min=None, sec=None, micro=None, offset=None, min=None, sec=None, micro=None, offset=None,
separators=True, obj=False): separators=True, obj=False):
@ -181,7 +186,7 @@ def datetime(year=None, month=None, day=None, hour=None,
value = dt.datetime(year, month, day, hour, value = dt.datetime(year, month, day, hour,
min, sec, micro, offset) min, sec, micro, offset)
if obj: if obj:
return value return value
return format_datetime(value) return format_datetime(value)

View File

@ -0,0 +1,16 @@
"""
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)

View File

@ -0,0 +1,101 @@
"""
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):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
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 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,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
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 = []
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', ''))
for pointer in pointers:
metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(payload,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
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)

View File

@ -0,0 +1,78 @@
"""
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.thirdparty.suelta.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': 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)

View File

@ -47,28 +47,28 @@ class LegacyError(ElementBase):
interfaces = set(('condition',)) interfaces = set(('condition',))
overrides = ['set_condition'] overrides = ['set_condition']
error_map = {'bad-request': ('modify','400'), error_map = {'bad-request': ('modify', '400'),
'conflict': ('cancel','409'), 'conflict': ('cancel', '409'),
'feature-not-implemented': ('cancel','501'), 'feature-not-implemented': ('cancel', '501'),
'forbidden': ('auth','403'), 'forbidden': ('auth', '403'),
'gone': ('modify','302'), 'gone': ('modify', '302'),
'internal-server-error': ('wait','500'), 'internal-server-error': ('wait', '500'),
'item-not-found': ('cancel','404'), 'item-not-found': ('cancel', '404'),
'jid-malformed': ('modify','400'), 'jid-malformed': ('modify', '400'),
'not-acceptable': ('modify','406'), 'not-acceptable': ('modify', '406'),
'not-allowed': ('cancel','405'), 'not-allowed': ('cancel', '405'),
'not-authorized': ('auth','401'), 'not-authorized': ('auth', '401'),
'payment-required': ('auth','402'), 'payment-required': ('auth', '402'),
'recipient-unavailable': ('wait','404'), 'recipient-unavailable': ('wait', '404'),
'redirect': ('modify','302'), 'redirect': ('modify', '302'),
'registration-required': ('auth','407'), 'registration-required': ('auth', '407'),
'remote-server-not-found': ('cancel','404'), 'remote-server-not-found': ('cancel', '404'),
'remote-server-timeout': ('wait','504'), 'remote-server-timeout': ('wait', '504'),
'resource-constraint': ('wait','500'), 'resource-constraint': ('wait', '500'),
'service-unavailable': ('cancel','503'), 'service-unavailable': ('cancel', '503'),
'subscription-required': ('auth','407'), 'subscription-required': ('auth', '407'),
'undefined-condition': (None,'500'), 'undefined-condition': (None, '500'),
'unexpected-request': ('wait','400')} 'unexpected-request': ('wait', '400')}
def setup(self, xml): def setup(self, xml):
"""Don't create XML for the plugin.""" """Don't create XML for the plugin."""

View File

@ -79,5 +79,7 @@ class XEP_0092(BasePlugin):
result = iq.send() result = iq.send()
if result and result['type'] != 'error': if result and result['type'] != 'error':
return result['software_version'].values values = result['software_version'].values
del values['lang']
return values
return False return False

View File

@ -34,7 +34,7 @@ class XEP_0107(BasePlugin):
register_stanza_plugin(Message, UserMood) register_stanza_plugin(Message, UserMood)
self.xmpp['xep_0163'].register_pep('user_mood', UserMood) self.xmpp['xep_0163'].register_pep('user_mood', UserMood)
def publish_mood(self, value=None, text=None, options=None, def publish_mood(self, value=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None): ifrom=None, block=True, callback=None, timeout=None):
""" """
Publish the user's current mood. Publish the user's current mood.
@ -79,7 +79,7 @@ class XEP_0107(BasePlugin):
be executed when a reply stanza is received. be executed when a reply stanza is received.
""" """
mood = UserMood() mood = UserMood()
return self.xmpp['xep_0163'].publish(mood, return self.xmpp['xep_0163'].publish(mood,
node=UserMood.namespace, node=UserMood.namespace,
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,

View File

@ -21,7 +21,7 @@ class UserActivity(ElementBase):
'talking', 'traveling', 'undefined', 'working']) 'talking', 'traveling', 'undefined', 'working'])
specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries', specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
'cleaning', 'coding', 'commuting', 'cooking', 'cycling', 'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
'dancing', 'day_off', 'doing_maintenance', 'dancing', 'day_off', 'doing_maintenance',
'doing_the_dishes', 'doing_the_laundry', 'driving', 'doing_the_dishes', 'doing_the_laundry', 'driving',
'fishing', 'gaming', 'gardening', 'getting_a_haircut', 'fishing', 'gaming', 'gardening', 'getting_a_haircut',
'going_out', 'hanging_out', 'having_a_beer', 'going_out', 'hanging_out', 'having_a_beer',
@ -31,11 +31,11 @@ class UserActivity(ElementBase):
'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', 'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
'on_a_trip', 'on_the_phone', 'on_vacation', 'on_a_trip', 'on_the_phone', 'on_vacation',
'on_video_phone', 'other', 'partying', 'playing_sports', 'on_video_phone', 'other', 'partying', 'playing_sports',
'praying', 'reading', 'rehearsing', 'running', 'praying', 'reading', 'rehearsing', 'running',
'running_an_errand', 'scheduled_holiday', 'shaving', 'running_an_errand', 'scheduled_holiday', 'shaving',
'shopping', 'skiing', 'sleeping', 'smoking', 'shopping', 'skiing', 'sleeping', 'smoking',
'socializing', 'studying', 'sunbathing', 'swimming', 'socializing', 'studying', 'sunbathing', 'swimming',
'taking_a_bath', 'taking_a_shower', 'thinking', 'taking_a_bath', 'taking_a_shower', 'thinking',
'walking', 'walking_the_dog', 'watching_a_movie', 'walking', 'walking_the_dog', 'watching_a_movie',
'watching_tv', 'working_out', 'writing']) 'watching_tv', 'working_out', 'writing'])
@ -46,7 +46,7 @@ class UserActivity(ElementBase):
if isinstance(value, tuple) or isinstance(value, list): if isinstance(value, tuple) or isinstance(value, list):
general = value[0] general = value[0]
specific = value[1] specific = value[1]
if general in self.general: if general in self.general:
gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
if specific: if specific:

View File

@ -29,7 +29,7 @@ class XEP_0108(BasePlugin):
def plugin_init(self): def plugin_init(self):
self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
def publish_activity(self, general, specific=None, text=None, options=None, def publish_activity(self, general, specific=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None): ifrom=None, block=True, callback=None, timeout=None):
""" """
Publish the user's current activity. Publish the user's current activity.
@ -76,7 +76,7 @@ class XEP_0108(BasePlugin):
be executed when a reply stanza is received. be executed when a reply stanza is received.
""" """
activity = UserActivity() activity = UserActivity()
return self.xmpp['xep_0163'].publish(activity, return self.xmpp['xep_0163'].publish(activity,
node=UserActivity.namespace, node=UserActivity.namespace,
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,

View File

@ -35,7 +35,7 @@ class XEP_0115(BasePlugin):
stanza = stanza stanza = stanza
def plugin_init(self): def plugin_init(self):
self.hashes = {'sha-1': hashlib.sha1, self.hashes = {'sha-1': hashlib.sha1,
'sha1': hashlib.sha1, 'sha1': hashlib.sha1,
'md5': hashlib.md5} 'md5': hashlib.md5}
@ -124,7 +124,7 @@ class XEP_0115(BasePlugin):
existing_verstring = self.get_verstring(pres['from'].full) existing_verstring = self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(pres['caps']['ver']): if str(existing_verstring) == str(pres['caps']['ver']):
return return
if pres['caps']['hash'] not in self.hashes: if pres['caps']['hash'] not in self.hashes:
try: try:
log.debug("Unknown caps hash: %s", pres['caps']['hash']) log.debug("Unknown caps hash: %s", pres['caps']['hash'])
@ -132,7 +132,7 @@ class XEP_0115(BasePlugin):
return return
except XMPPError: except XMPPError:
return return
log.debug("New caps verification string: %s", pres['caps']['ver']) log.debug("New caps verification string: %s", pres['caps']['ver'])
try: try:
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver']) node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
@ -140,7 +140,7 @@ class XEP_0115(BasePlugin):
if isinstance(caps, Iq): if isinstance(caps, Iq):
caps = caps['disco_info'] caps = caps['disco_info']
if self._validate_caps(caps, pres['caps']['hash'], if self._validate_caps(caps, pres['caps']['hash'],
pres['caps']['ver']): pres['caps']['ver']):
self.assign_verstring(pres['from'], pres['caps']['ver']) self.assign_verstring(pres['from'], pres['caps']['ver'])
@ -173,7 +173,8 @@ class XEP_0115(BasePlugin):
form_types.append(f_type) form_types.append(f_type)
deduped_form_types.add(f_type) deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types): if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, invalid for caps") log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False return False
if len(f_type) > 1: if len(f_type) > 1:
@ -183,7 +184,8 @@ class XEP_0115(BasePlugin):
return False return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden': if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', ignoring form for caps") log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml) caps.xml.remove(stanza.xml)
else: else:
log.debug("No FORM_TYPE found, ignoring form for caps") log.debug("No FORM_TYPE found, ignoring form for caps")
@ -212,7 +214,7 @@ class XEP_0115(BasePlugin):
identities = sorted(('/'.join(i) for i in identities)) identities = sorted(('/'.join(i) for i in identities))
features = sorted(info['features']) features = sorted(info['features'])
S += '<'.join(identities) + '<' S += '<'.join(identities) + '<'
S += '<'.join(features) + '<' S += '<'.join(features) + '<'
@ -254,7 +256,7 @@ class XEP_0115(BasePlugin):
info = info['disco_info'] info = info['disco_info']
ver = self.generate_verstring(info, self.hash) ver = self.generate_verstring(info, self.hash)
self.xmpp['xep_0030'].set_info( self.xmpp['xep_0030'].set_info(
jid=jid, jid=jid,
node='%s#%s' % (self.caps_node, ver), node='%s#%s' % (self.caps_node, ver),
info=info) info=info)
self.cache_caps(ver, info) self.cache_caps(ver, info)

View File

@ -69,7 +69,7 @@ class StaticCaps(object):
return True return True
try: try:
info = self.disco.get_info(jid=jid, node=node, info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data) ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True) info = self.disco._wrap(ifrom, jid, info, True)
return feature in info['disco_info']['features'] return feature in info['disco_info']['features']
@ -99,7 +99,7 @@ class StaticCaps(object):
be skipped, even if a result has already been be skipped, even if a result has already been
cached. Defaults to false. cached. Defaults to false.
""" """
identity = (data.get('category', None), identity = (data.get('category', None),
data.get('itype', None), data.get('itype', None),
data.get('lang', None)) data.get('lang', None))
@ -114,7 +114,7 @@ class StaticCaps(object):
return True return True
try: try:
info = self.disco.get_info(jid=jid, node=node, info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data) ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True) info = self.disco._wrap(ifrom, jid, info, True)
return identity in map(trunc, info['disco_info']['identities']) return identity in map(trunc, info['disco_info']['identities'])

View File

@ -14,7 +14,7 @@ class UserTune(ElementBase):
name = 'tune' name = 'tune'
namespace = 'http://jabber.org/protocol/tune' namespace = 'http://jabber.org/protocol/tune'
plugin_attrib = 'tune' plugin_attrib = 'tune'
interfaces = set(['artist', 'length', 'rating', 'source', interfaces = set(['artist', 'length', 'rating', 'source',
'title', 'track', 'uri']) 'title', 'track', 'uri'])
sub_interfaces = interfaces sub_interfaces = interfaces

View File

@ -30,7 +30,7 @@ class XEP_0118(BasePlugin):
self.xmpp['xep_0163'].register_pep('user_tune', UserTune) self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
def publish_tune(self, artist=None, length=None, rating=None, source=None, def publish_tune(self, artist=None, length=None, rating=None, source=None,
title=None, track=None, uri=None, options=None, title=None, track=None, uri=None, options=None,
ifrom=None, block=True, callback=None, timeout=None): ifrom=None, block=True, callback=None, timeout=None):
""" """
Publish the user's current tune. Publish the user's current tune.
@ -61,7 +61,7 @@ class XEP_0118(BasePlugin):
tune['title'] = title tune['title'] = title
tune['track'] = track tune['track'] = track
tune['uri'] = uri tune['uri'] = uri
return self.xmpp['xep_0163'].publish(tune, return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace, node=UserTune.namespace,
options=options, options=options,
ifrom=ifrom, ifrom=ifrom,
@ -84,7 +84,7 @@ class XEP_0118(BasePlugin):
be executed when a reply stanza is received. be executed when a reply stanza is received.
""" """
tune = UserTune() tune = UserTune()
return self.xmpp['xep_0163'].publish(tune, return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace, node=UserTune.namespace,
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,

View File

@ -45,7 +45,7 @@ class XEP_0153(BasePlugin):
self.api.register(self._set_hash, 'set_hash', default=True) self.api.register(self._set_hash, 'set_hash', default=True)
self.api.register(self._get_hash, 'get_hash', default=True) self.api.register(self._get_hash, 'get_hash', default=True)
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True, def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
timeout=None, callback=None): timeout=None, callback=None):
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
vcard = vcard['vcard_temp'] vcard = vcard['vcard_temp']
@ -69,7 +69,7 @@ class XEP_0153(BasePlugin):
own_jid = (jid.bare == self.xmpp.boundjid.bare) own_jid = (jid.bare == self.xmpp.boundjid.bare)
if self.xmpp.is_component: if self.xmpp.is_component:
own_jid = (jid.domain == self.xmpp.boundjid.domain) own_jid = (jid.domain == self.xmpp.boundjid.domain)
if jid is not None: if jid is not None:
jid = jid.bare jid = jid.bare
self.api['set_hash'](jid, args=None) self.api['set_hash'](jid, args=None)
@ -77,7 +77,7 @@ class XEP_0153(BasePlugin):
self.xmpp.roster[jid].send_last_presence() self.xmpp.roster[jid].send_last_presence()
iq = self.xmpp['xep_0054'].get_vcard( iq = self.xmpp['xep_0054'].get_vcard(
jid=jid, jid=jid,
ifrom=self.xmpp.boundjid) ifrom=self.xmpp.boundjid)
data = iq['vcard_temp']['PHOTO']['BINVAL'] data = iq['vcard_temp']['PHOTO']['BINVAL']
if not data: if not data:

View File

@ -56,7 +56,7 @@ class XEP_0163(BasePlugin):
jid -- Optionally specify the JID. jid -- Optionally specify the JID.
""" """
if not isinstance(namespace, set) and not isinstance(namespace, list): if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace] namespace = [namespace]
for ns in namespace: for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns, self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
@ -75,7 +75,7 @@ class XEP_0163(BasePlugin):
jid -- Optionally specify the JID. jid -- Optionally specify the JID.
""" """
if not isinstance(namespace, set) and not isinstance(namespace, list): if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace] namespace = [namespace]
for ns in namespace: for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid, self.xmpp['xep_0030'].del_feature(jid=jid,
@ -109,6 +109,7 @@ class XEP_0163(BasePlugin):
node = stanza.namespace node = stanza.namespace
return self.xmpp['xep_0060'].publish(ifrom, node, return self.xmpp['xep_0060'].publish(ifrom, node,
id=id,
payload=stanza.xml, payload=stanza.xml,
options=options, options=options,
ifrom=ifrom, ifrom=ifrom,

View File

@ -78,7 +78,7 @@ class XEP_0172(BasePlugin):
be executed when a reply stanza is received. be executed when a reply stanza is received.
""" """
nick = UserNick() nick = UserNick()
return self.xmpp['xep_0163'].publish(nick, return self.xmpp['xep_0163'].publish(nick,
node=UserNick.namespace, node=UserNick.namespace,
ifrom=ifrom, ifrom=ifrom,
block=block, block=block,

View File

@ -100,13 +100,13 @@ class XEP_0184(BasePlugin):
if not isinstance(stanza, Message): if not isinstance(stanza, Message):
return stanza return stanza
if stanza['request_receipt']: if stanza['request_receipt']:
return stanza return stanza
if not stanza['type'] in self.ack_types: if not stanza['type'] in self.ack_types:
return stanza return stanza
if stanza['receipt']: if stanza['receipt']:
return stanza return stanza

View File

@ -82,7 +82,6 @@ class Resumed(StanzaBase):
self._set_attr('h', str(val)) self._set_attr('h', str(val))
class Failed(StanzaBase, Error): class Failed(StanzaBase, Error):
name = 'failed' name = 'failed'
namespace = 'urn:xmpp:sm:3' namespace = 'urn:xmpp:sm:3'
@ -106,7 +105,7 @@ class StreamManagement(ElementBase):
self.del_required() self.del_required()
if val: if val:
self._set_sub_text('required', '', keep=True) self._set_sub_text('required', '', keep=True)
def del_required(self): def del_required(self):
self._del_sub('required') self._del_sub('required')
@ -117,7 +116,7 @@ class StreamManagement(ElementBase):
self.del_optional() self.del_optional()
if val: if val:
self._set_sub_text('optional', '', keep=True) self._set_sub_text('optional', '', keep=True)
def del_optional(self): def del_optional(self):
self._del_sub('optional') self._del_sub('optional')

View File

@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
MAX_SEQ = 2**32 MAX_SEQ = 2 ** 32
class XEP_0198(BasePlugin): class XEP_0198(BasePlugin):
@ -69,7 +69,7 @@ class XEP_0198(BasePlugin):
self.enabled = threading.Event() self.enabled = threading.Event()
self.unacked_queue = collections.deque() self.unacked_queue = collections.deque()
self.seq_lock = threading.Lock() self.seq_lock = threading.Lock()
self.handled_lock = threading.Lock() self.handled_lock = threading.Lock()
self.ack_lock = threading.Lock() self.ack_lock = threading.Lock()
@ -197,7 +197,7 @@ class XEP_0198(BasePlugin):
def _handle_enabled(self, stanza): def _handle_enabled(self, stanza):
"""Save the SM-ID, if provided. """Save the SM-ID, if provided.
Raises an :term:`sm_enabled` event. Raises an :term:`sm_enabled` event.
""" """
self.xmpp.features.add('stream_management') self.xmpp.features.add('stream_management')
@ -231,7 +231,7 @@ class XEP_0198(BasePlugin):
def _handle_ack(self, ack): def _handle_ack(self, ack):
"""Process a server ack by freeing acked stanzas from the queue. """Process a server ack by freeing acked stanzas from the queue.
Raises a :term:`stanza_acked` event for each acked stanza. Raises a :term:`stanza_acked` event for each acked stanza.
""" """
if ack['h'] == self.last_ack: if ack['h'] == self.last_ack:
@ -243,10 +243,10 @@ class XEP_0198(BasePlugin):
log.debug("Ack: %s, Last Ack: %s, " + \ log.debug("Ack: %s, Last Ack: %s, " + \
"Unacked: %s, Num Acked: %s, " + \ "Unacked: %s, Num Acked: %s, " + \
"Remaining: %s", "Remaining: %s",
ack['h'], ack['h'],
self.last_ack, self.last_ack,
num_unacked, num_unacked,
num_acked, num_acked,
num_unacked - num_acked) num_unacked - num_acked)
for x in range(num_acked): for x in range(num_acked):
seq, stanza = self.unacked_queue.popleft() seq, stanza = self.unacked_queue.popleft()

View File

@ -40,8 +40,12 @@ class XEP_0202(BasePlugin):
# custom function can be supplied which accepts # custom function can be supplied which accepts
# the JID of the entity to query for the time. # the JID of the entity to query for the time.
self.local_time = self.config.get('local_time', None) self.local_time = self.config.get('local_time', None)
def default_local_time(jid):
return xep_0082.datetime(offset=self.tz_offset)
if not self.local_time: if not self.local_time:
self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset) self.local_time = default_local_time
self.xmpp.registerHandler( self.xmpp.registerHandler(
Callback('Entity Time', Callback('Entity Time',

View File

@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay
from sleekxmpp.plugins.xep_0203.delay import XEP_0203 from sleekxmpp.plugins.xep_0203.delay import XEP_0203
register_plugin(XEP_0203) register_plugin(XEP_0203)
# Retain some backwards compatibility # Retain some backwards compatibility
xep_0203 = XEP_0203 xep_0203 = XEP_0203

View File

@ -0,0 +1,126 @@
"""
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 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 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')
for field, value in self.profile.items():
if field not in options.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)

View File

@ -0,0 +1,126 @@
"""
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')
for field, value in self.profile.items():
if field not in options.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)

View File

@ -1,6 +1,6 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP. This file is part of SleekXMPP.

View File

@ -1,6 +1,6 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP. This file is part of SleekXMPP.
@ -58,7 +58,6 @@ class XEP_0231(BasePlugin):
self.api.register(self._set_bob, 'set_bob', default=True) self.api.register(self._set_bob, 'set_bob', default=True)
self.api.register(self._del_bob, 'del_bob', default=True) self.api.register(self._del_bob, 'del_bob', default=True)
def set_bob(self, data, mtype, cid=None, max_age=None): def set_bob(self, data, mtype, cid=None, max_age=None):
if cid is None: if cid is None:
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest() cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()
@ -73,7 +72,7 @@ class XEP_0231(BasePlugin):
return cid return cid
def get_bob(self, jid=None, cid=None, cached=True, ifrom=None, def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
block=True, timeout=None, callback=None): block=True, timeout=None, callback=None):
if cached: if cached:
data = self.api['get_bob'](None, None, ifrom, args=cid) data = self.api['get_bob'](None, None, ifrom, args=cid)
@ -112,7 +111,7 @@ class XEP_0231(BasePlugin):
iq.send() iq.send()
def _handle_bob(self, stanza): def _handle_bob(self, stanza):
self.api['set_bob'](stanza['from'], None, self.api['set_bob'](stanza['from'], None,
stanza['to'], args=stanza['bob']) stanza['to'], args=stanza['bob'])
self.xmpp.event('bob', stanza) self.xmpp.event('bob', stanza)

View File

@ -1,6 +1,6 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP. This file is part of SleekXMPP.

View File

@ -0,0 +1,18 @@
"""
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)

View File

@ -0,0 +1,40 @@
"""
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):
self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
register_stanza_plugin(Message, SecurityLabel)
register_stanza_plugin(Iq, Catalog)
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)

View File

@ -0,0 +1,142 @@
"""
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.thirdparty.suelta.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)

View File

@ -307,34 +307,29 @@ class RosterItem(object):
p['from'] = self.owner p['from'] = self.owner
p.send() p.send()
def send_presence(self, ptype=None, pshow=None, pstatus=None, def send_presence(self, **kwargs):
ppriority=None, pnick=None):
""" """
Create, initialize, and send a Presence stanza. 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: Arguments:
pshow -- The presence's show value. pshow -- The presence's show value.
pstatus -- The presence's status message. pstatus -- The presence's status message.
ppriority -- This connections' priority. 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'. ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender. pnick -- Optional nickname of the presence's sender.
""" """
p = self.xmpp.make_presence(pshow=pshow, if self.xmpp.is_component and not kwargs.get('pfrom', ''):
pstatus=pstatus, kwargs['pfrom'] = self.owner
ppriority=ppriority, if not kwargs.get('pto', ''):
ptype=ptype, kwargs['pto'] = self.jid
pnick=pnick, self.xmpp.send_presence(**kwargs)
pto=self.jid)
if self.xmpp.is_component:
p['from'] = self.owner
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
def send_last_presence(self): def send_last_presence(self):
if self.last_status is None: if self.last_status is None:

View File

@ -6,9 +6,11 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
from sleekxmpp.stanza import Presence
from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterNode from sleekxmpp.roster import RosterNode
class Roster(object): class Roster(object):
""" """
@ -55,6 +57,33 @@ class Roster(object):
for node in self.db.entries(None, {}): for node in self.db.entries(None, {}):
self.add(node) 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): def __getitem__(self, key):
""" """
Return the roster node for a JID. Return the roster node for a JID.
@ -121,29 +150,27 @@ class Roster(object):
for node in self: for node in self:
self[node].reset() self[node].reset()
def send_presence(self, pshow=None, pstatus=None, ppriority=None, def send_presence(self, **kwargs):
pto=None, pfrom=None, ptype=None, pnick=None):
""" """
Create, initialize, and send a Presence stanza. Create, initialize, and send a Presence stanza.
Forwards the send request to the appropriate roster to If no recipient is specified, send the presence immediately.
perform the actual sending. Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments: Arguments:
pshow -- The presence's show value. pshow -- The presence's show value.
pstatus -- The presence's status message. pstatus -- The presence's status message.
ppriority -- This connections' priority. ppriority -- This connections' priority.
pto -- The recipient of a directed presence. 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'. ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
pnick -- Optional nickname of the presence's sender. pnick -- Optional nickname of the presence's sender.
""" """
self[pfrom].send_presence(ptype=ptype, if self.xmpp.is_component and not kwargs.get('pfrom', ''):
pshow=pshow, kwargs['pfrom'] = self.jid
pstatus=pstatus, self.xmpp.send_presence(**kwargs)
ppriority=ppriority,
pnick=pnick,
pto=pto)
@property @property
def auto_authorize(self): def auto_authorize(self):

View File

@ -6,6 +6,8 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import threading
from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterItem from sleekxmpp.roster import RosterItem
@ -59,13 +61,14 @@ class RosterNode(object):
self.last_status = None self.last_status = None
self._version = '' self._version = ''
self._jids = {} self._jids = {}
self._last_status_lock = threading.Lock()
if self.db: if self.db:
if hasattr(self.db, 'version'): if hasattr(self.db, 'version'):
self._version = self.db.version(self.jid) self._version = self.db.version(self.jid)
for jid in self.db.entries(self.jid): for jid in self.db.entries(self.jid):
self.add(jid) self.add(jid)
@property @property
def version(self): def version(self):
"""Retrieve the roster's version ID.""" """Retrieve the roster's version ID."""
@ -146,7 +149,7 @@ class RosterNode(object):
self.db = db self.db = db
existing_entries = set(self._jids) existing_entries = set(self._jids)
new_entries = set(self.db.entries(self.jid, {})) new_entries = set(self.db.entries(self.jid, {}))
for jid in existing_entries: for jid in existing_entries:
self._jids[jid].set_backend(db, save) self._jids[jid].set_backend(db, save)
for jid in new_entries - existing_entries: for jid in new_entries - existing_entries:
@ -291,8 +294,7 @@ class RosterNode(object):
for jid in self: for jid in self:
self[jid].reset() self[jid].reset()
def send_presence(self, ptype=None, pshow=None, pstatus=None, def send_presence(self, **kwargs):
ppriority=None, pnick=None, pto=None):
""" """
Create, initialize, and send a Presence stanza. Create, initialize, and send a Presence stanza.
@ -305,27 +307,14 @@ class RosterNode(object):
pstatus -- The presence's status message. pstatus -- The presence's status message.
ppriority -- This connections' priority. ppriority -- This connections' priority.
pto -- The recipient of a directed presence. 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'. ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
""" """
if pto: if self.xmpp.is_component and not kwargs.get('pfrom', ''):
self[pto].send_presence(ptype, pshow, pstatus, kwargs['pfrom'] = self.jid
ppriority, pnick) self.xmpp.send_presence(**kwargs)
else:
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick)
if self.xmpp.is_component:
p['from'] = self.jid
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
def send_last_presence(self): def send_last_presence(self):
if self.last_status is None: if self.last_status is None:

View File

@ -51,7 +51,8 @@ class Error(ElementBase):
namespace = 'jabber:client' namespace = 'jabber:client'
name = 'error' name = 'error'
plugin_attrib = 'error' plugin_attrib = 'error'
interfaces = set(('code', 'condition', 'text', 'type')) interfaces = set(('code', 'condition', 'text', 'type',
'gone', 'redirect'))
sub_interfaces = set(('text',)) sub_interfaces = set(('text',))
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
@ -88,7 +89,7 @@ class Error(ElementBase):
def get_condition(self): def get_condition(self):
"""Return the condition element's name.""" """Return the condition element's name."""
for child in self.xml.getchildren(): for child in self.xml:
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1] cond = child.tag.split('}', 1)[-1]
if cond in self.conditions: if cond in self.conditions:
@ -109,7 +110,7 @@ class Error(ElementBase):
def del_condition(self): def del_condition(self):
"""Remove the condition element.""" """Remove the condition element."""
for child in self.xml.getchildren(): for child in self.xml:
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1] tag = child.tag.split('}', 1)[-1]
if tag in self.conditions: if tag in self.conditions:
@ -135,6 +136,33 @@ class Error(ElementBase):
self._del_sub('{%s}text' % self.condition_ns) self._del_sub('{%s}text' % self.condition_ns)
return self 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. # To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility. # Deprecated method names are re-mapped for backwards compatibility.

View File

@ -122,7 +122,7 @@ class Iq(RootStanza):
def get_query(self): def get_query(self):
"""Return the namespace of the <query> element.""" """Return the namespace of the <query> element."""
for child in self.xml.getchildren(): for child in self.xml:
if child.tag.endswith('query'): if child.tag.endswith('query'):
ns = child.tag.split('}')[0] ns = child.tag.split('}')[0]
if '{' in ns: if '{' in ns:
@ -132,7 +132,7 @@ class Iq(RootStanza):
def del_query(self): def del_query(self):
"""Remove the <query> element.""" """Remove the <query> element."""
for child in self.xml.getchildren(): for child in self.xml:
if child.tag.endswith('query'): if child.tag.endswith('query'):
self.xml.remove(child) self.xml.remove(child)
return self return self

View File

@ -78,7 +78,8 @@ class RootStanza(StanzaBase):
self['error']['type'] = 'cancel' self['error']['type'] = 'cancel'
self.send() self.send()
# log the error # log the error
log.exception('Error handling {%s}%s stanza' , self.namespace, self.name) log.exception('Error handling {%s}%s stanza',
self.namespace, self.name)
# Finally raise the exception to a global exception handler # Finally raise the exception to a global exception handler
self.stream.exception(e) self.stream.exception(e)

View File

@ -47,7 +47,7 @@ class Roster(ElementBase):
roster versioning. roster versioning.
""" """
return self.xml.attrib.get('ver', None) return self.xml.attrib.get('ver', None)
def set_ver(self, ver): def set_ver(self, ver):
""" """
Ensure handling an empty ver attribute propery. Ensure handling an empty ver attribute propery.
@ -101,7 +101,8 @@ class Roster(ElementBase):
items[item['jid']] = item.values items[item['jid']] = item.values
# Remove extra JID reference to keep everything # Remove extra JID reference to keep everything
# backward compatible # backward compatible
del items[item['jid']]['jid'] del items[item['jid']]['jid']
del items[item['jid']]['lang']
return items return items
def del_items(self): def del_items(self):

View File

@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase):
""" """
namespace = 'http://etherx.jabber.org/streams' namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text')) interfaces = set(('condition', 'text', 'see_other_host'))
conditions = set(( conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict', 'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown', 'connection-timeout', 'host-gone', 'host-unknown',
@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase):
'unsupported-feature', 'unsupported-stanza-type', 'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version')) 'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams' 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)

View File

@ -6,6 +6,7 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.xmlstream import StanzaBase from sleekxmpp.xmlstream import StanzaBase
@ -28,7 +29,10 @@ class StreamFeatures(StanzaBase):
def get_features(self): def get_features(self):
""" """
""" """
return self.plugins features = OrderedDict()
for (name, lang), plugin in self.plugins.items():
features[name] = plugin
return features
def set_features(self, value): def set_features(self, value):
""" """

View File

@ -76,7 +76,7 @@ class SleekTest(unittest.TestCase):
known_prefixes[prefix], known_prefixes[prefix],
xml_string) xml_string)
xml = self.parse_xml(xml_string) xml = self.parse_xml(xml_string)
xml = xml.getchildren()[0] xml = list(xml)[0]
return xml return xml
else: else:
self.fail("XML data was mal-formed:\n%s" % xml_string) self.fail("XML data was mal-formed:\n%s" % xml_string)
@ -333,6 +333,8 @@ class SleekTest(unittest.TestCase):
# Remove unique ID prefix to make it easier to test # Remove unique ID prefix to make it easier to test
self.xmpp._id_prefix = '' self.xmpp._id_prefix = ''
self.xmpp._disconnect_wait_for_threads = False 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 # We will use this to wait for the session_start event
# for live connections. # for live connections.
@ -386,6 +388,7 @@ class SleekTest(unittest.TestCase):
sid='', sid='',
stream_ns="http://etherx.jabber.org/streams", stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client", default_ns="jabber:client",
default_lang="en",
version="1.0", version="1.0",
xml_header=True): xml_header=True):
""" """
@ -413,6 +416,8 @@ class SleekTest(unittest.TestCase):
parts.append('from="%s"' % sfrom) parts.append('from="%s"' % sfrom)
if sid: if sid:
parts.append('id="%s"' % sid) parts.append('id="%s"' % sid)
if default_lang:
parts.append('xml:lang="%s"' % default_lang)
parts.append('version="%s"' % version) parts.append('version="%s"' % version)
parts.append('xmlns:stream="%s"' % stream_ns) parts.append('xmlns:stream="%s"' % stream_ns)
parts.append('xmlns="%s"' % default_ns) parts.append('xmlns="%s"' % default_ns)
@ -512,9 +517,9 @@ class SleekTest(unittest.TestCase):
if '{%s}lang' % xml_ns in recv_xml.attrib: if '{%s}lang' % xml_ns in recv_xml.attrib:
del recv_xml.attrib['{%s}lang' % xml_ns] del recv_xml.attrib['{%s}lang' % xml_ns]
if recv_xml.getchildren: if list(recv_xml):
# We received more than just the header # We received more than just the header
for xml in recv_xml.getchildren(): for xml in recv_xml:
self.xmpp.socket.recv_data(tostring(xml)) self.xmpp.socket.recv_data(tostring(xml))
attrib = recv_xml.attrib attrib = recv_xml.attrib
@ -564,6 +569,7 @@ class SleekTest(unittest.TestCase):
sid='', sid='',
stream_ns="http://etherx.jabber.org/streams", stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client", default_ns="jabber:client",
default_lang="en",
version="1.0", version="1.0",
xml_header=False, xml_header=False,
timeout=1): timeout=1):
@ -585,6 +591,7 @@ class SleekTest(unittest.TestCase):
header = self.make_header(sto, sfrom, sid, header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns, stream_ns=stream_ns,
default_ns=default_ns, default_ns=default_ns,
default_lang=default_lang,
version=version, version=version,
xml_header=xml_header) xml_header=xml_header)
sent_header = self.xmpp.socket.next_sent(timeout) sent_header = self.xmpp.socket.next_sent(timeout)
@ -691,7 +698,7 @@ class SleekTest(unittest.TestCase):
if xml.tag.startswith('{'): if xml.tag.startswith('{'):
return return
xml.tag = '{%s}%s' % (ns, xml.tag) xml.tag = '{%s}%s' % (ns, xml.tag)
for child in xml.getchildren(): for child in xml:
self.fix_namespaces(child, ns) self.fix_namespaces(child, ns)
def compare(self, xml, *other): def compare(self, xml, *other):
@ -734,7 +741,7 @@ class SleekTest(unittest.TestCase):
return False return False
# Step 4: Check children count # Step 4: Check children count
if len(xml.getchildren()) != len(other.getchildren()): if len(list(xml)) != len(list(other)):
return False return False
# Step 5: Recursively check children # Step 5: Recursively check children

View File

@ -7,11 +7,12 @@ try:
from pyasn1.type.univ import Any, ObjectIdentifier, OctetString from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
from pyasn1.type.char import BMPString, IA5String, UTF8String from pyasn1.type.char import BMPString, IA5String, UTF8String
from pyasn1.type.useful import GeneralizedTime from pyasn1.type.useful import GeneralizedTime
from pyasn1_modules.rfc2459 import Certificate, DirectoryString, SubjectAltName, GeneralNames, GeneralName 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_ce_subjectAltName as SUBJECT_ALT_NAME
from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5') XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7') SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
@ -149,7 +150,7 @@ def verify(expected, raw_cert):
expected_wild = expected[expected.index('.'):] expected_wild = expected[expected.index('.'):]
expected_srv = '_xmpp-client.%s' % expected expected_srv = '_xmpp-client.%s' % expected
for name in cert_names['XMPPAddr']: for name in cert_names['XMPPAddr']:
if name == expected: if name == expected:
return True return True
for name in cert_names['SRV']: for name in cert_names['SRV']:

View File

@ -49,7 +49,7 @@ class BaseHandler(object):
def match(self, xml): def match(self, xml):
"""Compare a stanza or XML object with the handler's matcher. """Compare a stanza or XML object with the handler's matcher.
:param xml: An XML or :param xml: An XML or
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object
""" """
return self._matcher.match(xml) return self._matcher.match(xml)
@ -73,7 +73,7 @@ class BaseHandler(object):
self._payload = payload self._payload = payload
def check_delete(self): def check_delete(self):
"""Check if the handler should be removed from the list """Check if the handler should be removed from the list
of stream handlers. of stream handlers.
""" """
return self._destroy return self._destroy

View File

@ -33,7 +33,7 @@ class Callback(BaseHandler):
:param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase` :param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
derived object for matching stanza objects. derived object for matching stanza objects.
:param pointer: The function to execute during callback. :param pointer: The function to execute during callback.
:param bool thread: **DEPRECATED.** Remains only for :param bool thread: **DEPRECATED.** Remains only for
backwards compatibility. backwards compatibility.
:param bool once: Indicates if the handler should be used only :param bool once: Indicates if the handler should be used only
once. Defaults to False. once. Defaults to False.

View File

@ -34,9 +34,9 @@ class MatchXMLMask(MatcherBase):
<message xmlns="jabber:client"><body /></message> <message xmlns="jabber:client"><body /></message>
Use of XMLMask is discouraged, and Use of XMLMask is discouraged, and
:class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or :class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
should be used instead. should be used instead.
The use of namespaces in the mask comparison is controlled by The use of namespaces in the mask comparison is controlled by
@ -151,8 +151,8 @@ class MatchXMLMask(MatcherBase):
""" """
tag = tag.split('}')[-1] tag = tag.split('}')[-1]
try: try:
children = [c.tag.split('}')[-1] for c in xml.getchildren()] children = [c.tag.split('}')[-1] for c in xml]
index = children.index(tag) index = children.index(tag)
except ValueError: except ValueError:
return None return None
return xml.getchildren()[index] return list(xml)[index]

View File

@ -77,10 +77,10 @@ class MatchXPath(MatcherBase):
# Skip empty tag name artifacts from the cleanup phase. # Skip empty tag name artifacts from the cleanup phase.
continue continue
children = [c.tag.split('}')[-1] for c in xml.getchildren()] children = [c.tag.split('}')[-1] for c in xml]
try: try:
index = children.index(tag) index = children.index(tag)
except ValueError: except ValueError:
return False return False
xml = xml.getchildren()[index] xml = list(xml)[index]
return True return True

View File

@ -57,7 +57,7 @@ class Task(object):
#: The keyword arguments to pass to :attr:`callback`. #: The keyword arguments to pass to :attr:`callback`.
self.kwargs = kwargs or {} self.kwargs = kwargs or {}
#: Indicates if the task should repeat after executing, #: Indicates if the task should repeat after executing,
#: using the same :attr:`seconds` delay. #: using the same :attr:`seconds` delay.
self.repeat = repeat self.repeat = repeat
@ -103,7 +103,7 @@ class Scheduler(object):
def __init__(self, parentstop=None): def __init__(self, parentstop=None):
#: A queue for storing tasks #: A queue for storing tasks
self.addq = queue.Queue() self.addq = queue.Queue()
#: A list of tasks in order of execution time. #: A list of tasks in order of execution time.
self.schedule = [] self.schedule = []

View File

@ -12,6 +12,8 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
from __future__ import with_statement, unicode_literals
import copy import copy
import logging import logging
import weakref import weakref
@ -29,6 +31,9 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml')) 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): def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
@ -40,7 +45,7 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
substanzas for the parent, using ``parent['substanzas']``. If the substanzas for the parent, using ``parent['substanzas']``. If the
attribute ``plugin_multi_attrib`` was defined for the plugin, then attribute ``plugin_multi_attrib`` was defined for the plugin, then
the substanza set can be filtered to only instances of the plugin the substanza set can be filtered to only instances of the plugin
class. For example, given a plugin class ``Foo`` with class. For example, given a plugin class ``Foo`` with
``plugin_multi_attrib = 'foos'`` then:: ``plugin_multi_attrib = 'foos'`` then::
parent['foos'] parent['foos']
@ -94,6 +99,14 @@ def multifactory(stanza, plugin_attrib):
""" """
Returns a ElementBase class for handling reoccuring child stanzas 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): class Multi(ElementBase):
""" """
Template class for multifactory Template class for multifactory
@ -101,28 +114,45 @@ def multifactory(stanza, plugin_attrib):
def setup(self, xml=None): def setup(self, xml=None):
self.xml = ET.Element('') self.xml = ET.Element('')
def get_multi(self): def get_multi(self, lang=None):
parent = self.parent() parent = self.parent()
res = filter(lambda sub: isinstance(sub, self._multistanza), parent) if not lang or lang == '*':
res = filter(plugin_filter(self), parent)
else:
res = filter(plugin_filter(self, lang), parent)
return list(res) return list(res)
def set_multi(self, val): def set_multi(self, val, lang=None):
parent = self.parent() parent = self.parent()
del parent[self.plugin_attrib] del_multi = getattr(self, 'del_%s' % plugin_attrib)
del_multi(lang)
for sub in val: for sub in val:
parent.append(sub) parent.append(sub)
def del_multi(self): def del_multi(self, lang=None):
parent = self.parent() parent = self.parent()
res = filter(lambda sub: isinstance(sub, self._multistanza), parent) if not lang or lang == '*':
for stanza in list(res): res = filter(plugin_filter(self), parent)
parent.iterables.remove(stanza) else:
parent.xml.remove(stanza.xml) 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:
pass
else:
for stanza in list(res):
parent.iterables.remove(stanza)
parent.xml.remove(stanza.xml)
Multi.is_extension = True Multi.is_extension = True
Multi.plugin_attrib = plugin_attrib Multi.plugin_attrib = plugin_attrib
Multi._multistanza = stanza Multi._multistanza = stanza
Multi.interfaces = (plugin_attrib,) Multi.interfaces = set([plugin_attrib])
Multi.lang_interfaces = set([plugin_attrib])
setattr(Multi, "get_%s" % plugin_attrib, get_multi) setattr(Multi, "get_%s" % plugin_attrib, get_multi)
setattr(Multi, "set_%s" % plugin_attrib, set_multi) setattr(Multi, "set_%s" % plugin_attrib, set_multi)
setattr(Multi, "del_%s" % plugin_attrib, del_multi) setattr(Multi, "del_%s" % plugin_attrib, del_multi)
@ -231,8 +261,10 @@ class ElementBase(object):
directly from the parent stanza, as shown below, but retrieving directly from the parent stanza, as shown below, but retrieving
information will require all interfaces to be used, as so:: information will require all interfaces to be used, as so::
>>> message['custom'] = 'bar' # Same as using message['custom']['custom'] >>> # Same as using message['custom']['custom']
>>> message['custom']['custom'] # Must use all interfaces >>> message['custom'] = 'bar'
>>> # Must use all interfaces
>>> message['custom']['custom']
'bar' 'bar'
If the plugin sets :attr:`is_extension` to ``True``, then both setting If the plugin sets :attr:`is_extension` to ``True``, then both setting
@ -245,13 +277,13 @@ class ElementBase(object):
:param xml: Initialize the stanza object with an existing XML object. :param xml: Initialize the stanza object with an existing XML object.
:param parent: Optionally specify a parent stanza object will will :param parent: Optionally specify a parent stanza object will
contain this substanza. contain this substanza.
""" """
#: The XML tag name of the element, not including any namespace #: The XML tag name of the element, not including any namespace
#: prefixes. For example, an :class:`ElementBase` object for ``<message />`` #: prefixes. For example, an :class:`ElementBase` object for
#: would use ``name = 'message'``. #: ``<message />`` would use ``name = 'message'``.
name = 'stanza' name = 'stanza'
#: The XML namespace for the element. Given ``<foo xmlns="bar" />``, #: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
@ -289,14 +321,17 @@ class ElementBase(object):
#: subelements of the underlying XML object. Using this set, the text #: subelements of the underlying XML object. Using this set, the text
#: of these subelements may be set, retrieved, or removed without #: of these subelements may be set, retrieved, or removed without
#: needing to define custom methods. #: needing to define custom methods.
sub_interfaces = tuple() sub_interfaces = set()
#: A subset of :attr:`interfaces` which maps the presence of #: A subset of :attr:`interfaces` which maps the presence of
#: subelements to boolean values. Using this set allows for quickly #: subelements to boolean values. Using this set allows for quickly
#: checking for the existence of empty subelements like ``<required />``. #: checking for the existence of empty subelements like ``<required />``.
#: #:
#: .. versionadded:: 1.1 #: .. versionadded:: 1.1
bool_interfaces = tuple() bool_interfaces = set()
#: .. versionadded:: 1.1.2
lang_interfaces = set()
#: In some cases you may wish to override the behaviour of one of the #: In some cases you may wish to override the behaviour of one of the
#: parent stanza's interfaces. The ``overrides`` list specifies the #: parent stanza's interfaces. The ``overrides`` list specifies the
@ -363,7 +398,7 @@ class ElementBase(object):
subitem = set() subitem = set()
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``. #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
xml_ns = 'http://www.w3.org/XML/1998/namespace' xml_ns = XML_NS
def __init__(self, xml=None, parent=None): def __init__(self, xml=None, parent=None):
self._index = 0 self._index = 0
@ -375,6 +410,7 @@ class ElementBase(object):
#: An ordered dictionary of plugin stanzas, mapped by their #: An ordered dictionary of plugin stanzas, mapped by their
#: :attr:`plugin_attrib` value. #: :attr:`plugin_attrib` value.
self.plugins = OrderedDict() self.plugins = OrderedDict()
self.loaded_plugins = set()
#: A list of child stanzas whose class is included in #: A list of child stanzas whose class is included in
#: :attr:`plugin_iterables`. #: :attr:`plugin_iterables`.
@ -385,6 +421,12 @@ class ElementBase(object):
#: ``'{namespace}elementname'``. #: ``'{namespace}elementname'``.
self.tag = self.tag_name() self.tag = self.tag_name()
if 'lang' not in self.interfaces:
if isinstance(self.interfaces, tuple):
self.interfaces += ('lang',)
else:
self.interfaces.add('lang')
#: A :class:`weakref.weakref` to the parent stanza, if there is one. #: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``. #: If not, then :attr:`parent` is ``None``.
self.parent = None self.parent = None
@ -403,13 +445,12 @@ class ElementBase(object):
return return
# Initialize values using provided XML # Initialize values using provided XML
for child in self.xml.getchildren(): for child in self.xml:
if child.tag in self.plugin_tag_map: if child.tag in self.plugin_tag_map:
plugin_class = self.plugin_tag_map[child.tag] plugin_class = self.plugin_tag_map[child.tag]
plugin = plugin_class(child, self) self.init_plugin(plugin_class.plugin_attrib,
self.plugins[plugin.plugin_attrib] = plugin existing_xml=child,
if plugin_class in self.plugin_iterables: reuse=False)
self.iterables.append(plugin)
def setup(self, xml=None): def setup(self, xml=None):
"""Initialize the stanza's XML contents. """Initialize the stanza's XML contents.
@ -443,7 +484,7 @@ class ElementBase(object):
# We did not generate XML # We did not generate XML
return False return False
def enable(self, attrib): def enable(self, attrib, lang=None):
"""Enable and initialize a stanza plugin. """Enable and initialize a stanza plugin.
Alias for :meth:`init_plugin`. Alias for :meth:`init_plugin`.
@ -451,24 +492,67 @@ class ElementBase(object):
:param string attrib: The :attr:`plugin_attrib` value of the :param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable. plugin to enable.
""" """
return self.init_plugin(attrib) return self.init_plugin(attrib, lang)
def init_plugin(self, attrib): def _get_plugin(self, name, lang=None):
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 self.init_plugin(name, lang)
else:
if (name, lang) in self.plugins:
return self.plugins[(name, lang)]
else:
return self.init_plugin(name, lang)
def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
"""Enable and initialize a stanza plugin. """Enable and initialize a stanza plugin.
:param string attrib: The :attr:`plugin_attrib` value of the :param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable. plugin to enable.
""" """
if attrib not in self.plugins: if lang is None:
plugin_class = self.plugin_attrib_map[attrib] lang = self.get_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)]
if existing_xml is None:
existing_xml = self.xml.find(plugin_class.tag_name()) existing_xml = self.xml.find(plugin_class.tag_name())
plugin = plugin_class(parent=self, xml=existing_xml)
self.plugins[attrib] = plugin if existing_xml is not None:
if plugin_class in self.plugin_iterables: if existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang:
self.iterables.append(plugin) existing_xml = None
if plugin_class.plugin_multi_attrib:
self.init_plugin(plugin_class.plugin_multi_attrib) plugin = plugin_class(parent=self, xml=existing_xml)
return self
if plugin.is_extension:
self.plugins[(attrib, None)] = plugin
else:
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): def _get_stanza_values(self):
"""Return A JSON/dictionary version of the XML content """Return A JSON/dictionary version of the XML content
@ -492,8 +576,14 @@ class ElementBase(object):
values = {} values = {}
for interface in self.interfaces: for interface in self.interfaces:
values[interface] = self[interface] values[interface] = self[interface]
if interface in self.lang_interfaces:
values['%s|*' % interface] = self['%s|*' % interface]
for plugin, stanza in self.plugins.items(): for plugin, stanza in self.plugins.items():
values[plugin] = stanza.values lang = stanza['lang']
if lang:
values['%s|%s' % (plugin, lang)] = stanza.values
else:
values[plugin[0]] = stanza.values
if self.iterables: if self.iterables:
iterables = [] iterables = []
for stanza in self.iterables: for stanza in self.iterables:
@ -517,6 +607,11 @@ class ElementBase(object):
p in self.plugin_iterables] p in self.plugin_iterables]
for interface, value in values.items(): 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 == 'substanzas': if interface == 'substanzas':
# Remove existing substanzas # Remove existing substanzas
for stanza in self.iterables: for stanza in self.iterables:
@ -535,12 +630,12 @@ class ElementBase(object):
self.iterables.append(sub) self.iterables.append(sub)
break break
elif interface in self.interfaces: elif interface in self.interfaces:
self[interface] = value self[full_interface] = value
elif interface in self.plugin_attrib_map: elif interface in self.plugin_attrib_map:
if interface not in iterable_interfaces: if interface not in iterable_interfaces:
if interface not in self.plugins: plugin = self._get_plugin(interface, lang)
self.init_plugin(interface) if plugin:
self.plugins[interface].values = value plugin.values = value
return self return self
def __getitem__(self, attrib): def __getitem__(self, attrib):
@ -572,6 +667,15 @@ class ElementBase(object):
:param string attrib: The name of the requested stanza interface. :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 ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib == 'substanzas': if attrib == 'substanzas':
return self.iterables return self.iterables
elif attrib in self.interfaces: elif attrib in self.interfaces:
@ -579,32 +683,31 @@ class ElementBase(object):
get_method2 = "get%s" % attrib.title() get_method2 = "get%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None) name = self.plugin_overrides.get(get_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(name, lang)
self.init_plugin(plugin) if plugin:
handler = getattr(self.plugins[plugin], get_method, None) handler = getattr(plugin, get_method, None)
if handler: if handler:
return handler() return handler(**kwargs)
if hasattr(self, get_method): if hasattr(self, get_method):
return getattr(self, get_method)() return getattr(self, get_method)(**kwargs)
elif hasattr(self, get_method2): elif hasattr(self, get_method2):
return getattr(self, get_method2)() return getattr(self, get_method2)(**kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._get_sub_text(attrib) return self._get_sub_text(attrib, lang=lang)
elif attrib in self.bool_interfaces: elif attrib in self.bool_interfaces:
elem = self.xml.find('{%s}%s' % (self.namespace, attrib)) elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
return elem is not None return elem is not None
else: else:
return self._get_attr(attrib) return self._get_attr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(attrib) if plugin and plugin.is_extension:
if self.plugins[attrib].is_extension: return plugin[full_attrib]
return self.plugins[attrib][attrib] return plugin
return self.plugins[attrib]
else: else:
return '' return ''
@ -640,41 +743,58 @@ class ElementBase(object):
:param string attrib: The name of the stanza interface to modify. :param string attrib: The name of the stanza interface to modify.
:param value: The new value of the stanza interface. :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 ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces: if attrib in self.interfaces:
if value is not None: if value is not None:
set_method = "set_%s" % attrib.lower() set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title() set_method2 = "set%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None) name = self.plugin_overrides.get(set_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(name, lang)
self.init_plugin(plugin) if plugin:
handler = getattr(self.plugins[plugin], handler = getattr(plugin, set_method, None)
set_method, None) if handler:
if handler: return handler(value, **kwargs)
return handler(value)
if hasattr(self, set_method): if hasattr(self, set_method):
getattr(self, set_method)(value,) getattr(self, set_method)(value, **kwargs)
elif hasattr(self, set_method2): elif hasattr(self, set_method2):
getattr(self, set_method2)(value,) getattr(self, set_method2)(value, **kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._set_sub_text(attrib, text=value) 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: elif attrib in self.bool_interfaces:
if value: if value:
return self._set_sub_text(attrib, '', keep=True) return self._set_sub_text(attrib, '',
keep=True,
lang=lang)
else: else:
return self._set_sub_text(attrib, '', keep=False) return self._set_sub_text(attrib, '',
keep=False,
lang=lang)
else: else:
self._set_attr(attrib, value) self._set_attr(attrib, value)
else: else:
self.__delitem__(attrib) self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(attrib) if plugin:
self.plugins[attrib][attrib] = value plugin[full_attrib] = value
return self return self
def __delitem__(self, attrib): def __delitem__(self, attrib):
@ -709,40 +829,53 @@ class ElementBase(object):
:param attrib: The name of the affected stanza interface. :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 ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces: if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower() del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title() del_method2 = "del%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None) name = self.plugin_overrides.get(del_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(plugin) if plugin:
handler = getattr(self.plugins[plugin], del_method, None) handler = getattr(plugin, del_method, None)
if handler: if handler:
return handler() return handler(**kwargs)
if hasattr(self, del_method): if hasattr(self, del_method):
getattr(self, del_method)() getattr(self, del_method)(**kwargs)
elif hasattr(self, del_method2): elif hasattr(self, del_method2):
getattr(self, del_method2)() getattr(self, del_method2)(**kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._del_sub(attrib) return self._del_sub(attrib, lang=lang)
elif attrib in self.bool_interfaces: elif attrib in self.bool_interfaces:
return self._del_sub(attrib) return self._del_sub(attrib, lang=lang)
else: else:
self._del_attr(attrib) self._del_attr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib in self.plugins: plugin = self._get_plugin(attrib, lang)
xml = self.plugins[attrib].xml if not plugin:
if self.plugins[attrib].is_extension: return self
del self.plugins[attrib][attrib] if plugin.is_extension:
del self.plugins[attrib] del plugin[full_attrib]
try: del self.plugins[(attrib, None)]
self.xml.remove(xml) else:
except: del self.plugins[(attrib, lang)]
pass self.loaded_plugins.remove(attrib)
try:
self.xml.remove(plugin.xml)
except:
pass
return self return self
def _set_attr(self, name, value): def _set_attr(self, name, value):
@ -781,7 +914,7 @@ class ElementBase(object):
""" """
return self.xml.attrib.get(name, default) return self.xml.attrib.get(name, default)
def _get_sub_text(self, name, default=''): def _get_sub_text(self, name, default='', lang=None):
"""Return the text contents of a sub element. """Return the text contents of a sub element.
In case the element does not exist, or it has no textual content, In case the element does not exist, or it has no textual content,
@ -793,13 +926,38 @@ class ElementBase(object):
not exists. An empty string is returned otherwise. not exists. An empty string is returned otherwise.
""" """
name = self._fix_ns(name) name = self._fix_ns(name)
stanza = self.xml.find(name) if lang == '*':
if stanza is None or stanza.text is None: return self._get_all_sub_text(name, default, None)
return default
else:
return stanza.text
def _set_sub_text(self, name, text=None, keep=False): 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. """Set the text contents of a sub element.
In case the element does not exist, a element will be created, In case the element does not exist, a element will be created,
@ -817,9 +975,14 @@ class ElementBase(object):
""" """
path = self._fix_ns(name, split=True) path = self._fix_ns(name, split=True)
element = self.xml.find(name) element = self.xml.find(name)
parent = self.xml
default_lang = self.get_lang()
if lang is None:
lang = default_lang
if not text and not keep: if not text and not keep:
return self._del_sub(name) return self._del_sub(name, lang=lang)
if element is None: if element is None:
# We need to add the element. If the provided name was # We need to add the element. If the provided name was
@ -833,14 +996,31 @@ class ElementBase(object):
element = self.xml.find("/".join(walked)) element = self.xml.find("/".join(walked))
if element is None: if element is None:
element = ET.Element(ename) element = ET.Element(ename)
if lang:
element.attrib['{%s}lang' % XML_NS] = lang
last_xml.append(element) last_xml.append(element)
parent = last_xml
last_xml = element last_xml = element
element = last_xml element = last_xml
if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang:
element = ET.Element(ename)
if lang:
element.attrib['{%s}lang' % XML_NS] = lang
parent.append(element)
element.text = text element.text = text
return element return element
def _del_sub(self, name, all=False): 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. """Remove sub elements that match the given name or XPath.
If the element is in a path, then any parent elements that become If the element is in a path, then any parent elements that become
@ -854,6 +1034,10 @@ class ElementBase(object):
path = self._fix_ns(name, split=True) path = self._fix_ns(name, split=True)
original_target = path[-1] original_target = path[-1]
default_lang = self.get_lang()
if not lang:
lang = default_lang
for level, _ in enumerate(path): for level, _ in enumerate(path):
# Generate the paths to the target elements and their parent. # Generate the paths to the target elements and their parent.
element_path = "/".join(path[:len(path) - level]) element_path = "/".join(path[:len(path) - level])
@ -866,11 +1050,13 @@ class ElementBase(object):
if parent is None: if parent is None:
parent = self.xml parent = self.xml
for element in elements: for element in elements:
if element.tag == original_target or \ if element.tag == original_target or not list(element):
not element.getchildren():
# Only delete the originally requested elements, and # Only delete the originally requested elements, and
# any parent elements that have become empty. # any parent elements that have become empty.
parent.remove(element) elem_lang = element.attrib.get('{%s}lang' % XML_NS,
default_lang)
if lang == '*' or elem_lang == lang:
parent.remove(element)
if not all: if not all:
# If we don't want to delete elements up the tree, stop # If we don't want to delete elements up the tree, stop
# after deleting the first level of elements. # after deleting the first level of elements.
@ -903,7 +1089,7 @@ class ElementBase(object):
attributes = components[1:] attributes = components[1:]
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \ if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
tag not in self.plugins and tag not in self.plugin_attrib: tag not in self.loaded_plugins and tag not in self.plugin_attrib:
# The requested tag is not in this stanza, so no match. # The requested tag is not in this stanza, so no match.
return False return False
@ -932,10 +1118,12 @@ class ElementBase(object):
if not matched_substanzas and len(xpath) > 1: if not matched_substanzas and len(xpath) > 1:
# Convert {namespace}tag@attribs to just tag # Convert {namespace}tag@attribs to just tag
next_tag = xpath[1].split('@')[0].split('}')[-1] next_tag = xpath[1].split('@')[0].split('}')[-1]
if next_tag in self.plugins: langs = [name[1] for name in self.plugins if name[0] == next_tag]
return self.plugins[next_tag].match(xpath[1:]) for lang in langs:
else: plugin = self._get_plugin(next_tag, lang)
return False if plugin and plugin.match(xpath[1:]):
return True
return False
# Everything matched. # Everything matched.
return True return True
@ -995,7 +1183,7 @@ class ElementBase(object):
""" """
out = [] out = []
out += [x for x in self.interfaces] out += [x for x in self.interfaces]
out += [x for x in self.plugins] out += [x for x in self.loaded_plugins]
if self.iterables: if self.iterables:
out.append('substanzas') out.append('substanzas')
return out return out
@ -1075,6 +1263,23 @@ class ElementBase(object):
""" """
return "{%s}%s" % (cls.namespace, cls.name) return "{%s}%s" % (cls.namespace, cls.name)
def get_lang(self):
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 @property
def attrib(self): def attrib(self):
"""Return the stanza object itself. """Return the stanza object itself.
@ -1090,8 +1295,8 @@ class ElementBase(object):
return self return self
def _fix_ns(self, xpath, split=False, propagate_ns=True): def _fix_ns(self, xpath, split=False, propagate_ns=True):
return fix_ns(xpath, split=split, return fix_ns(xpath, split=split,
propagate_ns=propagate_ns, propagate_ns=propagate_ns,
default_ns=self.namespace) default_ns=self.namespace)
def __eq__(self, other): def __eq__(self, other):
@ -1219,6 +1424,8 @@ class StanzaBase(ElementBase):
:param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID` :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
object of the sender's JID. object of the sender's JID.
:param string sid: Optional ID value for the stanza. :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 #: The default XMPP client namespace
@ -1233,11 +1440,11 @@ class StanzaBase(ElementBase):
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
def __init__(self, stream=None, xml=None, stype=None, def __init__(self, stream=None, xml=None, stype=None,
sto=None, sfrom=None, sid=None): sto=None, sfrom=None, sid=None, parent=None):
self.stream = stream self.stream = stream
if stream is not None: if stream is not None:
self.namespace = stream.default_ns self.namespace = stream.default_ns
ElementBase.__init__(self, xml) ElementBase.__init__(self, xml, parent)
if stype is not None: if stype is not None:
self['type'] = stype self['type'] = stype
if sto is not None: if sto is not None:
@ -1285,7 +1492,7 @@ class StanzaBase(ElementBase):
def get_payload(self): def get_payload(self):
"""Return a list of XML objects contained in the stanza.""" """Return a list of XML objects contained in the stanza."""
return self.xml.getchildren() return list(self.xml)
def set_payload(self, value): def set_payload(self, value):
"""Add XML content to the stanza. """Add XML content to the stanza.

View File

@ -13,14 +13,19 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
from __future__ import unicode_literals
import sys import sys
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
import types import types
XML_NS = 'http://www.w3.org/XML/1998/namespace'
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
outbuffer='', top_level=False): outbuffer='', top_level=False, open_only=False):
"""Serialize an XML object to a Unicode string. """Serialize an XML object to a Unicode string.
If namespaces are provided using ``xmlns`` or ``stanza_ns``, then If namespaces are provided using ``xmlns`` or ``stanza_ns``, then
@ -88,6 +93,13 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
output.append(' %s:%s="%s"' % (mapped_ns, output.append(' %s:%s="%s"' % (mapped_ns,
attrib, attrib,
value)) value))
elif attrib_ns == XML_NS:
output.append(' xml:%s="%s"' % (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 len(xml) or xml.text:
# If there are additional child elements to serialize. # If there are additional child elements to serialize.
@ -95,7 +107,7 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
if xml.text: if xml.text:
output.append(xml_escape(xml.text)) output.append(xml_escape(xml.text))
if len(xml): if len(xml):
for child in xml.getchildren(): for child in xml:
output.append(tostring(child, tag_xmlns, stanza_ns, stream)) output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append("</%s>" % tag_name) output.append("</%s>" % tag_name)
elif xml.text: elif xml.text:

View File

@ -55,7 +55,7 @@ RESPONSE_TIMEOUT = 30
WAIT_TIMEOUT = 0.1 WAIT_TIMEOUT = 0.1
#: The number of threads to use to handle XML stream events. This is not the #: The number of threads to use to handle XML stream events. This is not the
#: same as the number of custom event handling threads. #: same as the number of custom event handling threads.
#: :data:`HANDLER_THREADS` must be at least 1. For Python implementations #: :data:`HANDLER_THREADS` must be at least 1. For Python implementations
#: with a GIL, this should be left at 1, but for implemetnations without #: with a GIL, this should be left at 1, but for implemetnations without
#: a GIL increasing this value can provide better performance. #: a GIL increasing this value can provide better performance.
@ -124,7 +124,7 @@ class XMLStream(object):
self.ssl_support = SSL_SUPPORT self.ssl_support = SSL_SUPPORT
#: Most XMPP servers support TLSv1, but OpenFire in particular #: Most XMPP servers support TLSv1, but OpenFire in particular
#: does not work well with it. For OpenFire, set #: does not work well with it. For OpenFire, set
#: :attr:`ssl_version` to use ``SSLv23``:: #: :attr:`ssl_version` to use ``SSLv23``::
#: #:
#: import ssl #: import ssl
@ -134,30 +134,30 @@ class XMLStream(object):
#: Path to a file containing certificates for verifying the #: Path to a file containing certificates for verifying the
#: server SSL certificate. A non-``None`` value will trigger #: server SSL certificate. A non-``None`` value will trigger
#: certificate checking. #: certificate checking.
#: #:
#: .. note:: #: .. note::
#: #:
#: On Mac OS X, certificates in the system keyring will #: On Mac OS X, certificates in the system keyring will
#: be consulted, even if they are not in the provided file. #: be consulted, even if they are not in the provided file.
self.ca_certs = None self.ca_certs = None
#: The time in seconds to wait for events from the event queue, #: The time in seconds to wait for events from the event queue,
#: and also the time between checks for the process stop signal. #: and also the time between checks for the process stop signal.
self.wait_timeout = WAIT_TIMEOUT self.wait_timeout = WAIT_TIMEOUT
#: The time in seconds to wait before timing out waiting #: The time in seconds to wait before timing out waiting
#: for response stanzas. #: for response stanzas.
self.response_timeout = RESPONSE_TIMEOUT self.response_timeout = RESPONSE_TIMEOUT
#: The current amount to time to delay attempting to reconnect. #: The current amount to time to delay attempting to reconnect.
#: This value doubles (with some jitter) with each failed #: This value doubles (with some jitter) with each failed
#: connection attempt up to :attr:`reconnect_max_delay` seconds. #: connection attempt up to :attr:`reconnect_max_delay` seconds.
self.reconnect_delay = None self.reconnect_delay = None
#: Maximum time to delay between connection attempts is one hour. #: Maximum time to delay between connection attempts is one hour.
self.reconnect_max_delay = RECONNECT_MAX_DELAY self.reconnect_max_delay = RECONNECT_MAX_DELAY
#: Maximum number of attempts to connect to the server before #: Maximum number of attempts to connect to the server before
#: quitting and raising a 'connect_failed' event. Setting to #: quitting and raising a 'connect_failed' event. Setting to
#: ``None`` allows infinite reattempts, while setting it to ``0`` #: ``None`` allows infinite reattempts, while setting it to ``0``
#: will disable reconnection attempts. Defaults to ``None``. #: will disable reconnection attempts. Defaults to ``None``.
@ -178,16 +178,16 @@ class XMLStream(object):
#: The default port to return when querying DNS records. #: The default port to return when querying DNS records.
self.default_port = int(port) self.default_port = int(port)
#: The domain to try when querying DNS records. #: The domain to try when querying DNS records.
self.default_domain = '' self.default_domain = ''
#: The expected name of the server, for validation. #: The expected name of the server, for validation.
self._expected_server_name = '' self._expected_server_name = ''
#: The desired, or actual, address of the connected server. #: The desired, or actual, address of the connected server.
self.address = (host, int(port)) self.address = (host, int(port))
#: A file-like wrapper for the socket for use with the #: A file-like wrapper for the socket for use with the
#: :mod:`~xml.etree.ElementTree` module. #: :mod:`~xml.etree.ElementTree` module.
self.filesocket = None self.filesocket = None
@ -223,6 +223,9 @@ class XMLStream(object):
#: stream wrapper itself. #: stream wrapper itself.
self.default_ns = '' self.default_ns = ''
self.default_lang = None
self.peer_default_lang = None
#: The namespace of the enveloping stream element. #: The namespace of the enveloping stream element.
self.stream_ns = '' self.stream_ns = ''
@ -255,7 +258,7 @@ class XMLStream(object):
#: and data is sent immediately over the wire. #: and data is sent immediately over the wire.
self.session_started_event = threading.Event() self.session_started_event = threading.Event()
#: The default time in seconds to wait for a session to start #: The default time in seconds to wait for a session to start
#: after connecting before reconnecting and trying again. #: after connecting before reconnecting and trying again.
self.session_timeout = 45 self.session_timeout = 45
@ -414,12 +417,12 @@ class XMLStream(object):
if use_tls is not None: if use_tls is not None:
self.use_tls = use_tls self.use_tls = use_tls
# Repeatedly attempt to connect until a successful connection # Repeatedly attempt to connect until a successful connection
# is established. # is established.
attempts = self.reconnect_max_attempts attempts = self.reconnect_max_attempts
connected = self.state.transition('disconnected', 'connected', connected = self.state.transition('disconnected', 'connected',
func=self._connect, args=(reattempt,)) func=self._connect,
args=(reattempt,))
while reattempt and not connected and not self.stop.is_set(): while reattempt and not connected and not self.stop.is_set():
connected = self.state.transition('disconnected', 'connected', connected = self.state.transition('disconnected', 'connected',
func=self._connect) func=self._connect)
@ -434,7 +437,7 @@ class XMLStream(object):
def _connect(self, reattempt=True): def _connect(self, reattempt=True):
self.scheduler.remove('Session timeout check') self.scheduler.remove('Session timeout check')
self.stop.clear() self.stop.clear()
if self.reconnect_delay is None or not reattempt: if self.reconnect_delay is None or not reattempt:
delay = 1.0 delay = 1.0
else: else:
@ -480,7 +483,7 @@ class XMLStream(object):
if self.use_proxy: if self.use_proxy:
connected = self._connect_proxy() connected = self._connect_proxy()
if not connected: if not connected:
if reattempt: if reattempt:
self.reconnect_delay = delay self.reconnect_delay = delay
return False return False
@ -517,7 +520,8 @@ class XMLStream(object):
except (Socket.error, ssl.SSLError): except (Socket.error, ssl.SSLError):
log.error('CERT: Invalid certificate trust chain.') log.error('CERT: Invalid certificate trust chain.')
if not self.event_handled('ssl_invalid_chain'): if not self.event_handled('ssl_invalid_chain'):
self.disconnect(self.auto_reconnect, send_close=False) self.disconnect(self.auto_reconnect,
send_close=False)
else: else:
self.event('ssl_invalid_chain', direct=True) self.event('ssl_invalid_chain', direct=True)
return False return False
@ -525,7 +529,7 @@ class XMLStream(object):
self._der_cert = self.socket.getpeercert(binary_form=True) self._der_cert = self.socket.getpeercert(binary_form=True)
pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert) pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
log.debug('CERT: %s', pem_cert) log.debug('CERT: %s', pem_cert)
self.event('ssl_cert', pem_cert, direct=True) self.event('ssl_cert', pem_cert, direct=True)
try: try:
cert.verify(self._expected_server_name, self._der_cert) cert.verify(self._expected_server_name, self._der_cert)
@ -534,7 +538,9 @@ class XMLStream(object):
if not self.event_handled('ssl_invalid_cert'): if not self.event_handled('ssl_invalid_cert'):
self.disconnect(send_close=False) self.disconnect(send_close=False)
else: else:
self.event('ssl_invalid_cert', pem_cert, direct=True) self.event('ssl_invalid_cert',
pem_cert,
direct=True)
self.set_socket(self.socket, ignore=True) self.set_socket(self.socket, ignore=True)
#this event is where you should set your application state #this event is where you should set your application state
@ -627,7 +633,7 @@ class XMLStream(object):
If the disconnect should take place after all items If the disconnect should take place after all items
in the send queue have been sent, use ``wait=True``. in the send queue have been sent, use ``wait=True``.
.. warning:: .. warning::
If you are constantly adding items to the queue If you are constantly adding items to the queue
@ -648,7 +654,7 @@ class XMLStream(object):
""" """
self.state.transition('connected', 'disconnected', self.state.transition('connected', 'disconnected',
wait=2.0, wait=2.0,
func=self._disconnect, func=self._disconnect,
args=(reconnect, wait, send_close)) args=(reconnect, wait, send_close))
def _disconnect(self, reconnect=False, wait=None, send_close=True): def _disconnect(self, reconnect=False, wait=None, send_close=True):
@ -702,16 +708,18 @@ class XMLStream(object):
"""Reset the stream's state and reconnect to the server.""" """Reset the stream's state and reconnect to the server."""
log.debug("reconnecting...") log.debug("reconnecting...")
if self.state.ensure('connected'): if self.state.ensure('connected'):
self.state.transition('connected', 'disconnected', self.state.transition('connected', 'disconnected',
wait=2.0, wait=2.0,
func=self._disconnect, func=self._disconnect,
args=(True, wait, send_close)) args=(True, wait, send_close))
attempts = self.reconnect_max_attempts attempts = self.reconnect_max_attempts
log.debug("connecting...") log.debug("connecting...")
connected = self.state.transition('disconnected', 'connected', connected = self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect, args=(reattempt,)) wait=2.0,
func=self._connect,
args=(reattempt,))
while reattempt and not connected and not self.stop.is_set(): while reattempt and not connected and not self.stop.is_set():
connected = self.state.transition('disconnected', 'connected', connected = self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect) wait=2.0, func=self._connect)
@ -759,8 +767,8 @@ class XMLStream(object):
""" """
Configure and set options for a :class:`~dns.resolver.Resolver` Configure and set options for a :class:`~dns.resolver.Resolver`
instance, and other DNS related tasks. For example, you instance, and other DNS related tasks. For example, you
can also check :meth:`~socket.socket.getaddrinfo` to see can also check :meth:`~socket.socket.getaddrinfo` to see
if you need to call out to ``libresolv.so.2`` to if you need to call out to ``libresolv.so.2`` to
run ``res_init()``. run ``res_init()``.
Meant to be overridden. Meant to be overridden.
@ -814,7 +822,7 @@ class XMLStream(object):
log.debug('CERT: %s', pem_cert) log.debug('CERT: %s', pem_cert)
self.event('ssl_cert', pem_cert, direct=True) self.event('ssl_cert', pem_cert, direct=True)
try: try:
cert.verify(self._expected_server_name, self._der_cert) cert.verify(self._expected_server_name, self._der_cert)
except cert.CertificateError as err: except cert.CertificateError as err:
log.error(err.message) log.error(err.message)
@ -874,8 +882,8 @@ class XMLStream(object):
self.schedule('Whitespace Keepalive', self.schedule('Whitespace Keepalive',
self.whitespace_keepalive_interval, self.whitespace_keepalive_interval,
self.send_raw, self.send_raw,
args = (' ',), args=(' ',),
kwargs = {'now': True}, kwargs={'now': True},
repeat=True) repeat=True)
def _remove_schedules(self, event): def _remove_schedules(self, event):
@ -884,7 +892,7 @@ class XMLStream(object):
self.scheduler.remove('Certificate Expiration') self.scheduler.remove('Certificate Expiration')
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
"""Perform any initialization actions, such as handshakes, """Perform any initialization actions, such as handshakes,
once the stream header has been sent. once the stream header has been sent.
Meant to be overridden. Meant to be overridden.
@ -892,8 +900,8 @@ class XMLStream(object):
pass pass
def register_stanza(self, stanza_class): def register_stanza(self, stanza_class):
"""Add a stanza object class as a known root stanza. """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 A root stanza is one that appears as a direct child of the stream's
root element. root element.
@ -910,8 +918,8 @@ class XMLStream(object):
self.__root_stanza.append(stanza_class) self.__root_stanza.append(stanza_class)
def remove_stanza(self, stanza_class): def remove_stanza(self, stanza_class):
"""Remove a stanza from being a known root stanza. """Remove a stanza from being a known root stanza.
A root stanza is one that appears as a direct child of the stream's A root stanza is one that appears as a direct child of the stream's
root element. root element.
@ -976,8 +984,9 @@ class XMLStream(object):
"""Add a stream event handler that will be executed when a matching """Add a stream event handler that will be executed when a matching
stanza is received. stanza is received.
:param handler: The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler` :param handler:
derived object to execute. The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
derived object to execute.
""" """
if handler.stream is None: if handler.stream is None:
self.__handlers.append(handler) self.__handlers.append(handler)
@ -1004,11 +1013,12 @@ class XMLStream(object):
""" """
if port is None: if port is None:
port = self.default_port port = self.default_port
resolver = default_resolver() resolver = default_resolver()
self.configure_dns(resolver, domain=domain, port=port) self.configure_dns(resolver, domain=domain, port=port)
return resolve(domain, port, service=self.dns_service, resolver=resolver) return resolve(domain, port, service=self.dns_service,
resolver=resolver)
def pick_dns_answer(self, domain, port=None): def pick_dns_answer(self, domain, port=None):
"""Pick a server and port from DNS answers. """Pick a server and port from DNS answers.
@ -1026,7 +1036,7 @@ class XMLStream(object):
return self.dns_answers.next() return self.dns_answers.next()
else: else:
return next(self.dns_answers) return next(self.dns_answers)
def add_event_handler(self, name, pointer, def add_event_handler(self, name, pointer,
threaded=False, disposable=False): threaded=False, disposable=False):
"""Add a custom event handler that will be executed whenever """Add a custom event handler that will be executed whenever
@ -1141,9 +1151,9 @@ class XMLStream(object):
May optionally block until an expected response is received. May optionally block until an expected response is received.
:param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` :param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza to send on the stream. stanza to send on the stream.
:param mask: **DEPRECATED** :param mask: **DEPRECATED**
An XML string snippet matching the structure An XML string snippet matching the structure
of the expected response. Execution will block of the expected response. Execution will block
in this thread until the response is received in this thread until the response is received
@ -1195,9 +1205,9 @@ class XMLStream(object):
"""Send an XML object on the stream, and optionally wait """Send an XML object on the stream, and optionally wait
for a response. for a response.
:param data: The :class:`~xml.etree.ElementTree.Element` XML object :param data: The :class:`~xml.etree.ElementTree.Element` XML object
to send on the stream. to send on the stream.
:param mask: **DEPRECATED** :param mask: **DEPRECATED**
An XML string snippet matching the structure An XML string snippet matching the structure
of the expected response. Execution will block of the expected response. Execution will block
in this thread until the response is received in this thread until the response is received
@ -1237,14 +1247,15 @@ class XMLStream(object):
count += 1 count += 1
except ssl.SSLError as serr: except ssl.SSLError as serr:
if tries >= self.ssl_retry_max: if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached') log.debug('SSL error: max retries reached')
self.exception(serr) self.exception(serr)
log.warning("Failed to send %s", data) log.warning("Failed to send %s", data)
if reconnect is None: if reconnect is None:
reconnect = self.auto_reconnect reconnect = self.auto_reconnect
if not self.stop.is_set(): if not self.stop.is_set():
self.disconnect(reconnect, send_close=False) self.disconnect(reconnect,
log.warning('SSL write error - reattempting') send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set(): if not self.stop.is_set():
time.sleep(self.ssl_retry_delay) time.sleep(self.ssl_retry_delay)
tries += 1 tries += 1
@ -1299,7 +1310,7 @@ class XMLStream(object):
def _wait_for_threads(self): def _wait_for_threads(self):
with self.__thread_cond: with self.__thread_cond:
if self.__thread_count != 0: if self.__thread_count != 0:
log.debug("Waiting for %s threads to exit." % log.debug("Waiting for %s threads to exit." %
self.__thread_count) self.__thread_count)
name = threading.current_thread().name name = threading.current_thread().name
if name in self.__thread: if name in self.__thread:
@ -1331,7 +1342,7 @@ class XMLStream(object):
Defaults to ``True``. This does **not** mean that no Defaults to ``True``. This does **not** mean that no
threads are used at all if ``threaded=False``. threads are used at all if ``threaded=False``.
Regardless of these threading options, these threads will Regardless of these threading options, these threads will
always exist: always exist:
- The event queue processor - The event queue processor
@ -1421,7 +1432,7 @@ class XMLStream(object):
def __read_xml(self): def __read_xml(self):
"""Parse the incoming XML stream """Parse the incoming XML stream
Stream events are raised for each received stanza. Stream events are raised for each received stanza.
""" """
depth = 0 depth = 0
@ -1431,6 +1442,10 @@ class XMLStream(object):
if depth == 0: if depth == 0:
# We have received the start of the root element. # We have received the start of the root element.
root = xml 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 # Perform any stream initialization actions, such
# as handshakes. # as handshakes.
self.stream_end_event.clear() self.stream_end_event.clear()
@ -1461,10 +1476,10 @@ class XMLStream(object):
"""Create a stanza object from a given XML object. """Create a stanza object from a given XML object.
If a specialized stanza type is not found for the XML, then If a specialized stanza type is not found for the XML, then
a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase` a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase`
stanza will be returned. stanza will be returned.
:param xml: The :class:`~xml.etree.ElementTree.Element` XML object :param xml: The :class:`~xml.etree.ElementTree.Element` XML object
to convert into a stanza object. to convert into a stanza object.
:param default_ns: Optional default namespace to use instead of the :param default_ns: Optional default namespace to use instead of the
stream's current default namespace. stream's current default namespace.
@ -1478,6 +1493,8 @@ class XMLStream(object):
stanza_type = stanza_class stanza_type = stanza_class
break break
stanza = stanza_type(self, xml) stanza = stanza_type(self, xml)
if stanza['lang'] is None and self.peer_default_lang:
stanza['lang'] = self.peer_default_lang
return stanza return stanza
def __spawn_event(self, xml): def __spawn_event(self, xml):
@ -1647,12 +1664,13 @@ class XMLStream(object):
count += 1 count += 1
except ssl.SSLError as serr: except ssl.SSLError as serr:
if tries >= self.ssl_retry_max: if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached') log.debug('SSL error: max retries reached')
self.exception(serr) self.exception(serr)
log.warning("Failed to send %s", data) log.warning("Failed to send %s", data)
if not self.stop.is_set(): if not self.stop.is_set():
self.disconnect(self.auto_reconnect, send_close=False) self.disconnect(self.auto_reconnect,
log.warning('SSL write error - reattempting') send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set(): if not self.stop.is_set():
time.sleep(self.ssl_retry_delay) time.sleep(self.ssl_retry_delay)
tries += 1 tries += 1

View File

@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
stanza.append(substanza) stanza.append(substanza)
values = stanza.getStanzaValues() values = stanza.getStanzaValues()
expected = {'bar': 'a', expected = {'lang': '',
'bar': 'a',
'baz': '', 'baz': '',
'foo2': {'bar': '', 'foo2': {'lang': '',
'bar': '',
'baz': 'b'}, 'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}foo2', 'substanzas': [{'__childtag__': '{foo}foo2',
'lang': '',
'bar': '', 'bar': '',
'baz': 'b'}, 'baz': 'b'},
{'__childtag__': '{foo}subfoo', {'__childtag__': '{foo}subfoo',
'lang': '',
'bar': 'c', 'bar': 'c',
'baz': ''}]} 'baz': ''}]}
self.failUnless(values == expected, self.failUnless(values == expected,
@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
stanza = TestStanza() stanza = TestStanza()
self.failUnless(set(stanza.keys()) == set(('bar', 'baz')), self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz')),
"Returned set of interface keys does not match expected.") "Returned set of interface keys does not match expected.")
stanza.enable('qux') stanza.enable('qux')
self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')), self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz', 'qux')),
"Incorrect set of interface and plugin keys.") "Incorrect set of interface and plugin keys.")
def testGet(self): def testGet(self):

View File

@ -49,7 +49,7 @@ class TestAdHocCommandStanzas(SleekTest):
iq['command']['actions'] = ['prev', 'next'] iq['command']['actions'] = ['prev', 'next']
results = iq['command']['actions'] results = iq['command']['actions']
expected = ['prev', 'next'] expected = set(['prev', 'next'])
self.assertEqual(results, expected, self.assertEqual(results, expected,
"Incorrect next actions: %s" % results) "Incorrect next actions: %s" % results)