summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--slixmpp/plugins/__init__.py2
-rw-r--r--slixmpp/plugins/xep_0045/muc.py2
-rw-r--r--slixmpp/plugins/xep_0082.py18
-rw-r--r--slixmpp/plugins/xep_0202/stanza.py5
-rw-r--r--slixmpp/plugins/xep_0203/stanza.py4
-rw-r--r--slixmpp/plugins/xep_0356/stanza.py11
-rw-r--r--slixmpp/thirdparty/__init__.py1
-rw-r--r--slixmpp/thirdparty/mini_dateutil.py273
-rw-r--r--slixmpp/xmlstream/xmlstream.py25
-rw-r--r--tests/test_stanza_xep_0356.py2
-rw-r--r--tests/test_stream_xep_0356.py4
11 files changed, 47 insertions, 300 deletions
diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py
index 55627113..cc98255e 100644
--- a/slixmpp/plugins/__init__.py
+++ b/slixmpp/plugins/__init__.py
@@ -1,4 +1,3 @@
-
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2010 Nathanael C. Fritz
# This file is part of Slixmpp.
@@ -93,6 +92,7 @@ __all__ = [
'xep_0335', # JSON Containers
'xep_0352', # Client State Indication
'xep_0353', # Jingle Message Initiation
+ 'xep_0356', # Privileged entity
'xep_0359', # Unique and Stable Stanza IDs
'xep_0363', # HTTP File Upload
'xep_0369', # MIX-CORE
diff --git a/slixmpp/plugins/xep_0045/muc.py b/slixmpp/plugins/xep_0045/muc.py
index e5971bee..90cb73d7 100644
--- a/slixmpp/plugins/xep_0045/muc.py
+++ b/slixmpp/plugins/xep_0045/muc.py
@@ -493,6 +493,8 @@ class XEP_0045(BasePlugin):
"""
if affiliation not in AFFILIATIONS:
raise ValueError('%s is not a valid affiliation' % affiliation)
+ if affiliation == 'outcast' and not jid:
+ raise ValueError('Outcast affiliation requires a using a jid')
if not any((jid, nick)):
raise ValueError('One of jid or nick must be set')
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
diff --git a/slixmpp/plugins/xep_0082.py b/slixmpp/plugins/xep_0082.py
index e8050286..0cdee465 100644
--- a/slixmpp/plugins/xep_0082.py
+++ b/slixmpp/plugins/xep_0082.py
@@ -6,7 +6,6 @@
import datetime as dt
from slixmpp.plugins import BasePlugin, register_plugin
-from slixmpp.thirdparty import tzutc, tzoffset, parse_iso
# =====================================================================
@@ -21,7 +20,10 @@ def parse(time_str):
Arguments:
time_str -- A formatted timestamp string.
"""
- return parse_iso(time_str)
+ try:
+ return dt.datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f%z')
+ except ValueError:
+ return dt.datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S%z')
def format_date(time_obj):
@@ -52,7 +54,7 @@ def format_time(time_obj):
if isinstance(time_obj, dt.datetime):
time_obj = time_obj.timetz()
timestamp = time_obj.isoformat()
- if time_obj.tzinfo == tzutc():
+ if time_obj.tzinfo == dt.timezone.utc:
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
@@ -69,7 +71,7 @@ def format_datetime(time_obj):
time_obj -- A datetime object.
"""
timestamp = time_obj.isoformat('T')
- if time_obj.tzinfo == tzutc():
+ if time_obj.tzinfo == dt.timezone.utc:
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
@@ -128,9 +130,9 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
if micro is None:
micro = now.microsecond
if offset in (None, 0):
- offset = tzutc()
+ offset = dt.timezone.utc
elif not isinstance(offset, dt.tzinfo):
- offset = tzoffset(None, offset)
+ offset = dt.timezone(dt.timedelta(seconds=offset))
value = dt.time(hour, min, sec, micro, offset)
if obj:
return value
@@ -175,9 +177,9 @@ def datetime(year=None, month=None, day=None, hour=None,
if micro is None:
micro = now.microsecond
if offset in (None, 0):
- offset = tzutc()
+ offset = dt.timezone.utc
elif not isinstance(offset, dt.tzinfo):
- offset = tzoffset(None, offset)
+ offset = dt.timezone(dt.timedelta(seconds=offset))
value = dt.datetime(year, month, day, hour,
min, sec, micro, offset)
diff --git a/slixmpp/plugins/xep_0202/stanza.py b/slixmpp/plugins/xep_0202/stanza.py
index 7d09de50..faa230f9 100644
--- a/slixmpp/plugins/xep_0202/stanza.py
+++ b/slixmpp/plugins/xep_0202/stanza.py
@@ -8,7 +8,6 @@ import datetime as dt
from slixmpp.xmlstream import ElementBase
from slixmpp.plugins import xep_0082
-from slixmpp.thirdparty import tzutc, tzoffset
class EntityTime(ElementBase):
@@ -87,7 +86,7 @@ class EntityTime(ElementBase):
seconds (positive or negative) to offset.
"""
time = xep_0082.time(offset=value)
- if xep_0082.parse(time).tzinfo == tzutc():
+ if xep_0082.parse(time).tzinfo == dt.timezone.utc:
self._set_sub_text('tzo', 'Z')
else:
self._set_sub_text('tzo', time[-6:])
@@ -111,6 +110,6 @@ class EntityTime(ElementBase):
date = value
if not isinstance(value, dt.datetime):
date = xep_0082.parse(value)
- date = date.astimezone(tzutc())
+ date = date.astimezone(dt.timezone.utc)
value = xep_0082.format_datetime(date)
self._set_sub_text('utc', value)
diff --git a/slixmpp/plugins/xep_0203/stanza.py b/slixmpp/plugins/xep_0203/stanza.py
index f173d41c..a84cb52f 100644
--- a/slixmpp/plugins/xep_0203/stanza.py
+++ b/slixmpp/plugins/xep_0203/stanza.py
@@ -30,6 +30,10 @@ class Delay(ElementBase):
def set_stamp(self, value):
if isinstance(value, dt.datetime):
+ if value.tzinfo is None:
+ raise ValueError(f'Datetime provided without timezone information: {value}')
+ if value.tzinfo != dt.timezone.utc:
+ value = value.astimezone(dt.timezone.utc)
value = xep_0082.format_datetime(value)
self._set_attr('stamp', value)
diff --git a/slixmpp/plugins/xep_0356/stanza.py b/slixmpp/plugins/xep_0356/stanza.py
index ef01ee3e..46f1523a 100644
--- a/slixmpp/plugins/xep_0356/stanza.py
+++ b/slixmpp/plugins/xep_0356/stanza.py
@@ -7,7 +7,7 @@ from slixmpp.plugins.xep_0297 import Forwarded
class Privilege(ElementBase):
- namespace = "urn:xmpp:privilege:1"
+ namespace = "urn:xmpp:privilege:2"
name = "privilege"
plugin_attrib = "privilege"
@@ -24,7 +24,10 @@ class Privilege(ElementBase):
def presence(self):
return self.permission("presence")
-
+
+ def iq(self):
+ return self.permission("iq")
+
def add_perm(self, access, type):
# This should only be needed for servers, so maybe out of scope for slixmpp
perm = Perm()
@@ -34,7 +37,7 @@ class Privilege(ElementBase):
class Perm(ElementBase):
- namespace = "urn:xmpp:privilege:1"
+ namespace = "urn:xmpp:privilege:2"
name = "perm"
plugin_attrib = "perm"
plugin_multi_attrib = "perms"
@@ -44,4 +47,4 @@ class Perm(ElementBase):
def register():
register_stanza_plugin(Message, Privilege)
register_stanza_plugin(Privilege, Forwarded)
- register_stanza_plugin(Privilege, Perm, iterable=True) \ No newline at end of file
+ register_stanza_plugin(Privilege, Perm, iterable=True)
diff --git a/slixmpp/thirdparty/__init__.py b/slixmpp/thirdparty/__init__.py
index d950f4f9..216a7b79 100644
--- a/slixmpp/thirdparty/__init__.py
+++ b/slixmpp/thirdparty/__init__.py
@@ -3,5 +3,4 @@ try:
except:
from slixmpp.thirdparty.gnupg import GPG
-from slixmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
from slixmpp.thirdparty.orderedset import OrderedSet
diff --git a/slixmpp/thirdparty/mini_dateutil.py b/slixmpp/thirdparty/mini_dateutil.py
deleted file mode 100644
index 882a531f..00000000
--- a/slixmpp/thirdparty/mini_dateutil.py
+++ /dev/null
@@ -1,273 +0,0 @@
-# This module is a very stripped down version of the dateutil
-# package for when dateutil has not been installed. As a replacement
-# for dateutil.parser.parse, the parsing methods from
-# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/
-
-#As such, the following copyrights and licenses applies:
-
-
-# dateutil - Extensions to the standard python 2.3+ datetime module.
-#
-# Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
-#
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of the copyright holder nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "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 THE COPYRIGHT OWNER OR
-# 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, WHETHER IN CONTRACT, STRICT 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 DAMAGE.
-
-
-# fixed_dateime
-#
-# Copyright (c) 2008, Red Innovation Ltd., Finland
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * 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.
-# * Neither the name of Red Innovation nor the names of its contributors
-# may be used to endorse or promote products derived from this software
-# without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``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 RED INNOVATION 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, WHETHER IN CONTRACT, STRICT 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 DAMAGE.
-
-
-
-import re
-import math
-import datetime
-
-
-ZERO = datetime.timedelta(0)
-
-
-try:
- from dateutil.parser import parse as parse_iso
- from dateutil.tz import tzoffset, tzutc
-except:
- # As a stopgap, define the two timezones here based
- # on the dateutil code.
-
- class tzutc(datetime.tzinfo):
-
- def utcoffset(self, dt):
- return ZERO
-
- def dst(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return "UTC"
-
- def __eq__(self, other):
- return (isinstance(other, tzutc) or
- (isinstance(other, tzoffset) and other._offset == ZERO))
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return "%s()" % self.__class__.__name__
-
- __reduce__ = object.__reduce__
-
- class tzoffset(datetime.tzinfo):
-
- def __init__(self, name, offset):
- self._name = name
- self._offset = datetime.timedelta(minutes=offset)
-
- def utcoffset(self, dt):
- return self._offset
-
- def dst(self, dt):
- return ZERO
-
- def tzname(self, dt):
- return self._name
-
- def __eq__(self, other):
- return (isinstance(other, tzoffset) and
- self._offset == other._offset)
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return "%s(%s, %s)" % (self.__class__.__name__,
- repr(self._name),
- self._offset.days*86400+self._offset.seconds)
-
- __reduce__ = object.__reduce__
-
-
- _fixed_offset_tzs = { }
- UTC = tzutc()
-
- def _get_fixed_offset_tz(offsetmins):
- """For internal use only: Returns a tzinfo with
- the given fixed offset. This creates only one instance
- for each offset; the zones are kept in a dictionary"""
-
- if offsetmins == 0:
- return UTC
-
- if not offsetmins in _fixed_offset_tzs:
- if offsetmins < 0:
- sign = '-'
- absoff = -offsetmins
- else:
- sign = '+'
- absoff = offsetmins
-
- name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60)
- inst = tzoffset(name,offsetmins)
- _fixed_offset_tzs[offsetmins] = inst
-
- return _fixed_offset_tzs[offsetmins]
-
-
- _iso8601_parser = re.compile(r"""
- ^
- (?P<year> [0-9]{4})?(?P<ymdsep>-?)?
- (?P<month>[0-9]{2})?(?P=ymdsep)?
- (?P<day> [0-9]{2})?
-
- (?P<time>
- (?: # time part... optional... at least hour must be specified
- (?:T|\s+)?
- (?P<hour>[0-9]{2})
- (?:
- # minutes, separated with :, or none, from hours
- (?P<hmssep>[:]?)
- (?P<minute>[0-9]{2})
- (?:
- # same for seconds, separated with :, or none, from hours
- (?P=hmssep)
- (?P<second>[0-9]{2})
- )?
- )?
-
- # fractions
- (?: [,.] (?P<frac>[0-9]{1,10}))?
-
- # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
- (
- (?P<tzempty>Z)
- |
- (?P<tzh>[+-][0-9]{2})
- (?: :? # optional separator
- (?P<tzm>[0-9]{2})
- )?
- )?
- )
- )?
- $
- """, re.X) # """
-
- def parse_iso(timestamp):
- """Internal function for parsing a timestamp in
- ISO 8601 format"""
-
- timestamp = timestamp.strip()
-
- m = _iso8601_parser.match(timestamp)
- if not m:
- raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp)
-
- vals = m.groupdict()
- def_vals = {'year': 1970, 'month': 1, 'day': 1}
- for key in vals:
- if vals[key] is None:
- vals[key] = def_vals.get(key, 0)
- elif key not in ['time', 'ymdsep', 'hmssep', 'tzempty']:
- vals[key] = int(vals[key])
-
- year = vals['year']
- month = vals['month']
- day = vals['day']
-
- if m.group('time') is None:
- return datetime.date(year, month, day)
-
- h, min, s, us = None, None, None, 0
- frac = 0
- if m.group('tzempty') == None and m.group('tzh') == None:
- raise ValueError("Not a proper ISO 8601 timestamp: " +
- "missing timezone (Z or +hh[:mm])!")
-
- if m.group('frac'):
- frac = m.group('frac')
- power = len(frac)
- frac = int(frac) / 10.0 ** power
-
- if m.group('hour'):
- h = vals['hour']
-
- if m.group('minute'):
- min = vals['minute']
-
- if m.group('second'):
- s = vals['second']
-
- if frac != None:
- # ok, fractions of hour?
- if min == None:
- frac, min = math.modf(frac * 60.0)
- min = int(min)
-
- # fractions of second?
- if s == None:
- frac, s = math.modf(frac * 60.0)
- s = int(s)
-
- # and extract microseconds...
- us = int(frac * 1000000)
-
- if m.group('tzempty') == 'Z':
- offsetmins = 0
- else:
- # timezone: hour diff with sign
- offsetmins = vals['tzh'] * 60
- tzm = m.group('tzm')
-
- # add optional minutes
- if tzm != None:
- tzm = int(tzm)
- offsetmins += tzm if offsetmins > 0 else -tzm
-
- tz = _get_fixed_offset_tz(offsetmins)
- return datetime.datetime(year, month, day, h, min, s, us, tz)
diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py
index 18143f87..18464ccd 100644
--- a/slixmpp/xmlstream/xmlstream.py
+++ b/slixmpp/xmlstream/xmlstream.py
@@ -493,16 +493,11 @@ class XMLStream(asyncio.BaseProtocol):
except Socket.gaierror as e:
self.event('connection_failed',
'No DNS record available for %s' % self.default_domain)
+ self.reschedule_connection_attempt()
except OSError as e:
log.debug('Connection failed: %s', 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._current_connection_attempt = asyncio.ensure_future(
- self._connect_routine(),
- loop=self.loop,
- )
+ self.reschedule_connection_attempt()
def process(self, *, forever: bool = True, timeout: Optional[int] = None) -> None:
"""Process all the available XMPP events (receiving or sending data on the
@@ -638,6 +633,20 @@ class XMLStream(asyncio.BaseProtocol):
self._set_disconnected_future()
self.event("disconnected", self.disconnect_reason or exception)
+ def reschedule_connection_attempt(self) -> None:
+ """
+ Increase the exponential back-off and initate another background
+ _connect_routine call to connect to the server.
+ """
+ # abort if there is no ongoing connection attempt
+ if self._current_connection_attempt is None:
+ return
+ self._connect_loop_wait = min(300, self._connect_loop_wait * 2 + 1)
+ self._current_connection_attempt = asyncio.ensure_future(
+ self._connect_routine(),
+ loop=self.loop,
+ )
+
def cancel_connection_attempt(self) -> None:
"""
Immediately cancel the current create_connection() Future.
@@ -800,6 +809,8 @@ class XMLStream(asyncio.BaseProtocol):
self.ssl_context.verify_mode = ssl.CERT_REQUIRED
self.ssl_context.load_verify_locations(cafile=ca_cert)
+ else:
+ self.ssl_context.set_default_verify_paths()
return self.ssl_context
diff --git a/tests/test_stanza_xep_0356.py b/tests/test_stanza_xep_0356.py
index ef116db2..cf14ccba 100644
--- a/tests/test_stanza_xep_0356.py
+++ b/tests/test_stanza_xep_0356.py
@@ -13,7 +13,7 @@ class TestPermissions(SlixTest):
def testAdvertisePermission(self):
xmlstring = """
<message from='capulet.net' to='pubub.capulet.lit'>
- <privilege xmlns='urn:xmpp:privilege:1'>
+ <privilege xmlns='urn:xmpp:privilege:2'>
<perm access='roster' type='both'/>
<perm access='message' type='outgoing'/>
<perm access='presence' type='managed_entity'/>
diff --git a/tests/test_stream_xep_0356.py b/tests/test_stream_xep_0356.py
index 2949daad..e2ce9569 100644
--- a/tests/test_stream_xep_0356.py
+++ b/tests/test_stream_xep_0356.py
@@ -31,7 +31,7 @@ class TestPermissions(SlixTest):
self.recv(
"""
<message from='capulet.net' to='pubub.capulet.lit' id='54321'>
- <privilege xmlns='urn:xmpp:privilege:1'>
+ <privilege xmlns='urn:xmpp:privilege:2'>
<perm access='roster' type='both'/>
<perm access='message' type='outgoing'/>
</privilege>
@@ -95,7 +95,7 @@ class TestPermissions(SlixTest):
def testMakeOutgoingMessage(self):
xmlstring = """
<message xmlns="jabber:component:accept" from='pubsub.capulet.lit' to='capulet.net'>
- <privilege xmlns='urn:xmpp:privilege:1'>
+ <privilege xmlns='urn:xmpp:privilege:2'>
<forwarded xmlns='urn:xmpp:forward:0'>
<message from="juliet@capulet.lit" to="romeo@montague.lit" xmlns="jabber:client">
<body>I do not hate you</body>