Compare commits

..

32 Commits

Author SHA1 Message Date
Nathan Fritz
45991e47ee scheduler no longer waits for the next event before exiting 2010-11-16 17:58:20 -08:00
Nathan Fritz
b8f40eb843 xep_0199 ping now uses scheduler instead of dedicated thread 2010-11-16 17:43:05 -08:00
Florent Le Coz
b73a859031 Add a groupchat_subject event
Use this event to get notified of the subject changes (or to get the
subject of the room when joining one)
2010-11-10 05:54:22 +08:00
Florent Le Coz
9dbf246f0b Doesn't fail if host has NO SRV record
Just catch an other exception type coming from the dns resolver that
could be raised with hosts like "anon.example.com" which just don't have
any SRV record.
2010-11-09 01:53:41 +08:00
Lance Stout
4fb77ac878 Logging no longer uses root logger.
Each module should now log into its own logger.
2010-11-06 01:28:59 -04:00
Lance Stout
d0c506f930 Simplified SleekTest.
* check_stanza does not require stanza_class parameter. Introspection!
* check_message, check_iq, and check_presence removed -- use check
  instead.
* stream_send_stanza, stream_send_message, stream_send_iq, and
  stream_send_presence removed -- use send instead.
* Use recv instead of recv_message, recv_presence, etc.
* check_jid instead of check_JID
* stream_start may accept multi=True to return a new SleekTest instance
  for testing multiple streams at once.
2010-11-05 21:18:48 -04:00
Lance Stout
7351fe1a02 Fix bug introduced while fixing another bug.
Threaded event handlers now handle exceptions again.
2010-11-04 14:35:35 -04:00
Nathan Fritz
38c2f51f83 fixed indent errors 2010-11-04 11:39:41 -07:00
Lance Stout
1bf34caa5b Fixes for XEP-0199 plugin.
Quick fixes to get the XEP-0199 plugin working until a proper cleanup is
done.
2010-11-03 14:04:18 -04:00
Lance Stout
5769935720 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2010-11-03 12:39:44 -04:00
Lance Stout
0214db7545 Catch exceptions for direct events.
Events triggered with direct=True will have exceptions caught.

Note that all event handlers in a direct event will currently run
in the same thread.
2010-11-03 12:38:13 -04:00
Lance Stout
ffc6f031d9 Updated namespaced used in the XEP-0199 plugin. 2010-11-03 12:37:26 -04:00
Lance Stout
9e248bb852 Fix bug in XEP-0030 plugin.
xep_0030 still referenced event_handlers. Added the method event_handled
which will return the number of registered handlers for an event to
resolve the issue.
2010-10-31 18:27:52 -04:00
Lance Stout
973890e2c9 Added try/except for setting signal handlers.
Setting signal handlers from inside a thread is not supported in Python,
but some applications need to run Sleek from a child thread.

SleekXMPP applications that run inside a child thread will NOT be able
to detect SIGHUP or SIGTERM events. Those must be caught and managed by
the main program.
2010-10-28 10:42:23 -04:00
Lance Stout
9c08e56ed0 SSL and signal fixes.
Made setting the SIG* handlers conditional on if the signal defined for
the OS.

Added the attribute ssl_version to XMLStream to set the version of SSL
used during connection. It defaults to ssl.PROTOCOL_TLSv1, but OpenFire
tends to require ssl.PROTOCOL_SSLv23.
2010-10-27 19:27:47 -04:00
Lance Stout
b888610525 Added XEP-202 Entity Time plugin.
Contributed by Cesar Alcalde.
2010-10-25 21:26:25 -04:00
Lance Stout
6d68706326 Added XEP-0012 Last Activity plugin.
Contributed by Cesar Alcalde.
2010-10-25 20:37:02 -04:00
Lance Stout
5bdcd9ef9d Made exceptions work.
Raising an XMPPError exception from an event handler now works, even if
from a threaded handler.

Added stream tests to verify.

We should start using XMPPError, it really makes things simple!
2010-10-25 15:09:56 -04:00
Lance Stout
2eff35cc7a Added more presence stream tests.
Tests auto_authorize=False, and got_online.
2010-10-25 13:21:00 -04:00
Lance Stout
ac330b5c6c Fixed bug in presence subscription handling.
Subscription requests and responses were not setting the correct 'to'
attribute.
2010-10-25 12:52:32 -04:00
Lance Stout
46ffa8e9fe Added stream tests for presence events.
First batch of tests, currently focuses on the got_offline event.
2010-10-24 19:57:07 -04:00
Lance Stout
03847497cc Added test for error stanzas. 2010-10-24 19:56:42 -04:00
Lance Stout
185d7cf28e More JID unit tests.
sleekxmpp.xmlstream.jid now has 100% coverage!
2010-10-24 19:06:54 -04:00
Lance Stout
8aa3d0c047 Fixed got_offline triggering bug. 2010-10-24 18:56:50 -04:00
Lance Stout
9e3d506651 Fixed resource bug in JIDs.
JIDs without resources will return '' instead of the bare JID.

Cleaned up JID tests, and added check_JID to SleekTest.
2010-10-24 18:22:41 -04:00
Lance Stout
2f3ff37a24 Make SleekTest streams register all plugins.
Makes test coverage nicer.
2010-10-24 17:35:11 -04:00
Lance Stout
1f09d60a52 ComponentXMPP saves all of its config data now.
ComponentXMPP was ignoring plugin_config and plugin_whitelist
parameters, making register_plugins() fail.
2010-10-24 17:33:11 -04:00
Lance Stout
d528884723 Added stream tests for rosters. 2010-10-24 12:53:14 -04:00
Lance Stout
d9aff3d36f Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2010-10-24 12:11:34 -04:00
Lance Stout
04cc48775d Fixed error in client roster handling.
The roster result iq was not being passed to the roster update
handler.
2010-10-24 12:08:59 -04:00
Nathan Fritz
27ebb6e8f6 presence no longer replies when exception is caught and tweaks to presence events 2010-10-21 16:59:15 -07:00
Lance Stout
8f55704928 Fixed mixed text and elements bug in tostring.
XML of the form <a>foo <b>bar</b> baz</a> was outputted as
<a>foo <b>bar</b> baz baz</a>.

Includes unit test.
2010-10-21 16:21:28 -04:00
48 changed files with 1714 additions and 927 deletions

View File

@@ -26,13 +26,7 @@ from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
# Flag indicating if DNS SRV records are available for use.
SRV_SUPPORT = True
try:
import dns.resolver
except:
SRV_SUPPORT = False
log = logging.getLogger(__name__)
# In order to make sure that Unicode is handled properly
# in Python 2.x, reset the default encoding.
@@ -192,9 +186,9 @@ class BaseXMPP(XMLStream):
xep = "(XEP-%s) " % self.plugin[plugin].xep
desc = (xep, self.plugin[plugin].description)
logging.debug("Loaded Plugin %s%s" % desc)
log.debug("Loaded Plugin %s%s" % desc)
except:
logging.exception("Unable to load plugin: %s", plugin)
log.exception("Unable to load plugin: %s", plugin)
def register_plugins(self):
"""
@@ -228,7 +222,7 @@ class BaseXMPP(XMLStream):
if key in self.plugin:
return self.plugin[key]
else:
logging.warning("""Plugin "%s" is not loaded.""" % key)
log.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
@@ -446,12 +440,12 @@ class BaseXMPP(XMLStream):
"""
Attribute accessor for bare jid
"""
logging.warning("jid property deprecated. Use boundjid.bare")
log.warning("jid property deprecated. Use boundjid.bare")
return self.boundjid.bare
@jid.setter
def jid(self, value):
logging.warning("jid property deprecated. Use boundjid.bare")
log.warning("jid property deprecated. Use boundjid.bare")
self.boundjid.bare = value
@property
@@ -459,12 +453,12 @@ class BaseXMPP(XMLStream):
"""
Attribute accessor for full jid
"""
logging.warning("fulljid property deprecated. Use boundjid.full")
log.warning("fulljid property deprecated. Use boundjid.full")
return self.boundjid.full
@fulljid.setter
def fulljid(self, value):
logging.warning("fulljid property deprecated. Use boundjid.full")
log.warning("fulljid property deprecated. Use boundjid.full")
self.boundjid.full = value
@property
@@ -472,12 +466,12 @@ class BaseXMPP(XMLStream):
"""
Attribute accessor for jid resource
"""
logging.warning("resource property deprecated. Use boundjid.resource")
log.warning("resource property deprecated. Use boundjid.resource")
return self.boundjid.resource
@resource.setter
def resource(self, value):
logging.warning("fulljid property deprecated. Use boundjid.full")
log.warning("fulljid property deprecated. Use boundjid.full")
self.boundjid.resource = value
@property
@@ -485,12 +479,12 @@ class BaseXMPP(XMLStream):
"""
Attribute accessor for jid usernode
"""
logging.warning("username property deprecated. Use boundjid.user")
log.warning("username property deprecated. Use boundjid.user")
return self.boundjid.user
@username.setter
def username(self, value):
logging.warning("username property deprecated. Use boundjid.user")
log.warning("username property deprecated. Use boundjid.user")
self.boundjid.user = value
@property
@@ -498,17 +492,17 @@ class BaseXMPP(XMLStream):
"""
Attribute accessor for jid host
"""
logging.warning("server property deprecated. Use boundjid.host")
log.warning("server property deprecated. Use boundjid.host")
return self.boundjid.server
@server.setter
def server(self, value):
logging.warning("server property deprecated. Use boundjid.host")
log.warning("server property deprecated. Use boundjid.host")
self.boundjid.server = value
def set_jid(self, jid):
"""Rip a JID apart and claim it as our own."""
logging.debug("setting jid to %s" % jid)
log.debug("setting jid to %s" % jid)
self.boundjid.full = jid
def getjidresource(self, fulljid):
@@ -553,6 +547,7 @@ class BaseXMPP(XMLStream):
priority = presence['priority']
was_offline = False
got_online = False
old_roster = self.roster.get(jid, {}).get(resource, {})
# Create a new roster entry if needed.
@@ -569,7 +564,7 @@ class BaseXMPP(XMLStream):
# Determine if the user has just come online.
if not resource in connections:
if show == 'available' or show in presence.showtypes:
self.event("got_online", presence)
got_online = True
was_offline = True
connections[resource] = {}
@@ -587,7 +582,7 @@ class BaseXMPP(XMLStream):
# disconnects. Determine if this was the last connection
# for the JID.
if show == 'unavailable':
logging.debug("%s %s got offline" % (jid, resource))
log.debug("%s %s got offline" % (jid, resource))
del connections[resource]
if not connections and not self.roster[jid]['in_roster']:
@@ -601,7 +596,9 @@ class BaseXMPP(XMLStream):
# Presence state has changed.
self.event("changed_status", presence)
logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
if got_online:
self.event("got_online", presence)
log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
show, status))
def _handle_subscribe(self, presence):
@@ -618,8 +615,8 @@ class BaseXMPP(XMLStream):
None * Disable automatic handling and use
a custom handler.
"""
presence = self.Presence()
presence['to'] = presence['from'].bare
presence.reply()
presence['to'] = presence['to'].bare
# We are using trinary logic, so conditions have to be
# more explicit than usual.

View File

@@ -32,6 +32,9 @@ except:
SRV_SUPPORT = False
log = logging.getLogger(__name__)
class ClientXMPP(BaseXMPP):
"""
@@ -133,7 +136,7 @@ class ClientXMPP(BaseXMPP):
def _session_timeout_check(self):
if not self.session_started_event.isSet():
logging.debug("Session start has taken more than 15 seconds")
log.debug("Session start has taken more than 15 seconds")
self.disconnect(reconnect=self.auto_reconnect)
def connect(self, address=tuple()):
@@ -150,19 +153,19 @@ class ClientXMPP(BaseXMPP):
self.session_started_event.clear()
if not address or len(address) < 2:
if not self.srv_support:
logging.debug("Did not supply (address, port) to connect" + \
log.debug("Did not supply (address, port) to connect" + \
" to and no SRV support is installed" + \
" (http://www.dnspython.org)." + \
" Continuing to attempt connection, using" + \
" server hostname from JID.")
else:
logging.debug("Since no address is supplied," + \
log.debug("Since no address is supplied," + \
"attempting SRV lookup.")
try:
xmpp_srv = "_xmpp-client._tcp.%s" % self.server
answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV)
except dns.resolver.NXDOMAIN:
logging.debug("No appropriate SRV record found." + \
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
log.debug("No appropriate SRV record found." + \
" Using JID server name.")
else:
# Pick a random server, weighted by priority.
@@ -219,8 +222,8 @@ class ClientXMPP(BaseXMPP):
iq['roster']['items'] = {jid: {'name': name,
'subscription': subscription,
'groups': groups}}
resp = iq.send()
return resp['type'] == 'result'
response = iq.send()
return response['type'] == 'result'
def del_roster_item(self, jid):
"""
@@ -235,8 +238,8 @@ class ClientXMPP(BaseXMPP):
def get_roster(self):
"""Request the roster from the server."""
iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster')
iq.send()
self._handle_roster(iq, request=True)
response = iq.send()
self._handle_roster(response, request=True)
def _handle_stream_features(self, features):
"""
@@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP):
self.send_xml(xml)
return True
else:
logging.warning("The module tlslite is required to log in" +\
log.warning("The module tlslite is required to log in" +\
" to some servers, and has not been found.")
return False
@@ -286,7 +289,7 @@ class ClientXMPP(BaseXMPP):
Restarts the stream.
"""
logging.debug("Starting TLS")
log.debug("Starting TLS")
if self.start_tls():
raise RestartStream()
@@ -300,7 +303,7 @@ class ClientXMPP(BaseXMPP):
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
return False
logging.debug("Starting SASL Auth")
log.debug("Starting SASL Auth")
sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl'
self.add_handler("<success xmlns='%s' />" % sasl_ns,
self._handle_auth_success,
@@ -334,7 +337,7 @@ class ClientXMPP(BaseXMPP):
sasl_ns,
'ANONYMOUS'))
else:
logging.error("No appropriate login method.")
log.error("No appropriate login method.")
self.disconnect()
return True
@@ -356,7 +359,7 @@ class ClientXMPP(BaseXMPP):
Arguments:
xml -- The SASL authentication failure element.
"""
logging.info("Authentication failed.")
log.info("Authentication failed.")
self.event("failed_auth", direct=True)
self.disconnect()
@@ -367,7 +370,7 @@ class ClientXMPP(BaseXMPP):
Arguments:
xml -- The bind feature element.
"""
logging.debug("Requesting resource: %s" % self.boundjid.resource)
log.debug("Requesting resource: %s" % self.boundjid.resource)
xml.clear()
iq = self.Iq(stype='set')
if self.boundjid.resource:
@@ -381,10 +384,10 @@ class ClientXMPP(BaseXMPP):
self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns,
bind_ns)).text)
self.bound = True
logging.info("Node set to: %s" % self.boundjid.fulljid)
log.info("Node set to: %s" % self.boundjid.fulljid)
session_ns = 'urn:ietf:params:xml:ns:xmpp-session'
if "{%s}session" % session_ns not in self.features or self.bindfail:
logging.debug("Established Session")
log.debug("Established Session")
self.sessionstarted = True
self.session_started_event.set()
self.event("session_start")
@@ -399,7 +402,7 @@ class ClientXMPP(BaseXMPP):
if self.authenticated and self.bound:
iq = self.makeIqSet(xml)
response = iq.send()
logging.debug("Established Session")
log.debug("Established Session")
self.sessionstarted = True
self.session_started_event.set()
self.event("session_start")

View File

@@ -15,13 +15,16 @@ import hashlib
from sleekxmpp import plugins
from sleekxmpp import stanza
from sleekxmpp.basexmpp import BaseXMPP, SRV_SUPPORT
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream import StanzaBase, ET
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
log = logging.getLogger(__name__)
class ComponentXMPP(BaseXMPP):
"""
@@ -67,6 +70,8 @@ class ComponentXMPP(BaseXMPP):
self.server_port = port
self.set_jid(jid)
self.secret = secret
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.is_component = True
self.register_handler(
@@ -80,7 +85,7 @@ class ComponentXMPP(BaseXMPP):
Overrides XMLStream.connect.
"""
logging.debug("Connecting to %s:%s" % (self.server_host,
log.debug("Connecting to %s:%s" % (self.server_host,
self.server_port))
return XMLStream.connect(self, self.server_host,
self.server_port)

View File

@@ -38,6 +38,9 @@ class XMPPError(Exception):
element. Same as the additional arguments to
the ET.Element constructor.
"""
if extension_args is None:
extension_args = {}
self.condition = condition
self.text = text
self.etype = etype

View File

@@ -5,5 +5,6 @@
See the file LICENSE for copying permission.
"""
__all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050',
'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']
__all__ = ['xep_0004', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045',
'xep_0050', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify',
'xep_0060', 'xep_0202']

View File

@@ -14,6 +14,9 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq
log = logging.getLogger(__name__)
class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
@@ -118,12 +121,12 @@ class gmail_notify(base.base_plugin):
def handle_gmail(self, iq):
mailbox = iq['mailbox']
approx = ' approximately' if mailbox['estimated'] else ''
logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
self.last_result_time = mailbox['result-time']
self.xmpp.event('gmail_messages', iq)
def handle_new_mail(self, iq):
logging.info("Gmail: New emails received!")
log.info("Gmail: New emails received!")
self.xmpp.event('gmail_notify')
self.checkEmail()
@@ -135,9 +138,9 @@ class gmail_notify(base.base_plugin):
def search(self, query=None, newer=None):
if query is None:
logging.info("Gmail: Checking for new emails")
log.info("Gmail: Checking for new emails")
else:
logging.info('Gmail: Searching for emails matching: "%s"' % query)
log.info('Gmail: Searching for emails matching: "%s"' % query)
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = self.xmpp.jid

View File

@@ -3,6 +3,10 @@ import logging
from xml.etree import cElementTree as ET
import types
log = logging.getLogger(__name__)
class jobs(base.base_plugin):
def plugin_init(self):
self.xep = 'pubsubjob'
@@ -40,7 +44,7 @@ class jobs(base.base_plugin):
iq['psstate']['payload'] = state
result = iq.send()
if result is None or type(result) == types.BooleanType or result['type'] != 'result':
logging.error("Unable to change %s:%s to %s" % (node, jobid, state))
log.error("Unable to change %s:%s to %s" % (node, jobid, state))
return False
return True

View File

@@ -6,12 +6,16 @@
See the file LICENSE for copying permission.
"""
from . import base
import logging
import log
from xml.etree import cElementTree as ET
import copy
import logging
#TODO support item groups and results
log = logging.getLogger(__name__)
class old_0004(base.base_plugin):
def plugin_init(self):
@@ -22,7 +26,7 @@ class old_0004(base.base_plugin):
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
logging.warning("This implementation of XEP-0004 is deprecated.")
log.warning("This implementation of XEP-0004 is deprecated.")
def handler_message_xform(self, xml):
object = self.handle_form(xml)

View File

@@ -16,6 +16,9 @@ from .. stanza.message import Message
import types
log = logging.getLogger(__name__)
class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
@@ -55,11 +58,11 @@ class Form(ElementBase):
return field
def getXML(self, type='submit'):
logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
return self.xml
def fromXML(self, xml):
logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
n = Form(xml=xml)
return n

View File

@@ -0,0 +1,118 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from datetime import datetime
import logging
from . import base
from .. stanza.iq import Iq
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin
log = logging.getLogger(__name__)
class LastActivity(ElementBase):
name = 'query'
namespace = 'jabber:iq:last'
plugin_attrib = 'last_activity'
interfaces = set(('seconds', 'status'))
def get_seconds(self):
return int(self._get_attr('seconds'))
def set_seconds(self, value):
self._set_attr('seconds', str(value))
def get_status(self):
return self.xml.text
def set_status(self, value):
self.xml.text = str(value)
def del_status(self):
self.xml.text = ''
class xep_0012(base.base_plugin):
"""
XEP-0012 Last Activity
"""
def plugin_init(self):
self.description = "Last Activity"
self.xep = "0012"
self.xmpp.registerHandler(
Callback('Last Activity',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
LastActivity.namespace)),
self.handle_last_activity_query))
register_stanza_plugin(Iq, LastActivity)
self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity)
def post_init(self):
base.base_plugin.post_init(self)
if self.xmpp.is_component:
# We are a component, so we track the uptime
self.xmpp.add_event_handler("session_start", self._reset_uptime)
self._start_datetime = datetime.now()
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last')
def _reset_uptime(self, event):
self._start_datetime = datetime.now()
def handle_last_activity_query(self, iq):
if iq['type'] == 'get':
log.debug("Last activity requested by %s" % iq['from'])
self.xmpp.event('last_activity_request', iq)
elif iq['type'] == 'result':
log.debug("Last activity result from %s" % iq['from'])
self.xmpp.event('last_activity', iq)
def handle_last_activity(self, iq):
jid = iq['from']
if self.xmpp.is_component:
# Send the uptime
result = LastActivity()
td = (datetime.now() - self._start_datetime)
result['seconds'] = td.seconds + td.days * 24 * 3600
reply = iq.reply().setPayload(result.xml).send()
else:
barejid = JID(jid).bare
if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or
barejid == self.xmpp.boundjid.bare ):
# We don't know how to calculate it
iq.reply().error().setPayload(iq['last_activity'].xml)
iq['error']['code'] = '503'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'service-unavailable'
iq.send()
else:
iq.reply().error().setPayload(iq['last_activity'].xml)
iq['error']['code'] = '403'
iq['error']['type'] = 'auth'
iq['error']['condition'] = 'forbidden'
iq.send()
def get_last_activity(self, jid):
"""Query the LastActivity of jid and return it in seconds"""
iq = self.xmpp.makeIqGet()
query = LastActivity()
iq.append(query.xml)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq.get('id')
result = iq.send()
if result and result is not None and result.get('type', 'error') != 'error':
return result['last_activity']['seconds']
else:
return False

View File

@@ -13,6 +13,10 @@ from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq
log = logging.getLogger(__name__)
class DiscoInfo(ElementBase):
namespace = 'http://jabber.org/protocol/disco#info'
name = 'query'
@@ -222,18 +226,18 @@ class xep_0030(base.base_plugin):
def handle_item_query(self, iq):
if iq['type'] == 'get':
logging.debug("Items requested by %s" % iq['from'])
log.debug("Items requested by %s" % iq['from'])
self.xmpp.event('disco_items_request', iq)
elif iq['type'] == 'result':
logging.debug("Items result from %s" % iq['from'])
log.debug("Items result from %s" % iq['from'])
self.xmpp.event('disco_items', iq)
def handle_info_query(self, iq):
if iq['type'] == 'get':
logging.debug("Info requested by %s" % iq['from'])
log.debug("Info requested by %s" % iq['from'])
self.xmpp.event('disco_info_request', iq)
elif iq['type'] == 'result':
logging.debug("Info result from %s" % iq['from'])
log.debug("Info result from %s" % iq['from'])
self.xmpp.event('disco_info', iq)
def handle_disco_info(self, iq, forwarded=False):
@@ -241,21 +245,20 @@ class xep_0030(base.base_plugin):
A default handler for disco#info requests. If another
handler is registered, this one will defer and not run.
"""
handlers = self.xmpp.event_handlers['disco_info_request']
if not forwarded and len(handlers) > 1:
if not forwarded and self.xmpp.event_handled('disco_info_request'):
return
node_name = iq['disco_info']['node']
if not node_name:
node_name = 'main'
logging.debug("Using default handler for disco#info on node '%s'." % node_name)
log.debug("Using default handler for disco#info on node '%s'." % node_name)
if node_name in self.nodes:
node = self.nodes[node_name]
iq.reply().setPayload(node.info.xml).send()
else:
logging.debug("Node %s requested, but does not exist." % node_name)
log.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_info'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
@@ -270,21 +273,20 @@ class xep_0030(base.base_plugin):
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
handlers = self.xmpp.event_handlers['disco_items_request']
if not forwarded and len(handlers) > 1:
if not forwarded and self.xmpp.event_handled('disco_items_request'):
return
node_name = iq['disco_items']['node']
if not node_name:
node_name = 'main'
logging.debug("Using default handler for disco#items on node '%s'." % node_name)
log.debug("Using default handler for disco#items on node '%s'." % node_name)
if node_name in self.nodes:
node = self.nodes[node_name]
iq.reply().setPayload(node.items.xml).send()
else:
logging.debug("Node %s requested, but does not exist." % node_name)
log.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_items'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'

View File

@@ -15,6 +15,10 @@ from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.matcher.xmlmask import MatchXMLMask
log = logging.getLogger(__name__)
class MUCPresence(ElementBase):
name = 'x'
namespace = 'http://jabber.org/protocol/muc#user'
@@ -87,19 +91,19 @@ class MUCPresence(ElementBase):
return self.parent()['from'].bare
def setNick(self, value):
logging.warning("Cannot set nick through mucpresence plugin.")
log.warning("Cannot set nick through mucpresence plugin.")
return self
def setRoom(self, value):
logging.warning("Cannot set room through mucpresence plugin.")
log.warning("Cannot set room through mucpresence plugin.")
return self
def delNick(self):
logging.warning("Cannot delete nick through mucpresence plugin.")
log.warning("Cannot delete nick through mucpresence plugin.")
return self
def delRoom(self):
logging.warning("Cannot delete room through mucpresence plugin.")
log.warning("Cannot delete room through mucpresence plugin.")
return self
class xep_0045(base.base_plugin):
@@ -116,6 +120,7 @@ class xep_0045(base.base_plugin):
registerStanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
def handle_groupchat_presence(self, pr):
""" Handle a presence in a muc.
@@ -135,7 +140,7 @@ class xep_0045(base.base_plugin):
if entry['nick'] not in self.rooms[entry['room']]:
got_online = True
self.rooms[entry['room']][entry['nick']] = entry
logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
self.xmpp.event("groupchat_presence", pr)
self.xmpp.event("muc::%s::presence" % entry['room'], pr)
if got_offline:
@@ -149,6 +154,12 @@ class xep_0045(base.base_plugin):
self.xmpp.event('groupchat_message', msg)
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def handle_groupchat_subject(self, msg):
""" Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room)
"""
self.xmpp.event('groupchat_subject', msg)
def jidInRoom(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]

View File

@@ -6,6 +6,10 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from . import stanza_pubsub
from . xep_0004 import Form
log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
"""
XEP-0060 Publish Subscribe
@@ -110,14 +114,14 @@ class xep_0060(base.base_plugin):
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
result = iq.send()
if result is None or result == False or result['type'] == 'error':
logging.warning("got error instead of config")
log.warning("got error instead of config")
return False
if node is not None:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
else:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
if not form or form is None:
logging.error("No form found.")
log.error("No form found.")
return False
return Form(xml=form)
@@ -133,7 +137,7 @@ class xep_0060(base.base_plugin):
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
logging.warning("got error instead of config")
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
@@ -156,7 +160,7 @@ class xep_0060(base.base_plugin):
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
logging.warning("got error instead of config")
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
@@ -264,7 +268,7 @@ class xep_0060(base.base_plugin):
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
logging.warning("pubsub#collection doesn't exist in config, trying to add it")
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False
@@ -298,7 +302,7 @@ class xep_0060(base.base_plugin):
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
logging.warning("pubsub#collection doesn't exist in config, trying to add it")
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False

View File

@@ -12,6 +12,9 @@ import hashlib
from . import base
log = logging.getLogger(__name__)
class xep_0078(base.base_plugin):
"""
XEP-0078 NON-SASL Authentication
@@ -30,7 +33,7 @@ class xep_0078(base.base_plugin):
self.auth()
def auth(self, xml=None):
logging.debug("Starting jabber:iq:auth Authentication")
log.debug("Starting jabber:iq:auth Authentication")
auth_request = self.xmpp.makeIqGet()
auth_request_query = ET.Element('{jabber:iq:auth}query')
auth_request.attrib['to'] = self.xmpp.server
@@ -47,12 +50,12 @@ class xep_0078(base.base_plugin):
query.append(username)
query.append(resource)
if rquery.find('{jabber:iq:auth}digest') is None:
logging.warning("Authenticating via jabber:iq:auth Plain.")
log.warning("Authenticating via jabber:iq:auth Plain.")
password = ET.Element('password')
password.text = self.xmpp.password
query.append(password)
else:
logging.debug("Authenticating via jabber:iq:auth Digest")
log.debug("Authenticating via jabber:iq:auth Digest")
digest = ET.Element('digest')
digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
query.append(digest)
@@ -64,6 +67,6 @@ class xep_0078(base.base_plugin):
self.xmpp.sessionstarted = True
self.xmpp.event("session_start")
else:
logging.info("Authentication failed")
log.info("Authentication failed")
self.xmpp.disconnect()
self.xmpp.event("failed_auth")

View File

@@ -14,6 +14,9 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
log = logging.getLogger(__name__)
class ChatState(ElementBase):
namespace = 'http://jabber.org/protocol/chatstates'
plugin_attrib = 'chat_state'
@@ -97,5 +100,5 @@ class xep_0085(base.base_plugin):
def _handleChatState(self, msg):
state = msg['chat_state'].name
logging.debug("Chat State: %s, %s" % (state, msg['from'].jid))
log.debug("Chat State: %s, %s" % (state, msg['from'].jid))
self.xmpp.event('chatstate_%s' % state, msg)

View File

@@ -10,32 +10,36 @@ from . import base
import time
import logging
log = logging.getLogger(__name__)
class xep_0199(base.base_plugin):
"""XEP-0199 XMPP Ping"""
def plugin_init(self):
self.description = "XMPP Ping"
self.xep = "0199"
self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping')
self.running = False
#if self.config.get('keepalive', True):
#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='urn:xmpp:ping'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping')
if self.config.get('keepalive', True):
self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns')
self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping')
def handler_pingserver(self, xml):
if not self.running:
time.sleep(self.config.get('frequency', 300))
while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False:
time.sleep(self.config.get('frequency', 300))
logging.debug("Did not recieve ping back in time. Requesting Reconnect.")
self.xmpp.disconnect(reconnect=True)
self.xmpp.schedule("xep-0119 ping", float(self.config.get('frequency', 300)), self.scheduled_ping, repeat=True)
def scheduled_ping(self):
log.debug("pinging...")
if self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is False:
log.debug("Did not recieve ping back in time. Requesting Reconnect.")
self.xmpp.reconnect()
def handler_ping(self, xml):
iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
iq.attrib['to'] = xml.get('from', self.xmpp.server)
iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain)
self.xmpp.send(iq)
def sendPing(self, jid, timeout = 30):
@@ -47,7 +51,7 @@ class xep_0199(base.base_plugin):
iq = self.xmpp.makeIq(id)
iq.attrib['type'] = 'get'
iq.attrib['to'] = jid
ping = ET.Element('{http://www.xmpp.org/extensions/xep-0199.html#ns}ping')
ping = ET.Element('{urn:xmpp:ping}ping')
iq.append(ping)
startTime = time.clock()
#pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout)

View File

@@ -0,0 +1,115 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from datetime import datetime, tzinfo
import logging
import time
from . import base
from .. stanza.iq import Iq
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin
log = logging.getLogger(__name__)
class EntityTime(ElementBase):
name = 'time'
namespace = 'urn:xmpp:time'
plugin_attrib = 'entity_time'
interfaces = set(('tzo', 'utc'))
sub_interfaces = set(('tzo', 'utc'))
#def get_utc(self): # TODO: return a datetime.tzinfo object?
#pass
def set_tzo(self, tzo): # TODO: support datetime.tzinfo objects?
if isinstance(tzo, tzinfo):
td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here'
seconds = td.seconds + td.days * 24 * 3600
sign = ('+' if seconds >= 0 else '-')
minutes = abs(seconds // 60)
tzo = '{sign}{hours:02d}:{minutes:02d}'.format(sign=sign, hours=minutes//60, minutes=minutes%60)
elif not isinstance(tzo, str):
raise TypeError('The time should be a string or a datetime.tzinfo object.')
self._set_sub_text('tzo', tzo)
def get_utc(self):
# Returns a datetime object instead the string. Is this a good idea?
value = self._get_sub_text('utc')
if '.' in value:
return datetime.strptime(value, '%Y-%m-%d.%fT%H:%M:%SZ')
else:
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
def set_utc(self, tim=None):
if isinstance(tim, datetime):
if tim.utcoffset():
tim = tim - tim.utcoffset()
tim = tim.strftime('%Y-%m-%dT%H:%M:%SZ')
elif isinstance(tim, time.struct_time):
tim = time.strftime('%Y-%m-%dT%H:%M:%SZ', tim)
elif not isinstance(tim, str):
raise TypeError('The time should be a string or a datetime.datetime or time.struct_time object.')
self._set_sub_text('utc', tim)
class xep_0202(base.base_plugin):
"""
XEP-0202 Entity Time
"""
def plugin_init(self):
self.description = "Entity Time"
self.xep = "0202"
self.xmpp.registerHandler(
Callback('Time Request',
MatchXPath('{%s}iq/{%s}time' % (self.xmpp.default_ns,
EntityTime.namespace)),
self.handle_entity_time_query))
register_stanza_plugin(Iq, EntityTime)
self.xmpp.add_event_handler('entity_time_request', self.handle_entity_time)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:time')
def handle_entity_time_query(self, iq):
if iq['type'] == 'get':
log.debug("Entity time requested by %s" % iq['from'])
self.xmpp.event('entity_time_request', iq)
elif iq['type'] == 'result':
log.debug("Entity time result from %s" % iq['from'])
self.xmpp.event('entity_time', iq)
def handle_entity_time(self, iq):
iq = iq.reply()
iq.enable('entity_time')
tzo = time.strftime('%z') # %z is not on all ANSI C libraries
tzo = tzo[:3] + ':' + tzo[3:]
iq['entity_time']['tzo'] = tzo
iq['entity_time']['utc'] = datetime.utcnow()
iq.send()
def get_entity_time(self, jid):
iq = self.xmpp.makeIqGet()
iq.enable('entity_time')
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq.get('id')
result = iq.send()
if result and result is not None and result.get('type', 'error') != 'error':
return {'utc': result['entity_time']['utc'], 'tzo': result['entity_time']['tzo']}
else:
return False

View File

@@ -92,6 +92,12 @@ class Presence(RootStanza):
return StanzaBase.setup(self, xml)
def exception(self, e):
"""
Override exception passback for presence.
"""
pass
def set_show(self, show):
"""
Set the value of the <show> element.

View File

@@ -15,6 +15,9 @@ from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin
log = logging.getLogger(__name__)
class RootStanza(StanzaBase):
"""
@@ -58,7 +61,7 @@ class RootStanza(StanzaBase):
self['error']['text'] = "SleekXMPP got into trouble."
else:
self['error']['text'] = traceback.format_tb(e.__traceback__)
logging.exception('Error handling {%s}%s stanza' %
log.exception('Error handling {%s}%s stanza' %
(self.namespace, self.name))
self.send()

View File

@@ -1,5 +1,4 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
@@ -27,27 +26,29 @@ class SleekTest(unittest.TestCase):
Message -- Create a Message stanza object.
Iq -- Create an Iq stanza object.
Presence -- Create a Presence stanza object.
check_stanza -- Compare a generic stanza against an XML string.
check_message -- Compare a Message stanza against an XML string.
check_iq -- Compare an Iq stanza against an XML string.
check_presence -- Compare a Presence stanza against an XML string.
check_jid -- Check a JID and its component parts.
check -- Compare a stanza against an XML string.
stream_start -- Initialize a dummy XMPP client.
stream_recv -- Queue data for XMPP client to receive.
stream_make_header -- Create a stream header.
stream_send_header -- Check that the given header has been sent.
stream_send_message -- Check that the XMPP client sent the given
Message stanza.
stream_send_iq -- Check that the XMPP client sent the given
Iq stanza.
stream_send_presence -- Check thatt the XMPP client sent the given
Presence stanza.
stream_send_stanza -- Check that the XMPP client sent the given
generic stanza.
stream_close -- Disconnect the XMPP client.
make_header -- Create a stream header.
send_header -- Check that the given header has been sent.
send_feature -- Send a raw XML element.
send -- Check that the XMPP client sent the given
generic stanza.
recv -- Queue data for XMPP client to receive, or
verify the data that was received from a
live connection.
recv_header -- Check that a given stream header
was received.
recv_feature -- Check that a given, raw XML element
was recveived.
fix_namespaces -- Add top-level namespace to an XML object.
compare -- Compare XML objects against each other.
"""
def runTest(self):
pass
def parse_xml(self, xml_string):
try:
xml = ET.fromstring(xml_string)
@@ -103,10 +104,43 @@ class SleekTest(unittest.TestCase):
"""
return Presence(None, *args, **kwargs)
def check_jid(self, jid, user=None, domain=None, resource=None,
bare=None, full=None, string=None):
"""
Verify the components of a JID.
Arguments:
jid -- The JID object to test.
user -- Optional. The user name portion of the JID.
domain -- Optional. The domain name portion of the JID.
resource -- Optional. The resource portion of the JID.
bare -- Optional. The bare JID.
full -- Optional. The full JID.
string -- Optional. The string version of the JID.
"""
if user is not None:
self.assertEqual(jid.user, user,
"User does not match: %s" % jid.user)
if domain is not None:
self.assertEqual(jid.domain, domain,
"Domain does not match: %s" % jid.domain)
if resource is not None:
self.assertEqual(jid.resource, resource,
"Resource does not match: %s" % jid.resource)
if bare is not None:
self.assertEqual(jid.bare, bare,
"Bare JID does not match: %s" % jid.bare)
if full is not None:
self.assertEqual(jid.full, full,
"Full JID does not match: %s" % jid.full)
if string is not None:
self.assertEqual(str(jid), string,
"String does not match: %s" % str(jid))
# ------------------------------------------------------------------
# Methods for comparing stanza objects to XML strings
def check_stanza(self, stanza_class, stanza, xml_string,
def check(self, stanza, xml_string,
defaults=None, use_values=True):
"""
Create and compare several stanza objects to a correct XML string.
@@ -126,7 +160,6 @@ class SleekTest(unittest.TestCase):
must take into account any extra elements that are included by default.
Arguments:
stanza_class -- The class of the stanza being tested.
stanza -- The stanza object to test.
xml_string -- A string version of the correct XML expected.
defaults -- A list of stanza interfaces that have default
@@ -137,6 +170,7 @@ class SleekTest(unittest.TestCase):
setStanzaValues() should be used. Defaults to
True.
"""
stanza_class = stanza.__class__
xml = self.parse_xml(xml_string)
# Ensure that top level namespaces are used, even if they
@@ -153,7 +187,11 @@ class SleekTest(unittest.TestCase):
# so that they will compare correctly.
default_stanza = stanza_class()
if defaults is None:
defaults = []
known_defaults = {
Message: ['type'],
Presence: ['priority']
}
defaults = known_defaults.get(stanza_class, [])
for interface in defaults:
stanza[interface] = stanza[interface]
stanza2[interface] = stanza2[interface]
@@ -184,62 +222,6 @@ class SleekTest(unittest.TestCase):
self.failUnless(result, debug)
def check_message(self, msg, xml_string, use_values=True):
"""
Create and compare several message stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
msg -- The Message stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.check_stanza(Message, msg, xml_string,
defaults=['type'],
use_values=use_values)
def check_iq(self, iq, xml_string, use_values=True):
"""
Create and compare several iq stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
iq -- The Iq stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.check_stanza(Iq, iq, xml_string, use_values=use_values)
def check_presence(self, pres, xml_string, use_values=True):
"""
Create and compare several presence stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
iq -- The Iq stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.check_stanza(Presence, pres, xml_string,
defaults=['priority'],
use_values=use_values)
# ------------------------------------------------------------------
# Methods for simulating stanza streams.
@@ -267,7 +249,6 @@ class SleekTest(unittest.TestCase):
port -- The port to use when connecting to the server.
Defaults to 5222.
"""
if mode == 'client':
self.xmpp = ClientXMPP(jid, password)
elif mode == 'component':
@@ -294,6 +275,7 @@ class SleekTest(unittest.TestCase):
else:
raise ValueError("Unknown socket type.")
self.xmpp.register_plugins()
self.xmpp.process(threaded=True)
if skip:
# Clear startup stanzas
@@ -301,7 +283,7 @@ class SleekTest(unittest.TestCase):
if mode == 'component':
self.xmpp.socket.next_sent(timeout=1)
def stream_make_header(self, sto='',
def make_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
@@ -338,7 +320,7 @@ class SleekTest(unittest.TestCase):
parts.append('xmlns="%s"' % default_ns)
return header % ' '.join(parts)
def stream_recv(self, data, stanza_class=StanzaBase, defaults=[],
def recv(self, data, stanza_class=StanzaBase, defaults=[],
use_values=True, timeout=1):
"""
Pass data to the dummy XMPP client as if it came from an XMPP server.
@@ -366,7 +348,7 @@ class SleekTest(unittest.TestCase):
if recv_data is None:
return False
stanza = stanza_class(xml=self.parse_xml(recv_data))
return self.check_stanza(stanza_class, stanza, data,
return self.check(stanza_class, stanza, data,
defaults=defaults,
use_values=use_values)
else:
@@ -374,7 +356,7 @@ class SleekTest(unittest.TestCase):
data = str(data)
self.xmpp.socket.recv_data(data)
def stream_recv_header(self, sto='',
def recv_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
@@ -397,7 +379,7 @@ class SleekTest(unittest.TestCase):
timeout -- Length of time to wait in seconds for a
response.
"""
header = self.stream_make_header(sto, sfrom, sid,
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
version=version,
@@ -441,9 +423,8 @@ class SleekTest(unittest.TestCase):
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
'%s %s' % (xml.tag, xml.attrib),
'%s %s' % (recv_xml.tag, recv_xml.attrib)))
#tostring(xml), tostring(recv_xml)))#recv_header))
def stream_recv_feature(self, data, use_values=True, timeout=1):
def recv_feature(self, data, use_values=True, timeout=1):
"""
"""
if self.xmpp.socket.is_live:
@@ -463,32 +444,7 @@ class SleekTest(unittest.TestCase):
data = str(data)
self.xmpp.socket.recv_data(data)
def stream_recv_message(self, data, use_values=True, timeout=1):
"""
"""
return self.stream_recv(data, stanza_class=Message,
defaults=['type'],
use_values=use_values,
timeout=timeout)
def stream_recv_iq(self, data, use_values=True, timeout=1):
"""
"""
return self.stream_recv(data, stanza_class=Iq,
use_values=use_values,
timeout=timeout)
def stream_recv_presence(self, data, use_values=True, timeout=1):
"""
"""
return self.stream_recv(data, stanza_class=Presence,
defaults=['priority'],
use_values=use_values,
timeout=timeout)
def stream_send_header(self, sto='',
def send_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
@@ -511,7 +467,7 @@ class SleekTest(unittest.TestCase):
timeout -- Length of time to wait in seconds for a
response.
"""
header = self.stream_make_header(sto, sfrom, sid,
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
version=version,
@@ -533,7 +489,7 @@ class SleekTest(unittest.TestCase):
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
header, sent_header))
def stream_send_feature(self, data, use_values=True, timeout=1):
def send_feature(self, data, use_values=True, timeout=1):
"""
"""
sent_data = self.xmpp.socket.next_sent(timeout)
@@ -545,13 +501,13 @@ class SleekTest(unittest.TestCase):
"Features do not match.\nDesired:\n%s\nSent:\n%s" % (
tostring(xml), tostring(sent_xml)))
def stream_send_stanza(self, stanza_class, data, defaults=None,
def send(self, data, defaults=None,
use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using check_stanza.
XML using check.
Arguments:
stanza_class -- The class of the sent stanza object.
@@ -563,70 +519,15 @@ class SleekTest(unittest.TestCase):
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isintance(data, str):
data = stanza_class(xml=self.parse_xml(data))
if isinstance(data, str):
xml = self.parse_xml(data)
self.fix_namespaces(xml, 'jabber:client')
data = self.xmpp._build_stanza(xml, 'jabber:client')
sent = self.xmpp.socket.next_sent(timeout)
self.check_stanza(stanza_class, data, sent,
self.check(data, sent,
defaults=defaults,
use_values=use_values)
def stream_send_message(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using check_message.
Arguments:
data -- The XML string of the expected Message stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by check_message.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Message(xml=self.parse_xml(data))
sent = self.xmpp.socket.next_sent(timeout)
self.check_message(data, sent, use_values)
def stream_send_iq(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using check_iq.
Arguments:
data -- The XML string of the expected Iq stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by check_iq.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Iq(xml=self.parse_xml(data))
sent = self.xmpp.socket.next_sent(timeout)
self.check_iq(data, sent, use_values)
def stream_send_presence(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using check_presence.
Arguments:
data -- The XML string of the expected Presence stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by check_presence.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Presence(xml=self.parse_xml(data))
sent = self.xmpp.socket.next_sent(timeout)
self.check_presence(data, sent, use_values)
def stream_close(self):
"""
Disconnect the dummy XMPP client.

View File

@@ -86,7 +86,7 @@ class StateMachine(object):
while not self.lock.acquire(False):
time.sleep(.001)
if (start + wait - time.time()) <= 0.0:
logging.debug("Could not acquire lock")
log.debug("Could not acquire lock")
return False
while not self.__current_state in from_states:
@@ -95,7 +95,7 @@ class StateMachine(object):
if remainder > 0:
self.notifier.wait(remainder)
else:
logging.debug("State was not ready")
log.debug("State was not ready")
self.lock.release()
return False

View File

@@ -16,6 +16,9 @@ from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT
from sleekxmpp.xmlstream.handler.base import BaseHandler
log = logging.getLogger(__name__)
class Waiter(BaseHandler):
"""
@@ -85,7 +88,7 @@ class Waiter(BaseHandler):
stanza = self._payload.get(True, timeout)
except queue.Empty:
stanza = False
logging.warning("Timed out waiting for %s" % self.name)
log.warning("Timed out waiting for %s" % self.name)
self.stream.removeHandler(self.name)
return stanza

View File

@@ -57,7 +57,7 @@ class JID(object):
full, or bare.
"""
if name == 'resource':
if self._resource is None:
if self._resource is None and '/' in self._jid:
self._resource = self._jid.split('/', 1)[-1]
return self._resource or ""
elif name == 'user':
@@ -94,21 +94,15 @@ class JID(object):
elif name in ('server', 'domain', 'host'):
self.domain = value
elif name in ('full', 'jid'):
if '@' not in value:
if '/' in value:
d, r = value.split('/', 1)
object.__setattr__(self, "_resource", r)
else:
d = value
object.__setattr__(self, "_domain", d)
else:
self.reset(value)
self.regenerate()
elif name == 'bare':
if '@' in value:
u, d = value.split('@', 1)
object.__setattr__(self, "_user", u)
object.__setattr__(self, "_domain", d)
else:
object.__setattr__(self, "_user", '')
object.__setattr__(self, "_domain", value)
self.regenerate()
else:

View File

@@ -6,6 +6,8 @@
See the file LICENSE for copying permission.
"""
import logging
from xml.parsers.expat import ExpatError
from sleekxmpp.xmlstream.stanzabase import ET
@@ -18,6 +20,9 @@ from sleekxmpp.xmlstream.matcher.base import MatcherBase
IGNORE_NS = False
log = logging.getLogger(__name__)
class MatchXMLMask(MatcherBase):
"""
@@ -97,8 +102,7 @@ class MatchXMLMask(MatcherBase):
try:
mask = ET.fromstring(mask)
except ExpatError:
logging.log(logging.WARNING,
"Expat error: %s\nIn parsing: %s" % ('', mask))
log.warning("Expat error: %s\nIn parsing: %s" % ('', mask))
if not use_ns:
# Compare the element without using namespaces.

View File

@@ -15,6 +15,9 @@ except ImportError:
import Queue as queue
log = logging.getLogger(__name__)
class Task(object):
"""
@@ -146,6 +149,8 @@ class Scheduler(object):
if wait <= 0.0:
newtask = self.addq.get(False)
else:
if wait >= 3.0:
wait = 3.0
newtask = self.addq.get(True, wait)
except queue.Empty:
cleanup = []
@@ -168,13 +173,13 @@ class Scheduler(object):
except KeyboardInterrupt:
self.run = False
if self.parentstop is not None:
logging.debug("stopping parent")
log.debug("stopping parent")
self.parentstop.set()
except SystemExit:
self.run = False
if self.parentstop is not None:
self.parentstop.set()
logging.debug("Quitting Scheduler thread")
log.debug("Quitting Scheduler thread")
if self.parentqueue is not None:
self.parentqueue.put(('quit', None, None))

View File

@@ -16,6 +16,9 @@ from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.tostring import tostring
log = logging.getLogger(__name__)
# Used to check if an argument is an XML object.
XML_TYPE = type(ET.Element('xml'))
@@ -1140,7 +1143,7 @@ class StanzaBase(ElementBase):
Meant to be overridden.
"""
logging.exception('Error handling {%s}%s stanza' % (self.namespace,
log.exception('Error handling {%s}%s stanza' % (self.namespace,
self.name))
def send(self):

View File

@@ -68,9 +68,6 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
for child in xml.getchildren():
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append(u"</%s>" % tag_name)
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
elif xml.text:
# If we only have text content.
output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name))

View File

@@ -44,6 +44,9 @@ HANDLER_THREADS = 1
SSL_SUPPORT = True
log = logging.getLogger(__name__)
class RestartStream(Exception):
"""
Exception to restart stream processing, including
@@ -87,6 +90,8 @@ class XMLStream(object):
send_queue -- A queue of stanzas to be sent on the stream.
socket -- The connection to the server.
ssl_support -- Indicates if a SSL library is available for use.
ssl_version -- The version of the SSL protocol to use.
Defaults to ssl.PROTOCOL_TLSv1.
state -- A state machine for managing the stream's
connection state.
stream_footer -- The start tag and any attributes for the stream's
@@ -155,6 +160,7 @@ class XMLStream(object):
self.sendXML = self.send_xml
self.ssl_support = SSL_SUPPORT
self.ssl_version = ssl.PROTOCOL_TLSv1
self.state = StateMachine(('disconnected', 'connected'))
self.state._set_state('disconnected')
@@ -196,8 +202,15 @@ class XMLStream(object):
self.auto_reconnect = True
self.is_client = False
try:
if hasattr(signal, 'SIGHUP'):
signal.signal(signal.SIGHUP, self._handle_kill)
signal.signal(signal.SIGTERM, self._handle_kill) # used in Windows
if hasattr(signal, 'SIGTERM'):
# Used in Windows
signal.signal(signal.SIGTERM, self._handle_kill)
except:
log.debug("Can not set interrupt signal handlers. " + \
"SleekXMPP is not running from a main thread.")
def _handle_kill(self, signum, frame):
"""
@@ -265,7 +278,7 @@ class XMLStream(object):
self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM)
self.socket.settimeout(None)
if self.use_ssl and self.ssl_support:
logging.debug("Socket Wrapped for SSL")
log.debug("Socket Wrapped for SSL")
ssl_socket = ssl.wrap_socket(self.socket)
if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top
@@ -275,7 +288,7 @@ class XMLStream(object):
self.socket = ssl_socket
try:
logging.debug("Connecting to %s:%s" % self.address)
log.debug("Connecting to %s:%s" % self.address)
self.socket.connect(self.address)
self.set_socket(self.socket, ignore=True)
#this event is where you should set your application state
@@ -283,7 +296,7 @@ class XMLStream(object):
return True
except Socket.error as serr:
error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
logging.error(error_msg % (self.address[0], self.address[1],
log.error(error_msg % (self.address[0], self.address[1],
serr.errno, serr.strerror))
time.sleep(1)
return False
@@ -328,10 +341,10 @@ class XMLStream(object):
"""
Reset the stream's state and reconnect to the server.
"""
logging.debug("reconnecting...")
log.debug("reconnecting...")
self.state.transition('connected', 'disconnected', wait=2.0,
func=self._disconnect, args=(True,))
logging.debug("connecting...")
log.debug("connecting...")
return self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect)
@@ -368,9 +381,10 @@ class XMLStream(object):
to be restarted.
"""
if self.ssl_support:
logging.info("Negotiating TLS")
log.info("Negotiating TLS")
log.info("Using SSL version: %s" % str(self.ssl_version))
ssl_socket = ssl.wrap_socket(self.socket,
ssl_version=ssl.PROTOCOL_TLSv1,
ssl_version=self.ssl_version,
do_handshake_on_connect=False)
if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top
@@ -382,7 +396,7 @@ class XMLStream(object):
self.set_socket(self.socket)
return True
else:
logging.warning("Tried to enable TLS, but ssl module not found.")
log.warning("Tried to enable TLS, but ssl module not found.")
return False
def start_stream_handler(self, xml):
@@ -517,6 +531,17 @@ class XMLStream(object):
self.__event_handlers[name] = filter(filter_pointers,
self.__event_handlers[name])
def event_handled(self, name):
"""
Indicates if an event has any associated handlers.
Returns the number of registered handlers.
Arguments:
name -- The name of the event to check.
"""
return len(self.__event_handlers.get(name, []))
def event(self, name, data={}, direct=False):
"""
Manually trigger a custom event.
@@ -525,13 +550,22 @@ class XMLStream(object):
name -- The name of the event to trigger.
data -- Data that will be passed to each event handler.
Defaults to an empty dictionary.
direct -- Runs the event directly if True.
direct -- Runs the event directly if True, skipping the
event queue. All event handlers will run in the
same thread.
"""
for handler in self.__event_handlers.get(name, []):
if direct:
try:
handler[0](copy.copy(data))
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(handler[0]))
if hasattr(data, 'exception'):
data.exception(e)
else:
self.event_queue.put(('event', handler, copy.copy(data)))
if handler[2]:
# If the handler is disposable, we will go ahead and
# remove it now instead of waiting for it to be
@@ -591,7 +625,7 @@ class XMLStream(object):
mask = mask.xml
data = str(data)
if mask is not None:
logging.warning("Use of send mask waiters is deprecated.")
log.warning("Use of send mask waiters is deprecated.")
wait_for = Waiter("SendWait_%s" % self.new_id(),
MatchXMLMask(mask))
self.register_handler(wait_for)
@@ -648,7 +682,7 @@ class XMLStream(object):
self.__thread[name].start()
for t in range(0, HANDLER_THREADS):
logging.debug("Starting HANDLER THREAD")
log.debug("Starting HANDLER THREAD")
start_thread('stream_event_handler_%s' % t, self._event_runner)
start_thread('send_thread', self._send_thread)
@@ -686,16 +720,16 @@ class XMLStream(object):
if self.is_client:
self.send_raw(self.stream_header)
except KeyboardInterrupt:
logging.debug("Keyboard Escape Detected in _process")
log.debug("Keyboard Escape Detected in _process")
self.stop.set()
except SystemExit:
logging.debug("SystemExit in _process")
log.debug("SystemExit in _process")
self.stop.set()
except Socket.error:
logging.exception('Socket Error')
log.exception('Socket Error')
except:
if not self.stop.isSet():
logging.exception('Connection error.')
log.exception('Connection error.')
if not self.stop.isSet() and self.auto_reconnect:
self.reconnect()
else:
@@ -725,7 +759,7 @@ class XMLStream(object):
if depth == 0:
# The stream's root element has closed,
# terminating the stream.
logging.debug("End of stream recieved")
log.debug("End of stream recieved")
self.stream_end_event.set()
return False
elif depth == 1:
@@ -739,7 +773,29 @@ class XMLStream(object):
# Keep the root element empty of children to
# save on memory use.
root.clear()
logging.debug("Ending read XML loop")
log.debug("Ending read XML loop")
def _build_stanza(self, xml, default_ns=None):
"""
Create a stanza object from a given XML object.
If a specialized stanza type is not found for the XML, then
a generic StanzaBase stanza will be returned.
Arguments:
xml -- The XML object to convert into a stanza object.
default_ns -- Optional default namespace to use instead of the
stream's current default namespace.
"""
if default_ns is None:
default_ns = self.default_ns
stanza_type = StanzaBase
for stanza_class in self.__root_stanza:
if xml.tag == "{%s}%s" % (default_ns, stanza_class.name):
stanza_type = stanza_class
break
stanza = stanza_type(self, xml)
return stanza
def __spawn_event(self, xml):
"""
@@ -750,7 +806,7 @@ class XMLStream(object):
Arguments:
xml -- The XML stanza to analyze.
"""
logging.debug("RECV: %s" % tostring(xml,
log.debug("RECV: %s" % tostring(xml,
xmlns=self.default_ns,
stream=self))
# Apply any preprocessing filters.
@@ -786,6 +842,23 @@ class XMLStream(object):
if unhandled:
stanza.unhandled()
def _threaded_event_wrapper(self, func, args):
"""
Capture exceptions for event handlers that run
in individual threads.
Arguments:
func -- The event handler to execute.
args -- Arguments to the event handler.
"""
try:
func(*args)
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(func))
if hasattr(args[0], 'exception'):
args[0].exception(e)
def _event_runner(self):
"""
Process the event queue and execute handlers.
@@ -795,7 +868,7 @@ class XMLStream(object):
Stream event handlers will all execute in this thread. Custom event
handlers may be spawned in individual threads.
"""
logging.debug("Loading event runner")
log.debug("Loading event runner")
try:
while not self.stop.isSet():
try:
@@ -813,31 +886,35 @@ class XMLStream(object):
handler.run(args[0])
except Exception as e:
error_msg = 'Error processing stream handler: %s'
logging.exception(error_msg % handler.name)
log.exception(error_msg % handler.name)
args[0].exception(e)
elif etype == 'schedule':
try:
logging.debug(args)
log.debug(args)
handler(*args[0])
except:
logging.exception('Error processing scheduled task')
log.exception('Error processing scheduled task')
elif etype == 'event':
func, threaded, disposable = handler
try:
if threaded:
x = threading.Thread(name="Event_%s" % str(func),
target=func,
args=args)
x = threading.Thread(
name="Event_%s" % str(func),
target=self._threaded_event_wrapper,
args=(func, args))
x.start()
else:
func(*args)
except:
logging.exception('Error processing event handler: %s')
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(func))
if hasattr(args[0], 'exception'):
args[0].exception(e)
elif etype == 'quit':
logging.debug("Quitting event runner thread")
log.debug("Quitting event runner thread")
return False
except KeyboardInterrupt:
logging.debug("Keyboard Escape Detected in _event_runner")
log.debug("Keyboard Escape Detected in _event_runner")
self.disconnect()
return
except SystemExit:
@@ -855,14 +932,14 @@ class XMLStream(object):
data = self.send_queue.get(True, 1)
except queue.Empty:
continue
logging.debug("SEND: %s" % data)
log.debug("SEND: %s" % data)
try:
self.socket.send(data.encode('utf-8'))
except:
logging.warning("Failed to send %s" % data)
log.warning("Failed to send %s" % data)
self.disconnect(self.auto_reconnect)
except KeyboardInterrupt:
logging.debug("Keyboard Escape Detected in _send_thread")
log.debug("Keyboard Escape Detected in _send_thread")
self.disconnect()
return
except SystemExit:

View File

@@ -20,9 +20,9 @@ class TestLiveStream(SleekTest):
# Use sid=None to ignore any id sent by the server since
# we can't know it in advance.
self.stream_recv_header(sfrom='localhost', sid=None)
self.stream_send_header(sto='localhost')
self.stream_recv_feature("""
self.recv_header(sfrom='localhost', sid=None)
self.send_header(sto='localhost')
self.recv_feature("""
<stream:features>
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls" />
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
@@ -35,15 +35,15 @@ class TestLiveStream(SleekTest):
<register xmlns="http://jabber.org/features/iq-register" />
</stream:features>
""")
self.stream_send_feature("""
self.send_feature("""
<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls" />
""")
self.stream_recv_feature("""
self.recv_feature("""
<proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls" />
""")
self.stream_send_header(sto='localhost')
self.stream_recv_header(sfrom='localhost', sid=None)
self.stream_recv_feature("""
self.send_header(sto='localhost')
self.recv_header(sfrom='localhost', sid=None)
self.recv_feature("""
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>DIGEST-MD5</mechanism>
@@ -56,16 +56,16 @@ class TestLiveStream(SleekTest):
<register xmlns="http://jabber.org/features/iq-register" />
</stream:features>
""")
self.stream_send_feature("""
self.send_feature("""
<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl"
mechanism="PLAIN">AHVzZXIAdXNlcg==</auth>
""")
self.stream_recv_feature("""
self.recv_feature("""
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />
""")
self.stream_send_header(sto='localhost')
self.stream_recv_header(sfrom='localhost', sid=None)
self.stream_recv_feature("""
self.send_header(sto='localhost')
self.recv_header(sfrom='localhost', sid=None)
self.recv_feature("""
<stream:features>
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" />
<session xmlns="urn:ietf:params:xml:ns:xmpp-session" />
@@ -77,16 +77,16 @@ class TestLiveStream(SleekTest):
</stream:features>
""")
# Should really use stream_send_iq, but our Iq stanza objects
# Should really use send, but our Iq stanza objects
# can't handle bind element payloads yet.
self.stream_send_feature("""
self.send_feature("""
<iq type="set" id="1">
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<resource>test</resource>
</bind>
</iq>
""")
self.stream_recv_feature("""
self.recv_feature("""
<iq type="result" id="1">
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<jid>user@localhost/test</jid>

View File

@@ -3,26 +3,126 @@ from sleekxmpp.xmlstream.jid import JID
class TestJIDClass(SleekTest):
def testJIDfromfull(self):
j = JID('user@someserver/some/resource')
self.assertEqual(j.user, 'user', "User does not match")
self.assertEqual(j.domain, 'someserver', "Domain does not match")
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
"""Verify that the JID class can parse and manipulate JIDs."""
def testJIDFromFull(self):
"""Test using JID of the form 'user@server/resource/with/slashes'."""
self.check_jid(JID('user@someserver/some/resource'),
'user',
'someserver',
'some/resource',
'user@someserver',
'user@someserver/some/resource',
'user@someserver/some/resource')
def testJIDchange(self):
"""Test changing JID of the form 'user@server/resource/with/slashes'"""
j = JID('user1@someserver1/some1/resource1')
j.user = 'user'
j.domain = 'someserver'
j.resource = 'some/resource'
self.assertEqual(j.user, 'user', "User does not match")
self.assertEqual(j.domain, 'someserver', "Domain does not match")
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
self.check_jid(j,
'user',
'someserver',
'some/resource',
'user@someserver',
'user@someserver/some/resource',
'user@someserver/some/resource')
def testJIDaliases(self):
"""Test changing JID using aliases for domain."""
j = JID('user@someserver/resource')
j.server = 'anotherserver'
self.check_jid(j, domain='anotherserver')
j.host = 'yetanother'
self.check_jid(j, domain='yetanother')
def testJIDSetFullWithUser(self):
"""Test setting the full JID with a user portion."""
j = JID('user@domain/resource')
j.full = 'otheruser@otherdomain/otherresource'
self.check_jid(j,
'otheruser',
'otherdomain',
'otherresource',
'otheruser@otherdomain',
'otheruser@otherdomain/otherresource',
'otheruser@otherdomain/otherresource')
def testJIDFullNoUserWithResource(self):
"""
Test setting the full JID without a user
portion and with a resource.
"""
j = JID('user@domain/resource')
j.full = 'otherdomain/otherresource'
self.check_jid(j,
'',
'otherdomain',
'otherresource',
'otherdomain',
'otherdomain/otherresource',
'otherdomain/otherresource')
def testJIDFullNoUserNoResource(self):
"""
Test setting the full JID without a user
portion and without a resource.
"""
j = JID('user@domain/resource')
j.full = 'otherdomain'
self.check_jid(j,
'',
'otherdomain',
'',
'otherdomain',
'otherdomain',
'otherdomain')
def testJIDBareUser(self):
"""Test setting the bare JID with a user."""
j = JID('user@domain/resource')
j.bare = 'otheruser@otherdomain'
self.check_jid(j,
'otheruser',
'otherdomain',
'resource',
'otheruser@otherdomain',
'otheruser@otherdomain/resource',
'otheruser@otherdomain/resource')
def testJIDBareNoUser(self):
"""Test setting the bare JID without a user."""
j = JID('user@domain/resource')
j.bare = 'otherdomain'
self.check_jid(j,
'',
'otherdomain',
'resource',
'otherdomain',
'otherdomain/resource',
'otherdomain/resource')
def testJIDNoResource(self):
"""Test using JID of the form 'user@domain'."""
self.check_jid(JID('user@someserver'),
'user',
'someserver',
'',
'user@someserver',
'user@someserver',
'user@someserver')
def testJIDNoUser(self):
"""Test JID of the form 'component.domain.tld'."""
self.check_jid(JID('component.someserver'),
'',
'component.someserver',
'',
'component.someserver',
'component.someserver',
'component.someserver')
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)

View File

@@ -27,7 +27,7 @@ class TestElementBase(SleekTest):
namespace = "test"
stanza = TestStanza()
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="test">
<bar>
<baz />
@@ -117,7 +117,7 @@ class TestElementBase(SleekTest):
'baz': ''}]}
stanza.setStanzaValues(values)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" bar="a">
<pluginfoo baz="b" />
<pluginfoo2 bar="d" baz="e" />
@@ -198,7 +198,7 @@ class TestElementBase(SleekTest):
stanza['qux'] = 'overridden'
stanza['foobar'] = 'plugin'
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" bar="attribute!">
<baz>element!</baz>
<foobar foobar="plugin" />
@@ -231,7 +231,7 @@ class TestElementBase(SleekTest):
stanza['qux'] = 'c'
stanza['foobar']['foobar'] = 'd'
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" baz="b" qux="c">
<bar>a</bar>
<foobar foobar="d" />
@@ -243,7 +243,7 @@ class TestElementBase(SleekTest):
del stanza['qux']
del stanza['foobar']
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" qux="c" />
""")
@@ -257,7 +257,7 @@ class TestElementBase(SleekTest):
stanza = TestStanza()
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" />
""")
@@ -267,7 +267,7 @@ class TestElementBase(SleekTest):
stanza._set_attr('bar', 'a')
stanza._set_attr('baz', 'b')
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" bar="a" baz="b" />
""")
@@ -277,7 +277,7 @@ class TestElementBase(SleekTest):
stanza._set_attr('bar', None)
stanza._del_attr('baz')
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo" />
""")
@@ -307,7 +307,7 @@ class TestElementBase(SleekTest):
"Default _get_sub_text value incorrect.")
stanza['bar'] = 'found'
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<wrapper>
<bar>found</bar>
@@ -340,7 +340,7 @@ class TestElementBase(SleekTest):
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['baz'] = 'b'
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<wrapper>
<bar>a</bar>
@@ -349,7 +349,7 @@ class TestElementBase(SleekTest):
</foo>
""")
stanza._set_sub_text('wrapper/bar', text='', keep=True)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<wrapper>
<bar />
@@ -360,7 +360,7 @@ class TestElementBase(SleekTest):
stanza['bar'] = 'a'
stanza._set_sub_text('wrapper/bar', text='')
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<wrapper>
<baz>b</baz>
@@ -398,7 +398,7 @@ class TestElementBase(SleekTest):
stanza['bar'] = 'a'
stanza['baz'] = 'b'
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<path>
<to>
@@ -416,7 +416,7 @@ class TestElementBase(SleekTest):
del stanza['bar']
del stanza['baz']
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<path>
<to>
@@ -432,7 +432,7 @@ class TestElementBase(SleekTest):
stanza._del_sub('path/to/only/bar', all=True)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<path>
<to>
@@ -603,7 +603,7 @@ class TestElementBase(SleekTest):
"Incorrect empty stanza size.")
stanza.append(substanza1)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<foobar qux="a" />
</foo>
@@ -612,7 +612,7 @@ class TestElementBase(SleekTest):
"Incorrect stanza size with 1 substanza.")
stanza.append(substanza2)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<foobar qux="a" />
<foobar qux="b" />
@@ -623,7 +623,7 @@ class TestElementBase(SleekTest):
# Test popping substanzas
stanza.pop(0)
self.check_stanza(TestStanza, stanza, """
self.check(stanza, """
<foo xmlns="foo">
<foobar qux="b" />
</foo>

View File

@@ -7,7 +7,7 @@ class TestErrorStanzas(SleekTest):
"""Test setting initial values in error stanza."""
msg = self.Message()
msg.enable('error')
self.check_message(msg, """
self.check(msg, """
<message type="error">
<error type="cancel">
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
@@ -20,7 +20,7 @@ class TestErrorStanzas(SleekTest):
msg = self.Message()
msg['error']['condition'] = 'item-not-found'
self.check_message(msg, """
self.check(msg, """
<message type="error">
<error type="cancel">
<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
@@ -32,7 +32,7 @@ class TestErrorStanzas(SleekTest):
msg['error']['condition'] = 'resource-constraint'
self.check_message(msg, """
self.check(msg, """
<message type="error">
<error type="cancel">
<resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
@@ -48,7 +48,7 @@ class TestErrorStanzas(SleekTest):
del msg['error']['condition']
self.check_message(msg, """
self.check(msg, """
<message type="error">
<error type="cancel">
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Error!</text>
@@ -56,4 +56,21 @@ class TestErrorStanzas(SleekTest):
</message>
""", use_values=False)
def testDelText(self):
"""Test deleting the text of an error."""
msg = self.Message()
msg['error']['test'] = 'Error!'
msg['error']['condition'] = 'internal-server-error'
del msg['error']['text']
self.check(msg, """
<message type="error">
<error type="cancel">
<internal-server-error xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas)

View File

@@ -18,7 +18,7 @@ class TestGmail(SleekTest):
iq['gmail']['newer-than-time'] = '1140638252542'
iq['gmail']['newer-than-tid'] = '11134623426430234'
self.check_iq(iq, """
self.check(iq, """
<iq type="get">
<query xmlns="google:mail:notify"
newer-than-time="1140638252542"

View File

@@ -11,7 +11,7 @@ class TestIqStanzas(SleekTest):
def testSetup(self):
"""Test initializing default Iq values."""
iq = self.Iq()
self.check_iq(iq, """
self.check(iq, """
<iq id="0" />
""")
@@ -19,7 +19,7 @@ class TestIqStanzas(SleekTest):
"""Test setting Iq stanza payload."""
iq = self.Iq()
iq.setPayload(ET.Element('{test}tester'))
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<tester xmlns="test" />
</iq>
@@ -29,7 +29,7 @@ class TestIqStanzas(SleekTest):
def testUnhandled(self):
"""Test behavior for Iq.unhandled."""
self.stream_start()
self.stream_recv("""
self.recv("""
<iq id="test" type="get">
<query xmlns="test" />
</iq>
@@ -40,7 +40,7 @@ class TestIqStanzas(SleekTest):
iq['error']['condition'] = 'feature-not-implemented'
iq['error']['text'] = 'No handlers registered for this request.'
self.stream_send_iq(iq, """
self.send(iq, """
<iq id="test" type="error">
<error type="cancel">
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
@@ -56,14 +56,14 @@ class TestIqStanzas(SleekTest):
iq = self.Iq()
iq['query'] = 'query_ns'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="query_ns" />
</iq>
""")
iq['query'] = 'query_ns2'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="query_ns2" />
</iq>
@@ -72,7 +72,7 @@ class TestIqStanzas(SleekTest):
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
del iq['query']
self.check_iq(iq, """
self.check(iq, """
<iq id="0" />
""")
@@ -83,7 +83,7 @@ class TestIqStanzas(SleekTest):
iq['type'] = 'get'
iq.reply()
self.check_iq(iq, """
self.check(iq, """
<iq id="0" type="result" />
""")

View File

@@ -33,7 +33,7 @@ class TestMessageStanzas(SleekTest):
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
p.text = "This is the htmlim message"
msg['html']['body'] = p
self.check_message(msg, """
self.check(msg, """
<message to="fritzy@netflint.net/sleekxmpp" type="chat">
<body>this is the plaintext message</body>
<html xmlns="http://jabber.org/protocol/xhtml-im">
@@ -47,7 +47,7 @@ class TestMessageStanzas(SleekTest):
"Test message/nick/nick stanza."
msg = self.Message()
msg['nick']['nick'] = 'A nickname!'
self.check_message(msg, """
self.check(msg, """
<message>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
</message>

View File

@@ -8,26 +8,26 @@ class TestPresenceStanzas(SleekTest):
"""Regression check presence['type'] = 'dnd' show value working"""
p = self.Presence()
p['type'] = 'dnd'
self.check_presence(p, "<presence><show>dnd</show></presence>")
self.check(p, "<presence><show>dnd</show></presence>")
def testPresenceType(self):
"""Test manipulating presence['type']"""
p = self.Presence()
p['type'] = 'available'
self.check_presence(p, "<presence />")
self.check(p, "<presence />")
self.failUnless(p['type'] == 'available',
"Incorrect presence['type'] for type 'available': %s" % p['type'])
for showtype in ['away', 'chat', 'dnd', 'xa']:
p['type'] = showtype
self.check_presence(p, """
self.check(p, """
<presence><show>%s</show></presence>
""" % showtype)
self.failUnless(p['type'] == showtype,
"Incorrect presence['type'] for type '%s'" % showtype)
p['type'] = None
self.check_presence(p, "<presence />")
self.check(p, "<presence />")
def testPresenceUnsolicitedOffline(self):
"""
@@ -56,7 +56,7 @@ class TestPresenceStanzas(SleekTest):
"""Test presence/nick/nick stanza."""
p = self.Presence()
p['nick']['nick'] = 'A nickname!'
self.check_presence(p, """
self.check(p, """
<presence>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
</presence>

View File

@@ -16,7 +16,7 @@ class TestRosterStanzas(SleekTest):
'name': 'Other User',
'subscription': 'both',
'groups': []}})
self.check_iq(iq, """
self.check(iq, """
<iq>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" name="User" subscription="both">
@@ -74,7 +74,7 @@ class TestRosterStanzas(SleekTest):
"""
iq = self.Iq(ET.fromstring(xml_string))
del iq['roster']['items']
self.check_iq(iq, """
self.check(iq, """
<iq>
<query xmlns="jabber:iq:roster" />
</iq>

View File

@@ -14,7 +14,7 @@ class TestDataForms(SleekTest):
msg = self.Message()
msg['form']['instructions'] = "Instructions\nSecond batch"
self.check_message(msg, """
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<instructions>Instructions</instructions>
@@ -35,7 +35,7 @@ class TestDataForms(SleekTest):
required=True,
value='Some text!')
self.check_message(msg, """
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
@@ -62,7 +62,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]})]
self.check_message(msg, """
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Username">
@@ -99,7 +99,7 @@ class TestDataForms(SleekTest):
form.setValues({'foo': 'Foo!',
'bar': ['a', 'b']})
self.check_message(msg, """
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="foo" type="text-single">

View File

@@ -14,7 +14,7 @@ class TestDisco(SleekTest):
iq['id'] = "0"
iq['disco_info']['node'] = ''
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" />
</iq>
@@ -26,7 +26,7 @@ class TestDisco(SleekTest):
iq['id'] = "0"
iq['disco_info']['node'] = 'foo'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo" />
</iq>
@@ -38,7 +38,7 @@ class TestDisco(SleekTest):
iq['id'] = "0"
iq['disco_items']['node'] = ''
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" />
</iq>
@@ -50,7 +50,7 @@ class TestDisco(SleekTest):
iq['id'] = "0"
iq['disco_items']['node'] = 'foo'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" node="foo" />
</iq>
@@ -63,7 +63,7 @@ class TestDisco(SleekTest):
iq['disco_info']['node'] = 'foo'
iq['disco_info'].addIdentity('conference', 'text', 'Chatroom')
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo">
<identity category="conference" type="text" name="Chatroom" />
@@ -79,7 +79,7 @@ class TestDisco(SleekTest):
iq['disco_info'].addFeature('foo')
iq['disco_info'].addFeature('bar')
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo">
<feature var="foo" />
@@ -97,7 +97,7 @@ class TestDisco(SleekTest):
iq['disco_items'].addItem('user@localhost', 'foo')
iq['disco_items'].addItem('user@localhost', 'bar', 'Testing')
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" node="foo">
<item jid="user@localhost" />

View File

@@ -11,7 +11,7 @@ class TestAddresses(SleekTest):
"""Testing adding extended stanza address."""
msg = self.Message()
msg['addresses'].addAddress(atype='to', jid='to@header1.org')
self.check_message(msg, """
self.check(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address jid="to@header1.org" type="to" />
@@ -23,7 +23,7 @@ class TestAddresses(SleekTest):
msg['addresses'].addAddress(atype='replyto',
jid='replyto@header1.org',
desc='Reply address')
self.check_message(msg, """
self.check(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address jid="replyto@header1.org" type="replyto" desc="Reply address" />
@@ -53,14 +53,14 @@ class TestAddresses(SleekTest):
'jid':'cc@header2.org'},
{'type':'bcc',
'jid':'bcc@header2.org'}])
self.check_message(msg, xmlstring)
self.check(msg, xmlstring)
msg = self.Message()
msg['addresses']['replyto'] = [{'jid':'replyto@header1.org',
'desc':'Reply address'}]
msg['addresses']['cc'] = [{'jid':'cc@header2.org'}]
msg['addresses']['bcc'] = [{'jid':'bcc@header2.org'}]
self.check_message(msg, xmlstring)
self.check(msg, xmlstring)
def testAddURI(self):
"""Testing adding URI attribute to extended stanza address."""
@@ -69,7 +69,7 @@ class TestAddresses(SleekTest):
addr = msg['addresses'].addAddress(atype='to',
jid='to@header1.org',
node='foo')
self.check_message(msg, """
self.check(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address node="foo" jid="to@header1.org" type="to" />
@@ -78,7 +78,7 @@ class TestAddresses(SleekTest):
""")
addr['uri'] = 'mailto:to@header2.org'
self.check_message(msg, """
self.check(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address type="to" uri="mailto:to@header2.org" />
@@ -99,13 +99,13 @@ class TestAddresses(SleekTest):
msg = self.Message()
addr = msg['addresses'].addAddress(jid='to@header1.org', atype='to')
self.check_message(msg, xmlstring % '')
self.check(msg, xmlstring % '')
addr['delivered'] = True
self.check_message(msg, xmlstring % 'delivered="true"')
self.check(msg, xmlstring % 'delivered="true"')
addr['delivered'] = False
self.check_message(msg, xmlstring % '')
self.check(msg, xmlstring % '')
suite = unittest.TestLoader().loadTestsFromTestCase(TestAddresses)

View File

@@ -16,7 +16,7 @@ class TestPubsubStanzas(SleekTest):
aff2['affiliation'] = 'publisher'
iq['pubsub']['affiliations'].append(aff1)
iq['pubsub']['affiliations'].append(aff2)
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<affiliations>
@@ -38,7 +38,7 @@ class TestPubsubStanzas(SleekTest):
sub2['subscription'] = 'subscribed'
iq['pubsub']['subscriptions'].append(sub1)
iq['pubsub']['subscriptions'].append(sub2)
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<subscriptions>
@@ -55,7 +55,7 @@ class TestPubsubStanzas(SleekTest):
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
iq['pubsub']['subscription']['subscription'] = 'unconfigured'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured">
@@ -88,7 +88,7 @@ class TestPubsubStanzas(SleekTest):
item2['payload'] = payload2
iq['pubsub']['items'].append(item)
iq['pubsub']['items'].append(item2)
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<items node="crap">
@@ -115,7 +115,7 @@ class TestPubsubStanzas(SleekTest):
iq['pubsub']['configure']['form'].addField('pubsub#title',
ftype='text-single',
value='This thing is awesome')
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<create node="mynode" />
@@ -136,7 +136,7 @@ class TestPubsubStanzas(SleekTest):
iq['psstate']['item']= 'myitem'
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
iq['psstate']['payload'] = pl
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
<claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
@@ -152,7 +152,7 @@ class TestPubsubStanzas(SleekTest):
iq['pubsub_owner']['default']['form'].addField('pubsub#title',
ftype='text-single',
value='This thing is awesome')
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
<default node="mynode" type="leaf">
@@ -176,7 +176,7 @@ class TestPubsubStanzas(SleekTest):
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
iq['pubsub']['subscribe']['options']['options'] = form
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
@@ -214,7 +214,7 @@ class TestPubsubStanzas(SleekTest):
iq['pubsub']['publish'].append(item)
iq['pubsub']['publish'].append(item2)
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<publish node="thingers">
@@ -238,7 +238,7 @@ class TestPubsubStanzas(SleekTest):
"Testing iq/pubsub_owner/delete stanzas"
iq = self.Iq()
iq['pubsub_owner']['delete']['node'] = 'thingers'
self.check_iq(iq, """
self.check(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
<delete node="thingers" />
@@ -300,7 +300,7 @@ class TestPubsubStanzas(SleekTest):
'label': 'Deliver notification only to available users'}),
])
self.check_iq(iq, """
self.check(iq, """
<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<create node="testnode2" />
@@ -357,7 +357,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['items'].append(item)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.check_message(msg, """
self.check(msg, """
<message type="normal">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="cheese">
@@ -383,7 +383,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.check_message(msg, """
self.check(msg, """
<message type="normal">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="cheese">
@@ -415,7 +415,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.check_message(msg, """
self.check(msg, """
<message type="normal">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="cheese">
@@ -435,7 +435,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline'
self.check_message(msg, """
self.check(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<collection node="cheeseburger">
@@ -450,7 +450,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline'
self.check_message(msg, """
self.check(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<collection node="cheeseburger">
@@ -467,7 +467,7 @@ class TestPubsubStanzas(SleekTest):
ftype='text-single',
value='This thing is awesome')
msg['type'] = 'headline'
self.check_message(msg, """
self.check(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<configuration node="cheese">
@@ -485,7 +485,7 @@ class TestPubsubStanzas(SleekTest):
msg = self.Message()
msg['pubsub_event']['purge']['node'] = 'pickles'
msg['type'] = 'headline'
self.check_message(msg, """
self.check(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<purge node="pickles" />
@@ -501,7 +501,7 @@ class TestPubsubStanzas(SleekTest):
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
msg['pubsub_event']['subscription']['expiry'] = 'presence'
msg['type'] = 'headline'
self.check_message(msg, """
self.check(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" />

View File

@@ -21,24 +21,24 @@ class TestChatStates(SleekTest):
msg = self.Message()
msg['chat_state'].active()
self.check_message(msg, xmlstring % 'active',
self.check(msg, xmlstring % 'active',
use_values=False)
msg['chat_state'].composing()
self.check_message(msg, xmlstring % 'composing',
self.check(msg, xmlstring % 'composing',
use_values=False)
msg['chat_state'].gone()
self.check_message(msg, xmlstring % 'gone',
self.check(msg, xmlstring % 'gone',
use_values=False)
msg['chat_state'].inactive()
self.check_message(msg, xmlstring % 'inactive',
self.check(msg, xmlstring % 'inactive',
use_values=False)
msg['chat_state'].paused()
self.check_message(msg, xmlstring % 'paused',
self.check(msg, xmlstring % 'paused',
use_values=False)
suite = unittest.TestLoader().loadTestsFromTestCase(TestChatStates)

View File

@@ -19,13 +19,13 @@ class TestStreamTester(SleekTest):
self.xmpp.add_event_handler('message', echo)
self.stream_recv("""
self.recv("""
<message to="tester@localhost" from="user@localhost">
<body>Hi!</body>
</message>
""")
self.stream_send_message("""
self.send("""
<message to="user@localhost">
<body>Thanks for sending: Hi!</body>
</message>
@@ -40,13 +40,13 @@ class TestStreamTester(SleekTest):
self.xmpp.add_event_handler('message', echo)
self.stream_recv("""
self.recv("""
<message to="tester.localhost" from="user@localhost">
<body>Hi!</body>
</message>
""")
self.stream_send_message("""
self.send("""
<message to="user@localhost" from="tester.localhost">
<body>Thanks for sending: Hi!</body>
</message>
@@ -55,6 +55,6 @@ class TestStreamTester(SleekTest):
def testSendStreamHeader(self):
"""Test that we can check a sent stream header."""
self.stream_start(mode='client', skip=False)
self.stream_send_header(sto='localhost')
self.send_header(sto='localhost')
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamTester)

View File

@@ -0,0 +1,110 @@
import sys
import sleekxmpp
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.test import *
class TestStreamExceptions(SleekTest):
"""
Test handling roster updates.
"""
def tearDown(self):
self.stream_close()
def testXMPPErrorException(self):
"""Test raising an XMPPError exception."""
def message(msg):
raise XMPPError(condition='feature-not-implemented',
text="We don't do things that way here.",
etype='cancel',
extension='foo',
extension_ns='foo:error',
extension_args={'test': 'true'})
self.stream_start()
self.xmpp.add_event_handler('message', message)
self.recv("""
<message>
<body>This is going to cause an error.</body>
</message>
""")
self.send("""
<message type="error">
<error type="cancel">
<feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
We don&apos;t do things that way here.
</text>
<foo xmlns="foo:error" test="true" />
</error>
</message>
""", use_values=False)
def testThreadedXMPPErrorException(self):
"""Test raising an XMPPError exception in a threaded handler."""
def message(msg):
raise XMPPError(condition='feature-not-implemented',
text="We don't do things that way here.",
etype='cancel')
self.stream_start()
self.xmpp.add_event_handler('message', message,
threaded=True)
self.recv("""
<message>
<body>This is going to cause an error.</body>
</message>
""")
self.send("""
<message type="error">
<error type="cancel">
<feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
We don&apos;t do things that way here.
</text>
</error>
</message>
""")
def testUnknownException(self):
"""Test raising an generic exception in a threaded handler."""
def message(msg):
raise ValueError("Did something wrong")
self.stream_start()
self.xmpp.add_event_handler('message', message)
self.recv("""
<message>
<body>This is going to cause an error.</body>
</message>
""")
if sys.version_info < (3, 0):
self.send("""
<message type="error">
<error type="cancel">
<undefined-condition
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
SleekXMPP got into trouble.
</text>
</error>
</message>
""")
else:
# Unfortunately, tracebacks do not make for very portable tests.
pass
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExceptions)

View File

@@ -30,11 +30,11 @@ class TestHandlers(SleekTest):
self.xmpp.registerHandler(callback)
self.stream_recv("""<tester xmlns="test" />""")
self.recv("""<tester xmlns="test" />""")
msg = self.Message()
msg['body'] = 'Success!'
self.stream_send_message(msg)
self.send(msg)
def testWaiter(self):
"""Test using stream waiter handler."""
@@ -55,7 +55,7 @@ class TestHandlers(SleekTest):
self.xmpp.add_event_handler('message', waiter_handler, threaded=True)
# Send message to trigger waiter_handler
self.stream_recv("""
self.recv("""
<message>
<body>Testing</body>
</message>
@@ -66,10 +66,10 @@ class TestHandlers(SleekTest):
iq['id'] = 'test'
iq['type'] = 'set'
iq['query'] = 'test'
self.stream_send_iq(iq)
self.send(iq)
# Send the reply Iq
self.stream_recv("""
self.recv("""
<iq id="test" type="result">
<query xmlns="test" />
</iq>
@@ -78,7 +78,7 @@ class TestHandlers(SleekTest):
# Check that waiter_handler received the reply
msg = self.Message()
msg['body'] = 'Successful: test'
self.stream_send_message(msg)
self.send(msg)
def testWaiterTimeout(self):
"""Test that waiter handler is removed after timeout."""
@@ -93,14 +93,14 @@ class TestHandlers(SleekTest):
self.xmpp.add_event_handler('message', waiter_handler, threaded=True)
# Start test by triggerig waiter_handler
self.stream_recv("""<message><body>Start Test</body></message>""")
self.recv("""<message><body>Start Test</body></message>""")
# Check that Iq was sent to trigger start of timeout period
iq = self.Iq()
iq['id'] = 'test2'
iq['type'] = 'set'
iq['query'] = 'test2'
self.stream_send_iq(iq)
self.send(iq)
# Check that the waiter is no longer registered
waiter_exists = self.xmpp.removeHandler('IqWait_test2')

View File

@@ -0,0 +1,188 @@
import time
from sleekxmpp.test import *
class TestStreamPresence(SleekTest):
"""
Test handling roster updates.
"""
def tearDown(self):
self.stream_close()
def testInitialUnavailablePresences(self):
"""
Test receiving unavailable presences from JIDs that
are not online.
"""
events = set()
def got_offline(presence):
# The got_offline event should not be triggered.
events.add('got_offline')
def unavailable(presence):
# The presence_unavailable event should be triggered.
events.add('unavailable')
self.stream_start()
self.xmpp.add_event_handler('got_offline', got_offline)
self.xmpp.add_event_handler('presence_unavailable', unavailable)
self.recv("""
<presence type="unavailable" from="otheruser@localhost" />
""")
# Give event queue time to process.
time.sleep(0.1)
self.assertEqual(events, set(('unavailable',)),
"Got offline incorrectly triggered: %s." % events)
def testGotOffline(self):
"""Test that got_offline is triggered properly."""
events = []
def got_offline(presence):
events.append('got_offline')
self.stream_start()
self.xmpp.add_event_handler('got_offline', got_offline)
# Setup roster. Use a 'set' instead of 'result' so we
# don't have to handle get_roster() blocking.
#
# We use the stream to initialize the roster to make
# the test independent of the roster implementation.
self.recv("""
<iq type="set">
<query xmlns="jabber:iq:roster">
<item jid="otheruser@localhost"
name="Other User"
subscription="both">
<group>Testers</group>
</item>
</query>
</iq>
""")
# Contact comes online.
self.recv("""
<presence from="otheruser@localhost/foobar" />
""")
# Contact goes offline, should trigger got_offline.
self.recv("""
<presence from="otheruser@localhost/foobar"
type="unavailable" />
""")
# Give event queue time to process.
time.sleep(0.1)
self.assertEqual(events, ['got_offline'],
"Got offline incorrectly triggered: %s" % events)
def testGotOnline(self):
"""Test that got_online is triggered properly."""
events = set()
def presence_available(p):
events.add('presence_available')
def got_online(p):
events.add('got_online')
self.stream_start()
self.xmpp.add_event_handler('presence_available', presence_available)
self.xmpp.add_event_handler('got_online', got_online)
self.recv("""
<presence from="user@localhost" />
""")
# Give event queue time to process.
time.sleep(0.1)
expected = set(('presence_available', 'got_online'))
self.assertEqual(events, expected,
"Incorrect events triggered: %s" % events)
def testAutoAuthorizeAndSubscribe(self):
"""
Test auto authorizing and auto subscribing
to subscription requests.
"""
events = set()
def presence_subscribe(p):
events.add('presence_subscribe')
def changed_subscription(p):
events.add('changed_subscription')
self.stream_start(jid='tester@localhost')
self.xmpp.add_event_handler('changed_subscription',
changed_subscription)
self.xmpp.add_event_handler('presence_subscribe',
presence_subscribe)
# With these settings we should accept a subscription
# and request a subscription in return.
self.xmpp.auto_authorize = True
self.xmpp.auto_subscribe = True
self.recv("""
<presence from="user@localhost" type="subscribe" />
""")
self.send("""
<presence to="user@localhost" type="subscribed" />
""")
self.send("""
<presence to="user@localhost" type="subscribe" />
""")
expected = set(('presence_subscribe', 'changed_subscription'))
self.assertEqual(events, expected,
"Incorrect events triggered: %s" % events)
def testNoAutoAuthorize(self):
"""Test auto rejecting subscription requests."""
events = set()
def presence_subscribe(p):
events.add('presence_subscribe')
def changed_subscription(p):
events.add('changed_subscription')
self.stream_start(jid='tester@localhost')
self.xmpp.add_event_handler('changed_subscription',
changed_subscription)
self.xmpp.add_event_handler('presence_subscribe',
presence_subscribe)
# With this setting we should reject all subscriptions.
self.xmpp.auto_authorize = False
self.recv("""
<presence from="user@localhost" type="subscribe" />
""")
self.send("""
<presence to="user@localhost" type="unsubscribed" />
""")
expected = set(('presence_subscribe', 'changed_subscription'))
self.assertEqual(events, expected,
"Incorrect events triggered: %s" % events)
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPresence)

View File

@@ -0,0 +1,86 @@
from sleekxmpp.test import *
import time
import threading
class TestStreamRoster(SleekTest):
"""
Test handling roster updates.
"""
def tearDown(self):
self.stream_close()
def testGetRoster(self):
"""Test handling roster requests."""
self.stream_start(mode='client')
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
# Since get_roster blocks, we need to run it in a thread.
t = threading.Thread(name='get_roster', target=self.xmpp.get_roster)
t.start()
self.send("""
<iq type="get" id="1">
<query xmlns="jabber:iq:roster" />
</iq>
""")
self.recv("""
<iq type="result" id="1">
<query xmlns="jabber:iq:roster">
<item jid="user@localhost"
name="User"
subscription="both">
<group>Friends</group>
<group>Examples</group>
</item>
</query>
</iq>
""")
# Wait for get_roster to return.
t.join()
roster = {'user@localhost': {'name': 'User',
'subscription': 'both',
'groups': ['Friends', 'Examples'],
'presence': {},
'in_roster': True}}
self.failUnless(self.xmpp.roster == roster,
"Unexpected roster values: %s" % self.xmpp.roster)
def testRosterSet(self):
"""Test handling pushed roster updates."""
self.stream_start(mode='client')
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
self.recv("""
<iq type="set" id="1">
<query xmlns="jabber:iq:roster">
<item jid="user@localhost"
name="User"
subscription="both">
<group>Friends</group>
<group>Examples</group>
</item>
</query>
</iq>
""")
self.send("""
<iq type="result" id="1">
<query xmlns="jabber:iq:roster" />
</iq>
""")
roster = {'user@localhost': {'name': 'User',
'subscription': 'both',
'groups': ['Friends', 'Examples'],
'presence': {},
'in_roster': True}}
self.failUnless(self.xmpp.roster == roster,
"Unexpected roster values: %s" % self.xmpp.roster)
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster)

View File

@@ -73,6 +73,16 @@ class TestToString(SleekTest):
message="The xmlns parameter was not used properly.",
xmlns='foo')
def testTailContent(self):
"""
Test that elements of the form <a>foo <b>bar</b> baz</a> only
include " baz" once.
"""
self.tryTostring(
original='<a>foo <b>bar</b> baz</a>',
message='Element tail content is incorrect.')
def testStanzaNs(self):
"""
Test using the stanza_ns tostring parameter, which will prevent