summaryrefslogtreecommitdiff
path: root/sleekxmpp
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp')
-rw-r--r--sleekxmpp/__init__.py8
-rw-r--r--sleekxmpp/basexmpp.py8
-rw-r--r--sleekxmpp/clientxmpp.py21
-rw-r--r--sleekxmpp/componentxmpp.py6
-rw-r--r--sleekxmpp/features/feature_bind/bind.py2
-rw-r--r--sleekxmpp/features/feature_mechanisms/mechanisms.py1
-rw-r--r--sleekxmpp/features/feature_preapproval/preapproval.py2
-rw-r--r--sleekxmpp/features/feature_rosterver/rosterver.py2
-rw-r--r--sleekxmpp/jid.py134
-rw-r--r--sleekxmpp/plugins/__init__.py2
-rw-r--r--sleekxmpp/plugins/xep_0027/gpg.py2
-rw-r--r--sleekxmpp/plugins/xep_0027/stanza.py2
-rw-r--r--sleekxmpp/plugins/xep_0047/ibb.py107
-rw-r--r--sleekxmpp/plugins/xep_0047/stream.py23
-rw-r--r--sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py2
-rw-r--r--sleekxmpp/plugins/xep_0065/proxy.py64
-rw-r--r--sleekxmpp/plugins/xep_0082.py1
-rw-r--r--sleekxmpp/plugins/xep_0084/avatar.py1
-rw-r--r--sleekxmpp/plugins/xep_0095/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0095/stanza.py25
-rw-r--r--sleekxmpp/plugins/xep_0095/stream_initiation.py214
-rw-r--r--sleekxmpp/plugins/xep_0096/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0096/file_transfer.py58
-rw-r--r--sleekxmpp/plugins/xep_0096/stanza.py48
-rw-r--r--sleekxmpp/plugins/xep_0152/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0152/reachability.py93
-rw-r--r--sleekxmpp/plugins/xep_0152/stanza.py29
-rw-r--r--sleekxmpp/plugins/xep_0319/__init__.py16
-rw-r--r--sleekxmpp/plugins/xep_0319/idle.py75
-rw-r--r--sleekxmpp/plugins/xep_0319/stanza.py28
-rw-r--r--sleekxmpp/roster/__init__.py1
-rw-r--r--sleekxmpp/test/sleektest.py6
-rw-r--r--sleekxmpp/thirdparty/mini_dateutil.py4
-rw-r--r--sleekxmpp/thirdparty/socks.py6
-rw-r--r--sleekxmpp/thirdparty/statemachine.py3
-rw-r--r--sleekxmpp/util/sasl/__init__.py4
-rw-r--r--sleekxmpp/util/sasl/client.py4
-rw-r--r--sleekxmpp/util/sasl/mechanisms.py4
-rw-r--r--sleekxmpp/util/stringprep_profiles.py3
-rw-r--r--sleekxmpp/xmlstream/cert.py4
-rw-r--r--sleekxmpp/xmlstream/filesocket.py9
-rw-r--r--sleekxmpp/xmlstream/matcher/xmlmask.py4
-rw-r--r--sleekxmpp/xmlstream/resolver.py5
-rw-r--r--sleekxmpp/xmlstream/scheduler.py34
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py49
45 files changed, 992 insertions, 170 deletions
diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py
index f0dc2ce2..542bb4b6 100644
--- a/sleekxmpp/__init__.py
+++ b/sleekxmpp/__init__.py
@@ -6,14 +6,14 @@
See the file LICENSE for copying permission.
"""
-from sleekxmpp.basexmpp import BaseXMPP
-from sleekxmpp.clientxmpp import ClientXMPP
-from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.jid import JID, InvalidJID
+from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
-from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
+from sleekxmpp.basexmpp import BaseXMPP
+from sleekxmpp.clientxmpp import ClientXMPP
+from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.version import __version__, __version_info__
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index a54e4bb6..bf0ae4df 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -18,8 +18,7 @@ import sys
import logging
import threading
-import sleekxmpp
-from sleekxmpp import plugins, features, roster
+from sleekxmpp import plugins, roster, stanza
from sleekxmpp.api import APIRegistry
from sleekxmpp.exceptions import IqError, IqTimeout
@@ -34,8 +33,7 @@ from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
-from sleekxmpp.features import *
-from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
+from sleekxmpp.plugins import PluginManager, load_plugin
log = logging.getLogger(__name__)
@@ -148,7 +146,7 @@ class BaseXMPP(XMLStream):
#: A reference to :mod:`sleekxmpp.stanza` to make accessing
#: stanza classes easier.
- self.stanza = sleekxmpp.stanza
+ self.stanza = stanza
self.register_handler(
Callback('IM',
diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index 905e1944..6bcb36ba 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -96,6 +96,7 @@ class ClientXMPP(BaseXMPP):
self.add_event_handler('connected', self._reset_connection_state)
self.add_event_handler('session_bind', self._handle_session_bind)
+ self.add_event_handler('roster_update', self._handle_roster)
self.register_stanza(StreamFeatures)
@@ -106,7 +107,7 @@ class ClientXMPP(BaseXMPP):
self.register_handler(
Callback('Roster Update',
StanzaPath('iq@type=set/roster'),
- self._handle_roster))
+ lambda iq: self.event('roster_update', iq)))
# Setup default stream features
self.register_plugin('feature_starttls')
@@ -154,8 +155,6 @@ class ClientXMPP(BaseXMPP):
address = (self.boundjid.host, 5222)
self.dns_service = 'xmpp-client'
- self._expected_server_name = self.boundjid.host
-
return XMLStream.connect(self, address[0], address[1],
use_tls=use_tls, use_ssl=use_ssl,
reattempt=reattempt)
@@ -244,13 +243,22 @@ class ClientXMPP(BaseXMPP):
if 'rosterver' in self.features:
iq['roster']['ver'] = self.client_roster.version
- if not block and callback is None:
- callback = lambda resp: self._handle_roster(resp)
+
+ if not block or callback is not None:
+ block = False
+ if callback is None:
+ callback = lambda resp: self.event('roster_update', resp)
+ else:
+ orig_cb = callback
+ def wrapped(resp):
+ self.event('roster_update', resp)
+ orig_cb(resp)
+ callback = wrapped
response = iq.send(block, timeout, callback)
if block:
- self._handle_roster(response)
+ self.event('roster_update', response)
return response
def _reset_connection_state(self, event=None):
@@ -301,7 +309,6 @@ class ClientXMPP(BaseXMPP):
roster[jid].save(remove=(item['subscription'] == 'remove'))
- self.event("roster_update", iq)
if iq['type'] == 'set':
resp = self.Iq(stype='result',
sto=iq['from'],
diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py
index 5284f9d4..bac455e2 100644
--- a/sleekxmpp/componentxmpp.py
+++ b/sleekxmpp/componentxmpp.py
@@ -123,12 +123,6 @@ class ComponentXMPP(BaseXMPP):
"""
if xml.tag.startswith('{jabber:client}'):
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
-
- # The incoming_filter call is only made on top level stanza
- # elements. So we manually continue filtering on sub-elements.
- for sub in xml:
- self.incoming_filter(sub)
-
return xml
def start_stream_handler(self, xml):
diff --git a/sleekxmpp/features/feature_bind/bind.py b/sleekxmpp/features/feature_bind/bind.py
index bc145620..ee4c1e9b 100644
--- a/sleekxmpp/features/feature_bind/bind.py
+++ b/sleekxmpp/features/feature_bind/bind.py
@@ -12,7 +12,7 @@ from sleekxmpp.jid import JID
from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_bind import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
-from sleekxmpp.plugins import BasePlugin, register_plugin
+from sleekxmpp.plugins import BasePlugin
log = logging.getLogger(__name__)
diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py
index 58ed9b91..ca94bce1 100644
--- a/sleekxmpp/features/feature_mechanisms/mechanisms.py
+++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py
@@ -6,7 +6,6 @@
See the file LICENSE for copying permission.
"""
-import sys
import ssl
import logging
diff --git a/sleekxmpp/features/feature_preapproval/preapproval.py b/sleekxmpp/features/feature_preapproval/preapproval.py
index 3823c472..c7106ed3 100644
--- a/sleekxmpp/features/feature_preapproval/preapproval.py
+++ b/sleekxmpp/features/feature_preapproval/preapproval.py
@@ -8,7 +8,7 @@
import logging
-from sleekxmpp.stanza import Iq, StreamFeatures
+from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.features.feature_preapproval import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin
diff --git a/sleekxmpp/features/feature_rosterver/rosterver.py b/sleekxmpp/features/feature_rosterver/rosterver.py
index 9e0bb8e8..2991f587 100644
--- a/sleekxmpp/features/feature_rosterver/rosterver.py
+++ b/sleekxmpp/features/feature_rosterver/rosterver.py
@@ -8,7 +8,7 @@
import logging
-from sleekxmpp.stanza import Iq, StreamFeatures
+from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.features.feature_rosterver import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin
diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py
index 752c3210..620d4160 100644
--- a/sleekxmpp/jid.py
+++ b/sleekxmpp/jid.py
@@ -228,7 +228,7 @@ def _validate_domain(domain):
for char in label:
if char in ILLEGAL_CHARS:
- raise InvalidJID('Domain contains illegar characters')
+ raise InvalidJID('Domain contains illegal characters')
if '-' in (label[0], label[-1]):
raise InvalidJID('Domain started or ended with -')
@@ -506,50 +506,100 @@ class JID(object):
"""
self._jid = JID(data)._jid
- # pylint: disable=R0911
- def __getattr__(self, name):
- """Retrieve the given JID component.
+ @property
+ def resource(self):
+ return self._jid[2] or ''
- :param name: one of: user, server, domain, resource,
- full, or bare.
- """
- if name == 'resource':
- return self._jid[2] or ''
- elif name in ('user', 'username', 'local', 'node'):
- return self._jid[0] or ''
- elif name in ('server', 'domain', 'host'):
- return self._jid[1] or ''
- elif name in ('full', 'jid'):
- return _format_jid(*self._jid)
- elif name == 'bare':
- return _format_jid(self._jid[0], self._jid[1])
- elif name == '_jid':
- return getattr(super(JID, self), '_jid')
- else:
- return None
+ @property
+ def user(self):
+ return self._jid[0] or ''
- # pylint: disable=W0212
- def __setattr__(self, name, value):
- """Update the given JID component.
+ @property
+ def local(self):
+ return self._jid[0] or ''
+
+ @property
+ def node(self):
+ return self._jid[0] or ''
+
+ @property
+ def username(self):
+ return self._jid[0] or ''
+
+ @property
+ def bare(self):
+ return _format_jid(self._jid[0], self._jid[1])
+
+ @property
+ def server(self):
+ return self._jid[1] or ''
+
+ @property
+ def domain(self):
+ return self._jid[1] or ''
+
+ @property
+ def host(self):
+ return self._jid[1] or ''
+
+ @property
+ def full(self):
+ return _format_jid(*self._jid)
+
+ @property
+ def jid(self):
+ return _format_jid(*self._jid)
+
+ @property
+ def bare(self):
+ return _format_jid(self._jid[0], self._jid[1])
+
+
+ @resource.setter
+ def resource(self, value):
+ self._jid = JID(self, resource=value)._jid
+
+ @user.setter
+ def user(self, value):
+ self._jid = JID(self, local=value)._jid
+
+ @username.setter
+ def username(self, value):
+ self._jid = JID(self, local=value)._jid
+
+ @local.setter
+ def local(self, value):
+ self._jid = JID(self, local=value)._jid
+
+ @node.setter
+ def node(self, value):
+ self._jid = JID(self, local=value)._jid
+
+ @server.setter
+ def server(self, value):
+ self._jid = JID(self, domain=value)._jid
+
+ @domain.setter
+ def domain(self, value):
+ self._jid = JID(self, domain=value)._jid
+
+ @host.setter
+ def host(self, value):
+ self._jid = JID(self, domain=value)._jid
+
+ @full.setter
+ def full(self, value):
+ self._jid = JID(value)._jid
+
+ @jid.setter
+ def jid(self, value):
+ self._jid = JID(value)._jid
+
+ @bare.setter
+ def bare(self, value):
+ parsed = JID(value)._jid
+ self._jid = (parsed[0], parsed[1], self._jid[2])
- :param name: one of: ``user``, ``username``, ``local``,
- ``node``, ``server``, ``domain``, ``host``,
- ``resource``, ``full``, ``jid``, or ``bare``.
- :param value: The new string value of the JID component.
- """
- if name == '_jid':
- super(JID, self).__setattr__('_jid', value)
- elif name == 'resource':
- self._jid = JID(self, resource=value)._jid
- elif name in ('user', 'username', 'local', 'node'):
- self._jid = JID(self, local=value)._jid
- elif name in ('server', 'domain', 'host'):
- self._jid = JID(self, domain=value)._jid
- elif name in ('full', 'jid'):
- self._jid = JID(value)._jid
- elif name == 'bare':
- parsed = JID(value)._jid
- self._jid = (parsed[0], parsed[1], self._jid[2])
def __str__(self):
"""Use the full JID as the string value."""
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index bdd06d50..951f31eb 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -50,6 +50,7 @@ __all__ = [
'xep_0128', # Extended Service Discovery
'xep_0131', # Standard Headers and Internet Metadata
'xep_0133', # Service Administration
+ 'xep_0152', # Reachability Addresses
'xep_0153', # vCard-Based Avatars
'xep_0163', # Personal Eventing Protocol
'xep_0172', # User Nickname
@@ -79,6 +80,7 @@ __all__ = [
'xep_0302', # XMPP Compliance Suites 2012
'xep_0308', # Last Message Correction
'xep_0313', # Message Archive Management
+ 'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
]
diff --git a/sleekxmpp/plugins/xep_0027/gpg.py b/sleekxmpp/plugins/xep_0027/gpg.py
index 2aa6e5a0..52c1c461 100644
--- a/sleekxmpp/plugins/xep_0027/gpg.py
+++ b/sleekxmpp/plugins/xep_0027/gpg.py
@@ -24,7 +24,7 @@ def _extract_data(data, kind):
if not begin_headers and 'BEGIN PGP %s' % kind in line:
begin_headers = True
continue
- if begin_headers and line.stripped() == '':
+ if begin_headers and line.strip() == '':
begin_data = True
continue
if 'END PGP %s' % kind in line:
diff --git a/sleekxmpp/plugins/xep_0027/stanza.py b/sleekxmpp/plugins/xep_0027/stanza.py
index 3170ca6e..08f2032b 100644
--- a/sleekxmpp/plugins/xep_0027/stanza.py
+++ b/sleekxmpp/plugins/xep_0027/stanza.py
@@ -39,7 +39,7 @@ class Encrypted(ElementBase):
def set_encrypted(self, value):
parent = self.parent()
xmpp = parent.stream
- data = xmpp['xep_0027'].encrypt(value, parent['to'].bare)
+ data = xmpp['xep_0027'].encrypt(value, parent['to'])
if data:
self.xml.text = data
else:
diff --git a/sleekxmpp/plugins/xep_0047/ibb.py b/sleekxmpp/plugins/xep_0047/ibb.py
index e341433f..62dddac2 100644
--- a/sleekxmpp/plugins/xep_0047/ibb.py
+++ b/sleekxmpp/plugins/xep_0047/ibb.py
@@ -21,18 +21,21 @@ class XEP_0047(BasePlugin):
dependencies = set(['xep_0030'])
stanza = stanza
default_config = {
+ 'block_size': 4096,
'max_block_size': 8192,
'window_size': 1,
- 'auto_accept': True,
- 'accept_stream': None
+ 'auto_accept': False,
}
def plugin_init(self):
- self.streams = {}
- self.pending_streams = {}
- self.pending_close_streams = {}
+ self._streams = {}
+ self._pending_streams = {}
+ self._pending_lock = threading.Lock()
self._stream_lock = threading.Lock()
+ self._preauthed_sids_lock = threading.Lock()
+ self._preauthed_sids = {}
+
register_stanza_plugin(Iq, Open)
register_stanza_plugin(Iq, Close)
register_stanza_plugin(Iq, Data)
@@ -58,6 +61,13 @@ class XEP_0047(BasePlugin):
StanzaPath('message/ibb_data'),
self._handle_data))
+ self.api.register(self._authorized, 'authorized', default=True)
+ self.api.register(self._authorized_sid, 'authorized_sid', default=True)
+ self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
+ self.api.register(self._get_stream, 'get_stream', default=True)
+ self.api.register(self._set_stream, 'set_stream', default=True)
+ self.api.register(self._del_stream, 'del_stream', default=True)
+
def plugin_end(self):
self.xmpp.remove_handler('IBB Open')
self.xmpp.remove_handler('IBB Close')
@@ -68,18 +78,49 @@ class XEP_0047(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
+ def _get_stream(self, jid, sid, peer_jid, data):
+ return self._streams.get((jid, sid, peer_jid), None)
+
+ def _set_stream(self, jid, sid, peer_jid, stream):
+ self._streams[(jid, sid, peer_jid)] = stream
+
+ def _del_stream(self, jid, sid, peer_jid, data):
+ with self._stream_lock:
+ if (jid, sid, peer_jid) in self._streams:
+ del self._streams[(jid, sid, peer_jid)]
+
def _accept_stream(self, iq):
- if self.accept_stream is not None:
- return self.accept_stream(iq)
+ receiver = iq['to']
+ sender = iq['from']
+ sid = iq['ibb_open']['sid']
+
+ if self.api['authorized_sid'](receiver, sid, sender, iq):
+ return True
+ return self.api['authorized'](receiver, sid, sender, iq)
+
+ def _authorized(self, jid, sid, ifrom, iq):
if self.auto_accept:
if iq['ibb_open']['block_size'] <= self.max_block_size:
return True
return False
- def open_stream(self, jid, block_size=4096, sid=None, window=1, use_messages=False,
+ def _authorized_sid(self, jid, sid, ifrom, iq):
+ with self._preauthed_sids_lock:
+ if (jid, sid, ifrom) in self._preauthed_sids:
+ del self._preauthed_sids[(jid, sid, ifrom)]
+ return True
+ return False
+
+ def _preauthorize_sid(self, jid, sid, ifrom, data):
+ with self._preauthed_sids_lock:
+ self._preauthed_sids[(jid, sid, ifrom)] = True
+
+ def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
ifrom=None, block=True, timeout=None, callback=None):
if sid is None:
sid = str(uuid.uuid4())
+ if block_size is None:
+ block_size = self.block_size
iq = self.xmpp.Iq()
iq['type'] = 'set'
@@ -90,13 +131,13 @@ class XEP_0047(BasePlugin):
iq['ibb_open']['stanza'] = 'iq'
stream = IBBytestream(self.xmpp, sid, block_size,
- iq['to'], iq['from'], window,
+ iq['from'], iq['to'], window,
use_messages)
with self._stream_lock:
- self.pending_streams[iq['id']] = stream
+ self._pending_streams[iq['id']] = stream
- self.pending_streams[iq['id']] = stream
+ self._pending_streams[iq['id']] = stream
if block:
resp = iq.send(timeout=timeout)
@@ -116,49 +157,59 @@ class XEP_0047(BasePlugin):
def _handle_opened_stream(self, iq):
if iq['type'] == 'result':
with self._stream_lock:
- stream = self.pending_streams.get(iq['id'], None)
- if stream is not None:
- stream.sender = iq['to']
- stream.receiver = iq['from']
- stream.stream_started.set()
- self.streams[stream.sid] = stream
- self.xmpp.event('ibb_stream_start', stream)
+ stream = self._pending_streams.get(iq['id'], None)
+ if stream is not None:
+ log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
+ stream.self_jid = iq['to']
+ stream.peer_jid = iq['from']
+ stream.stream_started.set()
+ self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
+ self.xmpp.event('ibb_stream_start', stream)
+ self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
with self._stream_lock:
- if iq['id'] in self.pending_streams:
- del self.pending_streams[iq['id']]
+ if iq['id'] in self._pending_streams:
+ del self._pending_streams[iq['id']]
def _handle_open_request(self, iq):
sid = iq['ibb_open']['sid']
- size = iq['ibb_open']['block_size']
+ size = iq['ibb_open']['block_size'] or self.block_size
+
+ log.debug('Received IBB stream request from %s', iq['from'])
+
+ if not sid:
+ raise XMPPError(etype='modify', condition='bad-request')
+
if not self._accept_stream(iq):
- raise XMPPError('not-acceptable')
+ raise XMPPError(etype='modify', condition='not-acceptable')
if size > self.max_block_size:
raise XMPPError('resource-constraint')
stream = IBBytestream(self.xmpp, sid, size,
- iq['from'], iq['to'],
+ iq['to'], iq['from'],
self.window_size)
stream.stream_started.set()
- self.streams[sid] = stream
+ self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
iq.reply()
iq.send()
self.xmpp.event('ibb_stream_start', stream)
+ self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
def _handle_data(self, stanza):
sid = stanza['ibb_data']['sid']
- stream = self.streams.get(sid, None)
- if stream is not None and stanza['from'] != stream.sender:
+ stream = self.api['get_stream'](stanza['to'], sid, stanza['from'])
+ if stream is not None and stanza['from'] == stream.peer_jid:
stream._recv_data(stanza)
else:
raise XMPPError('item-not-found')
def _handle_close(self, iq):
sid = iq['ibb_close']['sid']
- stream = self.streams.get(sid, None)
- if stream is not None and iq['from'] != stream.sender:
+ stream = self.api['get_stream'](iq['to'], sid, iq['from'])
+ if stream is not None and iq['from'] == stream.peer_jid:
stream._closed(iq)
+ self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid)
else:
raise XMPPError('item-not-found')
diff --git a/sleekxmpp/plugins/xep_0047/stream.py b/sleekxmpp/plugins/xep_0047/stream.py
index adc86450..9651edf8 100644
--- a/sleekxmpp/plugins/xep_0047/stream.py
+++ b/sleekxmpp/plugins/xep_0047/stream.py
@@ -12,15 +12,17 @@ log = logging.getLogger(__name__)
class IBBytestream(object):
- def __init__(self, xmpp, sid, block_size, to, ifrom, window_size=1, use_messages=False):
+ def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False):
self.xmpp = xmpp
self.sid = sid
self.block_size = block_size
self.window_size = window_size
self.use_messages = use_messages
- self.receiver = to
- self.sender = ifrom
+ if jid is None:
+ jid = xmpp.boundjid
+ self.self_jid = jid
+ self.peer_jid = peer
self.send_seq = -1
self.recv_seq = -1
@@ -50,8 +52,8 @@ class IBBytestream(object):
seq = self.send_seq
if self.use_messages:
msg = self.xmpp.Message()
- msg['to'] = self.receiver
- msg['from'] = self.sender
+ msg['to'] = self.peer_jid
+ msg['from'] = self.self_jid
msg['id'] = self.xmpp.new_id()
msg['ibb_data']['sid'] = self.sid
msg['ibb_data']['seq'] = seq
@@ -61,8 +63,8 @@ class IBBytestream(object):
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
- iq['to'] = self.receiver
- iq['from'] = self.sender
+ iq['to'] = self.peer_jid
+ iq['from'] = self.self_jid
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
@@ -121,8 +123,8 @@ class IBBytestream(object):
def close(self):
iq = self.xmpp.Iq()
iq['type'] = 'set'
- iq['to'] = self.receiver
- iq['from'] = self.sender
+ iq['to'] = self.peer_jid
+ iq['from'] = self.self_jid
iq['ibb_close']['sid'] = self.sid
self.stream_out_closed.set()
iq.send(block=False,
@@ -132,9 +134,6 @@ class IBBytestream(object):
def _closed(self, iq):
self.stream_in_closed.set()
self.stream_out_closed.set()
- while not self.window_empty.is_set():
- log.info('waiting for send window to empty')
- self.window_empty.wait(timeout=1)
iq.reply()
iq.send()
self.xmpp.event('ibb_stream_end', self)
diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
index c10ac762..d975a46d 100644
--- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
+++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py
@@ -94,7 +94,9 @@ class OwnerRedirect(ElementBase):
class OwnerSubscriptions(Subscriptions):
+ name = 'subscriptions'
namespace = 'http://jabber.org/protocol/pubsub#owner'
+ plugin_attrib = name
interfaces = set(('node',))
def append(self, subscription):
diff --git a/sleekxmpp/plugins/xep_0065/proxy.py b/sleekxmpp/plugins/xep_0065/proxy.py
index 473dd033..265d3030 100644
--- a/sleekxmpp/plugins/xep_0065/proxy.py
+++ b/sleekxmpp/plugins/xep_0065/proxy.py
@@ -25,6 +25,9 @@ class XEP_0065(base_plugin):
name = 'xep_0065'
description = "Socks5 Bytestreams"
dependencies = set(['xep_0030'])
+ default_config = {
+ 'auto_accept': False
+ }
def plugin_init(self):
register_stanza_plugin(Iq, Socks5)
@@ -33,11 +36,18 @@ class XEP_0065(base_plugin):
self._sessions = {}
self._sessions_lock = threading.Lock()
+ self._preauthed_sids_lock = threading.Lock()
+ self._preauthed_sids = {}
+
self.xmpp.register_handler(
Callback('Socks5 Bytestreams',
StanzaPath('iq@type=set/socks/streamhost'),
self._handle_streamhost))
+ self.api.register(self._authorized, 'authorized', default=True)
+ self.api.register(self._authorized_sid, 'authorized_sid', default=True)
+ self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
+
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Socks5.namespace)
@@ -50,14 +60,15 @@ class XEP_0065(base_plugin):
"""Returns the socket associated to the SID."""
return self._sessions.get(sid, None)
- def handshake(self, to, ifrom=None, timeout=None):
+ def handshake(self, to, ifrom=None, sid=None, timeout=None):
""" Starts the handshake to establish the socks5 bytestreams
connection.
"""
if not self._proxies:
self._proxies = self.discover_proxies()
- sid = uuid4().hex
+ if sid is None:
+ sid = uuid4().hex
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid']
@@ -72,10 +83,12 @@ class XEP_0065(base_plugin):
self.xmpp.boundjid,
to,
self._proxies[proxy][0],
- self._proxies[proxy][1])
+ self._proxies[proxy][1],
+ peer=to)
# Request that the proxy activate the session with the target.
self.activate(proxy, sid, to, timeout=timeout)
+ self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
return self.get_socket(sid)
def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
@@ -139,19 +152,24 @@ class XEP_0065(base_plugin):
"""Handle incoming SOCKS5 session request."""
sid = iq['socks']['sid']
if not sid:
+ raise XMPPError(etype='modify', condition='bad-request')
+
+ if not self._accept_stream(iq):
raise XMPPError(etype='modify', condition='not-acceptable')
streamhosts = iq['socks']['streamhosts']
conn = None
used_streamhost = None
+ sender = iq['from']
for streamhost in streamhosts:
try:
conn = self._connect_proxy(sid,
- iq['from'],
+ sender,
self.xmpp.boundjid,
streamhost['host'],
- streamhost['port'])
+ streamhost['port'],
+ peer=sender)
used_streamhost = streamhost['jid']
break
except socket.error:
@@ -165,6 +183,8 @@ class XEP_0065(base_plugin):
iq['socks']['sid'] = sid
iq['socks']['streamhost_used']['jid'] = used_streamhost
iq.send()
+ self.xmpp.event('socks5_stream', conn)
+ self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated."""
@@ -191,7 +211,7 @@ class XEP_0065(base_plugin):
with self._sessions_lock:
self._sessions = {}
- def _connect_proxy(self, sid, requester, target, proxy, proxy_port):
+ def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
""" Establishes a connection between the client and the server-side
Socks5 proxy.
@@ -200,6 +220,8 @@ class XEP_0065(base_plugin):
target : The JID of the target. <str>
proxy_host : The hostname or the IP of the proxy. <str>
proxy_port : The port of the proxy. <str> or <int>
+ peer : The JID for the other side of the stream, regardless
+ of target or requester status.
"""
# Because the xep_0065 plugin uses the proxy_port as string,
# the Proxy class accepts the proxy_port argument as a string
@@ -230,6 +252,34 @@ class XEP_0065(base_plugin):
_close()
sock.close = close
- self.xmpp.event('socks_connected', sid)
+ sock.peer_jid = peer
+ sock.self_jid = target if requester == peer else requester
+ self.xmpp.event('socks_connected', sid)
return sock
+
+ def _accept_stream(self, iq):
+ receiver = iq['to']
+ sender = iq['from']
+ sid = iq['socks']['sid']
+
+ if self.api['authorized_sid'](receiver, sid, sender, iq):
+ return True
+ return self.api['authorized'](receiver, sid, sender, iq)
+
+ def _authorized(self, jid, sid, ifrom, iq):
+ return self.auto_accept
+
+ def _authorized_sid(self, jid, sid, ifrom, iq):
+ with self._preauthed_sids_lock:
+ log.debug('>>> authed sids: %s', self._preauthed_sids)
+ log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
+ if (jid, sid, ifrom) in self._preauthed_sids:
+ del self._preauthed_sids[(jid, sid, ifrom)]
+ return True
+ return False
+
+ def _preauthorize_sid(self, jid, sid, ifrom, data):
+ log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data)
+ with self._preauthed_sids_lock:
+ self._preauthed_sids[(jid, sid, ifrom)] = True
diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py
index 02571fa7..26eb68fa 100644
--- a/sleekxmpp/plugins/xep_0082.py
+++ b/sleekxmpp/plugins/xep_0082.py
@@ -6,7 +6,6 @@
See the file LICENSE for copying permission.
"""
-import logging
import datetime as dt
from sleekxmpp.plugins import BasePlugin, register_plugin
diff --git a/sleekxmpp/plugins/xep_0084/avatar.py b/sleekxmpp/plugins/xep_0084/avatar.py
index 2454afc7..677a888d 100644
--- a/sleekxmpp/plugins/xep_0084/avatar.py
+++ b/sleekxmpp/plugins/xep_0084/avatar.py
@@ -82,6 +82,7 @@ class XEP_0084(BasePlugin):
metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(metadata,
+ id=info['id'],
ifrom=ifrom,
block=block,
callback=callback,
diff --git a/sleekxmpp/plugins/xep_0095/__init__.py b/sleekxmpp/plugins/xep_0095/__init__.py
new file mode 100644
index 00000000..4465ef5c
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0095/__init__.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0095 import stanza
+from sleekxmpp.plugins.xep_0095.stanza import SI
+from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095
+
+
+register_plugin(XEP_0095)
diff --git a/sleekxmpp/plugins/xep_0095/stanza.py b/sleekxmpp/plugins/xep_0095/stanza.py
new file mode 100644
index 00000000..34999a11
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0095/stanza.py
@@ -0,0 +1,25 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase
+
+
+class SI(ElementBase):
+ name = 'si'
+ namespace = 'http://jabber.org/protocol/si'
+ plugin_attrib = 'si'
+ interfaces = set(['id', 'mime_type', 'profile'])
+
+ def get_mime_type(self):
+ return self._get_attr('mime-type', 'application/octet-stream')
+
+ def set_mime_type(self, value):
+ self._set_attr('mime-type', value)
+
+ def del_mime_type(self):
+ self._del_attr('mime-type')
diff --git a/sleekxmpp/plugins/xep_0095/stream_initiation.py b/sleekxmpp/plugins/xep_0095/stream_initiation.py
new file mode 100644
index 00000000..927248a5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0095/stream_initiation.py
@@ -0,0 +1,214 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+import threading
+
+from uuid import uuid4
+
+from sleekxmpp import Iq, Message
+from sleekxmpp.exceptions import XMPPError
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin, JID
+from sleekxmpp.plugins.xep_0095 import stanza, SI
+
+
+log = logging.getLogger(__name__)
+
+
+SOCKS5 = 'http://jabber.org/protocol/bytestreams'
+IBB = 'http://jabber.org/protocol/ibb'
+
+
+class XEP_0095(BasePlugin):
+
+ name = 'xep_0095'
+ description = 'XEP-0095: Stream Initiation'
+ dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self._profiles = {}
+ self._methods = {}
+ self._methods_order = []
+ self._pending_lock = threading.Lock()
+ self._pending= {}
+
+ self.register_method(SOCKS5, 'xep_0065', 100)
+ self.register_method(IBB, 'xep_0047', 50)
+
+ register_stanza_plugin(Iq, SI)
+ register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation)
+
+ self.xmpp.register_handler(
+ Callback('SI Request',
+ StanzaPath('iq@type=set/si'),
+ self._handle_request))
+
+ self.api.register(self._add_pending, 'add_pending', default=True)
+ self.api.register(self._get_pending, 'get_pending', default=True)
+ self.api.register(self._del_pending, 'del_pending', default=True)
+
+ def session_bind(self, jid):
+ self.xmpp['xep_0030'].add_feature(SI.namespace)
+
+ def plugin_end(self):
+ self.xmpp.remove_handler('SI Request')
+ self.xmpp['xep_0030'].del_feature(feature=SI.namespace)
+
+ def register_profile(self, profile_name, plugin):
+ self._profiles[profile_name] = plugin
+
+ def unregister_profile(self, profile_name):
+ try:
+ del self._profiles[profile_name]
+ except KeyError:
+ pass
+
+ def register_method(self, method, plugin_name, order=50):
+ self._methods[method] = (plugin_name, order)
+ self._methods_order.append((order, method, plugin_name))
+ self._methods_order.sort()
+
+ def unregister_method(self, method):
+ if method in self._methods:
+ plugin_name, order = self._methods[method]
+ del self._methods[method]
+ self._methods_order.remove((order, method, plugin_name))
+ self._methods_order.sort()
+
+ def _handle_request(self, iq):
+ profile = iq['si']['profile']
+ sid = iq['si']['id']
+
+ if not sid:
+ raise XMPPError(etype='modify', condition='bad-request')
+ if profile not in self._profiles:
+ raise XMPPError(
+ etype='modify',
+ condition='bad-request',
+ extension='bad-profile',
+ extension_ns=SI.namespace)
+
+ neg = iq['si']['feature_neg']['form']['fields']
+ options = neg['stream-method']['options'] or []
+ methods = []
+ for opt in options:
+ methods.append(opt['value'])
+ for method in methods:
+ if method in self._methods:
+ supported = True
+ break
+ else:
+ raise XMPPError('bad-request',
+ extension='no-valid-streams',
+ extension_ns=SI.namespace)
+
+ selected_method = None
+ log.debug('Available: %s', methods)
+ for order, method, plugin in self._methods_order:
+ log.debug('Testing: %s', method)
+ if method in methods:
+ selected_method = method
+ break
+
+ receiver = iq['to']
+ sender = iq['from']
+
+ self.api['add_pending'](receiver, sid, sender, {
+ 'response_id': iq['id'],
+ 'method': selected_method,
+ 'profile': profile
+ })
+ self.xmpp.event('si_request', iq)
+
+ def offer(self, jid, sid=None, mime_type=None, profile=None,
+ methods=None, payload=None, ifrom=None,
+ **iqargs):
+ if sid is None:
+ sid = uuid4().hex
+ if methods is None:
+ methods = list(self._methods.keys())
+ if not isinstance(methods, (list, tuple, set)):
+ methods = [methods]
+
+ si = self.xmpp.Iq()
+ si['to'] = jid
+ si['from'] = ifrom
+ si['type'] = 'set'
+ si['si']['id'] = sid
+ si['si']['mime_type'] = mime_type
+ si['si']['profile'] = profile
+ if not isinstance(payload, (list, tuple, set)):
+ payload = [payload]
+ for item in payload:
+ si['si'].append(item)
+ si['si']['feature_neg']['form'].add_field(
+ var='stream-method',
+ ftype='list-single',
+ options=methods)
+ return si.send(**iqargs)
+
+ def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None):
+ stream = self.api['get_pending'](ifrom, sid, jid)
+ iq = self.xmpp.Iq()
+ iq['id'] = stream['response_id']
+ iq['to'] = jid
+ iq['from'] = ifrom
+ iq['type'] = 'result'
+ if payload:
+ iq['si'].append(payload)
+ iq['si']['feature_neg']['form']['type'] = 'submit'
+ iq['si']['feature_neg']['form'].add_field(
+ var='stream-method',
+ ftype='list-single',
+ value=stream['method'])
+
+ if ifrom is None:
+ ifrom = self.xmpp.boundjid
+
+ method_plugin = self._methods[stream['method']][0]
+ self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid)
+
+ self.api['del_pending'](ifrom, sid, jid)
+
+ if stream_handler:
+ self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
+ stream_handler,
+ threaded=True,
+ disposable=True)
+ return iq.send()
+
+ def decline(self, jid, sid, ifrom=None):
+ stream = self.api['get_pending'](ifrom, sid, jid)
+ if not stream:
+ return
+ iq = self.xmpp.Iq()
+ iq['id'] = stream['response_id']
+ iq['to'] = jid
+ iq['from'] = ifrom
+ iq['type'] = 'error'
+ iq['error']['condition'] = 'forbidden'
+ iq['error']['text'] = 'Offer declined'
+ self.api['del_pending'](ifrom, sid, jid)
+ return iq.send()
+
+ def _add_pending(self, jid, node, ifrom, data):
+ with self._pending_lock:
+ self._pending[(jid, node, ifrom)] = data
+
+ def _get_pending(self, jid, node, ifrom, data):
+ with self._pending_lock:
+ return self._pending.get((jid, node, ifrom), None)
+
+ def _del_pending(self, jid, node, ifrom, data):
+ with self._pending_lock:
+ if (jid, node, ifrom) in self._pending:
+ del self._pending[(jid, node, ifrom)]
diff --git a/sleekxmpp/plugins/xep_0096/__init__.py b/sleekxmpp/plugins/xep_0096/__init__.py
new file mode 100644
index 00000000..5f836169
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0096/__init__.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0096 import stanza
+from sleekxmpp.plugins.xep_0096.stanza import File
+from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096
+
+
+register_plugin(XEP_0096)
diff --git a/sleekxmpp/plugins/xep_0096/file_transfer.py b/sleekxmpp/plugins/xep_0096/file_transfer.py
new file mode 100644
index 00000000..6873c7f5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0096/file_transfer.py
@@ -0,0 +1,58 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp import Iq, Message
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.xmlstream import register_stanza_plugin, JID
+from sleekxmpp.plugins.xep_0096 import stanza, File
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0096(BasePlugin):
+
+ name = 'xep_0096'
+ description = 'XEP-0096: SI File Transfer'
+ dependencies = set(['xep_0095'])
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File)
+
+ self.xmpp['xep_0095'].register_profile(File.namespace, self)
+
+ def session_bind(self, jid):
+ self.xmpp['xep_0030'].add_feature(File.namespace)
+
+ def plugin_end(self):
+ self.xmpp['xep_0030'].del_feature(feature=File.namespace)
+ self.xmpp['xep_0095'].unregister_profile(File.namespace, self)
+
+ def request_file_transfer(self, jid, sid=None, name=None, size=None,
+ desc=None, hash=None, date=None,
+ allow_ranged=False, mime_type=None,
+ **iqargs):
+ data = File()
+ data['name'] = name
+ data['size'] = size
+ data['date'] = date
+ data['desc'] = desc
+ if allow_ranged:
+ data.enable('range')
+
+ return self.xmpp['xep_0095'].offer(jid,
+ sid=sid,
+ mime_type=mime_type,
+ profile=File.namespace,
+ payload=data,
+ **iqargs)
diff --git a/sleekxmpp/plugins/xep_0096/stanza.py b/sleekxmpp/plugins/xep_0096/stanza.py
new file mode 100644
index 00000000..65eb5bc5
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0096/stanza.py
@@ -0,0 +1,48 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import datetime as dt
+
+from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
+from sleekxmpp.plugins import xep_0082
+
+
+class File(ElementBase):
+ name = 'file'
+ namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
+ plugin_attrib = 'file'
+ interfaces = set(['name', 'size', 'date', 'hash', 'desc'])
+ sub_interfaces = set(['desc'])
+
+ def set_size(self, value):
+ self._set_attr('size', str(value))
+
+ def get_date(self):
+ timestamp = self._get_attr('date')
+ return xep_0082.parse(timestamp)
+
+ def set_date(self, value):
+ if isinstance(value, dt.datetime):
+ value = xep_0082.format_datetime(value)
+ self._set_attr('date', value)
+
+
+class Range(ElementBase):
+ name = 'range'
+ namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
+ plugin_attrib = 'range'
+ interfaces = set(['length', 'offset'])
+
+ def set_length(self, value):
+ self._set_attr('length', str(value))
+
+ def set_offset(self, value):
+ self._set_attr('offset', str(value))
+
+
+register_stanza_plugin(File, Range)
diff --git a/sleekxmpp/plugins/xep_0152/__init__.py b/sleekxmpp/plugins/xep_0152/__init__.py
new file mode 100644
index 00000000..7de031b7
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0152/__init__.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0152 import stanza
+from sleekxmpp.plugins.xep_0152.stanza import Reachability
+from sleekxmpp.plugins.xep_0152.reachability import XEP_0152
+
+
+register_plugin(XEP_0152)
diff --git a/sleekxmpp/plugins/xep_0152/reachability.py b/sleekxmpp/plugins/xep_0152/reachability.py
new file mode 100644
index 00000000..4cf81739
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0152/reachability.py
@@ -0,0 +1,93 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from sleekxmpp.plugins.base import BasePlugin
+from sleekxmpp.plugins.xep_0152 import stanza, Reachability
+
+
+log = logging.getLogger(__name__)
+
+
+class XEP_0152(BasePlugin):
+
+ """
+ XEP-0152: Reachability Addresses
+ """
+
+ name = 'xep_0152'
+ description = 'XEP-0152: Reachability Addresses'
+ dependencies = set(['xep_0163'])
+ stanza = stanza
+
+ def plugin_end(self):
+ self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace)
+ self.xmpp['xep_0163'].remove_interest(Reachability.namespace)
+
+ def session_bind(self, jid):
+ self.xmpp['xep_0163'].register_pep('reachability', Reachability)
+
+ def publish_reachability(self, addresses, options=None,
+ ifrom=None, block=True, callback=None, timeout=None):
+ """
+ Publish alternative addresses where the user can be reached.
+
+ Arguments:
+ addresses -- A list of dictionaries containing the URI and
+ optional description for each address.
+ options -- Optional form of publish options.
+ ifrom -- Specify the sender's JID.
+ block -- Specify if the send call will block until a response
+ is received, or a timeout occurs. Defaults to True.
+ timeout -- The length of time (in seconds) to wait for a response
+ before exiting the send call if blocking is used.
+ Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
+ callback -- Optional reference to a stream handler function. Will
+ be executed when a reply stanza is received.
+ """
+ if not isinstance(addresses, (list, tuple)):
+ addresses = [addresses]
+ reach = Reachability()
+ for address in addresses:
+ if not hasattr(address, 'items'):
+ address = {'uri': address}
+
+ addr = stanza.Address()
+ for key, val in address.items():
+ addr[key] = val
+ reach.append(addr)
+ return self.xmpp['xep_0163'].publish(reach,
+ node=Reachability.namespace,
+ options=options,
+ ifrom=ifrom,
+ block=block,
+ callback=callback,
+ timeout=timeout)
+
+ def stop(self, ifrom=None, block=True, callback=None, timeout=None):
+ """
+ Clear existing user activity information to stop notifications.
+
+ Arguments:
+ ifrom -- Specify the sender's JID.
+ block -- Specify if the send call will block until a response
+ is received, or a timeout occurs. Defaults to True.
+ timeout -- The length of time (in seconds) to wait for a response
+ before exiting the send call if blocking is used.
+ Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
+ callback -- Optional reference to a stream handler function. Will
+ be executed when a reply stanza is received.
+ """
+ reach = Reachability()
+ return self.xmpp['xep_0163'].publish(reach,
+ node=Reachability.namespace,
+ ifrom=ifrom,
+ block=block,
+ callback=callback,
+ timeout=timeout)
diff --git a/sleekxmpp/plugins/xep_0152/stanza.py b/sleekxmpp/plugins/xep_0152/stanza.py
new file mode 100644
index 00000000..bd173ce1
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0152/stanza.py
@@ -0,0 +1,29 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
+
+
+class Reachability(ElementBase):
+ name = 'reach'
+ namespace = 'urn:xmpp:reach:0'
+ plugin_attrib = 'reach'
+ interfaces = set()
+
+
+class Address(ElementBase):
+ name = 'addr'
+ namespace = 'urn:xmpp:reach:0'
+ plugin_attrib = 'address'
+ plugin_multi_attrib = 'addresses'
+ interfaces = set(['uri', 'desc'])
+ lang_interfaces = set(['desc'])
+ sub_interfaces = set(['desc'])
+
+
+register_stanza_plugin(Reachability, Address, iterable=True)
diff --git a/sleekxmpp/plugins/xep_0319/__init__.py b/sleekxmpp/plugins/xep_0319/__init__.py
new file mode 100644
index 00000000..4756e63e
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0319/__init__.py
@@ -0,0 +1,16 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.base import register_plugin
+
+from sleekxmpp.plugins.xep_0319 import stanza
+from sleekxmpp.plugins.xep_0319.stanza import Idle
+from sleekxmpp.plugins.xep_0319.idle import XEP_0319
+
+
+register_plugin(XEP_0319)
diff --git a/sleekxmpp/plugins/xep_0319/idle.py b/sleekxmpp/plugins/xep_0319/idle.py
new file mode 100644
index 00000000..e541e0ad
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0319/idle.py
@@ -0,0 +1,75 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from datetime import datetime, timedelta
+
+from sleekxmpp.stanza import Presence
+from sleekxmpp.plugins import BasePlugin
+from sleekxmpp.xmlstream import register_stanza_plugin
+from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.matcher import StanzaPath
+from sleekxmpp.plugins.xep_0319 import stanza
+
+
+class XEP_0319(BasePlugin):
+ name = 'xep_0319'
+ description = 'XEP-0319: Last User Interaction in Presence'
+ dependencies = set(['xep_0012'])
+ stanza = stanza
+
+ def plugin_init(self):
+ self._idle_stamps = {}
+ register_stanza_plugin(Presence, stanza.Idle)
+ self.api.register(self._set_idle,
+ 'set_idle',
+ default=True)
+ self.api.register(self._get_idle,
+ 'get_idle',
+ default=True)
+ self.xmpp.register_handler(
+ Callback('Idle Presence',
+ StanzaPath('presence/idle'),
+ self._idle_presence))
+ self.xmpp.add_filter('out', self._stamp_idle_presence)
+
+ def session_bind(self, jid):
+ self.xmpp['xep_0030'].add_feature('urn:xmpp:idle:0')
+
+ def plugin_end(self):
+ self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:idle:0')
+ self.xmpp.del_filter('out', self._stamp_idle_presence)
+ self.xmpp.remove_handler('Idle Presence')
+
+ def idle(self, jid=None, since=None):
+ seconds = None
+ if since is None:
+ since = datetime.now()
+ else:
+ seconds = datetime.now() - since
+ self.api['set_idle'](jid, None, None, since)
+ self.xmpp['xep_0012'].set_last_activity(jid=jid, seconds=seconds)
+
+ def active(self, jid=None):
+ self.api['set_idle'](jid, None, None, None)
+ self.xmpp['xep_0012'].del_last_activity(jid)
+
+ def _set_idle(self, jid, node, ifrom, data):
+ self._idle_stamps[jid] = data
+
+ def _get_idle(self, jid, node, ifrom, data):
+ return self._idle_stamps.get(jid, None)
+
+ def _idle_presence(self, pres):
+ self.xmpp.event('presence_idle', pres)
+
+ def _stamp_idle_presence(self, stanza):
+ if isinstance(stanza, Presence):
+ since = self.api['get_idle'](stanza['from'] or self.xmpp.boundjid)
+ if since:
+ stanza['idle']['since'] = since
+ return stanza
diff --git a/sleekxmpp/plugins/xep_0319/stanza.py b/sleekxmpp/plugins/xep_0319/stanza.py
new file mode 100644
index 00000000..a0bb7272
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0319/stanza.py
@@ -0,0 +1,28 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+import datetime as dt
+
+from sleekxmpp.xmlstream import ElementBase
+from sleekxmpp.plugins import xep_0082
+
+
+class Idle(ElementBase):
+ name = 'idle'
+ namespace = 'urn:xmpp:idle:0'
+ plugin_attrib = 'idle'
+ interfaces = set(['since'])
+
+ def get_since(self):
+ timestamp = self._get_attr('since')
+ return xep_0082.parse(timestamp)
+
+ def set_since(self, value):
+ if isinstance(value, dt.datetime):
+ value = xep_0082.format_datetime(value)
+ self._set_attr('since', value)
diff --git a/sleekxmpp/roster/__init__.py b/sleekxmpp/roster/__init__.py
index 4335d367..18b380c9 100644
--- a/sleekxmpp/roster/__init__.py
+++ b/sleekxmpp/roster/__init__.py
@@ -6,7 +6,6 @@
See the file LICENSE for copying permission.
"""
-from sleekxmpp.xmlstream import JID
from sleekxmpp.roster.item import RosterItem
from sleekxmpp.roster.single import RosterNode
from sleekxmpp.roster.multi import Roster
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py
index 901c3a56..04fb106d 100644
--- a/sleekxmpp/test/sleektest.py
+++ b/sleekxmpp/test/sleektest.py
@@ -9,14 +9,12 @@
import unittest
from xml.parsers.expat import ExpatError
-import sleekxmpp
from sleekxmpp import ClientXMPP, ComponentXMPP
from sleekxmpp.util import Queue
from sleekxmpp.stanza import Message, Iq, Presence
from sleekxmpp.test import TestSocket, TestLiveSocket
-from sleekxmpp.exceptions import XMPPError, IqTimeout, IqError
-from sleekxmpp.xmlstream import ET, register_stanza_plugin
-from sleekxmpp.xmlstream import ElementBase, StanzaBase
+from sleekxmpp.xmlstream import ET
+from sleekxmpp.xmlstream import ElementBase
from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId
from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath
diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py
index 93f26312..e751a448 100644
--- a/sleekxmpp/thirdparty/mini_dateutil.py
+++ b/sleekxmpp/thirdparty/mini_dateutil.py
@@ -108,7 +108,7 @@ except:
def __init__(self, name, offset):
self._name = name
- self._offset = datetime.timedelta(seconds=offset)
+ self._offset = datetime.timedelta(minutes=offset)
def utcoffset(self, dt):
return self._offset
@@ -154,7 +154,7 @@ except:
absoff = offsetmins
name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60)
- inst = tzoffset(offsetmins, name)
+ inst = tzoffset(name,offsetmins)
_fixed_offset_tzs[offsetmins] = inst
return _fixed_offset_tzs[offsetmins]
diff --git a/sleekxmpp/thirdparty/socks.py b/sleekxmpp/thirdparty/socks.py
index a6c0d70e..9239a7b9 100644
--- a/sleekxmpp/thirdparty/socks.py
+++ b/sleekxmpp/thirdparty/socks.py
@@ -13,7 +13,7 @@ are permitted provided that the following conditions are met:
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
-
+
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
@@ -28,9 +28,6 @@ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
This module provides a standard socket-like interface for Python
for tunneling connections through SOCKS proxies.
-"""
-
-"""
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
for use in PyLoris (http://pyloris.sourceforge.net/)
@@ -42,7 +39,6 @@ mainly to merge bug fixes found in Sourceforge
import socket
import struct
-import sys
PROXY_TYPE_SOCKS4 = 1
PROXY_TYPE_SOCKS5 = 2
diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py
index 4b5ecd6b..113320fa 100644
--- a/sleekxmpp/thirdparty/statemachine.py
+++ b/sleekxmpp/thirdparty/statemachine.py
@@ -15,7 +15,8 @@ log = logging.getLogger(__name__)
class StateMachine(object):
- def __init__(self, states=[]):
+ def __init__(self, states=None):
+ if not states: states = []
self.lock = threading.Condition()
self.__states = []
self.addStates(states)
diff --git a/sleekxmpp/util/sasl/__init__.py b/sleekxmpp/util/sasl/__init__.py
index d054ce09..2d344e9b 100644
--- a/sleekxmpp/util/sasl/__init__.py
+++ b/sleekxmpp/util/sasl/__init__.py
@@ -7,7 +7,9 @@
Part of SleekXMPP: The Sleek XMPP Library
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ :copryight: (c) 2004-2013 David Alan Cridland
+ :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
+
:license: MIT, see LICENSE for more details
"""
diff --git a/sleekxmpp/util/sasl/client.py b/sleekxmpp/util/sasl/client.py
index 0bfb63f8..fd685547 100644
--- a/sleekxmpp/util/sasl/client.py
+++ b/sleekxmpp/util/sasl/client.py
@@ -7,7 +7,9 @@
Part of SleekXMPP: The Sleek XMPP Library
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ :copryight: (c) 2004-2013 David Alan Cridland
+ :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
+
:license: MIT, see LICENSE for more details
"""
diff --git a/sleekxmpp/util/sasl/mechanisms.py b/sleekxmpp/util/sasl/mechanisms.py
index 373ae039..1eb6af83 100644
--- a/sleekxmpp/util/sasl/mechanisms.py
+++ b/sleekxmpp/util/sasl/mechanisms.py
@@ -9,7 +9,9 @@
Part of SleekXMPP: The Sleek XMPP Library
- :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
+ :copryight: (c) 2004-2013 David Alan Cridland
+ :copyright: (c) 2013 Nathanael C. Fritz, Lance J.T. Stout
+
:license: MIT, see LICENSE for more details
"""
diff --git a/sleekxmpp/util/stringprep_profiles.py b/sleekxmpp/util/stringprep_profiles.py
index ad89d4cc..84326bc3 100644
--- a/sleekxmpp/util/stringprep_profiles.py
+++ b/sleekxmpp/util/stringprep_profiles.py
@@ -16,9 +16,8 @@
from __future__ import unicode_literals
-import sys
import stringprep
-import unicodedata
+from unicodedata import ucd_3_2_0 as unicodedata
from sleekxmpp.util import unicode
diff --git a/sleekxmpp/xmlstream/cert.py b/sleekxmpp/xmlstream/cert.py
index fa12f794..71146f36 100644
--- a/sleekxmpp/xmlstream/cert.py
+++ b/sleekxmpp/xmlstream/cert.py
@@ -1,6 +1,10 @@
import logging
from datetime import datetime, timedelta
+# Make a call to strptime before starting threads to
+# prevent thread safety issues.
+datetime.strptime('1970-01-01 12:00:00', "%Y-%m-%d %H:%M:%S")
+
try:
from pyasn1.codec.der import decoder, encoder
diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py
index d4537998..53b83bc7 100644
--- a/sleekxmpp/xmlstream/filesocket.py
+++ b/sleekxmpp/xmlstream/filesocket.py
@@ -13,6 +13,7 @@
"""
from socket import _fileobject
+import errno
import socket
@@ -29,7 +30,13 @@ class FileSocket(_fileobject):
"""Read data from the socket as if it were a file."""
if self._sock is None:
return None
- data = self._sock.recv(size)
+ while True:
+ try:
+ data = self._sock.recv(size)
+ break
+ except socket.error as serr:
+ if serr.errno != errno.EINTR:
+ raise
if data is not None:
return data
diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py
index cb202448..56f728e1 100644
--- a/sleekxmpp/xmlstream/matcher/xmlmask.py
+++ b/sleekxmpp/xmlstream/matcher/xmlmask.py
@@ -37,11 +37,11 @@ class MatchXMLMask(MatcherBase):
object or XML string to use as a mask.
"""
- def __init__(self, criteria):
+ def __init__(self, criteria, default_ns='jabber:client'):
MatcherBase.__init__(self, criteria)
if isinstance(criteria, str):
self._criteria = ET.fromstring(self._criteria)
- self.default_ns = 'jabber:client'
+ self.default_ns = default_ns
def setDefaultNS(self, ns):
"""Set the default namespace to use during comparisons.
diff --git a/sleekxmpp/xmlstream/resolver.py b/sleekxmpp/xmlstream/resolver.py
index 16f8a7ad..6f26797f 100644
--- a/sleekxmpp/xmlstream/resolver.py
+++ b/sleekxmpp/xmlstream/resolver.py
@@ -202,11 +202,14 @@ def get_AAAA(host, resolver=None):
# If not using dnspython, attempt lookup using the OS level
# getaddrinfo() method.
if resolver is None:
+ if not socket.has_ipv6:
+ log.debug("Unable to query %s for AAAA records: IPv6 is not supported", host)
+ return []
try:
recs = socket.getaddrinfo(host, None, socket.AF_INET6,
socket.SOCK_STREAM)
return [rec[4][0] for rec in recs]
- except socket.gaierror:
+ except (OSError, socket.gaierror):
log.debug("DNS: Error retreiving AAAA address " + \
"info for %s." % host)
return []
diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py
index b3e50983..e6fae37a 100644
--- a/sleekxmpp/xmlstream/scheduler.py
+++ b/sleekxmpp/xmlstream/scheduler.py
@@ -20,6 +20,11 @@ import itertools
from sleekxmpp.util import Queue, QueueEmpty
+#: The time in seconds to wait for events from the event queue, and also the
+#: time between checks for the process stop signal.
+WAIT_TIMEOUT = 1.0
+
+
log = logging.getLogger(__name__)
@@ -76,7 +81,7 @@ class Task(object):
"""
if self.qpointer is not None:
self.qpointer.put(('schedule', self.callback,
- self.args, self.name))
+ self.args, self.kwargs, self.name))
else:
self.callback(*self.args, **self.kwargs)
self.reset()
@@ -120,6 +125,10 @@ class Scheduler(object):
#: Lock for accessing the task queue.
self.schedule_lock = threading.RLock()
+ #: The time in seconds to wait for events from the event queue,
+ #: and also the time between checks for the process stop signal.
+ self.wait_timeout = WAIT_TIMEOUT
+
def process(self, threaded=True, daemon=False):
"""Begin accepting and processing scheduled tasks.
@@ -139,24 +148,25 @@ class Scheduler(object):
self.run = True
try:
while self.run and not self.stop.is_set():
- wait = 0.1
updated = False
if self.schedule:
wait = self.schedule[0].next - time.time()
+ else:
+ wait = self.wait_timeout
try:
if wait <= 0.0:
newtask = self.addq.get(False)
else:
- if wait >= 3.0:
- wait = 3.0
newtask = None
- elapsed = 0
- while not self.stop.is_set() and \
+ while self.run and \
+ not self.stop.is_set() and \
newtask is None and \
- elapsed < wait:
- newtask = self.addq.get(True, 0.1)
- elapsed += 0.1
- except QueueEmpty:
+ wait > 0:
+ try:
+ newtask = self.addq.get(True, min(wait, self.wait_timeout))
+ except QueueEmpty: # Nothing to add, nothing to do. Check run flags and continue waiting.
+ wait -= self.wait_timeout
+ except QueueEmpty: # Time to run some tasks, and no new tasks to add.
self.schedule_lock.acquire()
# select only those tasks which are to be executed now
relevant = itertools.takewhile(
@@ -174,11 +184,11 @@ class Scheduler(object):
# only need to resort tasks if a repeated task has
# been kept in the list.
updated = True
- else:
- updated = True
+ else: # Add new task
self.schedule_lock.acquire()
if newtask is not None:
self.schedule.append(newtask)
+ updated = True
finally:
if updated:
self.schedule.sort(key=lambda task: task.next)
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 1c0b84b9..8242a127 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -26,6 +26,7 @@ import time
import random
import weakref
import uuid
+import errno
from xml.parsers.expat import ExpatError
@@ -49,7 +50,7 @@ RESPONSE_TIMEOUT = 30
#: The time in seconds to wait for events from the event queue, and also the
#: time between checks for the process stop signal.
-WAIT_TIMEOUT = 0.1
+WAIT_TIMEOUT = 1.0
#: The number of threads to use to handle XML stream events. This is not the
#: same as the number of custom event handling threads.
@@ -461,10 +462,10 @@ class XMLStream(object):
time.sleep(0.1)
elapsed += 0.1
except KeyboardInterrupt:
- self.stop.set()
+ self.set_stop()
return False
except SystemExit:
- self.stop.set()
+ self.set_stop()
return False
if self.default_domain:
@@ -706,7 +707,7 @@ class XMLStream(object):
self.stream_end_event.set()
if not self.auto_reconnect:
- self.stop.set()
+ self.set_stop()
if self._disconnect_wait_for_threads:
self._wait_for_threads()
@@ -723,7 +724,7 @@ class XMLStream(object):
def abort(self):
self.session_started_event.clear()
- self.stop.set()
+ self.set_stop()
if self._disconnect_wait_for_threads:
self._wait_for_threads()
try:
@@ -1017,8 +1018,12 @@ class XMLStream(object):
if name is None:
name = 'add_handler_%s' % self.new_id()
- self.register_handler(XMLCallback(name, MatchXMLMask(mask), pointer,
- once=disposable, instream=instream))
+ self.register_handler(
+ XMLCallback(name,
+ MatchXMLMask(mask, self.default_ns),
+ pointer,
+ once=disposable,
+ instream=instream))
def register_handler(self, handler, before=None, after=None):
"""Add a stream event handler that will be executed when a matching
@@ -1290,6 +1295,9 @@ class XMLStream(object):
try:
sent += self.socket.send(data[sent:])
count += 1
+ except Socket.error as serr:
+ if serr.errno != errno.EINTR:
+ raise
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error: max retries reached')
@@ -1352,6 +1360,13 @@ class XMLStream(object):
if self.__thread_count == 0:
self.__thread_cond.notify()
+ def set_stop(self):
+ self.stop.set()
+
+ # Unlock queues
+ self.event_queue.put(None)
+ self.send_queue.put(None)
+
def _wait_for_threads(self):
with self.__thread_cond:
if self.__thread_count != 0:
@@ -1624,11 +1639,7 @@ class XMLStream(object):
log.debug("Loading event runner")
try:
while not self.stop.is_set():
- try:
- wait = self.wait_timeout
- event = self.event_queue.get(True, timeout=wait)
- except QueueEmpty:
- event = None
+ event = self.event_queue.get()
if event is None:
continue
@@ -1644,10 +1655,10 @@ class XMLStream(object):
log.exception(error_msg, handler.name)
orig.exception(e)
elif etype == 'schedule':
- name = args[1]
+ name = args[2]
try:
log.debug('Scheduled event: %s: %s', name, args[0])
- handler(*args[0])
+ handler(*args[0], **args[1])
except Exception as e:
log.exception('Error processing scheduled task')
self.exception(e)
@@ -1689,14 +1700,13 @@ class XMLStream(object):
while not self.stop.is_set():
while not self.stop.is_set() and \
not self.session_started_event.is_set():
- self.session_started_event.wait(timeout=0.1)
+ self.session_started_event.wait(timeout=0.1) # Wait for session start
if self.__failed_send_stanza is not None:
data = self.__failed_send_stanza
self.__failed_send_stanza = None
else:
- try:
- data = self.send_queue.get(True, 1)
- except QueueEmpty:
+ data = self.send_queue.get() # Wait for data to send
+ if data is None:
continue
log.debug("SEND: %s", data)
enc_data = data.encode('utf-8')
@@ -1711,6 +1721,9 @@ class XMLStream(object):
try:
sent += self.socket.send(enc_data[sent:])
count += 1
+ except Socket.error as serr:
+ if serr.errno != errno.EINTR:
+ raise
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error: max retries reached')