Compare commits

...

85 Commits

Author SHA1 Message Date
mathieui
9d378c611c Release 1.4.2 2019-01-31 14:50:26 +01:00
Link Mauve
d85d8f4479 Merge branch 'xep-0335' into 'master'
Add xep_0335: JSON Containers

See merge request poezio/slixmpp!5
2019-01-22 19:28:38 +01:00
Emmanuel Gil Peyrot
fb75f7cda9 Stop requesting avatar without the intervention of the client. 2019-01-22 15:12:00 +01:00
Emmanuel Gil Peyrot
41419a2161 Fix authenticating on a non-TLS socket.
This was broken since c1562b76b2.
2019-01-21 01:02:06 +01:00
Emmanuel Gil Peyrot
7cd73b594e XEP-0223: Fix default access_model, it MUST be whitelist. 2019-01-17 12:08:51 +01:00
Emmanuel Gil Peyrot
15c6b775ff Simplify the non-CDATA path of tostring.escape. 2019-01-09 15:03:05 +01:00
Emmanuel Gil Peyrot
4b482477e2 Split ns only once in fix_ns(). 2019-01-09 14:57:39 +01:00
Emmanuel Gil Peyrot
f7e4caadfe Split tag and attrib only once in tostring(). 2019-01-09 14:55:27 +01:00
Emmanuel Gil Peyrot
5f25b0b6a0 Add a default timeout to iq.send().
This fixes a leak of MatchIDSender in handlers, making it more and more
expensive to match stanzas as more iqs have been sent.
2019-01-09 14:20:31 +01:00
Mateusz Piotrowski
d228bc42ea Mention that GnuPG is required for tests 2018-12-27 17:21:12 +01:00
mathieui
ecdc44a601 Merge branch 'master' into 'master'
Decode bytes in GSSAPI handling, as expected by the kerberos module API.

See merge request poezio/slixmpp!8
2018-12-27 16:55:47 +01:00
Emmanuel Gil Peyrot
33370e42f1 XEP-0363: Use a specific exception for HTTP errors 2018-11-20 07:44:09 +01:00
Florian Klien
4699861925 catch http upload errors on upload 2018-11-20 07:34:56 +01:00
Jelmer Vernooij
2d228bdb56 Decode bytes in GSSAPI handling, as expected by the kerberos module API. 2018-10-30 22:29:20 +00:00
mathieui
570e653ac2 Release slixmpp 1.4.1 2018-10-28 14:15:51 +01:00
mathieui
282a481059 Merge branch 'setup_dependency_fix' into 'master'
added aiohttp dependency

See merge request poezio/slixmpp!6
2018-10-28 14:08:26 +01:00
Florian Klien
f386db380b docs: auto-set copyright year to current year 2018-10-28 10:48:23 +01:00
Florian Klien
7b87d98fff auto set version of Slixmpp in Docs
getting version of slixmpp from source tree for documentation
2018-10-28 10:48:23 +01:00
Florian Klien
8779d40602 typo 2018-10-27 23:38:09 +02:00
Emmanuel Gil Peyrot
f0b21c42d5 examples: Add the possibility to use another HTTP File Upload domain. 2018-10-27 23:25:59 +02:00
Emmanuel Gil Peyrot
e241d4e3c7 XEP-0030: Don’t call the timeout_callback on each domain which doesn’t reply to disco#info. 2018-10-27 23:21:27 +02:00
Emmanuel Gil Peyrot
bd22a41a78 XEP-0363: Also check for disco#info’s feature instead of just the identity. 2018-10-27 23:14:39 +02:00
Emmanuel Gil Peyrot
a29a29227a XEP-0363: Add a domain argument to discover an upload service on a specific domain. 2018-10-27 22:51:04 +02:00
Florian Klien
d4d542b741 fixing uncaught async exceptions due to missing await
fixes uncaught exceptions in the event loop.
passing timeout and timeout_callback through.
2018-10-27 22:51:04 +02:00
Florian Klien
dc4936a6d3 added aoihttp dependency
slixmpp uses aiohttp in the XEP_0363 plugin.
2018-10-22 12:41:20 +02:00
Florian Klien
897610d819 fix: failUnlessEqual -> assertEqual 2018-10-15 14:59:23 +02:00
Florian Klien
d33366badd fixing deprecation warnings for pytest 2018-10-15 14:59:23 +02:00
mathieui
809c500002 Add the loop parameters at places where it has been forgotten 2018-10-09 12:34:56 +02:00
Emmanuel Gil Peyrot
dda4e18b81 examples: Remove unused asyncio imports. 2018-10-09 10:57:19 +02:00
Emmanuel Gil Peyrot
8c09d932c8 stanzabase: Remove python2 legacy. 2018-10-03 14:56:07 +02:00
Maxime “pep” Buquet
31f5e84671 Add xep_0335: JSON Containers
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-09-16 22:13:41 +01:00
louiz’
ad0dc33df9 Trigger poezio’s build if this ours succeeded 2018-08-22 23:19:47 +02:00
Emmanuel Gil Peyrot
7c3b3827b4 jid: Make property aliases proper aliases. 2018-08-20 00:23:21 +01:00
Emmanuel Gil Peyrot
9f6fa65139 examples, tests: Replace all @asyncio.coroutines with proper async functions. 2018-08-19 17:47:26 +01:00
mathieui
35fa33e3c2 Release slixmpp 1.4.0 2018-08-12 21:11:21 +02:00
mathieui
86a2f280d2 Document that slixmpp is now 3.5+ only 2018-08-08 23:42:22 +02:00
mathieui
490f15b8fc Fix compatibility with python 3.5 and 3.6
which do not have loop.start_tls and require the old ssl implementation.
2018-08-08 23:35:33 +02:00
Emmanuel Gil Peyrot
62661ee04f xep_0092: Return <service-unavailable/> instead. Fixes #3415. 2018-08-08 16:52:40 +02:00
Emmanuel Gil Peyrot
37d1f2a6b0 xep_0092: Send a <forbidden/> error if we don’t want to send our version. (thanks lovetox!) 2018-08-08 16:49:16 +02:00
mathieui
20107ad516 features/starttls: handle the case where the socket is an sslobject
Thanks pep.
2018-08-07 23:30:41 +02:00
mathieui
7738a01311 Fix TLS with python 3.7
Use the "new" sslproto API instead of the deprecated TLS API.
Also remove the unused "socket" parameter in XMLStream.__init__.
2018-08-07 23:20:38 +02:00
mathieui
a9abed6151 xep-0054: XMPP clients should not reply to vcard "get" requests 2018-08-07 21:30:13 +02:00
Emmanuel Gil Peyrot
0f690d4005 tests: Fix the XEP-0323 stream test, broken since 59d4420739.
Thanks debacle!
2018-08-02 21:27:19 +02:00
Emmanuel Gil Peyrot
59d4420739 XEP-0323: Display the requested time in addition to the current time on error. 2018-07-29 10:37:28 +02:00
Emmanuel Gil Peyrot
a88f317bbf XEP-0009: Fix invalid function name under Python 3.7. 2018-07-24 18:21:03 +02:00
Link Mauve
2fc2a88970 Merge branch 'factor_find_identities' into 'master'
Factor find identities

See merge request poezio/slixmpp!2
2018-07-22 12:49:37 +02:00
Emmanuel Gil Peyrot
c55e9279ac Fix missing async def in function. 2018-07-02 14:33:21 +02:00
Emmanuel Gil Peyrot
3502480384 Switch from @asyncio.coroutine to async def everywhere. 2018-07-01 18:46:33 +02:00
Maxime “pep” Buquet
caae713dd6 xep_0030: rename find_identities; return all domain infos and let caller filter itself
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:36:26 +01:00
Maxime “pep” Buquet
df0198abfe xep_0030: Add callback parameter to find_identities
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:32:00 +01:00
Maxime “pep” Buquet
c20f4bf5fa xep_0030: Add cached parameter to find_identities, defaults to True
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
9740e93aeb xep_0030: Pass kwargs down in find_identities
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
e7872aaa29 xep_0030: Use self directly as we're already in disco
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
037706552c Factor out fetching of identities in xep_0363 to xep_0030
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
b881c6729b xep_0363: Remove unused parameters to find_upload_services
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Emmanuel Gil Peyrot
66909aafb3 XEP-0153: Prevent a panic when the BINVAL is invalid. 2018-06-23 14:34:24 +02:00
louiz’
cdfb5d56fc apt install gpg before running the ci tests 2018-06-10 21:40:56 +02:00
Emmanuel Gil Peyrot
d146ce9fb6 examples: Display only the form, and not the entire stanza. 2018-05-13 21:26:47 +02:00
Emmanuel Gil Peyrot
cb59d60034 examples: Display the actual cause for a failed command. 2018-05-13 21:21:06 +02:00
Emmanuel Gil Peyrot
1d9fe3553e examples: Use the existing get_node_config function. 2018-05-13 21:13:22 +02:00
Emmanuel Gil Peyrot
fe66c022ad Revert "XEP-0060: Add support for node configuration."
This reverts commit dd7f67d10d.
2018-05-13 21:12:46 +02:00
Emmanuel Gil Peyrot
92ea131721 examples: Add support for node configuration to pubsub_client. 2018-05-13 20:57:48 +02:00
Emmanuel Gil Peyrot
dd7f67d10d XEP-0060: Add support for node configuration. 2018-05-13 20:47:21 +02:00
Emmanuel Gil Peyrot
c1562b76b2 slixmpp is Python 3.4.4+, remove check for channel binding. 2018-03-31 02:22:53 +02:00
Emmanuel Gil Peyrot
32839f5252 util.cache: Let the user select the bare JID or not. 2018-03-31 00:44:53 +02:00
Emmanuel Gil Peyrot
80b7cf6ff8 util.cache: Support None for encode and decode. 2018-03-31 00:44:23 +02:00
Emmanuel Gil Peyrot
128cc2eeb4 XEP-0115: Use the new cache system. 2018-03-31 00:25:28 +02:00
Emmanuel Gil Peyrot
037912ee89 util.cache: New module handling both in-memory and on-file system caching. 2018-03-31 00:24:21 +02:00
Emmanuel Gil Peyrot
769bc6d3bf session: Also fire the session_start event. 2018-03-14 19:39:01 +01:00
Emmanuel Gil Peyrot
084d6cb5d9 session: Don’t bind if it is optional.
See https://tools.ietf.org/html/draft-cridland-xmpp-session-01
2018-03-14 18:54:17 +01:00
Emmanuel Gil Peyrot
5184713356 Rearm an iq callback if it was addressed to ourself. 2018-03-14 17:37:55 +01:00
Emmanuel Gil Peyrot
2f1225bad3 Carry the node attribute to the disco#info result.
Fixes #3323.
2018-03-14 16:25:21 +01:00
Emmanuel Gil Peyrot
841f5a5a5b xep_0363: Only send the basename() of the filename to the server. 2018-03-11 19:40:15 +01:00
Emmanuel Gil Peyrot
0c6de5e972 xep_0363: Simplify Content-Type guessing. 2018-03-11 19:39:51 +01:00
Emmanuel Gil Peyrot
81dc61c55c xep_0363: Fix max_file_size variable name. 2018-03-11 19:39:30 +01:00
Emmanuel Gil Peyrot
bd63b1ce70 Simplify usage of HTTP File Upload plugin.
This makes it usable only on Python 3.5, as documented.
2018-03-08 14:29:07 +01:00
Emmanuel Gil Peyrot
29faf114a7 Add max-file-size support to HTTP File Upload example. 2018-03-08 12:11:26 +01:00
Emmanuel Gil Peyrot
94ea8151d4 Add an HTTP File Upload example. 2018-03-08 03:39:23 +01:00
Emmanuel Gil Peyrot
66500ef5fb Add an HTTP File Upload plugin. 2018-03-08 03:38:59 +01:00
mathieui
979396bb1e asyncio.async has been scheduled for removal for a long time now
move to asyncio.ensure_future
2018-02-11 19:25:38 +01:00
mathieui
e177726387 Fix usage of the 0004 plugin interface
form['fields'] is an ordered list of fields while most plugins expect a
dict there. Fixes, among other things, a caps bug.
2018-02-11 16:42:59 +01:00
Emmanuel Gil Peyrot
20e88fda50 Fix typos, thanks codespell! 2018-01-10 02:18:07 +01:00
Emmanuel Gil Peyrot
f252be9b6d XEP-0115: Fix typo. 2018-01-05 18:03:42 +01:00
Emmanuel Gil Peyrot
ee98159586 Test all known python versions in travais 2017-12-27 15:49:52 +01:00
Emmanuel Gil Peyrot
c6443af29a stringprep: Make pure-Python punycode() return bytes.
Fixes #3366.
2017-12-27 15:48:42 +01:00
100 changed files with 1023 additions and 506 deletions

View File

@@ -1,8 +1,21 @@
stages:
- test
- trigger
test:
stage: test
tags:
- docker
image: ubuntu:latest
script:
- apt update
- apt install -y python3 cython3
- apt install -y python3 cython3 gpg
- ./run_tests.py
trigger_poezio:
stage: trigger
tags:
- docker
image: appropriate/curl:latest
script:
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline

View File

@@ -1,10 +1,9 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
install:
- "pip install ."
script: testall.py

View File

@@ -1,6 +1,7 @@
Pre-requisites:
- Python 3.4
- Python 3.5+
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
- GnuPG, for testing
Install:
> python3 setup.py install

View File

@@ -1,7 +1,7 @@
Slixmpp
#########
Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of
Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of
SleekXMPP.
Slixmpp's goals is to only rewrite the core of the library (the low level

View File

@@ -12,12 +12,17 @@
# serve to show the default.
import sys, os
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# get version automagically from source tree
from slixmpp.version import __version__ as version
release = ".".join(version.split(".")[0:2])
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -41,16 +46,18 @@ master_doc = 'index'
# General information about the project.
project = u'Slixmpp'
copyright = u'2011, Nathan Fritz, Lance Stout'
year = datetime.datetime.now().year
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# auto imported from code!
# The short X.Y version.
version = '1.1'
# version = '1.4'
# The full version, including alpha/beta/rc tags.
release = '1.1'
# release = '1.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -3,8 +3,8 @@
Differences from SleekXMPP
==========================
**Python 3.4+ only**
slixmpp will only work on python 3.4 and above.
**Python 3.5+ only**
slixmpp will only work on python 3.5 and above.
**Stanza copies**
The same stanza object is given through all the handlers; a handler that

View File

@@ -21,7 +21,7 @@ Slixmpp
which goal is to use asyncio instead of threads to handle networking. See
:ref:`differences`.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+,
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
Slixmpp's design goals and philosphy are:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)

View File

@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
else:
self.confirmed.set_result(True)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
log.info('Sending confirm request %s to %s who wants to access %s using '
'method %s...' % (self.id, self.recipient, self.url, self.method))
try:
confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
id=self.id,
url=self.url,
method=self.method,
message='Plz say yes or no for {method} {url} ({id}).')
if isinstance(confirmed, slixmpp.Message):
confirmed = yield from self.confirmed
confirmed = await self.confirmed
else:
confirmed = True
except IqError:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.xmlstream.asyncio import asyncio
class Disco(slixmpp.ClientXMPP):
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
try:
if self.get in self.info_types:
# function using the callback parameter.
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
info = await self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
items = await self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node)
if self.get not in self.info_types and self.get not in self.items_types:
logging.error("Invalid disco request type.")

View File

@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.roster_received.set()
self.presences_received.clear()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.get_roster(callback=self.roster_received_cb)
print('Waiting for presence updates...\n')
yield from self.roster_received.wait()
await self.roster_received.wait()
print('Roster received')
yield from self.presences_received.wait()
await self.presences_received.wait()
self.disconnect()
@asyncio.coroutine
def on_vcard_avatar(self, pres):
async def on_vcard_avatar(self, pres):
print("Received vCard avatar update from %s" % pres['from'].bare)
try:
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % pres['from'])
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
with open(filename, 'wb+') as img:
img.write(avatar['BINVAL'])
@asyncio.coroutine
def on_avatar(self, msg):
async def on_avatar(self, msg):
print("Received avatar update from %s" % msg['from'])
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
for info in metadata['items']:
if not info['url']:
try:
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % msg['from'])

96
examples/http_upload.py Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
log = logging.getLogger(__name__)
class HttpUpload(slixmpp.ClientXMPP):
"""
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, filename, domain=None):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.filename = filename
self.domain = domain
self.add_event_handler("session_start", self.start)
async def start(self, event):
log.info('Uploading file %s...', self.filename)
def timeout_callback(arg):
raise TimeoutError("could not send message in time")
url = await self['xep_0363'].upload_file(
self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
log.info('Upload success!')
log.info('Sending file to %s', self.recipient)
html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url)
self.send_message(self.recipient, url, mhtml=html)
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-r", "--recipient", required=True,
help="Recipient JID")
parser.add_argument("-f", "--file", required=True,
help="File to send")
parser.add_argument("--domain",
help="Domain to use for HTTP File Upload (leave out for your own servers)")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
xmpp.register_plugin('xep_0071')
xmpp.register_plugin('xep_0128')
xmpp.register_plugin('xep_0363')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
try:
# Open the IBB stream in which to write to.
stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
# If you want to send in-memory bytes, use stream.sendall() instead.
yield from stream.sendfile(self.file, timeout=10)
await stream.sendfile(self.file, timeout=10)
# And finally close the stream.
yield from stream.close(timeout=10)
await stream.close(timeout=10)
except (IqError, IqTimeout):
print('File transfer errored')
else:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)

View File

@@ -13,7 +13,6 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp import asyncio
import slixmpp
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
self.get_roster()
try:
rtt = yield from self['xep_0199'].ping(self.pingjid,
rtt = await self['xep_0199'].ping(self.pingjid,
timeout=10)
logging.info("Success! RTT: %s", rtt)
except IqError as e:

View File

@@ -5,7 +5,6 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
import asyncio
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import ET, tostring
@@ -21,7 +20,7 @@ class PubsubClient(slixmpp.ClientXMPP):
self.register_plugin('xep_0059')
self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete',
self.actions = ['nodes', 'create', 'delete', 'get_configure',
'publish', 'get', 'retract',
'purge', 'subscribe', 'unsubscribe']
@@ -32,80 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
self.add_event_handler('session_start', self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
self.get_roster()
self.send_presence()
try:
yield from getattr(self, self.action)()
await getattr(self, self.action)()
except:
logging.error('Could not execute: %s', self.action)
logging.exception('Could not execute %s:', self.action)
self.disconnect()
def nodes(self):
async def nodes(self):
try:
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
for item in result['disco_items']['items']:
logging.info(' - %s', str(item))
except XMPPError as error:
logging.error('Could not retrieve node list: %s', error.format())
def create(self):
async def create(self):
try:
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
await self['xep_0060'].create_node(self.pubsub_server, self.node)
logging.info('Created node %s', self.node)
except XMPPError as error:
logging.error('Could not create node %s: %s', self.node, error.format())
def delete(self):
async def delete(self):
try:
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
await self['xep_0060'].delete_node(self.pubsub_server, self.node)
logging.info('Deleted node %s', self.node)
except XMPPError as error:
logging.error('Could not delete node %s: %s', self.node, error.format())
def publish(self):
async def get_configure(self):
try:
configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
except XMPPError as error:
logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
async def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
try:
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
except XMPPError as error:
logging.error('Could not publish to %s: %s', self.node, error.format())
def get(self):
async def get(self):
try:
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
for item in result['pubsub']['items']['substanzas']:
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
except XMPPError as error:
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
def retract(self):
async def retract(self):
try:
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
await self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
logging.info('Retracted item %s from node %s', self.data, self.node)
except XMPPError as error:
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
def purge(self):
async def purge(self):
try:
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
await self['xep_0060'].purge(self.pubsub_server, self.node)
logging.info('Purged all items from node %s', self.node)
except XMPPError as error:
logging.error('Could not purge items from node %s: %s', self.node, error.format())
def subscribe(self):
async def subscribe(self):
try:
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node)
subscription = iq['pubsub']['subscription']
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
except XMPPError as error:
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
def unsubscribe(self):
async def unsubscribe(self):
try:
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
await self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
except XMPPError as error:
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
@@ -118,7 +123,7 @@ if __name__ == '__main__':
parser = ArgumentParser()
parser.version = '%%prog 0.1'
parser.usage = "Usage: %%prog [options] <jid> " + \
'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \
' [<node> <data>]'
parser.add_argument("-q","--quiet", help="set logging to ERROR",
@@ -139,7 +144,7 @@ if __name__ == '__main__':
help="password to use")
parser.add_argument("server")
parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("node", nargs='?')
parser.add_argument("data", nargs='?')

View File

@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
# We're only concerned about registering, so nothing more to do here.
self.disconnect()
def register(self, iq):
async def register(self, iq):
"""
Fill out and submit a registration form.
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
resp['register']['password'] = self.password
try:
yield from resp.send()
await resp.send()
logging.info("Account created for %s!" % self.boundjid)
except IqError as e:
logging.error("Could not register account: %s" %

View File

@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.received = set()
self.presences_received = asyncio.Event()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
future.set_result(None)
try:
self.get_roster(callback=callback)
yield from future
await future
except IqError as err:
print('Error: %s' % err.iq['error']['condition'])
except IqTimeout:
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
print('Waiting for presence updates...\n')
yield from asyncio.sleep(10)
await asyncio.sleep(10)
print('Roster for %s' % self.boundjid.bare)
groups = self.client_roster.groups()

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
# and the XML streams are ready for use.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
try:
# Open the S5B stream in which to write to.
proxy = yield from self['xep_0065'].handshake(self.receiver)
proxy = await self['xep_0065'].handshake(self.receiver)
# Send the entire file.
while True:
data = self.file.read(1048576)
if not data:
break
yield from proxy.write(data)
await proxy.write(data)
# And finally close the stream.
proxy.transport.write_eof()

View File

@@ -18,7 +18,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
class AvatarSetter(slixmpp.ClientXMPP):
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
self.filepath = filepath
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
used_xep84 = False
print('Publish XEP-0084 avatar data')
result = yield from self['xep_0084'].publish_avatar(avatar)
result = await self['xep_0084'].publish_avatar(avatar)
if isinstance(result, XMPPError):
print('Could not publish XEP-0084 avatar')
else:
used_xep84 = True
print('Update vCard with avatar')
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
if isinstance(result, XMPPError):
print('Could not set vCard avatar')
if used_xep84:
print('Advertise XEP-0084 avatar metadata')
result = yield from self['xep_0084'].publish_avatar_metadata([
result = await self['xep_0084'].publish_avatar_metadata([
{'id': avatar_id,
'type': avatar_type,
'bytes': avatar_bytes}

View File

@@ -29,9 +29,9 @@ CLASSIFIERS = [
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules',
]
@@ -84,7 +84,7 @@ setup(
platforms=['any'],
packages=packages,
ext_modules=ext_modules,
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
classifiers=CLASSIFIERS,
cmdclass={'test': TestCommand}
)

View File

@@ -6,12 +6,13 @@
See the file LICENSE for copying permission.
"""
import asyncio
if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this
asyncio.sslproto._is_sslproto_available=lambda: False
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())
import asyncio
# Required for python < 3.7 to use the old ssl implementation
# and manage to do starttls as an unintended side effect
asyncio.sslproto._is_sslproto_available = lambda: False
from slixmpp.stanza import Message, Presence, Iq
from slixmpp.jid import JID, InvalidJID

View File

@@ -265,8 +265,7 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False
self.features = set()
@asyncio.coroutine
def _handle_stream_features(self, features):
async def _handle_stream_features(self, features):
"""Process the received stream features.
:param features: The features stanza.
@@ -275,7 +274,7 @@ class ClientXMPP(BaseXMPP):
if name in features['features']:
handler, restart = self._stream_feature_handlers[name]
if asyncio.iscoroutinefunction(handler):
result = yield from handler(features)
result = await handler(features)
else:
result = handler(features)
if result and restart:

View File

@@ -35,8 +35,7 @@ class FeatureBind(BasePlugin):
register_stanza_plugin(Iq, stanza.Bind)
register_stanza_plugin(StreamFeatures, stanza.Bind)
@asyncio.coroutine
def _handle_bind_resource(self, features):
async def _handle_bind_resource(self, features):
"""
Handle requesting a specific resource.
@@ -51,7 +50,7 @@ class FeatureBind(BasePlugin):
if self.xmpp.requested_jid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource
yield from iq.send(callback=self._on_bind_response)
await iq.send(callback=self._on_bind_response)
def _on_bind_response(self, response):
self.xmpp.boundjid = JID(response['bind']['jid'])

View File

@@ -97,12 +97,9 @@ class FeatureMechanisms(BasePlugin):
jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid)
elif value == 'channel_binding':
if hasattr(self.xmpp.socket, 'get_channel_binding'):
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
result[value] = self.xmpp.socket.get_channel_binding()
else:
log.debug("Channel binding not supported.")
log.debug("Use Python 3.3+ for channel binding and " + \
"SCRAM-SHA-1-PLUS support")
result[value] = None
elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
@@ -122,7 +119,7 @@ class FeatureMechanisms(BasePlugin):
if value == 'encrypted':
if 'starttls' in self.xmpp.features:
result[value] = True
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
result[value] = True
else:
result[value] = False

View File

@@ -35,18 +35,22 @@ class FeatureSession(BasePlugin):
register_stanza_plugin(Iq, stanza.Session)
register_stanza_plugin(StreamFeatures, stanza.Session)
@asyncio.coroutine
def _handle_start_session(self, features):
async def _handle_start_session(self, features):
"""
Handle the start of the session.
Arguments:
feature -- The stream features element.
"""
if features['session']['optional']:
self.xmpp.sessionstarted = True
self.xmpp.event('session_start')
return
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('session')
yield from iq.send(callback=self._on_start_session_response)
await iq.send(callback=self._on_start_session_response)
def _on_start_session_response(self, response):
self.xmpp.features.add('session')

View File

@@ -6,7 +6,7 @@
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase
from slixmpp.xmlstream import ElementBase, ET
class Session(ElementBase):
@@ -16,5 +16,19 @@ class Session(ElementBase):
name = 'session'
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
interfaces = set()
interfaces = {'optional'}
plugin_attrib = 'session'
def get_optional(self):
return self.xml.find('{%s}optional' % self.namespace) is not None
def set_optional(self, value):
if value:
optional = ET.Element('{%s}optional' % self.namespace)
self.xml.append(optional)
else:
self.del_optional()
def del_optional(self):
optional = self.xml.find('{%s}optional' % self.namespace)
self.xml.remove(optional)

View File

@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.handler import CoroutineCallback
from slixmpp.features.feature_starttls import stanza
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
def plugin_init(self):
self.xmpp.register_handler(
Callback('STARTTLS Proceed',
CoroutineCallback('STARTTLS Proceed',
MatchXPath(stanza.Proceed.tag_name()),
self._handle_starttls_proceed,
instream=True))
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
self.xmpp.send(features['starttls'])
return True
def _handle_starttls_proceed(self, proceed):
async def _handle_starttls_proceed(self, proceed):
"""Restart the XML stream when TLS is accepted."""
log.debug("Starting TLS")
if self.xmpp.start_tls():
if await self.xmpp.start_tls():
self.xmpp.features.add('starttls')

View File

@@ -346,30 +346,10 @@ class JID:
def node(self):
return self._node
@property
def user(self):
return self._node
@property
def local(self):
return self._node
@property
def username(self):
return self._node
@property
def domain(self):
return self._domain
@property
def server(self):
return self._domain
@property
def host(self):
return self._domain
@property
def resource(self):
return self._resource
@@ -382,45 +362,16 @@ class JID:
def full(self):
return self._full
@property
def jid(self):
return self._full
@node.setter
def node(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@user.setter
def user(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@local.setter
def local(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@username.setter
def username(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@domain.setter
def domain(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@server.setter
def server(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@host.setter
def host(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@bare.setter
def bare(self, value):
node, domain, resource = _parse_jid(value)
@@ -439,10 +390,14 @@ class JID:
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
@jid.setter
def jid(self, value):
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
user = node
local = node
username = node
server = domain
host = domain
jid = full
def __str__(self):
"""Use the full JID as the string value."""

View File

@@ -23,7 +23,7 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values'))
sub_interfaces = {'title'}
form_types = {'cancel', 'form', 'result', 'submit'}

View File

@@ -61,7 +61,7 @@ def _intercept(method, name, public):
except InvocationException:
raise
except Exception as e:
raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
raise InvocationException("A problem occurred calling %s.%s!" % (instance.FQN(), method.__name__), e)
_resolver._rpc = public
_resolver._rpc_name = method.__name__ if name is None else name
return _resolver
@@ -405,8 +405,10 @@ class Proxy(Endpoint):
self._callback = callback
def __getattribute__(self, name, *args):
if name in ('__dict__', '_endpoint', 'async', '_callback'):
if name in ('__dict__', '_endpoint', '_callback'):
return object.__getattribute__(self, name)
elif name == 'async':
return lambda callback: Proxy(self._endpoint, callback)
else:
attribute = self._endpoint.__getattribute__(name)
if hasattr(attribute, '__call__'):
@@ -420,9 +422,6 @@ class Proxy(Endpoint):
pass # If the attribute doesn't exist, don't care!
return attribute
def async(self, callback):
return Proxy(self._endpoint, callback)
def get_endpoint(self):
'''
Returns the proxified endpoint.
@@ -696,7 +695,7 @@ class RemoteSession(object):
e = {
'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occurred trying to invoke %s at %s!" % (pmethod, iq['from'])),
}[condition]
if e is None:
RemoteException("An unexpected exception occurred at %s!" % iq['from'])

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from slixmpp import Iq
@@ -123,6 +124,8 @@ class XEP_0030(BasePlugin):
for op in self._disco_ops:
self.api.register(getattr(self.static, op), op, default=True)
self.domain_infos = {}
def session_bind(self, jid):
self.add_feature('http://jabber.org/protocol/disco#info')
@@ -295,6 +298,35 @@ class XEP_0030(BasePlugin):
'cached': cached}
return self.api['has_identity'](jid, node, ifrom, data)
async def get_info_from_domain(self, domain=None, timeout=None,
cached=True, callback=None):
if domain is None:
domain = self.xmpp.boundjid.domain
if not cached or domain not in self.domain_infos:
infos = [self.get_info(
domain, timeout=timeout)]
iq_items = await self.get_items(
domain, timeout=timeout)
items = iq_items['disco_items']['items']
infos += [
self.get_info(item[0], timeout=timeout)
for item in items]
info_futures, _ = await asyncio.wait(
infos,
timeout=timeout,
loop=self.xmpp.loop
)
self.domain_infos[domain] = [
future.result() for future in info_futures if not future.exception()]
results = self.domain_infos[domain]
if callback is not None:
callback(results)
return results
@future_wrapper
def get_info(self, jid=None, node=None, local=None,
cached=None, **kwargs):
@@ -646,9 +678,11 @@ class XEP_0030(BasePlugin):
info['id'] = iq['id']
info.send()
else:
node = iq['disco_info']['node']
iq = iq.reply()
if info:
info = self._fix_default_info(info)
info['node'] = node
iq.set_payload(info.xml)
iq.send()
elif iq['type'] == 'result':

View File

@@ -257,7 +257,7 @@ class StaticDisco(object):
def add_identity(self, jid, node, ifrom, data):
"""
Add a new identity to te JID/node combination.
Add a new identity to the JID/node combination.
The data parameter may provide:
category -- The general category to which the agent belongs.

View File

@@ -31,8 +31,7 @@ class IBBytestream(object):
self.recv_queue = asyncio.Queue()
@asyncio.coroutine
def send(self, data, timeout=None):
async def send(self, data, timeout=None):
if not self.stream_started or self.stream_out_closed:
raise socket.error
if len(data) > self.block_size:
@@ -56,22 +55,20 @@ class IBBytestream(object):
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
yield from iq.send(timeout=timeout)
await iq.send(timeout=timeout)
return len(data)
@asyncio.coroutine
def sendall(self, data, timeout=None):
async def sendall(self, data, timeout=None):
sent_len = 0
while sent_len < len(data):
sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout)
sent_len += await self.send(data[sent_len:self.block_size], timeout=timeout)
@asyncio.coroutine
def sendfile(self, file, timeout=None):
async def sendfile(self, file, timeout=None):
while True:
data = file.read(self.block_size)
if not data:
break
yield from self.send(data, timeout=timeout)
await self.send(data, timeout=timeout)
def _recv_data(self, stanza):
new_seq = stanza['ibb_data']['seq']

View File

@@ -611,7 +611,7 @@ class XEP_0050(BasePlugin):
def terminate_command(self, session):
"""
Delete a command's session after a command has completed
or an error has occured.
or an error has occurred.
Arguments:
session -- All stored data relevant to the current

View File

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

View File

@@ -22,7 +22,7 @@ log = logging.getLogger(__name__)
class ResultIterator:
"""
An iterator for Result Set Managment
An iterator for Result Set Management
"""
def __init__(self, query, interface, results='substanzas', amount=10,

View File

@@ -13,7 +13,7 @@ from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
class Set(ElementBase):
"""
XEP-0059 (Result Set Managment) can be used to manage the
XEP-0059 (Result Set Management) can be used to manage the
results of queries. For example, limiting the number of items
per response or starting at certain positions.

View File

@@ -185,14 +185,14 @@ class XEP_0060(BasePlugin):
if config is not None:
form_type = 'http://jabber.org/protocol/pubsub#node_config'
if 'FORM_TYPE' in config['fields']:
if 'FORM_TYPE' in config.get_fields():
config.field['FORM_TYPE']['value'] = form_type
else:
config.add_field(var='FORM_TYPE',
ftype='hidden',
value=form_type)
if ntype:
if 'pubsub#node_type' in config['fields']:
if 'pubsub#node_type' in config.get_fields():
config.field['pubsub#node_type']['value'] = ntype
else:
config.add_field(var='pubsub#node_type', value=ntype)

View File

@@ -82,9 +82,9 @@ class Item(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
children = list(self.xml)
if len(children) > 0:
return children[0]
def del_payload(self):
for child in self.xml:

View File

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

View File

@@ -55,18 +55,17 @@ class XEP_0065(BasePlugin):
"""Returns the socket associated to the SID."""
return self._sessions.get(sid, None)
@asyncio.coroutine
def handshake(self, to, ifrom=None, sid=None, timeout=None):
async 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 = yield from self.discover_proxies()
self._proxies = await self.discover_proxies()
if sid is None:
sid = uuid4().hex
used = yield from self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
used = await self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid']
if proxy not in self._proxies:
@@ -74,16 +73,16 @@ class XEP_0065(BasePlugin):
return
try:
self._sessions[sid] = (yield from self._connect_proxy(
self._sessions[sid] = (await self._connect_proxy(
self._get_dest_sha1(sid, self.xmpp.boundjid, to),
self._proxies[proxy][0],
self._proxies[proxy][1]))[1]
except socket.error:
return None
addr, port = yield from self._sessions[sid].connected
addr, port = await self._sessions[sid].connected
# Request that the proxy activate the session with the target.
yield from self.activate(proxy, sid, to, timeout=timeout)
await self.activate(proxy, sid, to, timeout=timeout)
sock = self.get_socket(sid)
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
return sock
@@ -105,8 +104,7 @@ class XEP_0065(BasePlugin):
iq['socks'].add_streamhost(proxy, host, port)
return iq.send(timeout=timeout, callback=callback)
@asyncio.coroutine
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
async def discover_proxies(self, jid=None, ifrom=None, timeout=None):
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
if jid is None:
if self.xmpp.is_component:
@@ -116,7 +114,7 @@ class XEP_0065(BasePlugin):
discovered = set()
disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
disco_items = await self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
disco_items = {item[0] for item in disco_items['disco_items']['items']}
disco_info_futures = {}
@@ -125,7 +123,7 @@ class XEP_0065(BasePlugin):
for item in disco_items:
try:
disco_info = yield from disco_info_futures[item]
disco_info = await disco_info_futures[item]
except XMPPError:
continue
else:
@@ -137,7 +135,7 @@ class XEP_0065(BasePlugin):
for jid in discovered:
try:
addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
addr = await self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
self._proxies[jid] = (addr['socks']['streamhost']['host'],
addr['socks']['streamhost']['port'])
except XMPPError:
@@ -182,9 +180,8 @@ class XEP_0065(BasePlugin):
streamhost['host'],
streamhost['port']))
@asyncio.coroutine
def gather(futures, iq, streamhosts):
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
async def gather(futures, iq, streamhosts):
proxies = await asyncio.gather(*futures, return_exceptions=True)
for streamhost, proxy in zip(streamhosts, proxies):
if isinstance(proxy, ValueError):
continue
@@ -194,7 +191,7 @@ class XEP_0065(BasePlugin):
proxy = proxy[1]
# TODO: what if the future never happens?
try:
addr, port = yield from proxy.connected
addr, port = await proxy.connected
except socket.error:
log.exception('Socket error while connecting to the proxy.')
continue
@@ -215,7 +212,7 @@ class XEP_0065(BasePlugin):
self.xmpp.event('socks5_stream', conn)
self.xmpp.event('stream:%s:%s' % (sid, requester), conn)
asyncio.async(gather(proxy_futures, iq, streamhosts))
asyncio.ensure_future(gather(proxy_futures, iq, streamhosts))
def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated."""
@@ -233,7 +230,7 @@ class XEP_0065(BasePlugin):
sock.close()
except socket.error:
pass
# Though this should not be neccessary remove the closed session anyway
# Though this should not be necessary remove the closed session anyway
if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid)

View File

@@ -137,8 +137,8 @@ class Socks5Protocol(asyncio.Protocol):
def resume_writing(self):
self.paused.set_result(None)
def write(self, data):
yield from self.paused
async def write(self, data):
await self.paused
self.transport.write(data)
def _send_methods(self):

View File

@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
def _force_stream_feature(self, stanza):
if isinstance(stanza, StreamFeatures):
if self.xmpp.use_tls or self.xmpp.use_ssl:
if not self.xmpp.disable_starttls:
if 'starttls' not in self.xmpp.features:
return stanza
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):

View File

@@ -65,9 +65,14 @@ class XEP_0092(BasePlugin):
iq -- The Iq stanza containing the software version query.
"""
iq = iq.reply()
iq['software_version']['name'] = self.software_name
iq['software_version']['version'] = self.version
iq['software_version']['os'] = self.os
if self.software_name:
iq['software_version']['name'] = self.software_name
iq['software_version']['version'] = self.version
iq['software_version']['os'] = self.os
else:
iq.error()
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'service-unavailable'
iq.send()
def get_version(self, jid, ifrom=None, timeout=None, callback=None,

View File

@@ -97,7 +97,7 @@ class XEP_0095(BasePlugin):
extension='bad-profile',
extension_ns=SI.namespace)
neg = iq['si']['feature_neg']['form']['fields']
neg = iq['si']['feature_neg']['form'].get_fields()
options = neg['stream-method']['options'] or []
methods = []
for opt in options:

View File

@@ -15,6 +15,7 @@ from slixmpp.stanza import StreamFeatures, Presence, Iq
from slixmpp.xmlstream import register_stanza_plugin, JID
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.util import MemoryCache
from slixmpp import asyncio
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
from slixmpp.plugins import BasePlugin
@@ -27,7 +28,7 @@ log = logging.getLogger(__name__)
class XEP_0115(BasePlugin):
"""
XEP-0115: Entity Capabalities
XEP-0115: Entity Capabilities
"""
name = 'xep_0115'
@@ -37,7 +38,8 @@ class XEP_0115(BasePlugin):
default_config = {
'hash': 'sha-1',
'caps_node': None,
'broadcast': True
'broadcast': True,
'cache': None,
}
def plugin_init(self):
@@ -48,6 +50,9 @@ class XEP_0115(BasePlugin):
if self.caps_node is None:
self.caps_node = 'http://slixmpp.com/ver/%s' % __version__
if self.cache is None:
self.cache = MemoryCache()
register_stanza_plugin(Presence, stanza.Capabilities)
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
@@ -132,8 +137,7 @@ class XEP_0115(BasePlugin):
self.xmpp.event('entity_caps', p)
@asyncio.coroutine
def _process_caps(self, pres):
async def _process_caps(self, pres):
if not pres['caps']['hash']:
log.debug("Received unsupported legacy caps: %s, %s, %s",
pres['caps']['node'],
@@ -164,7 +168,7 @@ class XEP_0115(BasePlugin):
log.debug("New caps verification string: %s", ver)
try:
node = '%s#%s' % (pres['caps']['node'], ver)
caps = yield from self.xmpp['xep_0030'].get_info(pres['from'], node,
caps = await self.xmpp['xep_0030'].get_info(pres['from'], node,
coroutine=True)
if isinstance(caps, Iq):
@@ -199,8 +203,8 @@ class XEP_0115(BasePlugin):
log.debug("Non form extension found, ignoring for caps")
caps.xml.remove(stanza.xml)
continue
if 'FORM_TYPE' in stanza['fields']:
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
if 'FORM_TYPE' in stanza.get_fields():
f_type = tuple(stanza.get_fields()['FORM_TYPE']['value'])
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
@@ -214,7 +218,7 @@ class XEP_0115(BasePlugin):
log.debug("Extra FORM_TYPE data, invalid for caps")
return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
if stanza.get_fields()['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
@@ -253,7 +257,7 @@ class XEP_0115(BasePlugin):
for stanza in info['substanzas']:
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
if 'FORM_TYPE' in stanza['fields']:
if 'FORM_TYPE' in stanza.get_fields():
f_type = stanza['values']['FORM_TYPE']
if len(f_type):
f_type = f_type[0]
@@ -265,11 +269,11 @@ class XEP_0115(BasePlugin):
for f_type in sorted_forms:
for form in form_types[f_type]:
S += '%s<' % f_type
fields = sorted(form['fields'].keys())
fields = sorted(form.get_fields().keys())
fields.remove('FORM_TYPE')
for field in fields:
S += '%s<' % field
vals = form['fields'][field].get_value(convert=False)
vals = form.get_fields()[field].get_value(convert=False)
if vals is None:
S += '<'
else:
@@ -280,10 +284,9 @@ class XEP_0115(BasePlugin):
binary = hash(S.encode('utf8')).digest()
return base64.b64encode(binary).decode('utf-8')
@asyncio.coroutine
def update_caps(self, jid=None, node=None, preserve=False):
async def update_caps(self, jid=None, node=None, preserve=False):
try:
info = yield from self.xmpp['xep_0030'].get_info(jid, node, local=True)
info = await self.xmpp['xep_0030'].get_info(jid, node, local=True)
if isinstance(info, Iq):
info = info['disco_info']
ver = self.generate_verstring(info, self.hash)

View File

@@ -33,7 +33,6 @@ class StaticCaps(object):
self.disco = self.xmpp['xep_0030']
self.caps = self.xmpp['xep_0115']
self.static = static
self.ver_cache = {}
self.jid_vers = {}
def supports(self, jid, node, ifrom, data):
@@ -128,7 +127,7 @@ class StaticCaps(object):
info = data.get('info', None)
if not verstring or not info:
return
self.ver_cache[verstring] = info
self.caps.cache.store(verstring, info)
def assign_verstring(self, jid, node, ifrom, data):
if isinstance(jid, JID):
@@ -139,4 +138,7 @@ class StaticCaps(object):
return self.jid_vers.get(jid, None)
def get_caps(self, jid, node, ifrom, data):
return self.ver_cache.get(data.get('verstring', None), None)
verstring = data.get('verstring', None)
if verstring is None:
return None
return self.caps.cache.retrieve(verstring)

View File

@@ -98,10 +98,9 @@ class XEP_0153(BasePlugin):
first_future.add_done_callback(propagate_timeout_exception)
return future
@asyncio.coroutine
def _start(self, event):
async def _start(self, event):
try:
vcard = yield from self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
vcard = await self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
data = vcard['vcard_temp']['PHOTO']['BINVAL']
if not data:
new_hash = ''
@@ -138,7 +137,11 @@ class XEP_0153(BasePlugin):
if iq['type'] == 'error':
log.debug('Could not retrieve vCard for %s', jid)
return
data = iq['vcard_temp']['PHOTO']['BINVAL']
try:
data = iq['vcard_temp']['PHOTO']['BINVAL']
except ValueError:
log.debug('Invalid BINVAL in vCards PHOTO for %s:', jid, exc_info=True)
data = None
if not data:
new_hash = ''
else:
@@ -164,10 +167,7 @@ class XEP_0153(BasePlugin):
data = pres['vcard_temp_update']['photo']
if data is None:
return
elif data == '' or data != self.api['get_hash'](pres['from']):
ifrom = pres['to'] if self.xmpp.is_component else None
self.api['reset_hash'](pres['from'], ifrom=ifrom)
self.xmpp.event('vcard_avatar_update', pres)
self.xmpp.event('vcard_avatar_update', pres)
# =================================================================

View File

@@ -62,7 +62,10 @@ class XEP_0163(BasePlugin):
for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
jid=jid)
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
asyncio.ensure_future(
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def remove_interest(self, namespace, jid=None):
"""
@@ -81,7 +84,10 @@ class XEP_0163(BasePlugin):
for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid,
feature='%s+notify' % namespace)
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
asyncio.ensure_future(
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
timeout_callback=None, callback=None, timeout=None):

View File

@@ -174,8 +174,7 @@ class XEP_0198(BasePlugin):
req = stanza.RequestAck(self.xmpp)
self.xmpp.send_raw(str(req))
@asyncio.coroutine
def _handle_sm_feature(self, features):
async def _handle_sm_feature(self, features):
"""
Enable or resume stream management.
@@ -203,7 +202,7 @@ class XEP_0198(BasePlugin):
MatchXPath(stanza.Enabled.tag_name()),
MatchXPath(stanza.Failed.tag_name())]))
self.xmpp.register_handler(waiter)
result = yield from waiter.wait()
result = await waiter.wait()
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
self.enabled = True
resume = stanza.Resume(self.xmpp)
@@ -219,7 +218,7 @@ class XEP_0198(BasePlugin):
MatchXPath(stanza.Resumed.tag_name()),
MatchXPath(stanza.Failed.tag_name())]))
self.xmpp.register_handler(waiter)
result = yield from waiter.wait()
result = await waiter.wait()
if result is not None and result.name == 'resumed':
return True
return False

View File

@@ -95,7 +95,10 @@ class XEP_0199(BasePlugin):
self.timeout = timeout
self.keepalive = True
handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
handler = lambda event=None: asyncio.ensure_future(
self._keepalive(event),
loop=self.xmpp.loop,
)
self.xmpp.schedule('Ping keepalive',
self.interval,
handler,
@@ -104,13 +107,12 @@ class XEP_0199(BasePlugin):
def disable_keepalive(self, event=None):
self.xmpp.cancel_schedule('Ping keepalive')
@asyncio.coroutine
def _keepalive(self, event=None):
async def _keepalive(self, event=None):
log.debug("Keepalive ping...")
try:
rtt = yield from self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
except IqTimeout:
log.debug("Did not recieve ping back in time." + \
log.debug("Did not receive ping back in time." + \
"Requesting Reconnect.")
self.xmpp.reconnect()
else:
@@ -145,8 +147,7 @@ class XEP_0199(BasePlugin):
return iq.send(timeout=timeout, callback=callback,
timeout_callback=timeout_callback)
@asyncio.coroutine
def ping(self, jid=None, ifrom=None, timeout=None):
async def ping(self, jid=None, ifrom=None, timeout=None):
"""Send a ping request and calculate RTT.
This is a coroutine.
@@ -174,7 +175,7 @@ class XEP_0199(BasePlugin):
log.debug('Pinging %s' % jid)
try:
yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout)
await self.send_ping(jid, ifrom=ifrom, timeout=timeout)
except IqError as e:
if own_host:
rtt = time.time() - start

View File

@@ -73,11 +73,11 @@ class XEP_0222(BasePlugin):
ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options')
fields = options['fields']
fields = options.get_fields()
for field, value in self.profile.items():
if field not in fields:
options.add_field(var=field)
options['fields'][field]['value'] = value
options.get_fields()[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node,
options=options,

View File

@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
'pubsub#access_model': 'whitelist'}
def configure(self, node, ifrom=None, callback=None, timeout=None):
"""
@@ -78,7 +78,7 @@ class XEP_0223(BasePlugin):
for field, value in self.profile.items():
if field not in fields:
options.add_field(var=field)
options['fields'][field]['value'] = value
options.get_fields()[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node, options=options,
ifrom=ifrom, callback=callback,

View File

@@ -25,7 +25,7 @@ class XEP_0300(BasePlugin):
stanza = stanza
default_config = {
'block_size': 1024 * 1024, # One MiB
'prefered': 'sha-256',
'preferded': 'sha-256',
'enable_sha-1': False,
'enable_sha-256': True,
'enable_sha-512': True,
@@ -73,7 +73,7 @@ class XEP_0300(BasePlugin):
def compute_hash(self, filename, function=None):
if function is None:
function = self.prefered
function = self.preferred
h = self._hashlib_function[function]()
with open(filename, 'rb') as f:
while True:

View File

@@ -291,7 +291,7 @@ class XEP_0323(BasePlugin):
request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
if request_delay_sec <= 0:
req_ok = False
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past (%s). Current time: %s" % (dt.isoformat(), dtnow.isoformat())
if req_ok:
session = self._new_session()

View File

@@ -399,7 +399,7 @@ class XEP_0325(BasePlugin):
"""
if not session in self.sessions:
# This can happend if a session was deleted, like in a timeout. Just drop the data.
# This can happen if a session was deleted, like in a timeout. Just drop the data.
return
if result == "error":
@@ -457,7 +457,7 @@ class XEP_0325(BasePlugin):
Arguments:
from_jid -- The jid of the requester
to_jid -- The jid of the device(s)
callback -- The callback function to call when data is availble.
callback -- The callback function to call when data is available.
The callback function must support the following arguments:

View File

@@ -0,0 +1,14 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0335.stanza import JSON_Container
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
register_plugin(XEP_0335)

View File

@@ -0,0 +1,22 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp import Message
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.xep_0335 import JSON_Container
class XEP_0335(BasePlugin):
name = 'xep_0335'
description = 'XEP-0335: JSON Containers'
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, JSON_Container)

View File

@@ -0,0 +1,28 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Maxime “pep” Buquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import json
from slixmpp.xmlstream import ElementBase
class JSON_Container(ElementBase):
name = 'json'
plugin_attrib = 'json'
namespace = 'urn:xmpp:json:0'
interfaces = {'value'}
def get_value(self):
return json.loads(self.xml.text)
def set_value(self, value):
if not isinstance(value, str):
value = json.dumps(value)
self.xml.text = value
def del_value(self):
self.xml.text = ''

View File

@@ -0,0 +1,14 @@
"""
slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header
from slixmpp.plugins.xep_0363.http_upload import XEP_0363
register_plugin(XEP_0363)

View File

@@ -0,0 +1,159 @@
"""
slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
import logging
import os.path
from aiohttp import ClientSession
from mimetypes import guess_type
from slixmpp import Iq, __version__
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.plugins.xep_0363 import stanza, Request, Slot, Put, Get, Header
log = logging.getLogger(__name__)
class FileUploadError(Exception):
pass
class UploadServiceNotFound(FileUploadError):
pass
class FileTooBig(FileUploadError):
pass
class HTTPError(FileUploadError):
def __str__(self):
return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
class XEP_0363(BasePlugin):
''' This plugin only supports Python 3.5+ '''
name = 'xep_0363'
description = 'XEP-0363: HTTP File Upload'
dependencies = {'xep_0030', 'xep_0128'}
stanza = stanza
default_config = {
'upload_service': None,
'max_file_size': float('+inf'),
'default_content_type': 'application/octet-stream',
}
def plugin_init(self):
register_stanza_plugin(Iq, Request)
register_stanza_plugin(Iq, Slot)
register_stanza_plugin(Slot, Put)
register_stanza_plugin(Slot, Get)
register_stanza_plugin(Put, Header, iterable=True)
self.xmpp.register_handler(
Callback('HTTP Upload Request',
StanzaPath('iq@type=get/http_upload_request'),
self._handle_request))
def plugin_end(self):
self._http_session.close()
self.xmpp.remove_handler('HTTP Upload Request')
self.xmpp.remove_handler('HTTP Upload Slot')
self.xmpp['xep_0030'].del_feature(feature=Request.namespace)
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(Request.namespace)
def _handle_request(self, iq):
self.xmpp.event('http_upload_request', iq)
async def find_upload_service(self, domain=None, timeout=None):
results = await self.xmpp['xep_0030'].get_info_from_domain(
domain=domain, timeout=timeout)
candidates = []
for info in results:
for identity in info['disco_info']['identities']:
if identity[0] == 'store' and identity[1] == 'file':
candidates.append(info)
for info in candidates:
for feature in info['disco_info']['features']:
if feature == Request.namespace:
return info
def request_slot(self, jid, filename, size, content_type=None, ifrom=None,
timeout=None, callback=None, timeout_callback=None):
iq = self.xmpp.Iq()
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'get'
request = iq['http_upload_request']
request['filename'] = filename
request['size'] = str(size)
request['content-type'] = content_type or self.default_content_type
return iq.send(timeout=timeout, callback=callback,
timeout_callback=timeout_callback)
async def upload_file(self, filename, size=None, content_type=None, *,
input_file=None, ifrom=None, domain=None, timeout=None,
callback=None, timeout_callback=None):
''' Helper function which does all of the uploading process. '''
if self.upload_service is None:
info_iq = await self.find_upload_service(
domain=domain, timeout=timeout)
if info_iq is None:
raise UploadServiceNotFound()
self.upload_service = info_iq['from']
for form in info_iq['disco_info'].iterables:
values = form['values']
if values['FORM_TYPE'] == ['urn:xmpp:http:upload:0']:
try:
self.max_file_size = int(values['max-file-size'])
except (TypeError, ValueError):
log.error('Invalid max size received from HTTP File Upload service')
self.max_file_size = float('+inf')
break
if input_file is None:
input_file = open(filename, 'rb')
if size is None:
size = input_file.seek(0, 2)
input_file.seek(0)
if size > self.max_file_size:
raise FileTooBig()
if content_type is None:
content_type = guess_type(filename)[0]
if content_type is None:
content_type = self.default_content_type
basename = os.path.basename(filename)
slot_iq = await self.request_slot(self.upload_service, basename, size,
content_type, ifrom, timeout,
timeout_callback=timeout_callback)
slot = slot_iq['http_upload_slot']
headers = {
'Content-Length': str(size),
'Content-Type': content_type or self.default_content_type,
**{header['name']: header['value'] for header in slot['put']['headers']}
}
# Do the actual upload here.
async with ClientSession(headers={'User-Agent': 'slixmpp ' + __version__}) as session:
response = await session.put(
slot['put']['url'],
data=input_file,
headers=headers,
timeout=timeout)
if response.status >= 400:
raise HTTPError(response.status, await response.text())
log.info('Response code: %d (%s)', response.status, await response.text())
response.close()
return slot['get']['url']

View File

@@ -0,0 +1,48 @@
"""
slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase
class Request(ElementBase):
plugin_attrib = 'http_upload_request'
name = 'request'
namespace = 'urn:xmpp:http:upload:0'
interfaces = {'filename', 'size', 'content-type'}
class Slot(ElementBase):
plugin_attrib = 'http_upload_slot'
name = 'slot'
namespace = 'urn:xmpp:http:upload:0'
class Put(ElementBase):
plugin_attrib = 'put'
name = 'put'
namespace = 'urn:xmpp:http:upload:0'
interfaces = {'url'}
class Get(ElementBase):
plugin_attrib = 'get'
name = 'get'
namespace = 'urn:xmpp:http:upload:0'
interfaces = {'url'}
class Header(ElementBase):
plugin_attrib = 'header'
name = 'header'
namespace = 'urn:xmpp:http:upload:0'
plugin_multi_attrib = 'headers'
interfaces = {'name', 'value'}
def get_value(self):
return self.xml.text
def set_value(self, value):
self.xml.text = value
def del_value(self):
self.xml.text = ''

View File

@@ -187,11 +187,24 @@ class Iq(RootStanza):
future = asyncio.Future()
# Prevents handlers from existing forever.
if timeout is None:
timeout = 120
def callback_success(result):
if result['type'] == 'error':
type_ = result['type']
if type_ == 'result':
future.set_result(result)
elif type_ == 'error':
future.set_exception(IqError(result))
else:
future.set_result(result)
# Most likely an iq addressed to ourself, rearm the callback.
handler = constr(handler_name,
matcher,
callback_success,
once=True)
self.stream.register_handler(handler)
return
if timeout is not None:
self.stream.cancel_schedule('IqTimeout_%s' % self['id'])

View File

@@ -40,7 +40,7 @@ class Roster(ElementBase):
def get_ver(self):
"""
Ensure handling an empty ver attribute propery.
Ensure handling an empty ver attribute property.
The ver attribute is special in that the presence of the
attribute with an empty value is important for boostrapping
@@ -50,7 +50,7 @@ class Roster(ElementBase):
def set_ver(self, ver):
"""
Ensure handling an empty ver attribute propery.
Ensure handling an empty ver attribute property.
The ver attribute is special in that the presence of the
attribute with an empty value is important for boostrapping

View File

@@ -114,7 +114,7 @@ def punycode(domain):
if char in ILLEGAL_CHARS:
raise StringprepError
domain_parts.append(label)
domain_parts.append(label.encode('ascii'))
return b'.'.join(domain_parts)
logging.getLogger(__name__).warning('Using slower stringprep, consider '

View File

@@ -222,7 +222,7 @@ class SlixTest(unittest.TestCase):
if Matcher is None:
raise ValueError("Unknown matching method.")
test = Matcher(criteria)
self.failUnless(test.match(stanza),
self.assertTrue(test.match(stanza),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % str(criteria) + \
"Stanza:\n%s" % str(stanza))
@@ -280,7 +280,7 @@ class SlixTest(unittest.TestCase):
debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
result = self.compare(xml, stanza.xml, stanza2.xml)
self.failUnless(result, debug)
self.assertTrue(result, debug)
# ------------------------------------------------------------------
# Methods for simulating stanza streams.
@@ -487,7 +487,7 @@ class SlixTest(unittest.TestCase):
recv_xml.clear()
recv_xml.attrib = attrib
self.failUnless(
self.assertTrue(
self.compare(xml, recv_xml),
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
'%s %s' % (xml.tag, xml.attrib),
@@ -543,7 +543,7 @@ class SlixTest(unittest.TestCase):
xml = self.parse_xml(header2)
sent_xml = self.parse_xml(sent_header2)
self.failUnless(
self.assertTrue(
self.compare(xml, sent_xml),
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
header, sent_header))
@@ -557,12 +557,12 @@ class SlixTest(unittest.TestCase):
if sent_data is None:
self.fail("No stanza was sent.")
if method == 'exact':
self.failUnless(self.compare(xml, sent_xml),
self.assertTrue(self.compare(xml, sent_xml),
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
highlight(tostring(xml)), highlight(tostring(sent_xml))))
elif method == 'mask':
matcher = MatchXMLMask(xml)
self.failUnless(matcher.match(sent_xml),
self.assertTrue(matcher.match(sent_xml),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % highlight(tostring(xml)) + \
"Stanza:\n%s" % highlight(tostring(sent_xml)))

View File

@@ -13,3 +13,5 @@
from slixmpp.util.misc_ops import bytes, unicode, hashes, hash, \
num_to_bytes, bytes_to_num, quote, \
XOR
from slixmpp.util.cache import MemoryCache, MemoryPerJidCache, \
FileSystemCache, FileSystemPerJidCache

105
slixmpp/util/cache.py Normal file
View File

@@ -0,0 +1,105 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import os
import logging
log = logging.getLogger(__name__)
class Cache:
def retrieve(self, key):
raise NotImplementedError
def store(self, key, value):
raise NotImplementedError
class PerJidCache:
def retrieve_by_jid(self, jid, key):
raise NotImplementedError
def store_by_jid(self, jid, key, value):
raise NotImplementedError
class MemoryCache(Cache):
def __init__(self):
self.cache = {}
def retrieve(self, key):
return self.cache.get(key, None)
def store(self, key, value):
self.cache[key] = value
return True
class MemoryPerJidCache(PerJidCache):
def __init__(self):
self.cache = {}
def retrieve_by_jid(self, jid, key):
cache = self.cache.get(jid, None)
if cache is None:
return None
return cache.get(key, None)
def store_by_jid(self, jid, key, value):
cache = self.cache.setdefault(jid, {})
cache[key] = value
return True
class FileSystemStorage:
def __init__(self, encode, decode, binary):
self.encode = encode if encode is not None else lambda x: x
self.decode = decode if decode is not None else lambda x: x
self.read = 'rb' if binary else 'r'
self.write = 'wb' if binary else 'w'
def _retrieve(self, directory, key):
filename = os.path.join(directory, key.replace('/', '_'))
try:
with open(filename, self.read) as cache_file:
return self.decode(cache_file.read())
except FileNotFoundError:
log.debug('%s not present in cache', key)
except OSError:
log.debug('Failed to read %s from cache:', key, exc_info=True)
return None
def _store(self, directory, key, value):
filename = os.path.join(directory, key.replace('/', '_'))
try:
os.makedirs(directory, exist_ok=True)
with open(filename, self.write) as output:
output.write(self.encode(value))
return True
except OSError:
log.debug('Failed to store %s to cache:', key, exc_info=True)
return False
class FileSystemCache(Cache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
FileSystemStorage.__init__(self, encode, decode, binary)
self.base_dir = os.path.join(directory, cache_type)
def retrieve(self, key):
return self._retrieve(self.base_dir, key)
def store(self, key, value):
return self._store(self.base_dir, key, value)
class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
FileSystemStorage.__init__(self, encode, decode, binary)
self.base_dir = os.path.join(directory, cache_type)
def retrieve_by_jid(self, jid, key):
directory = os.path.join(self.base_dir, jid)
return self._retrieve(directory, key)
def store_by_jid(self, jid, key, value):
directory = os.path.join(self.base_dir, jid)
return self._store(directory, key, value)

View File

@@ -516,13 +516,13 @@ else:
def setup(self, name):
authzid = self.credentials['authzid']
if not authzid:
authzid = 'xmpp@%s' % self.credentials['service-name']
authzid = 'xmpp@' + self.credentials['service-name'].decode()
_, self.gss = kerberos.authGSSClientInit(authzid)
self.step = 0
def process(self, challenge=b''):
b64_challenge = b64encode(challenge)
b64_challenge = b64encode(challenge).decode('ascii')
try:
if self.step == 0:
result = kerberos.authGSSClientStep(self.gss, b64_challenge)
@@ -536,7 +536,7 @@ else:
kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
resp = kerberos.authGSSClientResponse(self.gss)
kerberos.authGSSClientWrap(self.gss, resp, username)
kerberos.authGSSClientWrap(self.gss, resp, username.decode())
resp = kerberos.authGSSClientResponse(self.gss)
except kerberos.GSSError as e:

View File

@@ -9,5 +9,5 @@
# We don't want to have to import the entire library
# just to get the version info for setup.py
__version__ = '1.3.0'
__version_info__ = (1, 3, 0)
__version__ = '1.4.2'
__version_info__ = (1, 4, 2)

View File

@@ -45,10 +45,9 @@ class CoroutineCallback(BaseHandler):
if not asyncio.iscoroutinefunction(pointer):
raise ValueError("Given function is not a coroutine")
@asyncio.coroutine
def pointer_wrapper(stanza, *args, **kwargs):
async def pointer_wrapper(stanza, *args, **kwargs):
try:
yield from pointer(stanza, *args, **kwargs)
await pointer(stanza, *args, **kwargs)
except Exception as e:
stanza.exception(e)
@@ -78,7 +77,7 @@ class CoroutineCallback(BaseHandler):
:meth:`prerun()`. Defaults to ``False``.
"""
if not self._instream or instream:
asyncio.async(self._pointer(payload))
asyncio.ensure_future(self._pointer(payload))
if self._once:
self._destroy = True
del self._pointer

View File

@@ -50,8 +50,7 @@ class Waiter(BaseHandler):
"""Do not process this handler during the main event loop."""
pass
@asyncio.coroutine
def wait(self, timeout=None):
async def wait(self, timeout=None):
"""Block an event handler while waiting for a stanza to arrive.
Be aware that this will impact performance if called from a
@@ -70,7 +69,7 @@ class Waiter(BaseHandler):
stanza = None
try:
stanza = yield from self._payload.get()
stanza = await self._payload.get()
except TimeoutError:
log.warning("Timed out waiting for %s", self.name)
self.stream().remove_handler(self.name)

View File

@@ -75,7 +75,7 @@ class MatchXMLMask(MatcherBase):
Defaults to ``"__no_ns__"``.
"""
if source is None:
# If the element was not found. May happend during recursive calls.
# If the element was not found. May happen during recursive calls.
return False
# Convert the mask to an XML object if it is a string.

View File

@@ -45,8 +45,7 @@ def default_resolver(loop):
return None
@asyncio.coroutine
def resolve(host, port=None, service=None, proto='tcp',
async def resolve(host, port=None, service=None, proto='tcp',
resolver=None, use_ipv6=True, use_aiodns=True, loop=None):
"""Peform DNS resolution for a given hostname.
@@ -127,7 +126,7 @@ def resolve(host, port=None, service=None, proto='tcp',
if not service:
hosts = [(host, port)]
else:
hosts = yield from get_SRV(host, port, service, proto,
hosts = await get_SRV(host, port, service, proto,
resolver=resolver,
use_aiodns=use_aiodns)
if not hosts:
@@ -141,20 +140,19 @@ def resolve(host, port=None, service=None, proto='tcp',
results.append((host, '127.0.0.1', port))
if use_ipv6:
aaaa = yield from get_AAAA(host, resolver=resolver,
aaaa = await get_AAAA(host, resolver=resolver,
use_aiodns=use_aiodns, loop=loop)
for address in aaaa:
results.append((host, address, port))
a = yield from get_A(host, resolver=resolver,
a = await get_A(host, resolver=resolver,
use_aiodns=use_aiodns, loop=loop)
for address in a:
results.append((host, address, port))
return results
@asyncio.coroutine
def get_A(host, resolver=None, use_aiodns=True, loop=None):
async def get_A(host, resolver=None, use_aiodns=True, loop=None):
"""Lookup DNS A records for a given host.
If ``resolver`` is not provided, or is ``None``, then resolution will
@@ -178,7 +176,7 @@ def get_A(host, resolver=None, use_aiodns=True, loop=None):
# getaddrinfo() method.
if resolver is None or not use_aiodns:
try:
recs = yield from loop.getaddrinfo(host, None,
recs = await loop.getaddrinfo(host, None,
family=socket.AF_INET,
type=socket.SOCK_STREAM)
return [rec[4][0] for rec in recs]
@@ -189,15 +187,14 @@ def get_A(host, resolver=None, use_aiodns=True, loop=None):
# Using aiodns:
future = resolver.query(host, 'A')
try:
recs = yield from future
recs = await future
except Exception as e:
log.debug('DNS: Exception while querying for %s A records: %s', host, e)
recs = []
return [rec.host for rec in recs]
@asyncio.coroutine
def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
async def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
"""Lookup DNS AAAA records for a given host.
If ``resolver`` is not provided, or is ``None``, then resolution will
@@ -224,26 +221,25 @@ def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
log.debug("DNS: Unable to query %s for AAAA records: IPv6 is not supported", host)
return []
try:
recs = yield from loop.getaddrinfo(host, None,
recs = await loop.getaddrinfo(host, None,
family=socket.AF_INET6,
type=socket.SOCK_STREAM)
return [rec[4][0] for rec in recs]
except (OSError, socket.gaierror):
log.debug("DNS: Error retreiving AAAA address " + \
log.debug("DNS: Error retrieving AAAA address " + \
"info for %s." % host)
return []
# Using aiodns:
future = resolver.query(host, 'AAAA')
try:
recs = yield from future
recs = await future
except Exception as e:
log.debug('DNS: Exception while querying for %s AAAA records: %s', host, e)
recs = []
return [rec.host for rec in recs]
@asyncio.coroutine
def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
async def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
"""Perform SRV record resolution for a given host.
.. note::
@@ -277,7 +273,7 @@ def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
try:
future = resolver.query('_%s._%s.%s' % (service, proto, host),
'SRV')
recs = yield from future
recs = await future
except Exception as e:
log.debug('DNS: Exception while querying for %s SRV records: %s', host, e)
return []

View File

@@ -177,8 +177,9 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
if '}' in ns_block:
# Apply the found namespace to following elements
# that do not have namespaces.
namespace = ns_block.split('}')[0]
elements = ns_block.split('}')[1].split('/')
ns_block_split = ns_block.split('}')
namespace = ns_block_split[0]
elements = ns_block_split[1].split('/')
else:
# Apply the stanza's namespace to the following
# elements since no namespace was provided.
@@ -1291,15 +1292,6 @@ class ElementBase(object):
def __bool__(self):
"""Stanza objects should be treated as True in boolean contexts.
Python 3.x version.
"""
return True
def __nonzero__(self):
"""Stanza objects should be treated as True in boolean contexts.
Python 2.x version.
"""
return True

View File

@@ -45,11 +45,12 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
tag_split = xml.tag.split('}', 1)
tag_name = tag_split[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
tag_xmlns = tag_split[0][1:]
else:
tag_xmlns = ''
@@ -82,8 +83,9 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
if '}' not in attrib:
output.append(' %s="%s"' % (attrib, value))
else:
attrib_ns = attrib.split('}')[0][1:]
attrib = attrib.split('}')[1]
attrib_split = attrib.split('}')
attrib_ns = attrib_split[0][1:]
attrib = attrib_split[1]
if attrib_ns == XML_NS:
output.append(' xml:%s="%s"' % (attrib, value))
elif stream and attrib_ns in stream.namespace_map:
@@ -144,10 +146,7 @@ def escape(text, use_cdata=False):
'"': '&quot;'}
if not use_cdata:
text = list(text)
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return ''.join(text)
return ''.join(escapes.get(c, c) for c in text)
else:
escape_needed = False
for c in text:

View File

@@ -64,12 +64,12 @@ class XMLStream(asyncio.BaseProtocol):
:param int port: The port to use for the connection. Defaults to 0.
"""
def __init__(self, socket=None, host='', port=0):
def __init__(self, host='', port=0):
# The asyncio.Transport object provided by the connection_made()
# callback when we are connected
self.transport = None
# The socket the is used internally by the transport object
# The socket that is used internally by the transport object
self.socket = None
self.connect_loop_wait = 0
@@ -285,13 +285,15 @@ class XMLStream(asyncio.BaseProtocol):
self.disable_starttls = disable_starttls
self.event("connecting")
self._current_connection_attempt = asyncio.async(self._connect_routine())
self._current_connection_attempt = asyncio.ensure_future(
self._connect_routine(),
loop=self.loop,
)
@asyncio.coroutine
def _connect_routine(self):
async def _connect_routine(self):
self.event_when_connected = "connected"
record = yield from self.pick_dns_answer(self.default_domain)
record = await self.pick_dns_answer(self.default_domain)
if record is not None:
host, address, dns_port = record
port = dns_port if dns_port else self.address[1]
@@ -307,9 +309,9 @@ class XMLStream(asyncio.BaseProtocol):
else:
ssl_context = None
yield from asyncio.sleep(self.connect_loop_wait)
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
try:
yield from self.loop.create_connection(lambda: self,
await self.loop.create_connection(lambda: self,
self.address[0],
self.address[1],
ssl=ssl_context,
@@ -322,7 +324,10 @@ class XMLStream(asyncio.BaseProtocol):
log.debug('Connection failed: %s', e)
self.event("connection_failed", e)
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
self._current_connection_attempt = asyncio.async(self._connect_routine())
self._current_connection_attempt = asyncio.ensure_future(
self._connect_routine(),
loop=self.loop,
)
def process(self, *, forever=True, timeout=None):
"""Process all the available XMPP events (receiving or sending data on the
@@ -337,10 +342,10 @@ class XMLStream(asyncio.BaseProtocol):
else:
self.loop.run_until_complete(self.disconnected)
else:
tasks = [asyncio.sleep(timeout)]
tasks = [asyncio.sleep(timeout, loop=self.loop)]
if not forever:
tasks.append(self.disconnected)
self.loop.run_until_complete(asyncio.wait(tasks))
self.loop.run_until_complete(asyncio.wait(tasks, loop=self.loop))
def init_parser(self):
"""init the XML parser. The parser must always be reset for each new
@@ -355,7 +360,10 @@ class XMLStream(asyncio.BaseProtocol):
"""
self.event(self.event_when_connected)
self.transport = transport
self.socket = self.transport.get_extra_info("socket")
self.socket = self.transport.get_extra_info(
"ssl_object",
default=self.transport.get_extra_info("socket")
)
self.init_parser()
self.send_raw(self.stream_header)
self.dns_answers = None
@@ -436,7 +444,7 @@ class XMLStream(asyncio.BaseProtocol):
def cancel_connection_attempt(self):
"""
Immediatly cancel the current create_connection() Future.
Immediately cancel the current create_connection() Future.
This is useful when a client using slixmpp tries to connect
on flaky networks, where sometimes a connection just gets lost
and it needs to reconnect while the attempt is still ongoing.
@@ -449,7 +457,7 @@ class XMLStream(asyncio.BaseProtocol):
"""Close the XML stream and wait for an acknowldgement from the server for
at most `wait` seconds. After the given number of seconds has
passed without a response from the serveur, or when the server
successfuly 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()
directly.
@@ -528,37 +536,42 @@ class XMLStream(asyncio.BaseProtocol):
return self.ssl_context
def start_tls(self):
async def start_tls(self):
"""Perform handshakes for TLS.
If the handshake is successful, the XML stream will need
to be restarted.
"""
self.event_when_connected = "tls_success"
ssl_context = self.get_ssl_context()
ssl_connect_routine = self.loop.create_connection(lambda: self, ssl=ssl_context,
sock=self.socket,
server_hostname=self.default_domain)
@asyncio.coroutine
def ssl_coro():
try:
transp, prot = yield from ssl_connect_routine
except ssl.SSLError as e:
log.debug('SSL: Unable to connect', exc_info=True)
log.error('CERT: Invalid certificate trust chain.')
if not self.event_handled('ssl_invalid_chain'):
self.disconnect()
else:
self.event('ssl_invalid_chain', e)
try:
if hasattr(self.loop, 'start_tls'):
transp = await self.loop.start_tls(self.transport,
self, ssl_context)
# Python < 3.7
else:
# Workaround for a regression in 3.4 where ssl_object was not set.
der_cert = transp.get_extra_info("ssl_object",
default=transp.get_extra_info("socket")).getpeercert(True)
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
self.event('ssl_cert', pem_cert)
asyncio.async(ssl_coro())
transp, _ = await self.loop.create_connection(
lambda: self,
ssl=self.ssl_context,
sock=self.socket,
server_hostname=self.default_domain
)
except ssl.SSLError as e:
log.debug('SSL: Unable to connect', exc_info=True)
log.error('CERT: Invalid certificate trust chain.')
if not self.event_handled('ssl_invalid_chain'):
self.disconnect()
else:
self.event('ssl_invalid_chain', e)
return False
der_cert = transp.get_extra_info("ssl_object").getpeercert(True)
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
self.event('ssl_cert', pem_cert)
# If we use the builtin start_tls, the connection_made() protocol
# method is not called automatically
if hasattr(self.loop, 'start_tls'):
self.connection_made(transp)
return True
def _start_keepalive(self, event):
"""Begin sending whitespace periodically to keep the connection alive.
@@ -671,8 +684,7 @@ class XMLStream(asyncio.BaseProtocol):
idx += 1
return False
@asyncio.coroutine
def get_dns_records(self, domain, port=None):
async def get_dns_records(self, domain, port=None):
"""Get the DNS records for a domain.
:param domain: The domain in question.
@@ -684,7 +696,7 @@ class XMLStream(asyncio.BaseProtocol):
resolver = default_resolver(loop=self.loop)
self.configure_dns(resolver, domain=domain, port=port)
result = yield from resolve(domain, port,
result = await resolve(domain, port,
service=self.dns_service,
resolver=resolver,
use_ipv6=self.use_ipv6,
@@ -692,8 +704,7 @@ class XMLStream(asyncio.BaseProtocol):
loop=self.loop)
return result
@asyncio.coroutine
def pick_dns_answer(self, domain, port=None):
async def pick_dns_answer(self, domain, port=None):
"""Pick a server and port from DNS answers.
Gets DNS answers if none available.
@@ -703,7 +714,7 @@ class XMLStream(asyncio.BaseProtocol):
:param port: If the results don't include a port, use this one.
"""
if self.dns_answers is None:
dns_records = yield from self.get_dns_records(domain, port)
dns_records = await self.get_dns_records(domain, port)
self.dns_answers = iter(dns_records)
try:
@@ -768,16 +779,18 @@ class XMLStream(asyncio.BaseProtocol):
# If the callback is a coroutine, schedule it instead of
# running it directly
if asyncio.iscoroutinefunction(handler_callback):
@asyncio.coroutine
def handler_callback_routine(cb):
async def handler_callback_routine(cb):
try:
yield from cb(data)
await cb(data)
except Exception as e:
if old_exception:
old_exception(e)
else:
self.exception(e)
asyncio.async(handler_callback_routine(handler_callback))
asyncio.ensure_future(
handler_callback_routine(handler_callback),
loop=self.loop,
)
else:
try:
handler_callback(data)
@@ -991,4 +1004,3 @@ class XMLStream(asyncio.BaseProtocol):
:param exception: An unhandled exception object.
"""
pass

View File

@@ -23,7 +23,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event")
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True, True], msg)
self.assertTrue(happened == [True, True], msg)
def testDelEvent(self):
"""Test handler working, then deleted and not triggered"""
@@ -41,7 +41,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
self.assertTrue(happened == [True], msg % happened)
def testAddDelAddEvent(self):
"""Test adding, then removing, then adding an event handler."""
@@ -61,7 +61,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True, True], msg % happened)
self.assertTrue(happened == [True, True], msg % happened)
def testDisposableEvent(self):
"""Test disposable handler working, then not being triggered again."""
@@ -78,7 +78,7 @@ class TestEvents(SlixTest):
self.xmpp.event("test_event", {})
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
self.assertTrue(happened == [True], msg % happened)
suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)

View File

@@ -16,11 +16,11 @@ class TestOverall(unittest.TestCase):
"""Testing all modules by compiling them"""
src = '.%sslixmpp' % os.sep
rx = re.compile('/[.]svn|.*26.*')
self.failUnless(compileall.compile_dir(src, rx=rx, quiet=True))
self.assertTrue(compileall.compile_dir(src, rx=rx, quiet=True))
def testTabNanny(self):
"""Testing that indentation is consistent"""
self.failIf(tabnanny.check('..%sslixmpp' % os.sep))
self.assertFalse(tabnanny.check('..%sslixmpp' % os.sep))
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)

View File

@@ -9,37 +9,37 @@ class TestStanzaBase(SlixTest):
"""Test the 'to' interface of StanzaBase."""
stanza = StanzaBase()
stanza['to'] = 'user@example.com'
self.failUnless(str(stanza['to']) == 'user@example.com',
self.assertTrue(str(stanza['to']) == 'user@example.com',
"Setting and retrieving stanza 'to' attribute did not work.")
def testFrom(self):
"""Test the 'from' interface of StanzaBase."""
stanza = StanzaBase()
stanza['from'] = 'user@example.com'
self.failUnless(str(stanza['from']) == 'user@example.com',
self.assertTrue(str(stanza['from']) == 'user@example.com',
"Setting and retrieving stanza 'from' attribute did not work.")
def testPayload(self):
"""Test the 'payload' interface of StanzaBase."""
stanza = StanzaBase()
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Empty stanza does not have an empty payload.")
stanza['payload'] = ET.Element("{foo}foo")
self.failUnless(len(stanza['payload']) == 1,
self.assertTrue(len(stanza['payload']) == 1,
"Stanza contents and payload do not match.")
stanza['payload'] = ET.Element('{bar}bar')
self.failUnless(len(stanza['payload']) == 2,
self.assertTrue(len(stanza['payload']) == 2,
"Stanza payload was not appended.")
del stanza['payload']
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza payload not cleared after deletion.")
stanza['payload'] = [ET.Element('{foo}foo'),
ET.Element('{bar}bar')]
self.failUnless(len(stanza['payload']) == 2,
self.assertTrue(len(stanza['payload']) == 2,
"Adding multiple elements to stanza's payload did not work.")
def testClear(self):
@@ -49,9 +49,9 @@ class TestStanzaBase(SlixTest):
stanza['payload'] = ET.Element("{foo}foo")
stanza.clear()
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza payload was not cleared after calling .clear()")
self.failUnless(str(stanza['to']) == "user@example.com",
self.assertTrue(str(stanza['to']) == "user@example.com",
"Stanza attributes were not preserved after calling .clear()")
def testReply(self):
@@ -63,9 +63,9 @@ class TestStanzaBase(SlixTest):
stanza = stanza.reply()
self.failUnless(str(stanza['to'] == "sender@example.com"),
self.assertTrue(str(stanza['to'] == "sender@example.com"),
"Stanza reply did not change 'to' attribute.")
self.failUnless(stanza['payload'] == [],
self.assertTrue(stanza['payload'] == [],
"Stanza reply did not empty stanza payload.")
def testError(self):
@@ -73,7 +73,7 @@ class TestStanzaBase(SlixTest):
stanza = StanzaBase()
stanza['type'] = 'get'
stanza.error()
self.failUnless(stanza['type'] == 'error',
self.assertTrue(stanza['type'] == 'error',
"Stanza type is not 'error' after calling error()")

View File

@@ -17,7 +17,7 @@ class TestElementBase(SlixTest):
"{%s}bar" % ns,
"{abc}baz",
"{%s}more" % ns])
self.failUnless(expected == result,
self.assertTrue(expected == result,
"Incorrect namespace fixing result: %s" % str(result))
@@ -80,7 +80,7 @@ class TestElementBase(SlixTest):
'lang': '',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
self.assertTrue(values == expected,
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
@@ -170,13 +170,13 @@ class TestElementBase(SlixTest):
'meh': ''}
for interface, value in expected.items():
result = stanza[interface]
self.failUnless(result == value,
self.assertTrue(result == value,
"Incorrect stanza interface access result: %s" % result)
# Test plugin interfaces
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
self.assertTrue(isinstance(stanza['foobar'], TestStanzaPlugin),
"Incorrect plugin object result.")
self.failUnless(stanza['foobar']['fizz'] == 'c',
self.assertTrue(stanza['foobar']['fizz'] == 'c',
"Incorrect plugin subvalue result.")
def testSetItem(self):
@@ -269,7 +269,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" />
""")
self.failUnless(stanza._get_attr('bar') == '',
self.assertTrue(stanza._get_attr('bar') == '',
"Incorrect value returned for an unset XML attribute.")
stanza._set_attr('bar', 'a')
@@ -279,7 +279,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" bar="a" baz="b" />
""")
self.failUnless(stanza._get_attr('bar') == 'a',
self.assertTrue(stanza._get_attr('bar') == 'a',
"Retrieved XML attribute value is incorrect.")
stanza._set_attr('bar', None)
@@ -289,7 +289,7 @@ class TestElementBase(SlixTest):
<foo xmlns="foo" />
""")
self.failUnless(stanza._get_attr('bar', 'c') == 'c',
self.assertTrue(stanza._get_attr('bar', 'c') == 'c',
"Incorrect default value returned for an unset XML attribute.")
def testGetSubText(self):
@@ -311,7 +311,7 @@ class TestElementBase(SlixTest):
return self._get_sub_text("wrapper/bar", default="not found")
stanza = TestStanza()
self.failUnless(stanza['bar'] == 'not found',
self.assertTrue(stanza['bar'] == 'not found',
"Default _get_sub_text value incorrect.")
stanza['bar'] = 'found'
@@ -322,7 +322,7 @@ class TestElementBase(SlixTest):
</wrapper>
</foo>
""")
self.failUnless(stanza['bar'] == 'found',
self.assertTrue(stanza['bar'] == 'found',
"_get_sub_text value incorrect: %s." % stanza['bar'])
def testSubElement(self):
@@ -481,45 +481,45 @@ class TestElementBase(SlixTest):
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
self.failUnless(stanza.match("foo"),
self.assertTrue(stanza.match("foo"),
"Stanza did not match its own tag name.")
self.failUnless(stanza.match("{foo}foo"),
self.assertTrue(stanza.match("{foo}foo"),
"Stanza did not match its own namespaced name.")
stanza['bar'] = 'a'
self.failUnless(stanza.match("foo@bar=a"),
self.assertTrue(stanza.match("foo@bar=a"),
"Stanza did not match its own name with attribute value check.")
stanza['baz'] = 'b'
self.failUnless(stanza.match("foo@bar=a@baz=b"),
self.assertTrue(stanza.match("foo@bar=a@baz=b"),
"Stanza did not match its own name with multiple attributes.")
stanza['qux'] = 'c'
self.failUnless(stanza.match("foo/qux"),
self.assertTrue(stanza.match("foo/qux"),
"Stanza did not match with subelements.")
stanza['qux'] = ''
self.failUnless(stanza.match("foo/qux") == False,
self.assertTrue(stanza.match("foo/qux") == False,
"Stanza matched missing subinterface element.")
self.failUnless(stanza.match("foo/bar") == False,
self.assertTrue(stanza.match("foo/bar") == False,
"Stanza matched nonexistent element.")
stanza['plugin']['attrib'] = 'c'
self.failUnless(stanza.match("foo/plugin@attrib=c"),
self.assertTrue(stanza.match("foo/plugin@attrib=c"),
"Stanza did not match with plugin and attribute.")
self.failUnless(stanza.match("foo/{http://test/slash/bar}plugin"),
self.assertTrue(stanza.match("foo/{http://test/slash/bar}plugin"),
"Stanza did not match with namespaced plugin.")
substanza = TestSubStanza()
substanza['attrib'] = 'd'
stanza.append(substanza)
self.failUnless(stanza.match("foo/sub@attrib=d"),
self.assertTrue(stanza.match("foo/sub@attrib=d"),
"Stanza did not match with substanzas and attribute.")
self.failUnless(stanza.match("foo/{baz}sub"),
self.assertTrue(stanza.match("foo/{baz}sub"),
"Stanza did not match with namespaced substanza.")
def testComparisons(self):
@@ -533,19 +533,19 @@ class TestElementBase(SlixTest):
stanza1 = TestStanza()
stanza1['bar'] = 'a'
self.failUnless(stanza1,
self.assertTrue(stanza1,
"Stanza object does not evaluate to True")
stanza2 = TestStanza()
stanza2['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
self.assertTrue(stanza1 != stanza2,
"Different stanza objects incorrectly compared equal.")
stanza1['baz'] = 'b'
stanza2['bar'] = 'a'
self.failUnless(stanza1 == stanza2,
self.assertTrue(stanza1 == stanza2,
"Equal stanzas incorrectly compared inequal.")
def testKeys(self):
@@ -561,12 +561,12 @@ class TestElementBase(SlixTest):
stanza = TestStanza()
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz'},
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz'},
"Returned set of interface keys does not match expected.")
stanza.enable('qux')
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
"Incorrect set of interface and plugin keys.")
def testGet(self):
@@ -580,10 +580,10 @@ class TestElementBase(SlixTest):
stanza = TestStanza()
stanza['bar'] = 'a'
self.failUnless(stanza.get('bar') == 'a',
self.assertTrue(stanza.get('bar') == 'a',
"Incorrect value returned by stanza.get")
self.failUnless(stanza.get('baz', 'b') == 'b',
self.assertTrue(stanza.get('baz', 'b') == 'b',
"Incorrect default value returned by stanza.get")
def testSubStanzas(self):
@@ -608,7 +608,7 @@ class TestElementBase(SlixTest):
substanza2['qux'] = 'b'
# Test appending substanzas
self.failUnless(len(stanza) == 0,
self.assertTrue(len(stanza) == 0,
"Incorrect empty stanza size.")
stanza.append(substanza1)
@@ -617,7 +617,7 @@ class TestElementBase(SlixTest):
<foobar qux="a" />
</foo>
""", use_values=False)
self.failUnless(len(stanza) == 1,
self.assertTrue(len(stanza) == 1,
"Incorrect stanza size with 1 substanza.")
stanza.append(substanza2)
@@ -627,7 +627,7 @@ class TestElementBase(SlixTest):
<foobar qux="b" />
</foo>
""", use_values=False)
self.failUnless(len(stanza) == 2,
self.assertTrue(len(stanza) == 2,
"Incorrect stanza size with 2 substanzas.")
# Test popping substanzas
@@ -643,7 +643,7 @@ class TestElementBase(SlixTest):
results = []
for substanza in stanza:
results.append(substanza['qux'])
self.failUnless(results == ['b', 'a'],
self.assertTrue(results == ['b', 'a'],
"Iteration over substanzas failed: %s." % str(results))
def testCopy(self):
@@ -659,11 +659,11 @@ class TestElementBase(SlixTest):
stanza2 = stanza1.__copy__()
self.failUnless(stanza1 == stanza2,
self.assertTrue(stanza1 == stanza2,
"Copied stanzas are not equal to each other.")
stanza1['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
self.assertTrue(stanza1 != stanza2,
"Divergent stanza copies incorrectly compared equal.")
def testExtension(self):
@@ -701,7 +701,7 @@ class TestElementBase(SlixTest):
</foo>
""")
self.failUnless(stanza['extended'] == 'testing',
self.assertTrue(stanza['extended'] == 'testing',
"Could not retrieve stanza extension value.")
del stanza['extended']

View File

@@ -34,7 +34,7 @@ class TestErrorStanzas(SlixTest):
</message>
""")
self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
self.assertTrue(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
msg['error']['condition'] = 'resource-constraint'

View File

@@ -62,30 +62,30 @@ class TestGmail(SlixTest):
iq = self.Iq(xml=xml)
mailbox = iq['mailbox']
self.failUnless(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.failUnless(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.failUnless(mailbox['matched'] == '95', "total-matched incorrect")
self.failUnless(mailbox['estimate'] == False, "total-estimate incorrect")
self.failUnless(len(mailbox['threads']) == 1, "could not extract message threads")
self.assertTrue(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.assertTrue(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.assertTrue(mailbox['matched'] == '95', "total-matched incorrect")
self.assertTrue(mailbox['estimate'] == False, "total-estimate incorrect")
self.assertTrue(len(mailbox['threads']) == 1, "could not extract message threads")
thread = mailbox['threads'][0]
self.failUnless(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.failUnless(thread['participation'] == '1', "thread participation incorrect")
self.failUnless(thread['messages'] == '28', "thread message count incorrect")
self.failUnless(thread['date'] == '1118012394209', "thread date doesn't match")
self.failUnless(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.failUnless(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.failUnless(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.failUnless(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.failUnless(len(thread['senders']) == 3, "could not extract senders")
self.assertTrue(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.assertTrue(thread['participation'] == '1', "thread participation incorrect")
self.assertTrue(thread['messages'] == '28', "thread message count incorrect")
self.assertTrue(thread['date'] == '1118012394209', "thread date doesn't match")
self.assertTrue(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.assertTrue(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.assertTrue(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.assertTrue(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.assertTrue(len(thread['senders']) == 3, "could not extract senders")
sender1 = thread['senders'][0]
self.failUnless(sender1['name'] == 'Me', "sender name doesn't match")
self.failUnless(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.failUnless(sender1['originator'] == True, "sender originator incorrect")
self.failUnless(sender1['unread'] == False, "sender unread incorrectly True")
self.assertTrue(sender1['name'] == 'Me', "sender name doesn't match")
self.assertTrue(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.assertTrue(sender1['originator'] == True, "sender originator incorrect")
self.assertTrue(sender1['unread'] == False, "sender unread incorrectly True")
sender2 = thread['senders'][2]
self.failUnless(sender2['unread'] == True, "sender unread incorrectly False")
self.assertTrue(sender2['unread'] == True, "sender unread incorrectly False")
suite = unittest.TestLoader().loadTestsFromTestCase(TestGmail)

View File

@@ -70,7 +70,7 @@ class TestIqStanzas(SlixTest):
</iq>
""")
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
self.assertTrue(iq['query'] == 'query_ns2', "Query namespace doesn't match")
del iq['query']
self.check(iq, """

View File

@@ -18,7 +18,7 @@ class TestMessageStanzas(SlixTest):
msg['type'] = 'groupchat'
msg['body'] = "this is a message"
msg = msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
self.assertTrue(str(msg['to']) == 'room@someservice.someserver.tld')
def testHTMLPlugin(self):
"Test message/html/body stanza"

View File

@@ -15,7 +15,7 @@ class TestPresenceStanzas(SlixTest):
p = self.Presence()
p['type'] = 'available'
self.check(p, "<presence />")
self.failUnless(p['type'] == 'available',
self.assertTrue(p['type'] == 'available',
"Incorrect presence['type'] for type 'available': %s" % p['type'])
for showtype in ['away', 'chat', 'dnd', 'xa']:
@@ -23,7 +23,7 @@ class TestPresenceStanzas(SlixTest):
self.check(p, """
<presence><show>%s</show></presence>
""" % showtype)
self.failUnless(p['type'] == showtype,
self.assertTrue(p['type'] == showtype,
"Incorrect presence['type'] for type '%s'" % showtype)
p['type'] = None
@@ -47,10 +47,10 @@ class TestPresenceStanzas(SlixTest):
c.add_event_handler("changed_status", handlechangedpresence)
c._handle_presence(p)
self.failUnless(happened == [],
self.assertTrue(happened == [],
"changed_status event triggered for extra unavailable presence")
roster = c.roster['crap@wherever']
self.failUnless(roster['bill@chadmore.com'].resources == {},
self.assertTrue(roster['bill@chadmore.com'].resources == {},
"Roster updated for superfulous unavailable presence")
def testNickPlugin(self):

View File

@@ -61,7 +61,7 @@ class TestRosterStanzas(SlixTest):
debug = "Roster items don't match after retrieval."
debug += "\nReturned: %s" % str(iq['roster']['items'])
debug += "\nExpected: %s" % str(expected)
self.failUnless(iq['roster']['items'] == expected, debug)
self.assertTrue(iq['roster']['items'] == expected, debug)
def testDelItems(self):
"""Test clearing items from a roster stanza."""

View File

@@ -258,7 +258,7 @@ class TestDisco(SlixTest):
('client', 'pc', 'no', None),
('client', 'pc', 'en', None),
('client', 'pc', 'fr', None)}
self.failUnless(iq['disco_info']['identities'] == expected,
self.assertTrue(iq['disco_info']['identities'] == expected,
"Identities do not match:\n%s\n%s" % (
expected,
iq['disco_info']['identities']))
@@ -276,7 +276,7 @@ class TestDisco(SlixTest):
expected = {('client', 'pc', 'no', None)}
result = iq['disco_info'].get_identities(lang='no')
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Identities do not match:\n%s\n%s" % (
expected, result))
@@ -337,7 +337,7 @@ class TestDisco(SlixTest):
iq['disco_info'].add_feature('baz')
expected = {'foo', 'bar', 'baz'}
self.failUnless(iq['disco_info']['features'] == expected,
self.assertTrue(iq['disco_info']['features'] == expected,
"Features do not match:\n%s\n%s" % (
expected,
iq['disco_info']['features']))
@@ -475,7 +475,7 @@ class TestDisco(SlixTest):
expected = {('user@localhost', None, None),
('user@localhost', 'foo', None),
('test@localhost', 'bar', 'Tester')}
self.failUnless(iq['disco_items']['items'] == expected,
self.assertTrue(iq['disco_items']['items'] == expected,
"Items do not match:\n%s\n%s" % (
expected,
iq['disco_items']['items']))

View File

@@ -17,13 +17,13 @@ class TestAdHocCommandStanzas(SlixTest):
iq['command']['node'] = 'foo'
iq['command']['action'] = 'execute'
self.failUnless(iq['command']['action'] == 'execute')
self.assertTrue(iq['command']['action'] == 'execute')
iq['command']['action'] = 'complete'
self.failUnless(iq['command']['action'] == 'complete')
self.assertTrue(iq['command']['action'] == 'complete')
iq['command']['action'] = 'cancel'
self.failUnless(iq['command']['action'] == 'cancel')
self.assertTrue(iq['command']['action'] == 'cancel')
def testSetActions(self):
"""Test setting next actions in a command stanza."""
@@ -98,7 +98,7 @@ class TestAdHocCommandStanzas(SlixTest):
('error', "I can't let you do that")]
iq['command']['notes'] = notes
self.failUnless(iq['command']['notes'] == notes,
self.assertTrue(iq['command']['notes'] == notes,
"Notes don't match: %s %s" % (notes, iq['command']['notes']))
self.check(iq, """

View File

@@ -24,7 +24,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = '10'
self.failUnless(s['first_index'] == expected)
self.assertTrue(s['first_index'] == expected)
def testDelFirstIndex(self):
xml_string = """
@@ -57,7 +57,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = True
self.failUnless(s['before'] == expected)
self.assertTrue(s['before'] == expected)
def testGetBefore(self):
xml_string = """
@@ -89,7 +89,7 @@ class TestSetStanzas(SlixTest):
"""
s = Set(ET.fromstring(xml_string))
expected = 'id'
self.failUnless(s['before'] == expected)
self.assertTrue(s['before'] == expected)
def testGetBeforeVal(self):
xml_string = """

View File

@@ -112,7 +112,7 @@ class TestHandlers(SlixTest):
# Check that the waiter is no longer registered
waiter_exists = self.xmpp.remove_handler('IqWait_test2')
self.failUnless(waiter_exists == False,
self.assertTrue(waiter_exists == False,
"Waiter handler was not removed.")
def testIqCallback(self):
@@ -145,7 +145,7 @@ class TestHandlers(SlixTest):
</iq>
""")
self.failUnless(events == ['foo'],
self.assertTrue(events == ['foo'],
"Iq callback was not executed: %s" % events)
def testMultipleHandlersForStanza(self):

View File

@@ -52,7 +52,7 @@ class TestStreamRoster(SlixTest):
pending_out=True,
groups=['Friends', 'Examples'])
self.failUnless(len(roster_updates) == 1,
self.assertTrue(len(roster_updates) == 1,
"Wrong number of roster_update events fired: %s (should be 1)" % len(roster_updates))
def testRosterSet(self):
@@ -89,7 +89,7 @@ class TestStreamRoster(SlixTest):
groups=['Friends', 'Examples'])
self.failUnless('roster_update' in events,
self.assertTrue('roster_update' in events,
"Roster updated event not triggered: %s" % events)
def testRosterPushRemove(self):
@@ -188,7 +188,7 @@ class TestStreamRoster(SlixTest):
</iq>
""")
self.failUnless(events == ['roster_callback'],
self.assertTrue(events == ['roster_callback'],
"Roster timeout event not triggered: %s." % events)
def testRosterUnicode(self):
@@ -209,7 +209,7 @@ class TestStreamRoster(SlixTest):
groups=['Unicode'])
jids = list(self.xmpp.client_roster.keys())
self.failUnless(jids == ['andré@foo'],
self.assertTrue(jids == ['andré@foo'],
"Too many roster entries found: %s" % jids)
self.recv("""
@@ -223,7 +223,7 @@ class TestStreamRoster(SlixTest):
expected = {'bar': {'status':'Testing',
'show':'away',
'priority':0}}
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Unexpected roster values: %s" % result)
def testSendLastPresence(self):

View File

@@ -79,8 +79,7 @@ class TestInBandByteStreams(SlixTest):
self.assertEqual(events, {'ibb_stream_start', 'callback'})
@asyncio.coroutine
def testSendData(self):
async def testSendData(self):
"""Test sending data over an in-band bytestream."""
streams = []
@@ -117,7 +116,7 @@ class TestInBandByteStreams(SlixTest):
# Test sending data out
yield from stream.send("Testing")
await stream.send("Testing")
self.send("""
<iq type="set" id="2"

View File

@@ -626,7 +626,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo', 'bar', 'baz'],
self.assertTrue(results == ['foo', 'bar', 'baz'],
'Incomplete command workflow: %s' % results)
def testClientAPICancel(self):
@@ -689,7 +689,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo', 'bar'],
self.assertTrue(results == ['foo', 'bar'],
'Incomplete command workflow: %s' % results)
def testClientAPIError(self):
@@ -727,7 +727,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo'],
self.assertTrue(results == ['foo'],
'Incomplete command workflow: %s' % results)
def testClientAPIErrorStrippedResponse(self):
@@ -762,7 +762,7 @@ class TestAdHocCommands(SlixTest):
</iq>
""")
self.failUnless(results == ['foo'],
self.assertTrue(results == ['foo'],
'Incomplete command workflow: %s' % results)

View File

@@ -50,7 +50,7 @@ class TestStreamChatStates(SlixTest):
""")
expected = ['active', 'inactive', 'paused', 'composing', 'gone']
self.failUnless(results == expected,
self.assertTrue(results == expected,
"Chat state event not handled: %s" % results)

View File

@@ -35,7 +35,7 @@ class TestStreamDirectInvite(SlixTest):
</message>
""")
self.failUnless(events == [True],
self.assertTrue(events == [True],
"Event not raised: %s" % events)
def testSentDirectInvite(self):

View File

@@ -4,6 +4,7 @@ import sys
import datetime
import time
import threading
import re
from slixmpp.test import *
from slixmpp.xmlstream import ElementBase
@@ -455,7 +456,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnless(results == ["rejected"],
self.assertTrue(results == ["rejected"],
"Rejected callback was not properly executed")
def testRequestAcceptedAPI(self):
@@ -492,7 +493,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnless(results == ["accepted"],
self.assertTrue(results == ["accepted"],
"Accepted callback was not properly executed")
def testRequestFieldsAPI(self):
@@ -560,19 +561,19 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["accepted","fields","done"])
self.assertEqual(results, ["accepted","fields","done"])
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
#self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
#self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testServiceDiscoveryClient(self):
self.stream_start(mode='client',
@@ -674,16 +675,16 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["accepted","failure"]);
self.assertEqual(results, ["accepted","failure"]);
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
self.assertEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
# self.assertIn("error_msg", callback_data);
self.assertTrue("error_msg" in callback_data)
self.failUnlessEqual(callback_data["error_msg"], "Timeout.")
self.assertEqual(callback_data["error_msg"], "Timeout.")
def testDelayedRequest(self):
self.stream_start(mode='component',
@@ -771,7 +772,7 @@ class TestStreamSensorData(SlixTest):
# Remove the returned datetime to allow predictable test
xml_stanza = self._filtered_stanza_prepare()
error_text = xml_stanza['rejected']['error'] #['text']
error_text = error_text[:error_text.find(':')]
error_text = re.sub(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-]\d{2}:\d{2})?', '', error_text)
xml_stanza['rejected']['error'] = error_text
self._filtered_stanza_check("""
@@ -780,7 +781,7 @@ class TestStreamSensorData(SlixTest):
to='master@clayster.com/amr'
id='1'>
<rejected xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<error>Invalid datetime in 'when' flag, cannot set a time in the past. Current time</error>
<error>Invalid datetime in 'when' flag, cannot set a time in the past (…). Current time: …</error>
</rejected>
</iq>
""", xml_stanza)
@@ -1070,19 +1071,19 @@ class TestStreamSensorData(SlixTest):
</message>
""")
self.failUnlessEqual(results, ["queued","started","fields","done"]);
self.assertEqual(results, ["queued","started","fields","done"]);
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
self.assertEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
# self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
# self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testRequestFieldsCancelAPI(self):
@@ -1138,7 +1139,7 @@ class TestStreamSensorData(SlixTest):
</iq>
""")
self.failUnlessEqual(results, ["accepted","cancelled"])
self.assertEqual(results, ["accepted","cancelled"])
def testDelayedRequestCancel(self):
self.stream_start(mode='component',

View File

@@ -25,7 +25,7 @@ class TestToString(SlixTest):
else:
xml=original
result = tostring(xml, **kwargs)
self.failUnless(result == expected, "%s: %s" % (message, result))
self.assertTrue(result == expected, "%s: %s" % (message, result))
def testXMLEscape(self):
"""Test escaping XML special characters."""
@@ -34,7 +34,7 @@ class TestToString(SlixTest):
desired = """&lt;foo bar=&quot;baz&quot;&gt;&apos;Hi"""
desired += """ &amp; welcome!&apos;&lt;/foo&gt;"""
self.failUnless(escaped == desired,
self.assertTrue(escaped == desired,
"XML escaping did not work: %s." % escaped)
def testEmptyElement(self):
@@ -99,7 +99,7 @@ class TestToString(SlixTest):
msg['body'] = utf8_message.decode('utf-8')
expected = '<message><body>\xe0\xb2\xa0_\xe0\xb2\xa0</body></message>'
result = msg.__str__()
self.failUnless(result == expected,
self.assertTrue(result == expected,
"Stanza Unicode handling is incorrect: %s" % result)
def testXMLLang(self):
@@ -112,7 +112,7 @@ class TestToString(SlixTest):
expected = '<message xml:lang="no" />'
result = msg.__str__()
self.failUnless(expected == result,
self.assertTrue(expected == result,
"Serialization with xml:lang failed: %s" % result)