Compare commits

..

31 Commits

Author SHA1 Message Date
Maxime “pep” Buquet
412a9169bd Fixes #3432. Allow execute to be used with the meaning of 'next'.
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-14 02:34:22 +01:00
Maxime “pep” Buquet
72b355de8c xep_0050: Fix indentation
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-14 02:19:58 +01:00
Maxime “pep” Buquet
af246dcfe1 slixmpp/jid: add types
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-07 23:14:44 +01:00
Maxime Buquet
9612e518fb Merge branch 'master' into 'master'
Communicate the reason for a disconnect to the application

See merge request poezio/slixmpp!12
2019-04-07 00:24:50 +02:00
Maxime “pep” Buquet
fde8264191 xep_0202: Fix plugin_init docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-06 13:44:05 +01:00
Maxime “pep” Buquet
1cdc656208 Fixes poezio/poezio#3472: Don't remove TZ in 0202 utc tag
<utc/> MUST conform to 0082 dateTime profile and thus include a
timezone definition.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-06 13:42:26 +01:00
Maxime “pep” Buquet
0042108a67 poezio/poezio#3472: Ensure tz is correctly set when offset is an int
Thanks lovetox!

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-03 23:16:37 +01:00
Georg Lukas
704161a285 mark end-of-stream as session-ending event 2019-03-26 15:16:52 +01:00
Georg Lukas
6b1b58a339 XEP-0199: use new 0-timeout reconnect() with reason 2019-03-26 12:09:43 +01:00
Georg Lukas
4f96e5fa75 Do not directly enqueue connect() as event handler, parameter mismatch 2019-03-26 12:09:43 +01:00
Georg Lukas
bcb90a653e Do not close stream on 0-timeout disconnect, allows 0198 resume 2019-03-26 11:02:28 +01:00
Georg Lukas
7e435b703d Propagate disconnect() reason into 'disconnected' event 2019-03-26 11:01:36 +01:00
Maxime “pep” Buquet
2dda6b80d4 Partially fix poezio/poezio#3452. Prevent groupchat_subject from triggered sent when body or thread are in the message.
0045 says:
> The subject is changed by sending a message of type "groupchat" to the
> <room@service>, where the <message/> MUST contain a <subject/> element that
> specifies the new subject but MUST NOT contain a <body/> element (or a
> <thread/> element). In accordance with the core definition of XMPP, other child
> elements are allowed (although the entity that receives them might ignore
> them).
>
> Note: A message with a <subject/> and a <body/> or a <subject/> and a <thread/>
> is a legitimate message, but it SHALL NOT be interpreted as a subject change.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-03-23 17:06:07 +00:00
mathieui
5629e44710 Merge branch 'patch-1' into 'master'
Fix slixmpp.ClientXMPP.cancel_connection_attempt()

See merge request poezio/slixmpp!10
2019-03-15 00:38:35 +01:00
Maxime “pep” Buquet
6a06881d8b xep_0030: fix typo on 'remote' in get_info docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-03-05 00:00:14 +00:00
Maxime “pep” Buquet
2b666eb1de Revert "Remove license from unused project"
This reverts commit fbab3ad214.
2019-02-24 12:16:06 +00:00
Emmanuel Gil Peyrot
400e7a3903 Remove SocksiPy license
I wrote a SOCKS5 implementation back in
9c5dd024b1, and removed SocksiPy from
slixmpp’s codebase at the same time.
2019-02-24 13:02:08 +01:00
Maxime “pep” Buquet
fbab3ad214 Remove license from unused project
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-24 12:00:16 +00:00
mathieui
628b357b06 Merge branch 'eme-add-method' into 'master'
xep_0380: Add add_eme method

See merge request poezio/slixmpp!11
2019-02-23 14:07:15 +01:00
Maxime “pep” Buquet
88260cc240 xep_0380: Add add_eme method
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-23 12:54:56 +00:00
Maxime “pep” Buquet
e9f2f503b8 xep_0380: Remove remove_handler call in plugin_end
Remove probable copy paste fail in EME plugin_end handler.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-23 12:36:40 +00:00
ehendrix23
696a72247b Fix slixmpp.ClientXMPP.cancel_connection_attempt() 2019-02-22 00:41:02 +01:00
Maxime “pep” Buquet
05d76e4b1d Change more URLs to lab.louiz.org
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-12 10:34:57 +00:00
Maxime “pep” Buquet
d52d4fbbbe Update project URLs to lab.louiz.org
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-12 10:26:04 +00:00
Maxime “pep” Buquet
e53c0fcb30 README: Add pep as a contributor
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-10 14:18:28 +00:00
Maxime “pep” Buquet
97d68c5196 setup.py: GTalk is no more
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-02-10 14:18:28 +00:00
mathieui
b42fafabb4 Make the cache encode and decode not crash if something goes wrong 2019-02-02 17:42:24 +01:00
mathieui
3a44ec8f15 Add tests for the cache api 2019-02-02 17:32:10 +01:00
mathieui
93f385562f Add a "remove" action on the cache API 2019-02-02 17:31:48 +01:00
mathieui
9cab02438b Fix XEP-0335 2019-02-02 15:41:55 +01:00
Emmanuel Gil Peyrot
74ed50e626 Set @id by default on outgoing messages and presences.
Respects RFC6120 §8.1.3’s RECOMMENDED.
2019-01-31 16:46:51 +01:00
24 changed files with 216 additions and 93 deletions

View File

@@ -5,7 +5,7 @@ To contribute, the preferred way is to commit your changes on some
publicly-available git repository (on a fork `on github publicly-available git repository (on a fork `on github
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to <https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either: notify the developers with either:
- a ticket `on the bug tracker <https://dev.poez.io/new>`_ - a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
- a pull request on github - a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_

25
LICENSE
View File

@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
socksipy: A Python SOCKS client module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.

View File

@@ -113,6 +113,7 @@ Slixmpp Credits
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_) - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_) - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_) - Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
Credits (SleekXMPP) Credits (SleekXMPP)
------------------- -------------------

View File

@@ -11,7 +11,7 @@ Create and Run a Server Component
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <http://git.poez.io/slixmpp>`_. with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Many XMPP applications eventually graduate to requiring to run as a server Many XMPP applications eventually graduate to requiring to run as a server
component in order to meet scalability requirements. To demonstrate how to component in order to meet scalability requirements. To demonstrate how to

View File

@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <http://git.poez.io/slixmpp>`_. with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
As a basic starting project, we will create an echo bot which will reply to any As a basic starting project, we will create an echo bot which will reply to any
messages sent to it. We will also go through adding some basic command line configuration messages sent to it. We will also go through adding some basic command line configuration
@@ -329,7 +329,7 @@ The Final Product
----------------- -----------------
Here then is what the final result should look like after working through the guide above. The code Here then is what the final result should look like after working through the guide above. The code
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_. can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
.. compound:: .. compound::

View File

@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
from `Git <http://git.poez.io/slixmpp>`_. from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Now that you've got the basic gist of using Slixmpp by following the Now that you've got the basic gist of using Slixmpp by following the
echobot example (:ref:`echobot`), we can use one of the bundled plugins echobot example (:ref:`echobot`), we can use one of the bundled plugins

View File

@@ -4,9 +4,9 @@ Slixmpp
.. sidebar:: Get the Code .. sidebar:: Get the Code
The latest source code for Slixmpp may be found on the `Git repo The latest source code for Slixmpp may be found on the `Git repo
<http://git.poez.io/slixmpp>`_. :: <https://lab.louiz.org/poezio/slixmpp>`_. ::
git clone git://git.poez.io/slixmpp git clone https://lab.louiz.org/poezio/slixmpp
An XMPP chat room is available for discussing and getting help with slixmpp. An XMPP chat room is available for discussing and getting help with slixmpp.
@@ -14,7 +14,7 @@ Slixmpp
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
**Reporting bugs** **Reporting bugs**
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
.. note:: .. note::
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_ slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_

View File

@@ -20,8 +20,7 @@ from run_tests import TestCommand
from slixmpp.version import __version__ from slixmpp.version import __version__
VERSION = __version__ VERSION = __version__
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, ' DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
'Google Talk, etc).')
with open('README.rst', encoding='utf8') as readme: with open('README.rst', encoding='utf8') as readme:
LONG_DESCRIPTION = readme.read() LONG_DESCRIPTION = readme.read()
@@ -79,7 +78,7 @@ setup(
long_description=LONG_DESCRIPTION, long_description=LONG_DESCRIPTION,
author='Florent Le Coz', author='Florent Le Coz',
author_email='louiz@louiz.org', author_email='louiz@louiz.org',
url='https://dev.louiz.org/projects/slixmpp', url='https://lab.louiz.org/poezio/slixmpp',
license='MIT', license='MIT',
platforms=['any'], platforms=['any'],
packages=packages, packages=packages,

View File

@@ -104,12 +104,12 @@ class BaseXMPP(XMLStream):
#: :attr:`use_message_ids` to `True` will assign all outgoing #: :attr:`use_message_ids` to `True` will assign all outgoing
#: messages an ID. Some plugin features require enabling #: messages an ID. Some plugin features require enabling
#: this option. #: this option.
self.use_message_ids = False self.use_message_ids = True
#: Presence updates may optionally be tagged with ID values. #: Presence updates may optionally be tagged with ID values.
#: Setting :attr:`use_message_ids` to `True` will assign all #: Setting :attr:`use_message_ids` to `True` will assign all
#: outgoing messages an ID. #: outgoing messages an ID.
self.use_presence_ids = False self.use_presence_ids = True
#: The API registry is a way to process callbacks based on #: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is #: JID+node combinations. Each callback in the registry is

View File

@@ -16,6 +16,7 @@ import socket
from copy import deepcopy from copy import deepcopy
from functools import lru_cache from functools import lru_cache
from typing import Optional
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
@@ -71,7 +72,7 @@ def _parse_jid(data):
return node, domain, resource return node, domain, resource
def _validate_node(node): def _validate_node(node: Optional[str]):
"""Validate the local, or username, portion of a JID. """Validate the local, or username, portion of a JID.
:raises InvalidJID: :raises InvalidJID:
@@ -93,7 +94,7 @@ def _validate_node(node):
return node return node
def _validate_domain(domain): def _validate_domain(domain: str):
"""Validate the domain portion of a JID. """Validate the domain portion of a JID.
IP literal addresses are left as-is, if valid. Domain names IP literal addresses are left as-is, if valid. Domain names
@@ -152,7 +153,7 @@ def _validate_domain(domain):
return domain return domain
def _validate_resource(resource): def _validate_resource(resource: Optional[str]):
"""Validate the resource portion of a JID. """Validate the resource portion of a JID.
:raises InvalidJID: :raises InvalidJID:
@@ -174,7 +175,7 @@ def _validate_resource(resource):
return resource return resource
def _unescape_node(node): def _unescape_node(node: str):
"""Unescape a local portion of a JID. """Unescape a local portion of a JID.
.. note:: .. note::
@@ -199,7 +200,11 @@ def _unescape_node(node):
return ''.join(unescaped) return ''.join(unescaped)
def _format_jid(local=None, domain=None, resource=None): def _format_jid(
local: Optional[str] = None,
domain: Optional[str] = None,
resource: Optional[str] = None,
):
"""Format the given JID components into a full or bare JID. """Format the given JID components into a full or bare JID.
:param string local: Optional. The local portion of the JID. :param string local: Optional. The local portion of the JID.
@@ -237,12 +242,17 @@ class UnescapedJID:
__slots__ = ('_node', '_domain', '_resource') __slots__ = ('_node', '_domain', '_resource')
def __init__(self, node, domain, resource): def __init__(
self,
node: Optional[str],
domain: Optional[str],
resource: Optional[str],
):
self._node = node self._node = node
self._domain = domain self._domain = domain
self._resource = resource self._resource = resource
def __getattribute__(self, name): def __getattribute__(self, name: str):
"""Retrieve the given JID component. """Retrieve the given JID component.
:param name: one of: user, server, domain, resource, :param name: one of: user, server, domain, resource,
@@ -301,7 +311,7 @@ class JID:
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full') __slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
def __init__(self, jid=None): def __init__(self, jid: Optional[str] = None):
if not jid: if not jid:
self._node = '' self._node = ''
self._domain = '' self._domain = ''
@@ -363,17 +373,17 @@ class JID:
return self._full return self._full
@node.setter @node.setter
def node(self, value): def node(self, value: str):
self._node = _validate_node(value) self._node = _validate_node(value)
self._update_bare_full() self._update_bare_full()
@domain.setter @domain.setter
def domain(self, value): def domain(self, value: str):
self._domain = _validate_domain(value) self._domain = _validate_domain(value)
self._update_bare_full() self._update_bare_full()
@bare.setter @bare.setter
def bare(self, value): def bare(self, value: str):
node, domain, resource = _parse_jid(value) node, domain, resource = _parse_jid(value)
assert not resource assert not resource
self._node = node self._node = node
@@ -381,12 +391,12 @@ class JID:
self._update_bare_full() self._update_bare_full()
@resource.setter @resource.setter
def resource(self, value): def resource(self, value: str):
self._resource = _validate_resource(value) self._resource = _validate_resource(value)
self._update_bare_full() self._update_bare_full()
@full.setter @full.setter
def full(self, value): def full(self, value: str):
self._node, self._domain, self._resource = _parse_jid(value) self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full() self._update_bare_full()

View File

@@ -348,7 +348,7 @@ class XEP_0030(BasePlugin):
combination handled by this Slixmpp instance and combination handled by this Slixmpp instance and
no stanzas need to be sent. no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info. remote JID to retrieve the info.
cached -- If true, then look for the disco info data from cached -- If true, then look for the disco info data from
the local cache system. If no results are found, the local cache system. If no results are found,
send the query as usual. The self.use_cache send the query as usual. The self.use_cache

View File

@@ -9,7 +9,7 @@ from __future__ import with_statement
import logging import logging
from slixmpp import Presence from slixmpp import Presence, Message
from slixmpp.plugins import BasePlugin, register_plugin from slixmpp.plugins import BasePlugin, register_plugin
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.handler.callback import Callback
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
if got_online: if got_online:
self.xmpp.event("muc::%s::got_online" % entry['room'], pr) self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
def handle_groupchat_message(self, msg): def handle_groupchat_message(self, msg: Message) -> None:
""" Handle a message event in a muc. """ Handle a message event in a muc.
""" """
self.xmpp.event('groupchat_message', msg) self.xmpp.event('groupchat_message', msg)
@@ -195,10 +195,14 @@ class XEP_0045(BasePlugin):
def handle_groupchat_subject(self, msg): def handle_groupchat_subject(self, msg: Message) -> None:
""" Handle a message coming from a muc indicating """ Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room) a change of subject (or announcing it when joining the room)
""" """
# See poezio#3452. A message containing subject _and_ (body or thread)
# is not a subject change.
if msg['body'] or msg['thread']:
return None
self.xmpp.event('groupchat_subject', msg) self.xmpp.event('groupchat_subject', msg)
def jid_in_room(self, room, jid): def jid_in_room(self, room, jid):

View File

@@ -96,24 +96,10 @@ class XEP_0050(BasePlugin):
register_stanza_plugin(Iq, Command) register_stanza_plugin(Iq, Command)
register_stanza_plugin(Command, Form, iterable=True) register_stanza_plugin(Command, Form, iterable=True)
self.xmpp.add_event_handler('command_execute', self.xmpp.add_event_handler('command', self._handle_command_all)
self._handle_command_start)
self.xmpp.add_event_handler('command_next',
self._handle_command_next)
self.xmpp.add_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.add_event_handler('command_complete',
self._handle_command_complete)
def plugin_end(self): def plugin_end(self):
self.xmpp.del_event_handler('command_execute', self.xmpp.del_event_handler('command', self._handle_command_all)
self._handle_command_start)
self.xmpp.del_event_handler('command_next',
self._handle_command_next)
self.xmpp.del_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.del_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.remove_handler('Ad-Hoc Execute') self.xmpp.remove_handler('Ad-Hoc Execute')
self.xmpp['xep_0030'].del_feature(feature=Command.namespace) self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
def _handle_command(self, iq): def _handle_command(self, iq):
"""Raise command events based on the command action.""" """Raise command events based on the command action."""
self.xmpp.event('command', iq)
self.xmpp.event('command_%s' % iq['command']['action'], iq) self.xmpp.event('command_%s' % iq['command']['action'], iq)
def _handle_command_all(self, iq: Iq) -> None:
action = iq['command']['action']
sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid)
if session is None:
return self._handle_command_start(iq)
if action in ('next', 'execute'):
return self._handle_command_next(iq)
if action == 'prev':
return self._handle_command_prev(iq)
if action == 'complete':
return self._handle_command_complete(iq)
if action == 'cancel':
return self._handle_command_cancel(iq)
return None
def _handle_command_start(self, iq): def _handle_command_start(self, iq):
""" """
Process an initial request to execute a command. Process an initial request to execute a command.

View File

@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
sec = now.second sec = now.second
if micro is None: if micro is None:
micro = now.microsecond micro = now.microsecond
if offset is None: if offset in (None, 0):
offset = tzutc() offset = tzutc()
elif not isinstance(offset, dt.tzinfo): elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset) offset = tzoffset(None, offset)
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
sec = now.second sec = now.second
if micro is None: if micro is None:
micro = now.microsecond micro = now.microsecond
if offset is None: if offset in (None, 0):
offset = tzutc() offset = tzutc()
elif not isinstance(offset, dt.tzinfo): elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset) offset = tzoffset(None, offset)

View File

@@ -112,9 +112,9 @@ class XEP_0199(BasePlugin):
try: try:
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout) rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
except IqTimeout: except IqTimeout:
log.debug("Did not receive ping back in time." + \ log.debug("Did not receive ping back in time. " + \
"Requesting Reconnect.") "Requesting Reconnect.")
self.xmpp.reconnect() self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
else: else:
log.debug('Keepalive RTT: %s' % rtt) log.debug('Keepalive RTT: %s' % rtt)

View File

@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
if not isinstance(value, dt.datetime): if not isinstance(value, dt.datetime):
date = xep_0082.parse(value) date = xep_0082.parse(value)
date = date.astimezone(tzutc()) date = date.astimezone(tzutc())
value = xep_0082.format_datetime(date)[:-1] value = xep_0082.format_datetime(date)
self._set_sub_text('utc', value) self._set_sub_text('utc', value)

View File

@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
} }
def plugin_init(self): def plugin_init(self):
"""Start the XEP-0203 plugin.""" """Start the XEP-0202 plugin."""
if not self.local_time: if not self.local_time:
def default_local_time(jid): def default_local_time(jid):

View File

@@ -8,6 +8,7 @@
from slixmpp.plugins.base import register_plugin from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0335 import stanza
from slixmpp.plugins.xep_0335.stanza import JSON_Container from slixmpp.plugins.xep_0335.stanza import JSON_Container
from slixmpp.plugins.xep_0335.json_containers import XEP_0335 from slixmpp.plugins.xep_0335.json_containers import XEP_0335

View File

@@ -10,6 +10,7 @@ from slixmpp import Message
from slixmpp.plugins import BasePlugin from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.xep_0335 import JSON_Container from slixmpp.plugins.xep_0335 import JSON_Container
from slixmpp.plugins.xep_0335 import stanza
class XEP_0335(BasePlugin): class XEP_0335(BasePlugin):

View File

@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
register_stanza_plugin(Message, Encryption) register_stanza_plugin(Message, Encryption)
def plugin_end(self):
self.xmpp.remove_handler('Chat State')
def session_bind(self, jid): def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace) self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
def has_eme(self, msg): def has_eme(self, msg):
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
def add_eme(self, msg: Message, namespace: str) -> Message:
msg['eme']['name'] = self.mechanisms[namespace]
msg['eme']['namespace'] = namespace
return msg
def replace_body_with_eme(self, msg): def replace_body_with_eme(self, msg):
eme = msg['eme'] eme = msg['eme']
namespace = eme['namespace'] namespace = eme['namespace']

View File

@@ -361,6 +361,7 @@ class SlixTest(unittest.TestCase):
# Some plugins require messages to have ID values. Set # Some plugins require messages to have ID values. Set
# this to True in tests related to those plugins. # this to True in tests related to those plugins.
self.xmpp.use_message_ids = False self.xmpp.use_message_ids = False
self.xmpp.use_presence_ids = False
def make_header(self, sto='', def make_header(self, sto='',
sfrom='', sfrom='',

View File

@@ -18,6 +18,9 @@ class Cache:
def store(self, key, value): def store(self, key, value):
raise NotImplementedError raise NotImplementedError
def remove(self, key):
raise NotImplemented
class PerJidCache: class PerJidCache:
def retrieve_by_jid(self, jid, key): def retrieve_by_jid(self, jid, key):
raise NotImplementedError raise NotImplementedError
@@ -25,6 +28,9 @@ class PerJidCache:
def store_by_jid(self, jid, key, value): def store_by_jid(self, jid, key, value):
raise NotImplementedError raise NotImplementedError
def remove_by_jid(self, jid, key):
raise NotImplementedError
class MemoryCache(Cache): class MemoryCache(Cache):
def __init__(self): def __init__(self):
self.cache = {} self.cache = {}
@@ -36,6 +42,11 @@ class MemoryCache(Cache):
self.cache[key] = value self.cache[key] = value
return True return True
def remove(self, key):
if key in self.cache:
del self.cache[key]
return True
class MemoryPerJidCache(PerJidCache): class MemoryPerJidCache(PerJidCache):
def __init__(self): def __init__(self):
self.cache = {} self.cache = {}
@@ -51,6 +62,12 @@ class MemoryPerJidCache(PerJidCache):
cache[key] = value cache[key] = value
return True return True
def remove_by_jid(self, jid, key):
cache = self.cache.get(jid, None)
if cache is not None and key in cache:
del cache[key]
return True
class FileSystemStorage: class FileSystemStorage:
def __init__(self, encode, decode, binary): def __init__(self, encode, decode, binary):
self.encode = encode if encode is not None else lambda x: x self.encode = encode if encode is not None else lambda x: x
@@ -67,7 +84,10 @@ class FileSystemStorage:
log.debug('%s not present in cache', key) log.debug('%s not present in cache', key)
except OSError: except OSError:
log.debug('Failed to read %s from cache:', key, exc_info=True) log.debug('Failed to read %s from cache:', key, exc_info=True)
return None except Exception:
log.debug('Failed to decode %s from cache:', key, exc_info=True)
log.debug('Removing %s entry', key)
self._remove(directory, key)
def _store(self, directory, key, value): def _store(self, directory, key, value):
filename = os.path.join(directory, key.replace('/', '_')) filename = os.path.join(directory, key.replace('/', '_'))
@@ -79,6 +99,17 @@ class FileSystemStorage:
except OSError: except OSError:
log.debug('Failed to store %s to cache:', key, exc_info=True) log.debug('Failed to store %s to cache:', key, exc_info=True)
return False return False
except Exception:
log.debug('Failed to encode %s to cache:', key, exc_info=True)
def _remove(self, directory, key):
filename = os.path.join(directory, key.replace('/', '_'))
try:
os.remove(filename)
except OSError:
log.debug('Failed to remove %s from cache:', key, exc_info=True)
return False
return True
class FileSystemCache(Cache, FileSystemStorage): class FileSystemCache(Cache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False): def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
@@ -91,6 +122,9 @@ class FileSystemCache(Cache, FileSystemStorage):
def store(self, key, value): def store(self, key, value):
return self._store(self.base_dir, key, value) return self._store(self.base_dir, key, value)
def remove(self, key):
return self._remove(self.base_dir, key)
class FileSystemPerJidCache(PerJidCache, FileSystemStorage): class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False): def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
FileSystemStorage.__init__(self, encode, decode, binary) FileSystemStorage.__init__(self, encode, decode, binary)
@@ -103,3 +137,7 @@ class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
def store_by_jid(self, jid, key, value): def store_by_jid(self, jid, key, value):
directory = os.path.join(self.base_dir, jid) directory = os.path.join(self.base_dir, jid)
return self._store(directory, key, value) return self._store(directory, key, value)
def remove_by_jid(self, jid, key):
directory = os.path.join(self.base_dir, jid)
return self._remove(directory, key)

View File

@@ -215,6 +215,9 @@ class XMLStream(asyncio.BaseProtocol):
#: ``_xmpp-client._tcp`` service. #: ``_xmpp-client._tcp`` service.
self.dns_service = None self.dns_service = None
#: The reason why we are disconnecting from the server
self.disconnect_reason = None
#: An asyncio Future being done when the stream is disconnected. #: An asyncio Future being done when the stream is disconnected.
self.disconnected = asyncio.Future() self.disconnected = asyncio.Future()
@@ -268,6 +271,7 @@ class XMLStream(asyncio.BaseProtocol):
localhost localhost
""" """
self.disconnect_reason = None
self.cancel_connection_attempt() self.cancel_connection_attempt()
if host and port: if host and port:
self.address = (host, int(port)) self.address = (host, int(port))
@@ -310,6 +314,8 @@ class XMLStream(asyncio.BaseProtocol):
ssl_context = None ssl_context = None
await asyncio.sleep(self.connect_loop_wait, loop=self.loop) await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
if self._current_connection_attempt is None:
return
try: try:
await self.loop.create_connection(lambda: self, await self.loop.create_connection(lambda: self,
self.address[0], self.address[0],
@@ -323,6 +329,8 @@ class XMLStream(asyncio.BaseProtocol):
except OSError as e: except OSError as e:
log.debug('Connection failed: %s', e) log.debug('Connection failed: %s', e)
self.event("connection_failed", e) self.event("connection_failed", e)
if self._current_connection_attempt is None:
return
self.connect_loop_wait = self.connect_loop_wait * 2 + 1 self.connect_loop_wait = self.connect_loop_wait * 2 + 1
self._current_connection_attempt = asyncio.ensure_future( self._current_connection_attempt = asyncio.ensure_future(
self._connect_routine(), self._connect_routine(),
@@ -398,7 +406,9 @@ class XMLStream(asyncio.BaseProtocol):
if self.xml_depth == 0: if self.xml_depth == 0:
# The stream's root element has closed, # The stream's root element has closed,
# terminating the stream. # terminating the stream.
self.end_session_on_disconnect = True
log.debug("End of stream received") log.debug("End of stream received")
self.disconnect_reason = "End of stream"
self.abort() self.abort()
elif self.xml_depth == 1: elif self.xml_depth == 1:
# A stanza is an XML element that is a direct child of # A stanza is an XML element that is a direct child of
@@ -433,7 +443,7 @@ class XMLStream(asyncio.BaseProtocol):
closure of the TCP connection closure of the TCP connection
""" """
log.info("connection_lost: %s", (exception,)) log.info("connection_lost: %s", (exception,))
self.event("disconnected") self.event("disconnected", self.disconnect_reason or exception and exception.strerror)
if self.end_session_on_disconnect: if self.end_session_on_disconnect:
self.event('session_end') self.event('session_end')
# All these objects are associated with one TCP connection. Since # All these objects are associated with one TCP connection. Since
@@ -453,21 +463,23 @@ class XMLStream(asyncio.BaseProtocol):
self._current_connection_attempt.cancel() self._current_connection_attempt.cancel()
self._current_connection_attempt = None self._current_connection_attempt = None
def disconnect(self, wait=2.0): def disconnect(self, wait=2.0, reason=None):
"""Close the XML stream and wait for an acknowldgement from the server for """Close the XML stream and wait for an acknowldgement from the server for
at most `wait` seconds. After the given number of seconds has at most `wait` seconds. After the given number of seconds has
passed without a response from the serveur, or when the server passed without a response from the serveur, or when the server
successfully responds with a closure of its own stream, abort() is successfully responds with a closure of its own stream, abort() is
called. If wait is 0.0, this is almost equivalent to calling abort() called. If wait is 0.0, this will call abort() directly without closing
directly. the stream.
Does nothing if we are not connected. Does nothing if we are not connected.
:param wait: Time to wait for a response from the server. :param wait: Time to wait for a response from the server.
""" """
self.disconnect_reason = reason
self.cancel_connection_attempt() self.cancel_connection_attempt()
if self.transport: if self.transport:
if wait > 0.0:
self.send_raw(self.stream_footer) self.send_raw(self.stream_footer)
self.schedule('Disconnect wait', wait, self.schedule('Disconnect wait', wait,
self.abort, repeat=False) self.abort, repeat=False)
@@ -484,13 +496,13 @@ class XMLStream(asyncio.BaseProtocol):
self.disconnected.set_result(True) self.disconnected.set_result(True)
self.disconnected = asyncio.Future() self.disconnected = asyncio.Future()
def reconnect(self, wait=2.0): def reconnect(self, wait=2.0, reason="Reconnecting"):
"""Calls disconnect(), and once we are disconnected (after the timeout, or """Calls disconnect(), and once we are disconnected (after the timeout, or
when the server acknowledgement is received), call connect() when the server acknowledgement is received), call connect()
""" """
log.debug("reconnecting...") log.debug("reconnecting...")
self.disconnect(wait) self.disconnect(wait, reason)
self.add_event_handler('disconnected', self.connect, disposable=True) self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
def configure_socket(self): def configure_socket(self):
"""Set timeout and other options for self.socket. """Set timeout and other options for self.socket.

74
tests/test_cache.py Normal file
View File

@@ -0,0 +1,74 @@
import unittest
from slixmpp.test import SlixTest
from slixmpp.util import (
MemoryCache, MemoryPerJidCache,
FileSystemCache, FileSystemPerJidCache
)
from tempfile import TemporaryDirectory
class TestCacheClass(SlixTest):
def testMemoryCache(self):
cache = MemoryCache()
cache.store("test", "test_value")
self.assertEqual(cache.retrieve("test"), "test_value")
self.assertEqual(cache.retrieve("test2"), None)
cache.remove("test")
self.assertEqual(cache.retrieve("test"), None)
def testMemoryPerJidcache(self):
cache = MemoryPerJidCache()
cache.store_by_jid("test@example.com", "test", "test_value")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
"test_value"
)
cache.remove_by_jid("test@example.com", "test")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
None
)
def testFileSystemCache(self):
def failing_decode(value):
if value == "failme":
raise Exception("you failed")
return value
with TemporaryDirectory() as tmpdir:
cache = FileSystemCache(tmpdir, "test", decode=failing_decode)
cache.store("test", "test_value")
cache.store("test2", "failme")
self.assertEqual(
cache.retrieve("test"),
"test_value"
)
cache.remove("test")
self.assertEqual(
cache.retrieve("test"),
None
)
self.assertEqual(
cache.retrieve("test2"),
None
)
def testFileSystemPerJidCache(self):
with TemporaryDirectory() as tmpdir:
cache = FileSystemPerJidCache(tmpdir, "test")
cache.store_by_jid("test@example.com", "test", "test_value")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
"test_value"
)
cache.remove_by_jid("test@example.com", "test")
self.assertEqual(
cache.retrieve_by_jid("test@example.com", "test"),
None
)
suite = unittest.TestLoader().loadTestsFromTestCase(TestCacheClass)