Compare commits
	
		
			51 Commits
		
	
	
		
			slix-1.3.0
			...
			slix-1.4.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 35fa33e3c2 | ||
|   | 86a2f280d2 | ||
|   | 490f15b8fc | ||
|   | 62661ee04f | ||
|   | 37d1f2a6b0 | ||
|   | 20107ad516 | ||
|   | 7738a01311 | ||
|   | a9abed6151 | ||
|   | 0f690d4005 | ||
|   | 59d4420739 | ||
|   | a88f317bbf | ||
|   | 2fc2a88970 | ||
|   | c55e9279ac | ||
|   | 3502480384 | ||
|   | caae713dd6 | ||
|   | df0198abfe | ||
|   | c20f4bf5fa | ||
|   | 9740e93aeb | ||
|   | e7872aaa29 | ||
|   | 037706552c | ||
|   | b881c6729b | ||
|   | 66909aafb3 | ||
|   | cdfb5d56fc | ||
|   | d146ce9fb6 | ||
|   | cb59d60034 | ||
|   | 1d9fe3553e | ||
|   | fe66c022ad | ||
|   | 92ea131721 | ||
|   | dd7f67d10d | ||
|   | c1562b76b2 | ||
|   | 32839f5252 | ||
|   | 80b7cf6ff8 | ||
|   | 128cc2eeb4 | ||
|   | 037912ee89 | ||
|   | 769bc6d3bf | ||
|   | 084d6cb5d9 | ||
|   | 5184713356 | ||
|   | 2f1225bad3 | ||
|   | 841f5a5a5b | ||
|   | 0c6de5e972 | ||
|   | 81dc61c55c | ||
|   | bd63b1ce70 | ||
|   | 29faf114a7 | ||
|   | 94ea8151d4 | ||
|   | 66500ef5fb | ||
|   | 979396bb1e | ||
|   | e177726387 | ||
|   | 20e88fda50 | ||
|   | f252be9b6d | ||
|   | ee98159586 | ||
|   | c6443af29a | 
| @@ -4,5 +4,5 @@ test: | ||||
|   image: ubuntu:latest | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 cython3 | ||||
|     - apt install -y python3 cython3 gpg | ||||
|     - ./run_tests.py | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| Pre-requisites: | ||||
| - Python 3.4 | ||||
| - Python 3.5+ | ||||
| - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) | ||||
|  | ||||
| Install: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|  | ||||
|   | ||||
							
								
								
									
										92
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										92
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| #!/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 | ||||
| from slixmpp import asyncio | ||||
|  | ||||
| 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): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.recipient = recipient | ||||
|         self.filename = filename | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         log.info('Uploading file %s...', self.filename) | ||||
|         url = yield from self['xep_0363'].upload_file(self.filename) | ||||
|         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") | ||||
|  | ||||
|     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) | ||||
|     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) | ||||
| @@ -21,7 +21,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'] | ||||
|  | ||||
| @@ -40,7 +40,7 @@ class PubsubClient(slixmpp.ClientXMPP): | ||||
|         try: | ||||
|             yield from 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): | ||||
| @@ -65,6 +65,13 @@ class PubsubClient(slixmpp.ClientXMPP): | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not delete node %s: %s', self.node, error.format()) | ||||
|  | ||||
|     def get_configure(self): | ||||
|         try: | ||||
|             configuration_form = yield from 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()) | ||||
|  | ||||
|     def publish(self): | ||||
|         payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) | ||||
|         try: | ||||
| @@ -118,7 +125,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 +146,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='?') | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -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', | ||||
| ] | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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']) | ||||
|   | ||||
| @@ -97,13 +97,7 @@ 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'): | ||||
|                     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 | ||||
|                 result[value] = self.xmpp.socket.get_channel_binding() | ||||
|             elif value == 'host': | ||||
|                 result[value] = creds.get('host', self.xmpp.requested_jid.domain) | ||||
|             elif value == 'realm': | ||||
| @@ -122,7 +116,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 | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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'} | ||||
|  | ||||
|   | ||||
| @@ -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']) | ||||
|   | ||||
| @@ -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,31 @@ 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, **kwargs): | ||||
|         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, **kwargs)] | ||||
|             iq_items = await self.get_items( | ||||
|                 domain, timeout=timeout, **kwargs) | ||||
|             items = iq_items['disco_items']['items'] | ||||
|             infos += [ | ||||
|                 self.get_info(item[0], timeout=timeout, **kwargs) | ||||
|                 for item in items] | ||||
|             info_futures, _ = await asyncio.wait(infos, timeout=timeout) | ||||
|  | ||||
|             self.domain_infos[domain] = [ | ||||
|                 future.result() for future in info_futures] | ||||
|  | ||||
|         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 +674,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': | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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'] | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 vCard’s PHOTO for %s:', jid, exc_info=True) | ||||
|                 data = None | ||||
|             if not data: | ||||
|                 new_hash = '' | ||||
|             else: | ||||
|   | ||||
| @@ -62,7 +62,7 @@ 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)) | ||||
|  | ||||
|     def remove_interest(self, namespace, jid=None): | ||||
|         """ | ||||
| @@ -81,7 +81,7 @@ 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)) | ||||
|  | ||||
|     def publish(self, stanza, node=None, id=None, options=None, ifrom=None, | ||||
|                 timeout_callback=None, callback=None, timeout=None): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -104,13 +104,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 +144,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 +172,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 | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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: | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								slixmpp/plugins/xep_0363/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								slixmpp/plugins/xep_0363/__init__.py
									
									
									
									
									
										Normal 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) | ||||
							
								
								
									
										146
									
								
								slixmpp/plugins/xep_0363/http_upload.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								slixmpp/plugins/xep_0363/http_upload.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| """ | ||||
|     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 asyncio | ||||
| 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 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, timeout=None): | ||||
|         results = await self.xmpp['xep_0030'].get_info_from_domain() | ||||
|  | ||||
|         for info in results: | ||||
|             for identity in info['disco_info']['identities']: | ||||
|                 if identity[0] == 'store' and identity[1] == 'file': | ||||
|                     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, 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(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) | ||||
|         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) | ||||
|             log.info('Response code: %d (%s)', response.status, await response.text()) | ||||
|             response.close() | ||||
|             return slot['get']['url'] | ||||
							
								
								
									
										48
									
								
								slixmpp/plugins/xep_0363/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								slixmpp/plugins/xep_0363/stanza.py
									
									
									
									
									
										Normal 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 = '' | ||||
| @@ -188,10 +188,19 @@ class Iq(RootStanza): | ||||
|         future = asyncio.Future() | ||||
|  | ||||
|         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']) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 ' | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										105
									
								
								slixmpp/util/cache.py
									
									
									
									
									
										Normal 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) | ||||
| @@ -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.0' | ||||
| __version_info__ = (1, 4, 0) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 [] | ||||
|   | ||||
| @@ -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,12 @@ 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()) | ||||
|  | ||||
|     @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 +306,9 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         else: | ||||
|             ssl_context = None | ||||
|  | ||||
|         yield from asyncio.sleep(self.connect_loop_wait) | ||||
|         await asyncio.sleep(self.connect_loop_wait) | ||||
|         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 +321,7 @@ 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()) | ||||
|  | ||||
|     def process(self, *, forever=True, timeout=None): | ||||
|         """Process all the available XMPP events (receiving or sending data on the | ||||
| @@ -355,7 +354,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 +438,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 +451,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 +530,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 +678,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 +690,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 +698,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 +708,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 +773,15 @@ 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)) | ||||
|             else: | ||||
|                 try: | ||||
|                     handler_callback(data) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import sys | ||||
| import datetime | ||||
| import time | ||||
| import threading | ||||
| import re | ||||
|  | ||||
| from slixmpp.test import * | ||||
| from slixmpp.xmlstream import ElementBase | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user