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

View File

@ -16,24 +16,24 @@ class APIWrapper(object):
elif attr == 'settings':
return self.api.settings[self.name]
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)
return register(handler, self.name, op, jid, node, default)
return curried_handler
return partial
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 curried_handler
return partial
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 curried_handler
return partial
return None
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 curried_handler
return partial
class APIRegistry(object):

View File

@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element.
"""
self.stream_id = xml.get('id', '')
self.stream_version = xml.get('version', '')
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream.
@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs):
"""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):
"""Create an Iq stanza associated with this stream."""
@ -280,7 +285,9 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs):
"""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):
"""Create a new Iq stanza with a given Id and from JID.
@ -529,21 +536,8 @@ class BaseXMPP(XMLStream):
:param pfrom: The sender of the presence.
:param pnick: Optional nickname of the presence's sender.
"""
# Python2.6 chokes on Unicode strings for dict keys.
args = {str('ptype'): ptype,
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)
self.make_presence(pshow, pstatus, ppriority, pto,
ptype, pfrom, pnick).send()
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
@ -557,10 +551,10 @@ class BaseXMPP(XMLStream):
:param ptype: The type of presence, such as ``'subscribe'``.
:param pnick: Optional nickname of the presence's sender.
"""
self.send_presence(pto=pto,
self.make_presence(ptype=ptype,
pfrom=pfrom,
ptype=ptype,
pnick=pnick)
pto=JID(pto).bare,
pnick=pnick).send()
@property
def jid(self):

View File

@ -60,8 +60,8 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, ssl=False, plugin_config={},
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid)
@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.default_port = 5222
self.default_lang = lang
self.credentials = {}
self.password = password
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns)
"xmlns='%s'" % self.default_ns,
"xml:lang='%s'" % self.default_lang,
"version='1.0'")
self.stream_footer = "</stream:stream>"
self.features = set()

View File

@ -73,6 +73,7 @@ class IqTimeout(XMPPError):
#: did not arrive before the timeout expired.
self.iq = iq
class IqError(XMPPError):
"""

View File

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

View File

@ -33,6 +33,7 @@ __all__ = [
# 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0080', # User Location
'xep_0082', # XMPP Date and Time Profiles
'xep_0084', # User Avatar
'xep_0085', # Chat State Notifications
'xep_0086', # Legacy Error Codes
'xep_0092', # Software Version
@ -49,7 +50,10 @@ __all__ = [
'xep_0199', # Ping
'xep_0202', # Entity Time
'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_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations
'xep_0258', # Security Labels in XMPP
]

View File

@ -84,7 +84,7 @@ def load_plugin(name, module=None):
module = 'sleekxmpp.plugins.%s' % name
__import__(module)
mod = sys.modules[module]
except:
except ImportError:
module = 'sleekxmpp.features.%s' % name
__import__(module)
mod = sys.modules[module]
@ -103,7 +103,7 @@ def load_plugin(name, module=None):
# we can work around dependency issues.
plugin.old_style = True
register_plugin(plugin, name)
except:
except ImportError:
log.exception("Unable to load plugin: %s", name)

View File

@ -81,7 +81,8 @@ class XEP_0027(BasePlugin):
def _sign_presence(self, stanza):
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']
return stanza

View File

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

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.
"""
actions = []
actions = set()
actions_xml = self.find('{%s}actions' % self.namespace)
if actions_xml is not None:
for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
action))
if action_xml is not None:
actions.append(action)
actions.add(action)
return actions
def del_actions(self):

View File

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

View File

@ -107,7 +107,7 @@ class XEP_0054(BasePlugin):
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return
elif iq['type'] == 'get':
vcard = self.api['get_vard'](iq['from'].bare)
vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq):
vcard.send()
else:

View File

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

View File

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

View File

@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase):
def get_condition(self):
"""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:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase):
def del_condition(self):
"""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:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:

View File

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

View File

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

View File

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

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

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

View File

@ -173,7 +173,8 @@ class XEP_0115(BasePlugin):
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, invalid for caps")
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False
if len(f_type) > 1:
@ -183,7 +184,8 @@ class XEP_0115(BasePlugin):
return False
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)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")

View File

@ -109,6 +109,7 @@ class XEP_0163(BasePlugin):
node = stanza.namespace
return self.xmpp['xep_0060'].publish(ifrom, node,
id=id,
payload=stanza.xml,
options=options,
ifrom=ifrom,

View File

@ -82,7 +82,6 @@ class Resumed(StanzaBase):
self._set_attr('h', str(val))
class Failed(StanzaBase, Error):
name = 'failed'
namespace = 'urn:xmpp:sm:3'

View File

@ -40,8 +40,12 @@ class XEP_0202(BasePlugin):
# custom function can be supplied which accepts
# the JID of the entity to query for the time.
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:
self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset)
self.local_time = default_local_time
self.xmpp.registerHandler(
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
register_plugin(XEP_0203)
# Retain some backwards compatibility
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

@ -58,7 +58,6 @@ class XEP_0231(BasePlugin):
self.api.register(self._set_bob, 'set_bob', default=True)
self.api.register(self._del_bob, 'del_bob', default=True)
def set_bob(self, data, mtype, cid=None, max_age=None):
if cid is None:
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()

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.send()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick,
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
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.owner
if not kwargs.get('pto', ''):
kwargs['pto'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

@ -6,9 +6,11 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Presence
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterNode
class Roster(object):
"""
@ -55,6 +57,33 @@ class Roster(object):
for node in self.db.entries(None, {}):
self.add(node)
self.xmpp.add_filter('out', self._save_last_status)
def _save_last_status(self, stanza):
if isinstance(stanza, Presence):
sfrom = stanza['from'].full
sto = stanza['to'].full
if not sfrom:
sfrom = self.xmpp.boundjid
if stanza['type'] in stanza.showtypes or \
stanza['type'] in ('available', 'unavailable'):
if sto:
self[sfrom][sto].last_status = stanza
else:
self[sfrom].last_status = stanza
with self[sfrom]._last_status_lock:
for jid in self[sfrom]:
self[sfrom][jid].last_status = None
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
return stanza
def __getitem__(self, key):
"""
Return the roster node for a JID.
@ -121,29 +150,27 @@ class Roster(object):
for node in self:
self[node].reset()
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
Forwards the send request to the appropriate roster to
perform the actual sending.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
pnick -- Optional nickname of the presence's sender.
"""
self[pfrom].send_presence(ptype=ptype,
pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
pnick=pnick,
pto=pto)
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
@property
def auto_authorize(self):

View File

@ -6,6 +6,8 @@
See the file LICENSE for copying permission.
"""
import threading
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterItem
@ -59,6 +61,7 @@ class RosterNode(object):
self.last_status = None
self._version = ''
self._jids = {}
self._last_status_lock = threading.Lock()
if self.db:
if hasattr(self.db, 'version'):
@ -291,8 +294,7 @@ class RosterNode(object):
for jid in self:
self[jid].reset()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None, pto=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
@ -305,27 +307,14 @@ class RosterNode(object):
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
if pto:
self[pto].send_presence(ptype, pshow, pstatus,
ppriority, pnick)
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
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

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

View File

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

View File

@ -78,7 +78,8 @@ class RootStanza(StanzaBase):
self['error']['type'] = 'cancel'
self.send()
# 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
self.stream.exception(e)

View File

@ -102,6 +102,7 @@ class Roster(ElementBase):
# Remove extra JID reference to keep everything
# backward compatible
del items[item['jid']]['jid']
del items[item['jid']]['lang']
return items
def del_items(self):

View File

@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase):
"""
namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text'))
interfaces = set(('condition', 'text', 'see_other_host'))
conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown',
@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase):
'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
def get_see_other_host(self):
ns = self.condition_ns
return self._get_sub_text('{%s}see-other-host' % ns, '')
def set_see_other_host(self, value):
if value:
del self['condition']
ns = self.condition_ns
return self._set_sub_text('{%s}see-other-host' % ns, value)
elif self['condition'] == 'see-other-host':
del self['condition']
def del_see_other_host(self):
self._del_sub('{%s}see-other-host' % self.condition_ns)

View File

@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.xmlstream import StanzaBase
@ -28,7 +29,10 @@ class StreamFeatures(StanzaBase):
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):
"""

View File

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

View File

@ -7,11 +7,12 @@ try:
from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
from pyasn1.type.char import BMPString, IA5String, UTF8String
from pyasn1.type.useful import GeneralizedTime
from pyasn1_modules.rfc2459 import Certificate, DirectoryString, SubjectAltName, GeneralNames, GeneralName
from pyasn1_modules.rfc2459 import (Certificate, DirectoryString,
SubjectAltName, GeneralNames,
GeneralName)
from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME
from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')

View File

@ -151,8 +151,8 @@ class MatchXMLMask(MatcherBase):
"""
tag = tag.split('}')[-1]
try:
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
children = [c.tag.split('}')[-1] for c in xml]
index = children.index(tag)
except ValueError:
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.
continue
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
children = [c.tag.split('}')[-1] for c in xml]
try:
index = children.index(tag)
except ValueError:
return False
xml = xml.getchildren()[index]
xml = list(xml)[index]
return True

View File

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

View File

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

View File

@ -223,6 +223,9 @@ class XMLStream(object):
#: stream wrapper itself.
self.default_ns = ''
self.default_lang = None
self.peer_default_lang = None
#: The namespace of the enveloping stream element.
self.stream_ns = ''
@ -414,12 +417,12 @@ class XMLStream(object):
if use_tls is not None:
self.use_tls = use_tls
# Repeatedly attempt to connect until a successful connection
# is established.
attempts = self.reconnect_max_attempts
connected = self.state.transition('disconnected', 'connected',
func=self._connect, args=(reattempt,))
func=self._connect,
args=(reattempt,))
while reattempt and not connected and not self.stop.is_set():
connected = self.state.transition('disconnected', 'connected',
func=self._connect)
@ -517,7 +520,8 @@ class XMLStream(object):
except (Socket.error, ssl.SSLError):
log.error('CERT: Invalid certificate trust chain.')
if not self.event_handled('ssl_invalid_chain'):
self.disconnect(self.auto_reconnect, send_close=False)
self.disconnect(self.auto_reconnect,
send_close=False)
else:
self.event('ssl_invalid_chain', direct=True)
return False
@ -534,7 +538,9 @@ class XMLStream(object):
if not self.event_handled('ssl_invalid_cert'):
self.disconnect(send_close=False)
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)
#this event is where you should set your application state
@ -711,7 +717,9 @@ class XMLStream(object):
log.debug("connecting...")
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():
connected = self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect)
@ -976,7 +984,8 @@ class XMLStream(object):
"""Add a stream event handler that will be executed when a matching
stanza is received.
:param handler: The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
:param handler:
The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
derived object to execute.
"""
if handler.stream is None:
@ -1008,7 +1017,8 @@ class XMLStream(object):
resolver = default_resolver()
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):
"""Pick a server and port from DNS answers.
@ -1237,14 +1247,15 @@ class XMLStream(object):
count += 1
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached')
log.debug('SSL error: max retries reached')
self.exception(serr)
log.warning("Failed to send %s", data)
if reconnect is None:
reconnect = self.auto_reconnect
if not self.stop.is_set():
self.disconnect(reconnect, send_close=False)
log.warning('SSL write error - reattempting')
self.disconnect(reconnect,
send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1
@ -1431,6 +1442,10 @@ class XMLStream(object):
if depth == 0:
# We have received the start of the root element.
root = xml
log.debug('RECV: %s', tostring(root, xmlns=self.default_ns,
stream=self,
top_level=True,
open_only=True))
# Perform any stream initialization actions, such
# as handshakes.
self.stream_end_event.clear()
@ -1478,6 +1493,8 @@ class XMLStream(object):
stanza_type = stanza_class
break
stanza = stanza_type(self, xml)
if stanza['lang'] is None and self.peer_default_lang:
stanza['lang'] = self.peer_default_lang
return stanza
def __spawn_event(self, xml):
@ -1647,12 +1664,13 @@ class XMLStream(object):
count += 1
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached')
log.debug('SSL error: max retries reached')
self.exception(serr)
log.warning("Failed to send %s", data)
if not self.stop.is_set():
self.disconnect(self.auto_reconnect, send_close=False)
log.warning('SSL write error - reattempting')
self.disconnect(self.auto_reconnect,
send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1

View File

@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
stanza.append(substanza)
values = stanza.getStanzaValues()
expected = {'bar': 'a',
expected = {'lang': '',
'bar': 'a',
'baz': '',
'foo2': {'bar': '',
'foo2': {'lang': '',
'bar': '',
'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}foo2',
'lang': '',
'bar': '',
'baz': 'b'},
{'__childtag__': '{foo}subfoo',
'lang': '',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
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.")
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.")
def testGet(self):

View File

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