Compare commits
	
		
			72 Commits
		
	
	
		
			1.1.6
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e4b4c67637 | ||
|   | 422e77ae40 | ||
|   | 5ae6c8f8fa | ||
|   | 54656b331a | ||
|   | 9047b627a4 | ||
|   | 6645a3be40 | ||
|   | e3fab66dfb | ||
|   | 5867f08bf1 | ||
|   | a06fa2de67 | ||
|   | 35396d2977 | ||
|   | 3bff743d9f | ||
|   | 5a878f829b | ||
|   | 26dc6e90ea | ||
|   | 94c749fd5a | ||
|   | 7b80ed0807 | ||
|   | 98b7e8b10a | ||
|   | 9d8de7fc15 | ||
|   | 70883086b7 | ||
|   | 9a08dfc7d4 | ||
|   | 3e43b36a9d | ||
|   | 352ee2f2fd | ||
|   | 78aa5c3dfa | ||
|   | 613323b5fb | ||
|   | 6c4b01db8a | ||
|   | d06897a635 | ||
|   | 1600bb0aaf | ||
|   | b5c9c98a8b | ||
|   | e4e18a416f | ||
|   | 01cc0e6def | ||
|   | d571d691a7 | ||
|   | fb221a8dc0 | ||
|   | 459e1ed345 | ||
|   | 6680c244f5 | ||
|   | 06423964ec | ||
|   | 474390fa00 | ||
|   | 81d3723084 | ||
|   | 32e798967e | ||
|   | acd9c32a9f | ||
|   | b8581b0278 | ||
|   | 917faecdcb | ||
|   | f6edaa56a6 | ||
|   | 51fee28bf4 | ||
|   | e8a3e92ceb | ||
|   | 5df3839b7a | ||
|   | 8dcb441f44 | ||
|   | a347cf625a | ||
|   | 46f49c7a12 | ||
|   | 99701c947e | ||
|   | 1baae1b81e | ||
|   | 7d20f0e9a6 | ||
|   | fbad22a1cd | ||
|   | 5af2f62c04 | ||
|   | 4a4a03858e | ||
|   | 6819b57353 | ||
|   | 88b5e60807 | ||
|   | a26a8bd79c | ||
|   | 9307a6915f | ||
|   | 85ef2d8d0b | ||
|   | c2c7cc032b | ||
|   | e4911e9391 | ||
|   | b11e1ee92d | ||
|   | 5027d00c10 | ||
|   | 69ddeceb49 | ||
|   | 82698672bb | ||
|   | 9cec284947 | ||
|   | dc501d1902 | ||
|   | 100e504b7f | ||
|   | 8a745c5e81 | ||
|   | bf0a157c5d | ||
|   | f49818be06 | ||
|   | 1ad171dfe5 | ||
|   | 2a78570d65 | 
| @@ -45,13 +45,11 @@ The latest source code for SleekXMPP may be found on `Github | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Latest Release** | ||||
|     - `1.1.6 <http://github.com/fritzy/SleekXMPP/zipball/1.1.6>`_ | ||||
|     - `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_ | ||||
|  | ||||
| **Develop Releases** | ||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|  | ||||
| **Older Stable Releases** | ||||
|     - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ | ||||
|  | ||||
| Installing DNSPython | ||||
| --------------------- | ||||
| @@ -74,6 +72,7 @@ help with SleekXMPP. | ||||
| **Chat** | ||||
|     `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|  | ||||
|  | ||||
| Documentation and Testing | ||||
| ------------------------- | ||||
| Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. | ||||
|   | ||||
							
								
								
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class AdminCommands(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP bot that uses admin commands to | ||||
|     add a new user to a server. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, command): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.command = command | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         def command_success(iq, session): | ||||
|             print('Command completed') | ||||
|             if iq['command']['form']: | ||||
|                 for var, field in iq['command']['form']['fields'].items(): | ||||
|                     print('%s: %s' % (var, field['value'])) | ||||
|             if iq['command']['notes']: | ||||
|                 print('Command Notes:') | ||||
|                 for note in iq['command']['notes']: | ||||
|                     print('%s: %s' % note) | ||||
|             self.disconnect() | ||||
|  | ||||
|         def command_error(iq, session): | ||||
|             print('Error completing command') | ||||
|             print('%s: %s' % (iq['error']['condition'], | ||||
|                               iq['error']['text'])) | ||||
|             self['xep_0050'].terminate_command(session) | ||||
|             self.disconnect() | ||||
|  | ||||
|         def process_form(iq, session): | ||||
|             form = iq['command']['form'] | ||||
|             answers = {} | ||||
|             for var, field in form['fields'].items(): | ||||
|                 if var != 'FORM_TYPE': | ||||
|                     if field['type'] == 'boolean': | ||||
|                         answers[var] = raw_input('%s (y/n): ' % field['label']) | ||||
|                         if answers[var].lower() in ('1', 'true', 'y', 'yes'): | ||||
|                             answers[var] = '1' | ||||
|                         else: | ||||
|                             answers[var] = '0' | ||||
|                     else: | ||||
|                         answers[var] = raw_input('%s: ' % field['label']) | ||||
|                 else: | ||||
|                     answers['FORM_TYPE'] = field['value'] | ||||
|             form['type'] = 'submit' | ||||
|             form['values'] = answers | ||||
|  | ||||
|             session['next'] = command_success | ||||
|             session['payload'] = form | ||||
|  | ||||
|             self['xep_0050'].complete_command(session) | ||||
|  | ||||
|         session = {'next': process_form, | ||||
|                    'error': command_error} | ||||
|  | ||||
|         command = self.command.replace('-', '_') | ||||
|         handler = getattr(self['xep_0133'], command, None) | ||||
|  | ||||
|         if handler: | ||||
|             handler(session={ | ||||
|                 'next': process_form, | ||||
|                 'error': command_error | ||||
|             }) | ||||
|         else: | ||||
|             print('Invalid command name: %s' % self.command) | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     optp.add_option("-c", "--command", dest="command", | ||||
|                     help="admin command to use") | ||||
|  | ||||
|     opts, args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|     if opts.command is None: | ||||
|         opts.command = raw_input("Admin command: ") | ||||
|  | ||||
|     # Setup the CommandBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = AdminCommands(opts.jid, opts.password, opts.command) | ||||
|     xmpp.register_plugin('xep_0133') # Service Administration | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| import threading | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| FILE_TYPES = { | ||||
|     'image/png': 'png', | ||||
|     'image/gif': 'gif', | ||||
|     'image/jpeg': 'jpg' | ||||
| } | ||||
|  | ||||
|  | ||||
| class AvatarDownloader(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic script for downloading the avatars for a user's contacts. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|         self.add_event_handler("changed_status", self.wait_for_presences) | ||||
|  | ||||
|         self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar) | ||||
|         self.add_event_handler('avatar_metadata_publish', self.on_avatar) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(15) | ||||
|         self.disconnect(wait=True) | ||||
|  | ||||
|     def on_vcard_avatar(self, pres): | ||||
|         print("Received vCard avatar update from %s" % pres['from'].bare) | ||||
|         try: | ||||
|             result = self['xep_0054'].get_vcard(pres['from'], cached=True) | ||||
|         except XMPPError: | ||||
|             print("Error retrieving avatar for %s" % pres['from']) | ||||
|             return | ||||
|         avatar = result['vcard_temp']['PHOTO'] | ||||
|  | ||||
|         filetype = FILE_TYPES.get(avatar['TYPE'], 'png') | ||||
|         filename = 'vcard_avatar_%s_%s.%s' % ( | ||||
|                 pres['from'].bare, | ||||
|                 pres['vcard_temp_update']['photo'], | ||||
|                 filetype) | ||||
|         with open(filename, 'w+') as img: | ||||
|             img.write(avatar['BINVAL']) | ||||
|  | ||||
|     def on_avatar(self, msg): | ||||
|         print("Received avatar update from %s" % msg['from']) | ||||
|         metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] | ||||
|         for info in metadata['items']: | ||||
|             if not info['url']: | ||||
|                 try: | ||||
|                     result = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) | ||||
|                 except XMPPError: | ||||
|                     print("Error retrieving avatar for %s" % msg['from']) | ||||
|                     return | ||||
|  | ||||
|                 avatar = result['pubsub']['items']['item']['avatar_data'] | ||||
|  | ||||
|                 filetype = FILE_TYPES.get(metadata['type'], 'png') | ||||
|                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) | ||||
|                 with open(filename, 'w+') as img: | ||||
|                     img.write(avatar['value']) | ||||
|             else: | ||||
|                 # We could retrieve the avatar via HTTP, etc here instead. | ||||
|                 pass | ||||
|  | ||||
|     def wait_for_presences(self, pres): | ||||
|         """ | ||||
|         Wait to receive updates from all roster contacts. | ||||
|         """ | ||||
|         self.received.add(pres['from'].bare) | ||||
|         if len(self.received) >= len(self.client_roster.keys()): | ||||
|             self.presences_received.set() | ||||
|         else: | ||||
|             self.presences_received.clear() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.ERROR, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.DEBUG, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=5, | ||||
|                     default=logging.ERROR) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     opts,args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|  | ||||
|     xmpp = AvatarDownloader(opts.jid, opts.password) | ||||
|     xmpp.register_plugin('xep_0054') | ||||
|     xmpp.register_plugin('xep_0153') | ||||
|     xmpp.register_plugin('xep_0084') | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
| @@ -122,6 +122,19 @@ if __name__ == '__main__': | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # If you are connecting to Facebook and wish to use the | ||||
|     # X-FACEBOOK-PLATFORM authentication mechanism, you will need | ||||
|     # your API key and an access token. Then you'll set: | ||||
|     # xmpp.credentials['api_key'] = 'THE_API_KEY' | ||||
|     # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||
|  | ||||
|     # If you are connecting to MSN, then you will need an | ||||
|     # access token, and it does not matter what JID you | ||||
|     # specify other than that the domain is 'messenger.live.com', | ||||
|     # so '_@messenger.live.com' will work. You can specify | ||||
|     # the access token as so: | ||||
|     # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|   | ||||
							
								
								
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| import imghdr | ||||
| import logging | ||||
| import getpass | ||||
| import threading | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class AvatarSetter(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic script for downloading the avatars for a user's contacts. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, filepath): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|  | ||||
|         self.filepath = filepath | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         avatar_file = None | ||||
|         try: | ||||
|             avatar_file = open(os.path.expanduser(self.filepath)) | ||||
|         except IOError: | ||||
|             print('Could not find file: %s' % self.filepath) | ||||
|             return self.disconnect() | ||||
|  | ||||
|         avatar = avatar_file.read() | ||||
|  | ||||
|         avatar_type = 'image/%s' % imghdr.what('', avatar) | ||||
|         avatar_id = self['xep_0084'].generate_id(avatar) | ||||
|         avatar_bytes = len(avatar) | ||||
|  | ||||
|         avatar_file.close() | ||||
|  | ||||
|         used_xep84 = False | ||||
|         try: | ||||
|             print('Publish XEP-0084 avatar data') | ||||
|             self['xep_0084'].publish_avatar(avatar) | ||||
|             used_xep84 = True | ||||
|         except XMPPError: | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|  | ||||
|         try: | ||||
|             print('Update vCard with avatar') | ||||
|             self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         except XMPPError: | ||||
|             print('Could not set vCard avatar') | ||||
|  | ||||
|         if used_xep84: | ||||
|             try: | ||||
|                 print('Advertise XEP-0084 avatar metadata') | ||||
|                 self['xep_0084'].publish_avatar_metadata([ | ||||
|                     {'id': avatar_id, | ||||
|                      'type': avatar_type, | ||||
|                      'bytes': avatar_bytes} | ||||
|                     # We could advertise multiple avatars to provide | ||||
|                     # options in image type, source (HTTP vs pubsub), | ||||
|                     # size, etc. | ||||
|                     # {'id': ....} | ||||
|                 ]) | ||||
|             except XMPPError: | ||||
|                 print('Could not publish XEP-0084 metadata') | ||||
|  | ||||
|         print('Wait for presence updates to propagate...') | ||||
|         self.schedule('end', 5, self.disconnect, kwargs={'wait': True}) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.ERROR, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.DEBUG, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=5, | ||||
|                     default=logging.ERROR) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     optp.add_option("-f", "--file", dest="filepath", | ||||
|                     help="path to the avatar file") | ||||
|     opts,args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|     if opts.filepath is None: | ||||
|         opts.filepath = raw_input("Avatar file location: ") | ||||
|  | ||||
|     xmpp = AvatarSetter(opts.jid, opts.password, opts.filepath) | ||||
|     xmpp.register_plugin('xep_0054') | ||||
|     xmpp.register_plugin('xep_0153') | ||||
|     xmpp.register_plugin('xep_0084') | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										5
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								setup.py
									
									
									
									
									
								
							| @@ -49,6 +49,7 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/roster', | ||||
|                  'sleekxmpp/util', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
| @@ -81,13 +82,17 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0115', | ||||
|                  'sleekxmpp/plugins/xep_0118', | ||||
|                  'sleekxmpp/plugins/xep_0128', | ||||
|                  'sleekxmpp/plugins/xep_0131', | ||||
|                  'sleekxmpp/plugins/xep_0153', | ||||
|                  'sleekxmpp/plugins/xep_0172', | ||||
|                  'sleekxmpp/plugins/xep_0184', | ||||
|                  'sleekxmpp/plugins/xep_0186', | ||||
|                  'sleekxmpp/plugins/xep_0191', | ||||
|                  'sleekxmpp/plugins/xep_0198', | ||||
|                  'sleekxmpp/plugins/xep_0199', | ||||
|                  'sleekxmpp/plugins/xep_0202', | ||||
|                  'sleekxmpp/plugins/xep_0203', | ||||
|                  'sleekxmpp/plugins/xep_0221', | ||||
|                  'sleekxmpp/plugins/xep_0224', | ||||
|                  'sleekxmpp/plugins/xep_0231', | ||||
|                  'sleekxmpp/plugins/xep_0249', | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from sleekxmpp.basexmpp import BaseXMPP | ||||
| from sleekxmpp.clientxmpp import ClientXMPP | ||||
| from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| from sleekxmpp.stanza import Message, Presence, Iq | ||||
| from sleekxmpp.jid import JID, InvalidJID | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
|   | ||||
| @@ -99,10 +99,12 @@ class APIRegistry(object): | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|  | ||||
|         if jid in (None, ''): | ||||
|         if not jid: | ||||
|             jid = self.xmpp.boundjid | ||||
|         if jid and not isinstance(jid, JID): | ||||
|         elif jid and not isinstance(jid, JID): | ||||
|             jid = JID(jid) | ||||
|         elif jid == JID(''): | ||||
|             jid = self.xmpp.boundjid | ||||
|  | ||||
|         if node is None: | ||||
|             node = '' | ||||
| @@ -113,7 +115,7 @@ class APIRegistry(object): | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|         else: | ||||
|             if self.settings[ctype].get('client_bare', True): | ||||
|             if self.settings[ctype].get('client_bare', False): | ||||
|                 jid = jid.bare | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|   | ||||
| @@ -16,6 +16,7 @@ from __future__ import with_statement, unicode_literals | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins, features, roster | ||||
| @@ -69,7 +70,15 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection. | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|         self._redirect_attempts = 0 | ||||
|  | ||||
|         #: The maximum number of consecutive see-other-host | ||||
|         #: redirections that will be followed before quitting. | ||||
|         self.max_redirects = 5 | ||||
|  | ||||
|         self.session_bind_event = threading.Event() | ||||
|  | ||||
|         #: A dictionary mapping plugin names to plugins. | ||||
|         self.plugin = PluginManager(self) | ||||
| @@ -87,13 +96,13 @@ class BaseXMPP(XMLStream): | ||||
|         #: owner JIDs, as in the case for components. For clients | ||||
|         #: which only have a single JID, see :attr:`client_roster`. | ||||
|         self.roster = roster.Roster(self) | ||||
|         self.roster.add(self.boundjid.bare) | ||||
|         self.roster.add(self.boundjid) | ||||
|  | ||||
|         #: The single roster for the bound JID. This is the | ||||
|         #: equivalent of:: | ||||
|         #: | ||||
|         #:     self.roster[self.boundjid.bare] | ||||
|         self.client_roster = self.roster[self.boundjid.bare] | ||||
|         self.client_roster = self.roster[self.boundjid] | ||||
|  | ||||
|         #: The distinction between clients and components can be | ||||
|         #: important, primarily for choosing how to handle the | ||||
| @@ -134,11 +143,14 @@ class BaseXMPP(XMLStream): | ||||
|             Callback('Presence', | ||||
|                      MatchXPath("{%s}presence" % self.default_ns), | ||||
|                      self._handle_presence)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('Stream Error', | ||||
|                      MatchXPath("{%s}error" % self.stream_ns), | ||||
|                      self._handle_stream_error)) | ||||
|  | ||||
|         self.add_event_handler('session_start', | ||||
|                                self._handle_session_start) | ||||
|         self.add_event_handler('disconnected', | ||||
|                                self._handle_disconnected) | ||||
|         self.add_event_handler('presence_available', | ||||
| @@ -586,7 +598,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         log.warning("fulljid property deprecated. Use boundjid.full") | ||||
|         log.warning("fulljid property deprecated. Use boundjid.resource") | ||||
|         self.boundjid.resource = value | ||||
|  | ||||
|     @property | ||||
| @@ -651,30 +663,61 @@ class BaseXMPP(XMLStream): | ||||
|     def getjidbare(self, fulljid): | ||||
|         return fulljid.split('/', 1)[0] | ||||
|  | ||||
|     def _handle_session_start(self, event): | ||||
|         """Reset redirection attempt count.""" | ||||
|         self._redirect_attempts = 0 | ||||
|  | ||||
|     def _handle_disconnected(self, event): | ||||
|         """When disconnected, reset the roster""" | ||||
|         self.roster.reset() | ||||
|         self.session_bind_event.clear() | ||||
|  | ||||
|     def _handle_stream_error(self, error): | ||||
|         self.event('stream_error', error) | ||||
|  | ||||
|         if error['condition'] == 'see-other-host': | ||||
|             other_host = error['see_other_host'] | ||||
|             if not other_host: | ||||
|                 log.warning("No other host specified.") | ||||
|                 return | ||||
|  | ||||
|             if self._redirect_attempts > self.max_redirects: | ||||
|                 log.error("Exceeded maximum number of redirection attempts.") | ||||
|                 return | ||||
|  | ||||
|             self._redirect_attempts += 1 | ||||
|  | ||||
|             host = other_host | ||||
|             port = 5222 | ||||
|  | ||||
|             if '[' in other_host and ']' in other_host: | ||||
|                 host = other_host.split(']')[0][1:] | ||||
|             elif ':' in other_host: | ||||
|                 host = other_host.split(':')[0] | ||||
|  | ||||
|             port_sec = other_host.split(']')[-1] | ||||
|             if ':' in port_sec: | ||||
|                 port = int(port_sec.split(':')[1]) | ||||
|  | ||||
|             self.address = (host, port) | ||||
|             self.default_domain = host | ||||
|             self.dns_records = None | ||||
|             self.reconnect_delay = None | ||||
|             self.reconnect() | ||||
|  | ||||
|     def _handle_message(self, msg): | ||||
|         """Process incoming message stanzas.""" | ||||
|         if not self.is_component and not msg['to'].bare: | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message', msg) | ||||
|  | ||||
|     def _handle_available(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_available(presence) | ||||
|     def _handle_available(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_available(pres) | ||||
|  | ||||
|     def _handle_unavailable(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unavailable(presence) | ||||
|     def _handle_unavailable(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unavailable(pres) | ||||
|  | ||||
|     def _handle_new_subscription(self, stanza): | ||||
|     def _handle_new_subscription(self, pres): | ||||
|         """Attempt to automatically handle subscription requests. | ||||
|  | ||||
|         Subscriptions will be approved if the request is from | ||||
| @@ -686,8 +729,8 @@ class BaseXMPP(XMLStream): | ||||
|         If a subscription is accepted, a request for a mutual | ||||
|         subscription will be sent if :attr:`auto_subscribe` is ``True``. | ||||
|         """ | ||||
|         roster = self.roster[stanza['to'].bare] | ||||
|         item = self.roster[stanza['to'].bare][stanza['from'].bare] | ||||
|         roster = self.roster[pres['to']] | ||||
|         item = self.roster[pres['to']][pres['from']] | ||||
|         if item['whitelisted']: | ||||
|             item.authorize() | ||||
|         elif roster.auto_authorize: | ||||
| @@ -697,30 +740,20 @@ class BaseXMPP(XMLStream): | ||||
|         elif roster.auto_authorize == False: | ||||
|             item.unauthorize() | ||||
|  | ||||
|     def _handle_removed_subscription(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].unauthorize() | ||||
|     def _handle_removed_subscription(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unauthorize(pres) | ||||
|  | ||||
|     def _handle_subscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribe(presence) | ||||
|     def _handle_subscribe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_subscribe(pres) | ||||
|  | ||||
|     def _handle_subscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribed(presence) | ||||
|     def _handle_subscribed(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_subscribed(pres) | ||||
|  | ||||
|     def _handle_unsubscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribe(presence) | ||||
|     def _handle_unsubscribe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) | ||||
|  | ||||
|     def _handle_unsubscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribed(presence) | ||||
|     def _handle_unsubscribed(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) | ||||
|  | ||||
|     def _handle_presence(self, presence): | ||||
|         """Process incoming presence stanzas. | ||||
|   | ||||
| @@ -173,8 +173,13 @@ class ClientXMPP(BaseXMPP): | ||||
|         self._stream_feature_order.append((order, name)) | ||||
|         self._stream_feature_order.sort() | ||||
|  | ||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], | ||||
|                             block=True, timeout=None, callback=None): | ||||
|     def unregister_feature(self, name, order): | ||||
|         if name in self._stream_feature_handlers: | ||||
|             del self._stream_feature_handlers[name] | ||||
|         self._stream_feature_order.remove((order, name)) | ||||
|         self._stream_feature_order.sort() | ||||
|  | ||||
|     def update_roster(self, jid, **kwargs): | ||||
|         """Add or change a roster item. | ||||
|  | ||||
|         :param jid: The JID of the entry to modify. | ||||
| @@ -195,6 +200,16 @@ class ClientXMPP(BaseXMPP): | ||||
|                          Will be executed when the roster is received. | ||||
|                          Implies ``block=False``. | ||||
|         """ | ||||
|         current = self.client_roster[jid] | ||||
|  | ||||
|         name = kwargs.get('name', current['name']) | ||||
|         subscription = kwargs.get('subscription', current['subscription']) | ||||
|         groups = kwargs.get('groups', current['groups']) | ||||
|  | ||||
|         block = kwargs.get('block', True) | ||||
|         timeout = kwargs.get('timeout', None) | ||||
|         callback = kwargs.get('callback', None) | ||||
|  | ||||
|         return self.client_roster.update(jid, name, subscription, groups, | ||||
|                                          block, timeout, callback) | ||||
|  | ||||
| @@ -270,8 +285,9 @@ class ClientXMPP(BaseXMPP): | ||||
|         roster = self.client_roster | ||||
|         if iq['roster']['ver']: | ||||
|             roster.version = iq['roster']['ver'] | ||||
|         for jid in iq['roster']['items']: | ||||
|             item = iq['roster']['items'][jid] | ||||
|         items = iq['roster']['items'] | ||||
|         for jid in items: | ||||
|             item = items[jid] | ||||
|             roster[jid]['name'] = item['name'] | ||||
|             roster[jid]['groups'] = item['groups'] | ||||
|             roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||
|   | ||||
| @@ -156,10 +156,10 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         :param xml: The reply handshake stanza. | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         self.event("session_bind", self.boundjid, direct=True) | ||||
|         self.event("session_start") | ||||
|  | ||||
|     def _handle_probe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_probe(presence) | ||||
|     def _handle_probe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_probe(pres) | ||||
|   | ||||
| @@ -51,6 +51,7 @@ class FeatureBind(BasePlugin): | ||||
|         self.xmpp.set_jid(response['bind']['jid']) | ||||
|         self.xmpp.bound = True | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) | ||||
|         self.xmpp.session_bind_event.set() | ||||
|  | ||||
|         self.xmpp.features.add('bind') | ||||
|  | ||||
|   | ||||
| @@ -29,10 +29,13 @@ class FeatureMechanisms(BasePlugin): | ||||
|     description = 'RFC 6120: Stream Feature: SASL' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'use_mech': None, | ||||
|         'sasl_callback': None, | ||||
|         'order': 100 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.use_mech = self.config.get('use_mech', None) | ||||
|  | ||||
|         if not self.use_mech and not self.xmpp.boundjid.user: | ||||
|             self.use_mech = 'ANONYMOUS' | ||||
|  | ||||
| @@ -53,15 +56,14 @@ class FeatureMechanisms(BasePlugin): | ||||
|                     values[value] = creds[value] | ||||
|             mech.fulfill(values) | ||||
|  | ||||
|         sasl_callback = self.config.get('sasl_callback', None) | ||||
|         if sasl_callback is None: | ||||
|             sasl_callback = basic_callback | ||||
|         if self.sasl_callback is None: | ||||
|             self.sasl_callback = basic_callback | ||||
|  | ||||
|         self.mech = None | ||||
|         self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp', | ||||
|                                 username=self.xmpp.boundjid.user, | ||||
|                                 sec_query=suelta.sec_query_allow, | ||||
|                                 request_values=sasl_callback, | ||||
|                                 request_values=self.sasl_callback, | ||||
|                                 tls_active=tls_active, | ||||
|                                 mech=self.use_mech) | ||||
|  | ||||
| @@ -95,7 +97,7 @@ class FeatureMechanisms(BasePlugin): | ||||
|         self.xmpp.register_feature('mechanisms', | ||||
|                 self._handle_sasl_auth, | ||||
|                 restart=True, | ||||
|                 order=self.config.get('order', 100)) | ||||
|                 order=self.order) | ||||
|  | ||||
|     def _handle_sasl_auth(self, features): | ||||
|         """ | ||||
|   | ||||
							
								
								
									
										543
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,543 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
|     sleekxmpp.jid | ||||
|     ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     This module allows for working with Jabber IDs (JIDs). | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2011 Nathanael C. Fritz | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import re | ||||
| import socket | ||||
| import stringprep | ||||
| import encodings.idna | ||||
|  | ||||
| from sleekxmpp.util import stringprep_profiles | ||||
|  | ||||
| #: These characters are not allowed to appear in a JID. | ||||
| ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ | ||||
|                 '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ | ||||
|                 '\x1a\x1b\x1c\x1d\x1e\x1f' + \ | ||||
|                 ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' | ||||
|  | ||||
| #: The basic regex pattern that a JID must match in order to determine | ||||
| #: the local, domain, and resource parts. This regex does NOT do any | ||||
| #: validation, which requires application of nodeprep, resourceprep, etc. | ||||
| JID_PATTERN = re.compile( | ||||
|     "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" | ||||
| ) | ||||
|  | ||||
| #: The set of escape sequences for the characters not allowed by nodeprep. | ||||
| JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                             '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) | ||||
|  | ||||
| #: A mapping of unallowed characters to their escape sequences. An escape | ||||
| #: sequence for '\' is also included since it must also be escaped in | ||||
| #: certain situations. | ||||
| JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', | ||||
|                               '"': '\\22', | ||||
|                               '&': '\\26', | ||||
|                               "'": '\\27', | ||||
|                               '/': '\\2f', | ||||
|                               ':': '\\3a', | ||||
|                               '<': '\\3c', | ||||
|                               '>': '\\3e', | ||||
|                               '@': '\\40', | ||||
|                               '\\': '\\5c'} | ||||
|  | ||||
| #: The reverse mapping of escape sequences to their original forms. | ||||
| JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
|                                 '\\22': '"', | ||||
|                                 '\\26': '&', | ||||
|                                 '\\27': "'", | ||||
|                                 '\\2f': '/', | ||||
|                                 '\\3a': ':', | ||||
|                                 '\\3c': '<', | ||||
|                                 '\\3e': '>', | ||||
|                                 '\\40': '@', | ||||
|                                 '\\5c': '\\'} | ||||
|  | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The nodeprep profile of stringprep used to validate the local, | ||||
| #: or username, portion of a JID. | ||||
| nodeprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[ | ||||
|         stringprep_profiles.b1_mapping, | ||||
|         stringprep_profiles.c12_mapping], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c11, | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9, | ||||
|         lambda c: c in ' \'"&/:<>@'], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The resourceprep profile of stringprep, which is used to validate | ||||
| #: the resource portion of a JID. | ||||
| resourceprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[stringprep_profiles.b1_mapping], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
|  | ||||
| def _parse_jid(data): | ||||
|     """ | ||||
|     Parse string data into the node, domain, and resource | ||||
|     components of a JID, if possible. | ||||
|  | ||||
|     :param string data: A string that is potentially a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: tuple of the validated local, domain, and resource strings | ||||
|     """ | ||||
|     match = JID_PATTERN.match(data) | ||||
|     if not match: | ||||
|         raise InvalidJID('JID could not be parsed') | ||||
|  | ||||
|     (node, domain, resource) = match.groups() | ||||
|  | ||||
|     node = _validate_node(node) | ||||
|     domain = _validate_domain(domain) | ||||
|     resource = _validate_resource(resource) | ||||
|  | ||||
|     return node, domain, resource | ||||
|  | ||||
|  | ||||
| def _validate_node(node): | ||||
|     """Validate the local, or username, portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by nodeprep. | ||||
|     """ | ||||
|     try: | ||||
|         if node is not None: | ||||
|             node = nodeprep(node) | ||||
|  | ||||
|             if not node: | ||||
|                 raise InvalidJID('Localpart must not be 0 bytes') | ||||
|             if len(node) > 1023: | ||||
|                 raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|             return node | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid local part') | ||||
|  | ||||
|  | ||||
| def _validate_domain(domain): | ||||
|     """Validate the domain portion of a JID. | ||||
|  | ||||
|     IP literal addresses are left as-is, if valid. Domain names | ||||
|     are stripped of any trailing label separators (`.`), and are | ||||
|     checked with the nameprep profile of stringprep. If the given | ||||
|     domain is actually a punyencoded version of a domain name, it | ||||
|     is converted back into its original Unicode form. Domains must | ||||
|     also not start or end with a dash (`-`). | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The validated domain name | ||||
|     """ | ||||
|     ip_addr = False | ||||
|  | ||||
|     # First, check if this is an IPv4 address | ||||
|     try: | ||||
|         socket.inet_aton(domain) | ||||
|         ip_addr = True | ||||
|     except socket.error: | ||||
|         pass | ||||
|  | ||||
|     # Check if this is an IPv6 address | ||||
|     if not ip_addr and hasattr(socket, 'inet_pton'): | ||||
|         try: | ||||
|             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||
|             domain = '[%s]' % domain.strip('[]') | ||||
|             ip_addr = True | ||||
|         except socket.error: | ||||
|             pass | ||||
|  | ||||
|     if not ip_addr: | ||||
|         # This is a domain name, which must be checked further | ||||
|  | ||||
|         if domain and domain[-1] == '.': | ||||
|             domain = domain[:-1] | ||||
|  | ||||
|         domain_parts = [] | ||||
|         for label in domain.split('.'): | ||||
|             try: | ||||
|                 label = encodings.idna.nameprep(label) | ||||
|                 encodings.idna.ToASCII(label) | ||||
|                 pass_nameprep = True | ||||
|             except UnicodeError: | ||||
|                 pass_nameprep = False | ||||
|  | ||||
|             if not pass_nameprep: | ||||
|                 raise InvalidJID('Could not encode domain as ASCII') | ||||
|  | ||||
|             if label.startswith('xn--'): | ||||
|                 label = encodings.idna.ToUnicode(label) | ||||
|  | ||||
|             for char in label: | ||||
|                 if char in ILLEGAL_CHARS: | ||||
|                     raise InvalidJID('Domain contains illegar characters') | ||||
|  | ||||
|             if '-' in (label[0], label[-1]): | ||||
|                 raise InvalidJID('Domain started or ended with -') | ||||
|  | ||||
|             domain_parts.append(label) | ||||
|         domain = '.'.join(domain_parts) | ||||
|  | ||||
|     if not domain: | ||||
|         raise InvalidJID('Domain must not be 0 bytes') | ||||
|     if len(domain) > 1023: | ||||
|         raise InvalidJID('Domain must be less than 1024 bytes') | ||||
|  | ||||
|     return domain | ||||
|  | ||||
|  | ||||
| def _validate_resource(resource): | ||||
|     """Validate the resource portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by resourceprep. | ||||
|     """ | ||||
|     try: | ||||
|         if resource is not None: | ||||
|             resource = resourceprep(resource) | ||||
|  | ||||
|             if not resource: | ||||
|                 raise InvalidJID('Resource must not be 0 bytes') | ||||
|             if len(resource) > 1023: | ||||
|                 raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|             return resource | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid resource') | ||||
|  | ||||
|  | ||||
| def _escape_node(node): | ||||
|     """Escape the local portion of a JID.""" | ||||
|     result = [] | ||||
|  | ||||
|     for i, char in enumerate(node): | ||||
|         if char == '\\': | ||||
|             if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: | ||||
|                 result.append('\\5c') | ||||
|                 continue | ||||
|         result.append(char) | ||||
|  | ||||
|     for i, char in enumerate(result): | ||||
|         if char != '\\': | ||||
|             result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) | ||||
|  | ||||
|     escaped = ''.join(result) | ||||
|  | ||||
|     if escaped.startswith('\\20') or escaped.endswith('\\20'): | ||||
|         raise InvalidJID('Escaped local part starts or ends with "\\20"') | ||||
|  | ||||
|     _validate_node(escaped) | ||||
|  | ||||
|     return escaped | ||||
|  | ||||
|  | ||||
| def _unescape_node(node): | ||||
|     """Unescape a local portion of a JID. | ||||
|  | ||||
|     .. note:: | ||||
|         The unescaped local portion is meant ONLY for presentation, | ||||
|         and should not be used for other purposes. | ||||
|     """ | ||||
|     unescaped = [] | ||||
|     seq = '' | ||||
|     for i, char in enumerate(node): | ||||
|         if char == '\\': | ||||
|             seq = node[i:i+3] | ||||
|             if seq not in JID_ESCAPE_SEQUENCES: | ||||
|                 seq = '' | ||||
|         if seq: | ||||
|             if len(seq) == 3: | ||||
|                 unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char)) | ||||
|  | ||||
|             # Pop character off the escape sequence, and ignore it | ||||
|             seq = seq[1:] | ||||
|         else: | ||||
|             unescaped.append(char) | ||||
|     unescaped = ''.join(unescaped) | ||||
|  | ||||
|     return unescaped | ||||
|  | ||||
|  | ||||
| def _format_jid(local=None, domain=None, resource=None): | ||||
|     """Format the given JID components into a full or bare JID. | ||||
|  | ||||
|     :param string local: Optional. The local portion of the JID. | ||||
|     :param string domain: Required. The domain name portion of the JID. | ||||
|     :param strin resource: Optional. The resource portion of the JID. | ||||
|  | ||||
|     :return: A full or bare JID string. | ||||
|     """ | ||||
|     result = [] | ||||
|     if local: | ||||
|         result.append(local) | ||||
|         result.append('@') | ||||
|     if domain: | ||||
|         result.append(domain) | ||||
|     if resource: | ||||
|         result.append('/') | ||||
|         result.append(resource) | ||||
|     return ''.join(result) | ||||
|  | ||||
|  | ||||
| class InvalidJID(ValueError): | ||||
|     """ | ||||
|     Raised when attempting to create a JID that does not pass validation. | ||||
|  | ||||
|     It can also be raised if modifying an existing JID in such a way as | ||||
|     to make it invalid, such trying to remove the domain from an existing | ||||
|     full JID while the local and resource portions still exist. | ||||
|     """ | ||||
|  | ||||
| # pylint: disable=R0903 | ||||
| class UnescapedJID(object): | ||||
|  | ||||
|     """ | ||||
|     .. versionadded:: 1.1.10 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, local, domain, resource): | ||||
|         self._jid = (local, domain, resource) | ||||
|  | ||||
|     # pylint: disable=R0911 | ||||
|     def __getattr__(self, name): | ||||
|         """Retrieve the given JID component. | ||||
|  | ||||
|         :param name: one of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             return self._jid[2] or '' | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             return self._jid[0] or '' | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             return self._jid[1] or '' | ||||
|         elif name in ('full', 'jid'): | ||||
|             return _format_jid(*self._jid) | ||||
|         elif name == 'bare': | ||||
|             return _format_jid(self._jid[0], self._jid[1]) | ||||
|         elif name == '_jid': | ||||
|             return getattr(super(JID, self), '_jid') | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
|  | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
|  | ||||
|     Each JID may have three components: a user, a domain, and an optional | ||||
|     resource. For example: user@domain/resource | ||||
|  | ||||
|     When a resource is not used, the JID is called a bare JID. | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     **JID Properties:** | ||||
|         :jid: Alias for ``full``. | ||||
|         :full: The string value of the full JID. | ||||
|         :bare: The string value of the bare JID. | ||||
|         :user: The username portion of the JID. | ||||
|         :username: Alias for ``user``. | ||||
|         :local: Alias for ``user``. | ||||
|         :node: Alias for ``user``. | ||||
|         :domain: The domain name portion of the JID. | ||||
|         :server: Alias for ``domain``. | ||||
|         :host: Alias for ``domain``. | ||||
|         :resource: The resource portion of the JID. | ||||
|  | ||||
|     :param string jid: | ||||
|         A string of the form ``'[user@]domain[/resource]'``. | ||||
|     :param string local: | ||||
|         Optional. Specify the local, or username, portion | ||||
|         of the JID. If provided, it will override the local | ||||
|         value provided by the `jid` parameter. The given | ||||
|         local value will also be escaped if necessary. | ||||
|     :param string domain: | ||||
|         Optional. Specify the domain of the JID. If | ||||
|         provided, it will override the domain given by | ||||
|         the `jid` parameter. | ||||
|     :param string resource: | ||||
|         Optional. Specify the resource value of the JID. | ||||
|         If provided, it will override the domain given | ||||
|         by the `jid` parameter. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|     """ | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __init__(self, jid=None, **kwargs): | ||||
|         self._jid = (None, None, None) | ||||
|  | ||||
|         if jid is None or jid == '': | ||||
|             jid = (None, None, None) | ||||
|         elif not isinstance(jid, JID): | ||||
|             jid = _parse_jid(jid) | ||||
|         else: | ||||
|             jid = jid._jid | ||||
|  | ||||
|         local, domain, resource = jid | ||||
|  | ||||
|         local = kwargs.get('local', local) | ||||
|         domain = kwargs.get('domain', domain) | ||||
|         resource = kwargs.get('resource', resource) | ||||
|  | ||||
|         if 'local' in kwargs: | ||||
|             local = _escape_node(local) | ||||
|         if 'domain' in kwargs: | ||||
|             domain = _validate_domain(domain) | ||||
|         if 'resource' in kwargs: | ||||
|             resource = _validate_resource(resource) | ||||
|  | ||||
|         self._jid = (local, domain, resource) | ||||
|  | ||||
|     def unescape(self): | ||||
|         """Return an unescaped JID object. | ||||
|  | ||||
|         Using an unescaped JID is preferred for displaying JIDs | ||||
|         to humans, and they should NOT be used for any other | ||||
|         purposes than for presentation. | ||||
|  | ||||
|         :return: :class:`UnescapedJID` | ||||
|  | ||||
|         .. versionadded:: 1.1.10 | ||||
|         """ | ||||
|         return UnescapedJID(_unescape_node(self._jid[0]), | ||||
|                             self._jid[1], | ||||
|                             self._jid[2]) | ||||
|  | ||||
|     def regenerate(self): | ||||
|         """No-op | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def reset(self, data): | ||||
|         """Start fresh from a new JID string. | ||||
|  | ||||
|         :param string data: A string of the form ``'[user@]domain[/resource]'``. | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         self._jid = JID(data)._jid | ||||
|  | ||||
|     # pylint: disable=R0911 | ||||
|     def __getattr__(self, name): | ||||
|         """Retrieve the given JID component. | ||||
|  | ||||
|         :param name: one of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             return self._jid[2] or '' | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             return self._jid[0] or '' | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             return self._jid[1] or '' | ||||
|         elif name in ('full', 'jid'): | ||||
|             return _format_jid(*self._jid) | ||||
|         elif name == 'bare': | ||||
|             return _format_jid(self._jid[0], self._jid[1]) | ||||
|         elif name == '_jid': | ||||
|             return getattr(super(JID, self), '_jid') | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __setattr__(self, name, value): | ||||
|         """Update the given JID component. | ||||
|  | ||||
|         :param name: one of: ``user``, ``username``, ``local``, | ||||
|                              ``node``, ``server``, ``domain``, ``host``, | ||||
|                              ``resource``, ``full``, ``jid``, or ``bare``. | ||||
|         :param value: The new string value of the JID component. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             self._jid = JID(self, resource=value)._jid | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             self._jid = JID(self, local=value)._jid | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             self._jid = JID(self, domain=value)._jid | ||||
|         elif name in ('full', 'jid'): | ||||
|             self._jid = JID(value)._jid | ||||
|         elif name == 'bare': | ||||
|             parsed = JID(value)._jid | ||||
|             self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||
|         elif name == '_jid': | ||||
|             super(JID, self).__setattr__('_jid', value) | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __eq__(self, other): | ||||
|         """Two JIDs are equal if they have the same full JID value.""" | ||||
|         if isinstance(other, UnescapedJID): | ||||
|             return False | ||||
|  | ||||
|         other = JID(other) | ||||
|         return self._jid == other._jid | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __ne__(self, other): | ||||
|         """Two JIDs are considered unequal if they are not equal.""" | ||||
|         return not self == other | ||||
|  | ||||
|     def __hash__(self): | ||||
|         """Hash a JID based on the string version of its full JID.""" | ||||
|         return hash(self.__str__()) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(self) | ||||
| @@ -36,23 +36,32 @@ __all__ = [ | ||||
|     'xep_0085',  # Chat State Notifications | ||||
|     'xep_0086',  # Legacy Error Codes | ||||
|     'xep_0092',  # Software Version | ||||
|     'xep_0106',  # JID Escaping | ||||
|     'xep_0107',  # User Mood | ||||
|     'xep_0108',  # User Activity | ||||
|     'xep_0115',  # Entity Capabilities | ||||
|     'xep_0118',  # User Tune | ||||
|     'xep_0128',  # Extended Service Discovery | ||||
|     'xep_0131',  # Standard Headers and Internet Metadata | ||||
|     'xep_0133',  # Service Administration | ||||
|     'xep_0153',  # vCard-Based Avatars | ||||
|     'xep_0163',  # Personal Eventing Protocol | ||||
|     'xep_0172',  # User Nickname | ||||
|     'xep_0184',  # Message Receipts | ||||
|     'xep_0186',  # Invisible Command | ||||
|     'xep_0191',  # Blocking Command | ||||
|     'xep_0198',  # Stream Management | ||||
|     'xep_0199',  # Ping | ||||
|     'xep_0202',  # Entity Time | ||||
|     'xep_0203',  # Delayed Delivery | ||||
|     'xep_0221',  # Data Forms Media Element | ||||
|     'xep_0222',  # Persistent Storage of Public Data via Pubsub | ||||
|     'xep_0223',  # Persistent Storage of Private Data via Pubsub | ||||
|     'xep_0224',  # Attention | ||||
|     'xep_0231',  # Bits of Binary | ||||
|     'xep_0249',  # Direct MUC Invitations | ||||
|     'xep_0256',  # Last Activity in Presence | ||||
|     'xep_0258',  # Security Labels in XMPP | ||||
|     'xep_0270',  # XMPP Compliance Suites 2010 | ||||
|     'xep_0302',  # XMPP Compliance Suites 2012 | ||||
| ] | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| @@ -167,8 +168,7 @@ class PluginManager(object): | ||||
|                 self._plugins[name] = plugin | ||||
|                 for dep in plugin.dependencies: | ||||
|                     self.enable(dep, enabled=enabled) | ||||
|                 plugin.plugin_init() | ||||
|                 log.debug("Loaded Plugin: %s", plugin.description) | ||||
|                 plugin._init() | ||||
|  | ||||
|         if top_level: | ||||
|             for name in enabled: | ||||
| @@ -229,7 +229,7 @@ class PluginManager(object): | ||||
|                     raise PluginNotFound(name) | ||||
|                 for dep in PLUGIN_DEPENDENTS[name]: | ||||
|                     self.disable(dep, _disabled) | ||||
|                 plugin.plugin_end() | ||||
|                 plugin._end() | ||||
|                 if name in self._enabled: | ||||
|                     self._enabled.remove(name) | ||||
|                 del self._plugins[name] | ||||
| @@ -273,6 +273,14 @@ class BasePlugin(object): | ||||
|     #: be initialized as needed if this plugin is enabled. | ||||
|     dependencies = set() | ||||
|  | ||||
|     #: The basic, standard configuration for the plugin, which may | ||||
|     #: be overridden when initializing the plugin. The configuration | ||||
|     #: fields included here may be accessed directly as attributes of | ||||
|     #: the plugin. For example, including the configuration field 'foo' | ||||
|     #: would mean accessing `plugin.foo` returns the current value of | ||||
|     #: `plugin.config['foo']`. | ||||
|     default_config = {} | ||||
|  | ||||
|     def __init__(self, xmpp, config=None): | ||||
|         self.xmpp = xmpp | ||||
|         if self.xmpp: | ||||
| @@ -280,7 +288,54 @@ class BasePlugin(object): | ||||
|  | ||||
|         #: A plugin's behaviour may be configurable, in which case those | ||||
|         #: configuration settings will be provided as a dictionary. | ||||
|         self.config = config if config is not None else {} | ||||
|         self.config = copy.copy(self.default_config) | ||||
|         if config: | ||||
|             self.config.update(config) | ||||
|  | ||||
|     def __getattr__(self, key): | ||||
|         """Provide direct access to configuration fields. | ||||
|  | ||||
|         If the standard configuration includes the option `'foo'`, then | ||||
|         accessing `self.foo` should be the same as `self.config['foo']`. | ||||
|         """ | ||||
|         if key in self.default_config: | ||||
|             return self.config.get(key, None) | ||||
|         else: | ||||
|             return object.__getattribute__(self, key) | ||||
|  | ||||
|     def __setattr__(self, key, value): | ||||
|         """Provide direct assignment to configuration fields. | ||||
|  | ||||
|         If the standard configuration includes the option `'foo'`, then | ||||
|         assigning to `self.foo` should be the same as assigning to | ||||
|         `self.config['foo']`. | ||||
|         """ | ||||
|         if key in self.default_config: | ||||
|             self.config[key] = value | ||||
|         else: | ||||
|             super(BasePlugin, self).__setattr__(key, value) | ||||
|  | ||||
|     def _init(self): | ||||
|         """Initialize plugin state, such as registering event handlers. | ||||
|  | ||||
|         Also sets up required event handlers. | ||||
|         """ | ||||
|         if self.xmpp is not None: | ||||
|             self.xmpp.add_event_handler('session_bind', self.session_bind) | ||||
|             if self.xmpp.session_bind_event.is_set(): | ||||
|                 self.session_bind(self.xmpp.boundjid.full) | ||||
|         self.plugin_init() | ||||
|         log.debug('Loaded Plugin: %s', self.description) | ||||
|  | ||||
|     def _end(self): | ||||
|         """Cleanup plugin state, and prepare for plugin removal. | ||||
|  | ||||
|         Also removes required event handlers. | ||||
|         """ | ||||
|         if self.xmpp is not None: | ||||
|             self.xmpp.del_event_handler('session_bind', self.session_bind) | ||||
|         self.plugin_end() | ||||
|         log.debug('Disabled Plugin: %s' % self.description) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Initialize plugin state, such as registering event handlers.""" | ||||
| @@ -290,6 +345,10 @@ class BasePlugin(object): | ||||
|         """Cleanup plugin state, and prepare for plugin removal.""" | ||||
|         pass | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         """Initialize plugin state based on the bound JID.""" | ||||
|         pass | ||||
|  | ||||
|     def post_init(self): | ||||
|         """Initialize any cross-plugin state. | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class XEP_0004(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.registerHandler( | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Data Form', | ||||
|                  StanzaPath('message/form'), | ||||
|                  self.handle_form)) | ||||
| @@ -36,6 +36,11 @@ class XEP_0004(BasePlugin): | ||||
|         register_stanza_plugin(Form, FormField, iterable=True) | ||||
|         register_stanza_plugin(Message, Form) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Data Form') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jabber:x:data') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('jabber:x:data') | ||||
|  | ||||
|     def make_form(self, ftype='form', title='', instructions=''): | ||||
|   | ||||
| @@ -37,13 +37,11 @@ class XEP_0012(BasePlugin): | ||||
|  | ||||
|         self._last_activities = {} | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Last Activity', | ||||
|                  StanzaPath('iq@type=get/last_activity'), | ||||
|                  self._handle_get_last_activity)) | ||||
|  | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') | ||||
|  | ||||
|         self.api.register(self._default_get_last_activity, | ||||
|                 'get_last_activity', | ||||
|                 default=True) | ||||
| @@ -54,6 +52,13 @@ class XEP_0012(BasePlugin): | ||||
|                 'del_last_activity', | ||||
|                 default=True) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Last Activity') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('jabber:iq:last') | ||||
|  | ||||
|     def begin_idle(self, jid=None, status=None): | ||||
|         self.set_last_activity(jid, 0, status) | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ def _extract_data(data, kind): | ||||
|         if not begin_headers and 'BEGIN PGP %s' % kind in line: | ||||
|             begin_headers = True | ||||
|             continue | ||||
|         if begin_headers and line == '': | ||||
|         if begin_headers and line.stripped() == '': | ||||
|             begin_data = True | ||||
|             continue | ||||
|         if 'END PGP %s' % kind in line: | ||||
| @@ -40,14 +40,15 @@ class XEP_0027(BasePlugin): | ||||
|     description = 'XEP-0027: Current Jabber OpenPGP Usage' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'gpg_binary': 'gpg', | ||||
|         'gpg_home': '', | ||||
|         'use_agent': True, | ||||
|         'keyring': None, | ||||
|         'key_server': 'pgp.mit.edu' | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.gpg_binary = self.config.get('gpg_binary', 'gpg') | ||||
|         self.gpg_home = self.config.get('gpg_home', '') | ||||
|         self.use_agent = self.config.get('use_agent', True) | ||||
|         self.keyring = self.config.get('keyring', None) | ||||
|         self.key_server = self.config.get('key_server', 'pgp.mit.edu') | ||||
|  | ||||
|         self.gpg = GPG(gnupghome=self.gpg_home, | ||||
|                        gpgbinary=self.gpg_binary, | ||||
|                        use_agent=self.use_agent, | ||||
| @@ -79,6 +80,13 @@ class XEP_0027(BasePlugin): | ||||
|                     StanzaPath('message/encrypted'), | ||||
|                     self._handle_encrypted_message)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Encrypted Message') | ||||
|         self.xmpp.remove_handler('Signed Presence') | ||||
|         self.xmpp.del_filter('out', self._sign_presence) | ||||
|         self.xmpp.del_event_handler('unverified_signed_presence', | ||||
|                 self._handle_unverified_signed_presence) | ||||
|  | ||||
|     def _sign_presence(self, stanza): | ||||
|         if isinstance(stanza, Presence): | ||||
|             if stanza['type'] == 'available' or \ | ||||
|   | ||||
| @@ -88,6 +88,10 @@ class XEP_0030(BasePlugin): | ||||
|     description = 'XEP-0030: Service Discovery' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'use_cache': True, | ||||
|         'wrap_results': False | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """ | ||||
| @@ -108,9 +112,6 @@ class XEP_0030(BasePlugin): | ||||
|  | ||||
|         self.static = StaticDisco(self.xmpp, self) | ||||
|  | ||||
|         self.use_cache = self.config.get('use_cache', True) | ||||
|         self.wrap_results = self.config.get('wrap_results', False) | ||||
|  | ||||
|         self._disco_ops = [ | ||||
|                 'get_info', 'set_info', 'set_identities', 'set_features', | ||||
|                 'get_items', 'set_items', 'del_items', 'add_identity', | ||||
| @@ -622,11 +623,7 @@ class XEP_0030(BasePlugin): | ||||
|         if iq['type'] == 'get': | ||||
|             log.debug("Received disco info query from " + \ | ||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||
|             if self.xmpp.is_component: | ||||
|                 jid = iq['to'].full | ||||
|             else: | ||||
|                 jid = iq['to'].bare | ||||
|             info = self.api['get_info'](jid, | ||||
|             info = self.api['get_info'](iq['to'], | ||||
|                                         iq['disco_info']['node'], | ||||
|                                         iq['from'], | ||||
|                                         iq) | ||||
| @@ -649,7 +646,7 @@ class XEP_0030(BasePlugin): | ||||
|                     ito = iq['to'].full | ||||
|                 else: | ||||
|                     ito = None | ||||
|                 self.api['cache_info'](iq['from'].full, | ||||
|                 self.api['cache_info'](iq['from'], | ||||
|                                        iq['disco_info']['node'], | ||||
|                                        ito, | ||||
|                                        iq) | ||||
| @@ -667,13 +664,9 @@ class XEP_0030(BasePlugin): | ||||
|         if iq['type'] == 'get': | ||||
|             log.debug("Received disco items query from " + \ | ||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||
|             if self.xmpp.is_component: | ||||
|                 jid = iq['to'].full | ||||
|             else: | ||||
|                 jid = iq['to'].bare | ||||
|             items = self.api['get_items'](jid, | ||||
|             items = self.api['get_items'](iq['to'], | ||||
|                                           iq['disco_items']['node'], | ||||
|                                           iq['from'].full, | ||||
|                                           iq['from'], | ||||
|                                           iq) | ||||
|             if isinstance(items, Iq): | ||||
|                 items.send() | ||||
|   | ||||
| @@ -237,7 +237,7 @@ class StaticDisco(object): | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node): | ||||
|                 if not node: | ||||
|                     return DiscoInfo() | ||||
|                     return DiscoItems() | ||||
|                 else: | ||||
|                     raise XMPPError(condition='item-not-found') | ||||
|             else: | ||||
| @@ -424,9 +424,6 @@ class StaticDisco(object): | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if isinstance(jid, JID): | ||||
|                 jid = jid.full | ||||
|  | ||||
|             if not self.node_exists(jid, node, ifrom): | ||||
|                 return None | ||||
|             else: | ||||
|   | ||||
| @@ -26,7 +26,12 @@ class XEP_0033(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
|         register_stanza_plugin(Message, Addresses) | ||||
|         register_stanza_plugin(Presence, Addresses) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
|   | ||||
| @@ -137,7 +137,7 @@ class XEP_0045(BasePlugin): | ||||
|     def handle_groupchat_invite(self, inv): | ||||
|         """ Handle an invite into a muc. | ||||
|         """ | ||||
|         logging.debug("MUC invite to %s from %s: %s", inv['from'], inv["from"], inv) | ||||
|         logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv) | ||||
|         if inv['from'] not in self.rooms.keys(): | ||||
|             self.xmpp.event("groupchat_invite", inv) | ||||
|  | ||||
|   | ||||
| @@ -20,18 +20,19 @@ class XEP_0047(BasePlugin): | ||||
|     description = 'XEP-0047: In-band Bytestreams' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'max_block_size': 8192, | ||||
|         'window_size': 1, | ||||
|         'auto_accept': True, | ||||
|         'accept_stream': None | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.streams = {} | ||||
|         self.pending_streams = {3: 5} | ||||
|         self.pending_streams = {} | ||||
|         self.pending_close_streams = {} | ||||
|         self._stream_lock = threading.Lock() | ||||
|  | ||||
|         self.max_block_size = self.config.get('max_block_size', 8192) | ||||
|         self.window_size = self.config.get('window_size', 1) | ||||
|         self.auto_accept = self.config.get('auto_accept', True) | ||||
|         self.accept_stream = self.config.get('accept_stream', None) | ||||
|  | ||||
|         register_stanza_plugin(Iq, Open) | ||||
|         register_stanza_plugin(Iq, Close) | ||||
|         register_stanza_plugin(Iq, Data) | ||||
| @@ -51,6 +52,13 @@ class XEP_0047(BasePlugin): | ||||
|             StanzaPath('iq@type=set/ibb_data'), | ||||
|             self._handle_data)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('IBB Open') | ||||
|         self.xmpp.remove_handler('IBB Close') | ||||
|         self.xmpp.remove_handler('IBB Data') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') | ||||
|  | ||||
|     def _accept_stream(self, iq): | ||||
|   | ||||
| @@ -1,11 +1,8 @@ | ||||
| import socket | ||||
| import threading | ||||
| import logging | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| from sleekxmpp.util import Queue | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| @@ -33,7 +30,7 @@ class IBBytestream(object): | ||||
|         self.stream_in_closed = threading.Event() | ||||
|         self.stream_out_closed = threading.Event() | ||||
|  | ||||
|         self.recv_queue = queue.Queue() | ||||
|         self.recv_queue = Queue() | ||||
|  | ||||
|         self.send_window = threading.BoundedSemaphore(value=self.window_size) | ||||
|         self.window_ids = set() | ||||
|   | ||||
| @@ -82,12 +82,18 @@ class XEP_0050(BasePlugin): | ||||
|     description = 'XEP-0050: Ad-Hoc Commands' | ||||
|     dependencies = set(['xep_0030', 'xep_0004']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'threaded': True, | ||||
|         'session_db': None | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Start the XEP-0050 plugin.""" | ||||
|         self.threaded = self.config.get('threaded', True) | ||||
|         self.sessions = self.session_db | ||||
|         if self.sessions is None: | ||||
|             self.sessions = {} | ||||
|  | ||||
|         self.commands = {} | ||||
|         self.sessions = self.config.get('session_db', {}) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback("Ad-Hoc Execute", | ||||
| @@ -110,6 +116,20 @@ class XEP_0050(BasePlugin): | ||||
|                                     self._handle_command_complete, | ||||
|                                     threaded=self.threaded) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.del_event_handler('command_execute', | ||||
|                                     self._handle_command_start) | ||||
|         self.xmpp.del_event_handler('command_next', | ||||
|                                     self._handle_command_next) | ||||
|         self.xmpp.del_event_handler('command_cancel', | ||||
|                                     self._handle_command_cancel) | ||||
|         self.xmpp.del_event_handler('command_complete', | ||||
|                                     self._handle_command_complete) | ||||
|         self.xmpp.remove_handler('Ad-Hoc Execute') | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Command.namespace) | ||||
|         self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Command.namespace) | ||||
|         self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,6 @@ class XEP_0054(BasePlugin): | ||||
|         """ | ||||
|         register_stanza_plugin(Iq, VCardTemp) | ||||
|  | ||||
|         self.xmpp['xep_0030'].add_feature('vcard-temp') | ||||
|  | ||||
|         self.api.register(self._set_vcard, 'set_vcard', default=True) | ||||
|         self.api.register(self._get_vcard, 'get_vcard', default=True) | ||||
| @@ -50,6 +49,13 @@ class XEP_0054(BasePlugin): | ||||
|                     StanzaPath('iq/vcard_temp'), | ||||
|                     self._handle_get_vcard)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('VCardTemp') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='vcard-temp') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('vcard-temp') | ||||
|  | ||||
|     def make_vcard(self): | ||||
|         return VCardTemp() | ||||
|  | ||||
|   | ||||
| @@ -47,6 +47,7 @@ class ResultIterator(): | ||||
|         self.start = start | ||||
|         self.interface = interface | ||||
|         self.reverse = reverse | ||||
|         self._stop = False | ||||
|  | ||||
|     def __iter__(self): | ||||
|         return self | ||||
| @@ -62,6 +63,8 @@ class ResultIterator(): | ||||
|               results will be the items before the current page | ||||
|               of items. | ||||
|         """ | ||||
|         if self._stop: | ||||
|             raise StopIteration | ||||
|         self.query[self.interface]['rsm']['before'] = self.reverse | ||||
|         self.query['id'] = self.query.stream.new_id() | ||||
|         self.query[self.interface]['rsm']['max'] = str(self.amount) | ||||
| @@ -84,7 +87,7 @@ class ResultIterator(): | ||||
|                 first = int(r[self.interface]['rsm']['first_index']) | ||||
|                 num_items = len(r[self.interface]['substanzas']) | ||||
|                 if first + num_items == count: | ||||
|                     raise StopIteration | ||||
|                     self._stop = True | ||||
|  | ||||
|             if self.reverse: | ||||
|                 self.start = r[self.interface]['rsm']['first'] | ||||
| @@ -111,10 +114,15 @@ class XEP_0059(BasePlugin): | ||||
|         """ | ||||
|         Start the XEP-0059 plugin. | ||||
|         """ | ||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) | ||||
|         register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems, | ||||
|                                self.stanza.Set) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Set.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) | ||||
|  | ||||
|     def iterate(self, stanza, interface): | ||||
|         """ | ||||
|         Create a new result set iterator for a given stanza query. | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class XEP_0060(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0060' | ||||
|     description = 'XEP-0060: Publish-Subscribe' | ||||
|     dependencies = set(['xep_0030', 'xep_0004']) | ||||
|     dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
| @@ -53,6 +53,15 @@ class XEP_0060(BasePlugin): | ||||
|                     StanzaPath('message/pubsub_event/subscription'), | ||||
|                     self._handle_event_subscription)) | ||||
|  | ||||
|         self.xmpp['xep_0131'].supported_headers.add('SubID') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Pubsub Event: Items') | ||||
|         self.xmpp.remove_handler('Pubsub Event: Purge') | ||||
|         self.xmpp.remove_handler('Pubsub Event: Delete') | ||||
|         self.xmpp.remove_handler('Pubsub Event: Configuration') | ||||
|         self.xmpp.remove_handler('Pubsub Event: Subscription') | ||||
|  | ||||
|     def _handle_event_items(self, msg): | ||||
|         """Raise events for publish and retraction notifications.""" | ||||
|         node = msg['pubsub_event']['items']['node'] | ||||
|   | ||||
| @@ -62,6 +62,12 @@ class XEP_0066(BasePlugin): | ||||
|                          StanzaPath('iq@type=set/oob_transfer'), | ||||
|                          self._handle_transfer)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('OOB Transfer') | ||||
|         self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace) | ||||
|         self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) | ||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) | ||||
|  | ||||
|   | ||||
| @@ -27,24 +27,28 @@ class XEP_0077(BasePlugin): | ||||
|     description = 'XEP-0077: In-Band Registration' | ||||
|     dependencies = set(['xep_0004', 'xep_0066']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'create_account': True, | ||||
|         'order': 50 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.create_account = self.config.get('create_account', True) | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, RegisterFeature) | ||||
|         register_stanza_plugin(Iq, Register) | ||||
|  | ||||
|         if self.xmpp.is_component: | ||||
|             pass | ||||
|         else: | ||||
|         if not self.xmpp.is_component: | ||||
|             self.xmpp.register_feature('register', | ||||
|                 self._handle_register_feature, | ||||
|                 restart=False, | ||||
|                 order=self.config.get('order', 50)) | ||||
|                 order=self.order) | ||||
|  | ||||
|         register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) | ||||
|         register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         if not self.xmpp.is_component: | ||||
|             self.xmpp.unregister_feature('register', self.order) | ||||
|  | ||||
|     def _handle_register_feature(self, features): | ||||
|         if 'mechanisms' in self.xmpp.features: | ||||
|             # We have already logged in with an account | ||||
|   | ||||
| @@ -34,16 +34,22 @@ class XEP_0078(BasePlugin): | ||||
|     description = 'XEP-0078: Non-SASL Authentication' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'order': 15 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_feature('auth', | ||||
|                 self._handle_auth, | ||||
|                 restart=False, | ||||
|                 order=self.config.get('order', 15)) | ||||
|                 order=self.order) | ||||
|  | ||||
|         register_stanza_plugin(Iq, stanza.IqAuth) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.AuthFeature) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.unregister_feature('auth', self.order) | ||||
|  | ||||
|     def _handle_auth(self, features): | ||||
|         # If we can or have already authenticated with SASL, do nothing. | ||||
|         if 'mechanisms' in features['features']: | ||||
|   | ||||
| @@ -28,8 +28,11 @@ class XEP_0080(BasePlugin): | ||||
|     dependencies = set(['xep_0163']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Start the XEP-0080 plugin.""" | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0163'].remove_interest(Geoloc.namespace) | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('user_location', Geoloc) | ||||
|  | ||||
|     def publish_location(self, **kwargs): | ||||
|   | ||||
| @@ -28,14 +28,22 @@ class XEP_0084(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) | ||||
|  | ||||
|         pubsub_stanza = self.xmpp['xep_0060'].stanza | ||||
|         register_stanza_plugin(pubsub_stanza.Item, Data) | ||||
|         register_stanza_plugin(pubsub_stanza.EventItem, Data) | ||||
|  | ||||
|         self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace) | ||||
|         self.xmpp['xep_0163'].remove_interest(MetaData.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) | ||||
|  | ||||
|     def generate_id(self, data): | ||||
|         return hashlib.sha1(data).hexdigest() | ||||
|  | ||||
|     def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True, | ||||
|                               callback=None, timeout=None): | ||||
|         return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id, | ||||
| @@ -49,8 +57,7 @@ class XEP_0084(BasePlugin): | ||||
|         payload = Data() | ||||
|         payload['value'] = data | ||||
|         return self.xmpp['xep_0163'].publish(payload, | ||||
|                 node=Data.namespace, | ||||
|                 id=hashlib.sha1(data).hexdigest(), | ||||
|                 id=self.generate_id(data), | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
| @@ -67,12 +74,12 @@ class XEP_0084(BasePlugin): | ||||
|                     height=info.get('height', ''), | ||||
|                     width=info.get('width', ''), | ||||
|                     url=info.get('url', '')) | ||||
|         for pointer in pointers: | ||||
|             metadata.add_pointer(pointer) | ||||
|  | ||||
|         return self.xmpp['xep_0163'].publish(payload, | ||||
|                 node=Data.namespace, | ||||
|                 id=hashlib.sha1(data).hexdigest(), | ||||
|         if pointers is not None: | ||||
|             for pointer in pointers: | ||||
|                 metadata.add_pointer(pointer) | ||||
|  | ||||
|         return self.xmpp['xep_0163'].publish(metadata, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block, | ||||
|                 callback=callback, | ||||
|   | ||||
| @@ -43,7 +43,7 @@ class MetaData(ElementBase): | ||||
|         info = Info() | ||||
|         info.values = {'id': id, | ||||
|                        'type': itype, | ||||
|                        'bytes': ibytes, | ||||
|                        'bytes': '%s' % ibytes, | ||||
|                        'height': height, | ||||
|                        'width': width, | ||||
|                        'url': url} | ||||
|   | ||||
| @@ -43,6 +43,10 @@ class XEP_0085(BasePlugin): | ||||
|         register_stanza_plugin(Message, stanza.Inactive) | ||||
|         register_stanza_plugin(Message, stanza.Paused) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Chat State') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) | ||||
|  | ||||
|     def _handle_chat_state(self, msg): | ||||
|   | ||||
| @@ -37,7 +37,10 @@ class XEP_0086(BasePlugin): | ||||
|     description = 'XEP-0086: Error Condition Mappings' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'override': True | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Error, LegacyError, | ||||
|                                overrides=self.config.get('override', True)) | ||||
|                                overrides=self.override) | ||||
|   | ||||
| @@ -30,16 +30,18 @@ class XEP_0092(BasePlugin): | ||||
|     description = 'XEP-0092: Software Version' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'software_name': 'SleekXMPP', | ||||
|         'version': sleekxmpp.__version__, | ||||
|         'os': '' | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """ | ||||
|         Start the XEP-0092 plugin. | ||||
|         """ | ||||
|         self.name = self.config.get('name', 'SleekXMPP') | ||||
|         self.version = self.config.get('version', sleekxmpp.__version__) | ||||
|         self.os = self.config.get('os', '') | ||||
|  | ||||
|         self.getVersion = self.get_version | ||||
|         if 'name' in self.config: | ||||
|             self.software_name = self.config['name'] | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Software Version', | ||||
| @@ -48,6 +50,11 @@ class XEP_0092(BasePlugin): | ||||
|  | ||||
|         register_stanza_plugin(Iq, Version) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Software Version') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') | ||||
|  | ||||
|     def _handle_version(self, iq): | ||||
| @@ -58,7 +65,7 @@ class XEP_0092(BasePlugin): | ||||
|             iq -- The Iq stanza containing the software version query. | ||||
|         """ | ||||
|         iq.reply() | ||||
|         iq['software_version']['name'] = self.name | ||||
|         iq['software_version']['name'] = self.software_name | ||||
|         iq['software_version']['version'] = self.version | ||||
|         iq['software_version']['os'] = self.os | ||||
|         iq.send() | ||||
| @@ -83,3 +90,6 @@ class XEP_0092(BasePlugin): | ||||
|             del values['lang'] | ||||
|             return values | ||||
|         return False | ||||
|  | ||||
|  | ||||
| XEP_0092.getVersion = XEP_0092.get_version | ||||
|   | ||||
							
								
								
									
										26
									
								
								sleekxmpp/plugins/xep_0106.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								sleekxmpp/plugins/xep_0106.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class XEP_0106(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0106' | ||||
|     description = 'XEP-0106: JID Escaping' | ||||
|     dependencies = set(['xep_0030']) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping') | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0106) | ||||
| @@ -32,6 +32,12 @@ class XEP_0107(BasePlugin): | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, UserMood) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace) | ||||
|         self.xmpp['xep_0163'].remove_interest(UserMood.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('user_mood', UserMood) | ||||
|  | ||||
|     def publish_mood(self, value=None, text=None, options=None, | ||||
|   | ||||
| @@ -26,7 +26,11 @@ class XEP_0108(BasePlugin): | ||||
|     dependencies = set(['xep_0163']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace) | ||||
|         self.xmpp['xep_0163'].remove_interest(UserActivity.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) | ||||
|  | ||||
|     def publish_activity(self, general, specific=None, text=None, options=None, | ||||
|   | ||||
| @@ -33,16 +33,17 @@ class XEP_0115(BasePlugin): | ||||
|     description = 'XEP-0115: Entity Capabilities' | ||||
|     dependencies = set(['xep_0030', 'xep_0128', 'xep_0004']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'hash': 'sha-1', | ||||
|         'caps_node': None, | ||||
|         'broadcast': True | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.hashes = {'sha-1': hashlib.sha1, | ||||
|                        'sha1': hashlib.sha1, | ||||
|                        'md5': hashlib.md5} | ||||
|  | ||||
|         self.hash = self.config.get('hash', 'sha-1') | ||||
|         self.caps_node = self.config.get('caps_node', None) | ||||
|         self.broadcast = self.config.get('broadcast', True) | ||||
|  | ||||
|         if self.caps_node is None: | ||||
|             ver = sleekxmpp.__version__ | ||||
|             self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver | ||||
| @@ -73,16 +74,15 @@ class XEP_0115(BasePlugin): | ||||
|                     restart=False, | ||||
|                     order=10010) | ||||
|  | ||||
|         self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) | ||||
|  | ||||
|         disco = self.xmpp['xep_0030'] | ||||
|         self.static = StaticCaps(self.xmpp, disco.static) | ||||
|  | ||||
|         self.api.settings['client_bare'] = False | ||||
|         self.api.settings['component_bare'] = False | ||||
|         for op in self._disco_ops: | ||||
|             self.api.register(getattr(self.static, op), op, default=True) | ||||
|  | ||||
|         for op in ('supports', 'has_identity'): | ||||
|             self.xmpp['xep_0030'].api.register(getattr(self.static, op), op) | ||||
|  | ||||
|         self._run_node_handler = disco._run_node_handler | ||||
|  | ||||
|         disco.cache_caps = self.cache_caps | ||||
| @@ -90,13 +90,31 @@ class XEP_0115(BasePlugin): | ||||
|         disco.assign_verstring = self.assign_verstring | ||||
|         disco.get_verstring = self.get_verstring | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) | ||||
|         self.xmpp.del_filter('out', self._filter_add_caps) | ||||
|         self.xmpp.del_event_handler('entity_caps', self._process_caps) | ||||
|         self.xmpp.remove_handler('Entity Capabilities') | ||||
|         if not self.xmpp.is_component: | ||||
|             self.xmpp.unregister_feature('caps', 10010) | ||||
|         for op in ('supports', 'has_identity'): | ||||
|             self.xmpp['xep_0030'].restore_defaults(op) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) | ||||
|  | ||||
|     def _filter_add_caps(self, stanza): | ||||
|         if isinstance(stanza, Presence) and self.broadcast: | ||||
|             ver = self.get_verstring(stanza['from']) | ||||
|             if ver: | ||||
|                 stanza['caps']['node'] = self.caps_node | ||||
|                 stanza['caps']['hash'] = self.hash | ||||
|                 stanza['caps']['ver'] = ver | ||||
|         if not isinstance(stanza, Presence) or not self.broadcast: | ||||
|             return stanza | ||||
|  | ||||
|         if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'): | ||||
|             return stanza | ||||
|  | ||||
|         ver = self.get_verstring(stanza['from']) | ||||
|         if ver: | ||||
|             stanza['caps']['node'] = self.caps_node | ||||
|             stanza['caps']['hash'] = self.hash | ||||
|             stanza['caps']['ver'] = ver | ||||
|         return stanza | ||||
|  | ||||
|     def _handle_caps(self, presence): | ||||
|   | ||||
| @@ -26,7 +26,11 @@ class XEP_0118(BasePlugin): | ||||
|     dependencies = set(['xep_0163']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace) | ||||
|         self.xmpp['xep_0163'].remove_interest(UserTune.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('user_tune', UserTune) | ||||
|  | ||||
|     def publish_tune(self, artist=None, length=None, rating=None, source=None, | ||||
|   | ||||
| @@ -51,8 +51,6 @@ class XEP_0128(BasePlugin): | ||||
|  | ||||
|         register_stanza_plugin(DiscoInfo, Form, iterable=True) | ||||
|  | ||||
|     def post_init(self): | ||||
|         """Handle cross-plugin dependencies.""" | ||||
|         self.disco = self.xmpp['xep_0030'] | ||||
|         self.static = StaticExtendedDisco(self.disco.static) | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0131/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0131/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0131 import stanza | ||||
| from sleekxmpp.plugins.xep_0131.stanza import Headers | ||||
| from sleekxmpp.plugins.xep_0131.headers import XEP_0131 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0131) | ||||
							
								
								
									
										41
									
								
								sleekxmpp/plugins/xep_0131/headers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								sleekxmpp/plugins/xep_0131/headers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp import Message, Presence | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0131 import stanza | ||||
| from sleekxmpp.plugins.xep_0131.stanza import Headers | ||||
|  | ||||
|  | ||||
| class XEP_0131(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0131' | ||||
|     description = 'XEP-0131: Stanza Headers and Internet Metadata' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'supported_headers': set() | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, Headers) | ||||
|         register_stanza_plugin(Presence, Headers) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Headers.namespace) | ||||
|         for header in self.supported_headers: | ||||
|             self.xmpp['xep_0030'].del_feature( | ||||
|                     feature='%s#%s' % (Headers.namespace, header)) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Headers.namespace) | ||||
|         for header in self.supported_headers: | ||||
|             self.xmpp['xep_0030'].add_feature('%s#%s' % ( | ||||
|                 Headers.namespace, | ||||
|                 header)) | ||||
							
								
								
									
										51
									
								
								sleekxmpp/plugins/xep_0131/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								sleekxmpp/plugins/xep_0131/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
| from sleekxmpp.xmlstream import ET, ElementBase | ||||
|  | ||||
|  | ||||
| class Headers(ElementBase): | ||||
|     name = 'headers' | ||||
|     namespace = 'http://jabber.org/protocol/shim' | ||||
|     plugin_attrib = 'headers' | ||||
|     interfaces = set(['headers']) | ||||
|     is_extension = True | ||||
|  | ||||
|     def get_headers(self): | ||||
|         result = OrderedDict() | ||||
|         headers = self.xml.findall('{%s}header' % self.namespace) | ||||
|         for header in headers: | ||||
|             name = header.attrib.get('name', '') | ||||
|             value = header.text | ||||
|             if name in result: | ||||
|                 if not isinstance(result[name], set): | ||||
|                     result[name] = [result[name]] | ||||
|                 else: | ||||
|                     result[name] = [] | ||||
|                 result[name].add(value) | ||||
|             else: | ||||
|                 result[name] = value | ||||
|         return result | ||||
|  | ||||
|     def set_headers(self, values): | ||||
|         self.del_headers() | ||||
|         for name in values: | ||||
|             vals = values[name] | ||||
|             if not isinstance(vals, (list, set)): | ||||
|                 vals = [values[name]] | ||||
|             for value in vals: | ||||
|                 header = ET.Element('{%s}header' % self.namespace) | ||||
|                 header.attrib['name'] = name | ||||
|                 header.text = value | ||||
|                 self.xml.append(header) | ||||
|  | ||||
|     def del_headers(self): | ||||
|         headers = self.xml.findall('{%s}header' % self.namespace) | ||||
|         for header in headers: | ||||
|             self.xml.remove(header) | ||||
							
								
								
									
										54
									
								
								sleekxmpp/plugins/xep_0133.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								sleekxmpp/plugins/xep_0133.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class XEP_0133(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0133' | ||||
|     description = 'XEP-0133: Service Administration' | ||||
|     dependencies = set(['xep_0030', 'xep_0004', 'xep_0050']) | ||||
|     commands = set(['add-user', 'delete-user', 'disable-user', | ||||
|                     'reenable-user', 'end-user-session', 'get-user-password', | ||||
|                     'change-user-password', 'get-user-roster', | ||||
|                     'get-user-lastlogin', 'user-stats', 'edit-blacklist', | ||||
|                     'edit-whitelist', 'get-registered-users-num', | ||||
|                     'get-disabled-users-num', 'get-online-users-num', | ||||
|                     'get-active-users-num', 'get-idle-users-num', | ||||
|                     'get-registered-users-list', 'get-disabled-users-list', | ||||
|                     'get-online-users-list', 'get-online-users', | ||||
|                     'get-active-users', 'get-idle-userslist', 'announce', | ||||
|                     'set-motd', 'edit-motd', 'delete-motd', 'set-welcome', | ||||
|                     'delete-welcome', 'edit-admin', 'restart', 'shutdown']) | ||||
|  | ||||
|     def get_commands(self, jid=None, **kwargs): | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.server | ||||
|         return self.xmpp['xep_0050'].get_commands(jid, **kwargs) | ||||
|  | ||||
|  | ||||
| def create_command(name): | ||||
|     def admin_command(self, jid=None, session=None, ifrom=None, block=False): | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.server | ||||
|         self.xmpp['xep_0050'].start_command( | ||||
|                 jid=jid, | ||||
|                 node='http://jabber.org/protocol/admin#%s' % name, | ||||
|                 session=session, | ||||
|                 ifrom=ifrom, | ||||
|                 block=block) | ||||
|     return admin_command | ||||
|  | ||||
|  | ||||
| for cmd in XEP_0133.commands: | ||||
|     setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd)) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0133) | ||||
| @@ -8,7 +8,9 @@ | ||||
|  | ||||
| import hashlib | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from sleekxmpp import JID | ||||
| from sleekxmpp.stanza import Presence | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| @@ -30,11 +32,14 @@ class XEP_0153(BasePlugin): | ||||
|     def plugin_init(self): | ||||
|         self._hashes = {} | ||||
|  | ||||
|         self._allow_advertising = threading.Event() | ||||
|  | ||||
|         register_stanza_plugin(Presence, VCardTempUpdate) | ||||
|  | ||||
|         self.xmpp.add_filter('out', self._update_presence) | ||||
|  | ||||
|         self.xmpp.add_event_handler('session_start', self._start) | ||||
|         self.xmpp.add_event_handler('session_end', self._end) | ||||
|  | ||||
|         self.xmpp.add_event_handler('presence_available', self._recv_presence) | ||||
|         self.xmpp.add_event_handler('presence_dnd', self._recv_presence) | ||||
| @@ -44,59 +49,82 @@ class XEP_0153(BasePlugin): | ||||
|  | ||||
|         self.api.register(self._set_hash, 'set_hash', default=True) | ||||
|         self.api.register(self._get_hash, 'get_hash', default=True) | ||||
|         self.api.register(self._reset_hash, 'reset_hash', default=True) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.del_filter('out', self._update_presence) | ||||
|         self.xmpp.del_event_handler('session_start', self._start) | ||||
|         self.xmpp.del_event_handler('session_end', self._end) | ||||
|         self.xmpp.del_event_handler('presence_available', self._recv_presence) | ||||
|         self.xmpp.del_event_handler('presence_dnd', self._recv_presence) | ||||
|         self.xmpp.del_event_handler('presence_xa', self._recv_presence) | ||||
|         self.xmpp.del_event_handler('presence_chat', self._recv_presence) | ||||
|         self.xmpp.del_event_handler('presence_away', self._recv_presence) | ||||
|  | ||||
|     def set_avatar(self, jid=None, avatar=None, mtype=None, block=True, | ||||
|                    timeout=None, callback=None): | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.bare | ||||
|  | ||||
|         vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True) | ||||
|         vcard = vcard['vcard_temp'] | ||||
|         vcard['PHOTO']['TYPE'] = mtype | ||||
|         vcard['PHOTO']['BINVAL'] = avatar | ||||
|  | ||||
|         self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard) | ||||
|         self._reset_hash(jid) | ||||
|  | ||||
|         self.api['reset_hash'](jid) | ||||
|         self.xmpp.roster[jid].send_last_presence() | ||||
|  | ||||
|     def _start(self, event): | ||||
|         self.xmpp['xep_0054'].get_vcard() | ||||
|         vcard = self.xmpp['xep_0054'].get_vcard() | ||||
|         self._allow_advertising.set() | ||||
|  | ||||
|     def _end(self, event): | ||||
|         self._allow_advertising.clear() | ||||
|  | ||||
|     def _update_presence(self, stanza): | ||||
|         if not isinstance(stanza, Presence): | ||||
|             return stanza | ||||
|  | ||||
|         if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'): | ||||
|             return stanza | ||||
|  | ||||
|         current_hash = self.api['get_hash'](stanza['from']) | ||||
|         stanza['vcard_temp_update']['photo'] = current_hash | ||||
|         return stanza | ||||
|  | ||||
|     def _reset_hash(self, jid=None): | ||||
|     def _reset_hash(self, jid, node, ifrom, args): | ||||
|         own_jid = (jid.bare == self.xmpp.boundjid.bare) | ||||
|         if self.xmpp.is_component: | ||||
|             own_jid = (jid.domain == self.xmpp.boundjid.domain) | ||||
|  | ||||
|         if jid is not None: | ||||
|             jid = jid.bare | ||||
|         self.api['set_hash'](jid, args=None) | ||||
|         if own_jid: | ||||
|             self.xmpp.roster[jid].send_last_presence() | ||||
|  | ||||
|         iq = self.xmpp['xep_0054'].get_vcard( | ||||
|                 jid=jid, | ||||
|                 ifrom=self.xmpp.boundjid) | ||||
|         iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom) | ||||
|  | ||||
|         data = iq['vcard_temp']['PHOTO']['BINVAL'] | ||||
|         if not data: | ||||
|             new_hash = '' | ||||
|         else: | ||||
|             new_hash = hashlib.sha1(data).hexdigest() | ||||
|  | ||||
|         self.api['set_hash'](jid, args=new_hash) | ||||
|         if own_jid: | ||||
|             self.xmpp.roster[jid].send_last_presence() | ||||
|  | ||||
|     def _recv_presence(self, pres): | ||||
|         if not pres.match('presence/vcard_temp_update'): | ||||
|             self.api['set_hash'](pres['from'], args=None) | ||||
|             return | ||||
|  | ||||
|         data = pres['vcard_temp_update']['photo'] | ||||
|         if data is None: | ||||
|             return | ||||
|         elif data == '' or data != self.api['get_hash'](pres['to']): | ||||
|             self._reset_hash(pres['from']) | ||||
|             ifrom = pres['to'] if self.xmpp.is_component else None | ||||
|             self.api['reset_hash'](pres['from'], ifrom=ifrom) | ||||
|             self.xmpp.event('vcard_avatar_update', pres) | ||||
|  | ||||
|     # ================================================================= | ||||
|  | ||||
|   | ||||
| @@ -74,7 +74,7 @@ class XEP_0163(BasePlugin): | ||||
|                          be a list of such namespaces. | ||||
|             jid       -- Optionally specify the JID. | ||||
|         """ | ||||
|         if not isinstance(namespace, set) and not isinstance(namespace, list): | ||||
|         if not isinstance(namespace, (set, list)): | ||||
|             namespace = [namespace] | ||||
|  | ||||
|         for ns in namespace: | ||||
|   | ||||
| @@ -34,6 +34,12 @@ class XEP_0172(BasePlugin): | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, UserNick) | ||||
|         register_stanza_plugin(Presence, UserNick) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace) | ||||
|         self.xmpp['xep_0163'].remove_interest(UserNick.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0163'].register_pep('user_nick', UserNick) | ||||
|  | ||||
|     def publish_nick(self, nick=None, options=None, ifrom=None, block=True, | ||||
|   | ||||
| @@ -26,13 +26,14 @@ class XEP_0184(BasePlugin): | ||||
|     description = 'XEP-0184: Message Delivery Receipts' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'auto_ack': True, | ||||
|         'auto_request': False | ||||
|     } | ||||
|  | ||||
|     ack_types = ('normal', 'chat', 'headline') | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.auto_ack = self.config.get('auto_ack', True) | ||||
|         self.auto_request = self.config.get('auto_request', False) | ||||
|  | ||||
|         register_stanza_plugin(Message, Request) | ||||
|         register_stanza_plugin(Message, Received) | ||||
|  | ||||
| @@ -48,6 +49,13 @@ class XEP_0184(BasePlugin): | ||||
|                     StanzaPath('message/request_receipt'), | ||||
|                     self._handle_receipt_request)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts') | ||||
|         self.xmpp.del_filter('out', self._filter_add_receipt_request) | ||||
|         self.xmpp.remove_handler('Message Receipt') | ||||
|         self.xmpp.remove_handler('Message Receipt Request') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts') | ||||
|  | ||||
|     def ack(self, msg): | ||||
| @@ -61,7 +69,7 @@ class XEP_0184(BasePlugin): | ||||
|         ack['to'] = msg['from'] | ||||
|         ack['from'] = msg['to'] | ||||
|         ack['receipt'] = msg['id'] | ||||
|         ack['id'] = self.xmpp.new_id() | ||||
|         ack['id'] = msg['id'] | ||||
|         ack.send() | ||||
|  | ||||
|     def _handle_receipt_received(self, msg): | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0186/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0186/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0186 import stanza | ||||
| from sleekxmpp.plugins.xep_0186.stanza import Invisible, Visible | ||||
| from sleekxmpp.plugins.xep_0186.invisible_command import XEP_0186 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0186) | ||||
							
								
								
									
										44
									
								
								sleekxmpp/plugins/xep_0186/invisible_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								sleekxmpp/plugins/xep_0186/invisible_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0186 import stanza, Visible, Invisible | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0186(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0186' | ||||
|     description = 'XEP-0186: Invisible Command' | ||||
|     dependencies = set(['xep_0030']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, Visible) | ||||
|         register_stanza_plugin(Iq, Invisible) | ||||
|  | ||||
|     def set_invisible(self, ifrom=None, block=True, callback=None, | ||||
|                             timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('invisible') | ||||
|         iq.send(block=block, callback=callback, timeout=timeout) | ||||
|  | ||||
|     def set_visible(self, ifrom=None, block=True, callback=None, | ||||
|                           timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('visible') | ||||
|         iq.send(block=block, callback=callback, timeout=timeout) | ||||
							
								
								
									
										23
									
								
								sleekxmpp/plugins/xep_0186/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								sleekxmpp/plugins/xep_0186/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
|  | ||||
| class Invisible(ElementBase): | ||||
|     name = 'invisible' | ||||
|     namespace = 'urn:xmpp:invisible:0' | ||||
|     plugin_attrib = 'invisible' | ||||
|     interfaces = set() | ||||
|  | ||||
|  | ||||
| class Visible(ElementBase): | ||||
|     name = 'visible' | ||||
|     namespace = 'urn:xmpp:visible:0' | ||||
|     plugin_attrib = 'visible' | ||||
|     interfaces = set() | ||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0191/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0191/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList | ||||
| from sleekxmpp.plugins.xep_0191.blocking import XEP_0191 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0191) | ||||
							
								
								
									
										83
									
								
								sleekxmpp/plugins/xep_0191/blocking.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								sleekxmpp/plugins/xep_0191/blocking.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||
| from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0191(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0191' | ||||
|     description = 'XEP-0191: Blocking Command' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, BlockList) | ||||
|         register_stanza_plugin(Iq, Block) | ||||
|         register_stanza_plugin(Iq, Unblock) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Blocked Contact', | ||||
|                     StanzaPath('iq@type=set/block'), | ||||
|                     self._handle_blocked)) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Unblocked Contact', | ||||
|                     StanzaPath('iq@type=set/unblock'), | ||||
|                     self._handle_unblocked)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Blocked Contact') | ||||
|         self.xmpp.remove_handler('Unblocked Contact') | ||||
|  | ||||
|     def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = 'ifrom' | ||||
|         iq.enable('blocklist') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def block(self, jids, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|  | ||||
|         if not isinstance(jids, (set, list)): | ||||
|             jids = [jids] | ||||
|  | ||||
|         iq['block']['items'] = jids | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|  | ||||
|         if jids is None: | ||||
|             jids = [] | ||||
|         if not isinstance(jids, (set, list)): | ||||
|             jids = [jids] | ||||
|  | ||||
|         iq['unblock']['items'] = jids | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def _handle_blocked(self, iq): | ||||
|         self.xmpp.event('blocked', iq) | ||||
|  | ||||
|     def _handle_unblocked(self, iq): | ||||
|         self.xmpp.event('unblocked', iq) | ||||
							
								
								
									
										50
									
								
								sleekxmpp/plugins/xep_0191/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								sleekxmpp/plugins/xep_0191/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, JID | ||||
|  | ||||
|  | ||||
| class BlockList(ElementBase): | ||||
|     name = 'blocklist' | ||||
|     namespace = 'urn:xmpp:blocking' | ||||
|     plugin_attrib = 'blocklist' | ||||
|     interfaces = set(['items']) | ||||
|  | ||||
|     def get_items(self): | ||||
|         result = set() | ||||
|         items = self.xml.findall('{%s}item' % self.namespace) | ||||
|         if items is not None: | ||||
|             for item in items: | ||||
|                 jid = JID(item.attrib.get('jid', '')) | ||||
|                 if jid: | ||||
|                     result.add(jid) | ||||
|         return result | ||||
|  | ||||
|     def set_items(self, values): | ||||
|         self.del_items() | ||||
|         for jid in values: | ||||
|             if jid: | ||||
|                 item = ET.Element('{%s}item' % self.namespace) | ||||
|                 item.attrib['jid'] = JID(jid).full | ||||
|                 self.xml.append(item) | ||||
|  | ||||
|     def del_items(self): | ||||
|         items = self.xml.findall('{%s}item' % self.namespace) | ||||
|         if items is not None: | ||||
|             for item in items: | ||||
|                 self.xml.remove(item) | ||||
|  | ||||
|  | ||||
| class Block(BlockList): | ||||
|     name = 'block' | ||||
|     plugin_attrib = 'block' | ||||
|  | ||||
|  | ||||
| class Unblock(BlockList): | ||||
|     name = 'unblock' | ||||
|     plugin_attrib = 'unblock' | ||||
| @@ -34,6 +34,32 @@ class XEP_0198(BasePlugin): | ||||
|     description = 'XEP-0198: Stream Management' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         #: The last ack number received from the server. | ||||
|         'last_ack': 0, | ||||
|  | ||||
|         #: The number of stanzas to wait between sending ack requests to | ||||
|         #: the server. Setting this to ``1`` will send an ack request after | ||||
|         #: every sent stanza. Defaults to ``5``. | ||||
|         'window': 5, | ||||
|  | ||||
|         #: The stream management ID for the stream. Knowing this value is | ||||
|         #: required in order to do stream resumption. | ||||
|         'sm_id': None, | ||||
|  | ||||
|         #: A counter of handled incoming stanzas, mod 2^32. | ||||
|         'handled': 0, | ||||
|  | ||||
|         #: A counter of unacked outgoing stanzas, mod 2^32. | ||||
|         'seq': 0, | ||||
|  | ||||
|         #: Control whether or not the ability to resume the stream will be | ||||
|         #: requested when enabling stream management. Defaults to ``True``. | ||||
|         'allow_resume': True, | ||||
|  | ||||
|         'order': 10100, | ||||
|         'resume_order': 9000 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Start the XEP-0198 plugin.""" | ||||
| @@ -43,30 +69,9 @@ class XEP_0198(BasePlugin): | ||||
|         if self.xmpp.is_component: | ||||
|             return | ||||
|  | ||||
|         #: The stream management ID for the stream. Knowing this value is | ||||
|         #: required in order to do stream resumption. | ||||
|         self.sm_id = self.config.get('sm_id', None) | ||||
|  | ||||
|         #: A counter of handled incoming stanzas, mod 2^32. | ||||
|         self.handled = self.config.get('handled', 0) | ||||
|  | ||||
|         #: A counter of unacked outgoing stanzas, mod 2^32. | ||||
|         self.seq = self.config.get('seq', 0) | ||||
|  | ||||
|         #: The last ack number received from the server. | ||||
|         self.last_ack = self.config.get('last_ack', 0) | ||||
|  | ||||
|         #: The number of stanzas to wait between sending ack requests to | ||||
|         #: the server. Setting this to ``1`` will send an ack request after | ||||
|         #: every sent stanza. Defaults to ``5``. | ||||
|         self.window = self.config.get('window', 5) | ||||
|         self.window_counter = self.window | ||||
|         self.window_counter_lock = threading.Lock() | ||||
|  | ||||
|         #: Control whether or not the ability to resume the stream will be | ||||
|         #: requested when enabling stream management. Defaults to ``True``. | ||||
|         self.allow_resume = self.config.get('allow_resume', True) | ||||
|  | ||||
|         self.enabled = threading.Event() | ||||
|         self.unacked_queue = collections.deque() | ||||
|  | ||||
| @@ -92,11 +97,11 @@ class XEP_0198(BasePlugin): | ||||
|         self.xmpp.register_feature('sm', | ||||
|                 self._handle_sm_feature, | ||||
|                 restart=True, | ||||
|                 order=self.config.get('order', 10100)) | ||||
|                 order=self.order) | ||||
|         self.xmpp.register_feature('sm', | ||||
|                 self._handle_sm_feature, | ||||
|                 restart=True, | ||||
|                 order=self.config.get('resume_order', 9000)) | ||||
|                 order=self.resume_order) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Stream Management Enabled', | ||||
| @@ -133,6 +138,27 @@ class XEP_0198(BasePlugin): | ||||
|  | ||||
|         self.xmpp.add_event_handler('session_end', self.session_end) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         if self.xmpp.is_component: | ||||
|             return | ||||
|  | ||||
|         self.xmpp.unregister_feature('sm', self.order) | ||||
|         self.xmpp.unregister_feature('sm', self.resume_order) | ||||
|         self.xmpp.del_event_handler('session_end', self.session_end) | ||||
|         self.xmpp.del_filter('in', self._handle_incoming) | ||||
|         self.xmpp.del_filter('out_sync', self._handle_outgoing) | ||||
|         self.xmpp.remove_handler('Stream Management Enabled') | ||||
|         self.xmpp.remove_handler('Stream Management Resumed') | ||||
|         self.xmpp.remove_handler('Stream Management Failed') | ||||
|         self.xmpp.remove_handler('Stream Management Ack') | ||||
|         self.xmpp.remove_handler('Stream Management Request Ack') | ||||
|         self.xmpp.remove_stanza(stanza.Enable) | ||||
|         self.xmpp.remove_stanza(stanza.Enabled) | ||||
|         self.xmpp.remove_stanza(stanza.Resume) | ||||
|         self.xmpp.remove_stanza(stanza.Resumed) | ||||
|         self.xmpp.remove_stanza(stanza.Ack) | ||||
|         self.xmpp.remove_stanza(stanza.RequestAck) | ||||
|  | ||||
|     def session_end(self, event): | ||||
|         """Reset stream management state.""" | ||||
|         self.enabled.clear() | ||||
|   | ||||
| @@ -51,15 +51,16 @@ class XEP_0199(BasePlugin): | ||||
|     description = 'XEP-0199: XMPP Ping' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'keepalive': False, | ||||
|         'frequency': 300, | ||||
|         'timeout': 30 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """ | ||||
|         Start the XEP-0199 plugin. | ||||
|         """ | ||||
|         self.keepalive = self.config.get('keepalive', False) | ||||
|         self.frequency = float(self.config.get('frequency', 300)) | ||||
|         self.timeout = self.config.get('timeout', 30) | ||||
|  | ||||
|         register_stanza_plugin(Iq, Ping) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
| @@ -74,6 +75,16 @@ class XEP_0199(BasePlugin): | ||||
|             self.xmpp.add_event_handler('session_end', | ||||
|                                         self._handle_session_end) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Ping.namespace) | ||||
|         self.xmpp.remove_handler('Ping') | ||||
|         if self.keepalive: | ||||
|             self.xmpp.del_event_handler('session_start', | ||||
|                                         self._handle_keepalive) | ||||
|             self.xmpp.del_event_handler('session_end', | ||||
|                                         self._handle_session_end) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Ping.namespace) | ||||
|  | ||||
|     def _handle_keepalive(self, event): | ||||
|   | ||||
| @@ -30,21 +30,22 @@ class XEP_0202(BasePlugin): | ||||
|     description = 'XEP-0202: Entity Time' | ||||
|     dependencies = set(['xep_0030', 'xep_0082']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         #: As a default, respond to time requests with the | ||||
|         #: local time returned by XEP-0082. However, a | ||||
|         #: custom function can be supplied which accepts | ||||
|         #: the JID of the entity to query for the time. | ||||
|         'local_time': None, | ||||
|         'tz_offset': 0 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Start the XEP-0203 plugin.""" | ||||
|         self.tz_offset = self.config.get('tz_offset', 0) | ||||
|  | ||||
|         # As a default, respond to time requests with the | ||||
|         # local time returned by XEP-0082. However, a | ||||
|         # custom function can be supplied which accepts | ||||
|         # the JID of the entity to query for the time. | ||||
|         self.local_time = self.config.get('local_time', None) | ||||
|  | ||||
|         def default_local_time(jid): | ||||
|             return xep_0082.datetime(offset=self.tz_offset) | ||||
|  | ||||
|         if not self.local_time: | ||||
|             def default_local_time(jid): | ||||
|                 return xep_0082.datetime(offset=self.tz_offset) | ||||
|  | ||||
|             self.local_time = default_local_time | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
| @@ -53,6 +54,11 @@ class XEP_0202(BasePlugin): | ||||
|                  self._handle_time_request)) | ||||
|         register_stanza_plugin(Iq, stanza.EntityTime) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time') | ||||
|         self.xmpp.remove_handler('Entity Time') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:time') | ||||
|  | ||||
|     def _handle_time_request(self, iq): | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0221/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0221/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0221 import stanza | ||||
| from sleekxmpp.plugins.xep_0221.stanza import Media, URI | ||||
| from sleekxmpp.plugins.xep_0221.media import XEP_0221 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0221) | ||||
							
								
								
									
										27
									
								
								sleekxmpp/plugins/xep_0221/media.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								sleekxmpp/plugins/xep_0221/media.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0221 import stanza, Media, URI | ||||
| from sleekxmpp.plugins.xep_0004 import FormField | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0221(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0221' | ||||
|     description = 'XEP-0221: Data Forms Media Element' | ||||
|     dependencies = set(['xep_0004']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(FormField, Media) | ||||
							
								
								
									
										42
									
								
								sleekxmpp/plugins/xep_0221/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/plugins/xep_0221/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Media(ElementBase): | ||||
|     name = 'media' | ||||
|     namespace = 'urn:xmpp:media-element' | ||||
|     plugin_attrib = 'media' | ||||
|     interfaces = set(['height', 'width', 'alt']) | ||||
|  | ||||
|     def add_uri(self, value, itype): | ||||
|         uri = URI() | ||||
|         uri['value'] = value | ||||
|         uri['type'] = itype | ||||
|         self.append(uri) | ||||
|  | ||||
|  | ||||
| class URI(ElementBase): | ||||
|     name = 'uri' | ||||
|     namespace = 'urn:xmpp:media-element' | ||||
|     plugin_attrib = 'uri' | ||||
|     plugin_multi_attrib = 'uris' | ||||
|     interfaces = set(['type', 'value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self.xml.text = value | ||||
|  | ||||
|     def del_value(self): | ||||
|         sel.xml.text = '' | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Media, URI, iterable=True) | ||||
| @@ -22,7 +22,7 @@ class XEP_0222(BasePlugin): | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0222' | ||||
|     description = 'XEP-0222: Persistent Storage of Private Data via PubSub' | ||||
|     description = 'XEP-0222: Persistent Storage of Public Data via PubSub' | ||||
|     dependencies = set(['xep_0163', 'xep_0060', 'xep_0004']) | ||||
|  | ||||
|     profile = {'pubsub#persist_items': True, | ||||
|   | ||||
| @@ -39,6 +39,11 @@ class XEP_0224(BasePlugin): | ||||
|                     StanzaPath('message/attention'), | ||||
|                     self._handle_attention)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace) | ||||
|         self.xmpp.remove_handler('Attention') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace) | ||||
|  | ||||
|     def request_attention(self, to, mfrom=None, mbody=''): | ||||
|   | ||||
| @@ -35,8 +35,6 @@ class XEP_0231(BasePlugin): | ||||
|     def plugin_init(self): | ||||
|         self._cids = {} | ||||
|  | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:bob') | ||||
|  | ||||
|         register_stanza_plugin(Iq, BitsOfBinary) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
| @@ -58,6 +56,15 @@ class XEP_0231(BasePlugin): | ||||
|         self.api.register(self._set_bob, 'set_bob', default=True) | ||||
|         self.api.register(self._del_bob, 'del_bob', default=True) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob') | ||||
|         self.xmpp.remove_handler('Bits of Binary - Iq') | ||||
|         self.xmpp.remove_handler('Bits of Binary - Message') | ||||
|         self.xmpp.remove_handler('Bits of Binary - Presence') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:bob') | ||||
|  | ||||
|     def set_bob(self, data, mtype, cid=None, max_age=None): | ||||
|         if cid is None: | ||||
|             cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest() | ||||
|   | ||||
| @@ -39,6 +39,11 @@ class XEP_0249(BasePlugin): | ||||
|  | ||||
|         register_stanza_plugin(Message, Invite) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Invite.namespace) | ||||
|         self.xmpp.remove_handler('Direct MUC Invitations') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Invite.namespace) | ||||
|  | ||||
|     def _handle_invite(self, msg): | ||||
|   | ||||
							
								
								
									
										73
									
								
								sleekxmpp/plugins/xep_0256.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								sleekxmpp/plugins/xep_0256.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Presence | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0012 import stanza, LastActivity | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0256(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0256' | ||||
|     description = 'XEP-0256: Last Activity in Presence' | ||||
|     dependencies = set(['xep_0012']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'auto_last_activity': False | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Presence, LastActivity) | ||||
|  | ||||
|         self.xmpp.add_filter('out', self._initial_presence_activity) | ||||
|         self.xmpp.add_event_handler('connected', self._reset_presence_activity) | ||||
|  | ||||
|         self._initial_presence = set() | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.del_filter('out', self._initial_presence_activity) | ||||
|         self.xmpp.del_event_handler('connected', self._reset_presence_activity) | ||||
|  | ||||
|     def _reset_presence_activity(self, e): | ||||
|         self._initial_presence = set() | ||||
|  | ||||
|     def _initial_presence_activity(self, stanza): | ||||
|         if isinstance(stanza, Presence): | ||||
|             use_last_activity = False | ||||
|  | ||||
|             if self.auto_last_activity and  stanza['show'] in ('xa', 'away'): | ||||
|                 use_last_activity = True | ||||
|  | ||||
|             if stanza['from'] not in self._initial_presence: | ||||
|                 self._initial_presence.add(stanza['from']) | ||||
|                 use_last_activity = True | ||||
|  | ||||
|             if use_last_activity: | ||||
|                 plugin = self.xmpp['xep_0012'] | ||||
|                 try: | ||||
|                     result = plugin.api['get_last_activity'](stanza['from'], | ||||
|                                                              None, | ||||
|                                                              stanza['to']) | ||||
|                     seconds = result['last_activity']['seconds'] | ||||
|                 except XMPPError: | ||||
|                     seconds = None | ||||
|  | ||||
|                 if seconds is not None: | ||||
|                     stanza['last_activity']['seconds'] = seconds | ||||
|         return stanza | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0256) | ||||
| @@ -25,11 +25,15 @@ class XEP_0258(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace) | ||||
|  | ||||
|         register_stanza_plugin(Message, SecurityLabel) | ||||
|         register_stanza_plugin(Iq, Catalog) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=SecurityLabel.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace) | ||||
|  | ||||
|     def get_catalog(self, jid, ifrom=None, block=True, | ||||
|                           callback=None, timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|   | ||||
							
								
								
									
										20
									
								
								sleekxmpp/plugins/xep_0270.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								sleekxmpp/plugins/xep_0270.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class XEP_0270(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0270' | ||||
|     description = 'XEP-0270: XMPP Compliance Suites 2010' | ||||
|     dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', | ||||
|                         'xep_0163', 'xep_0045', 'xep_0085']) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0270) | ||||
							
								
								
									
										21
									
								
								sleekxmpp/plugins/xep_0302.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								sleekxmpp/plugins/xep_0302.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class XEP_0302(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0302' | ||||
|     description = 'XEP-0302: XMPP Compliance Suites 2012' | ||||
|     dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', | ||||
|                         'xep_0163', 'xep_0045', 'xep_0085', | ||||
|                         'xep_0184', 'xep_0198']) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0302) | ||||
| @@ -94,10 +94,12 @@ class Roster(object): | ||||
|         Arguments: | ||||
|             key -- Return the roster for this JID. | ||||
|         """ | ||||
|         if isinstance(key, JID): | ||||
|             key = key.bare | ||||
|         if key is None: | ||||
|             key = self.xmpp.boundjid.bare | ||||
|             key = self.xmpp.boundjid | ||||
|         if not isinstance(key, JID): | ||||
|             key = JID(key) | ||||
|         key = key.bare | ||||
|  | ||||
|         if key not in self._rosters: | ||||
|             self.add(key) | ||||
|             self._rosters[key].auto_authorize = self.auto_authorize | ||||
| @@ -119,8 +121,10 @@ class Roster(object): | ||||
|         Arguments: | ||||
|             node -- The JID for the new roster node. | ||||
|         """ | ||||
|         if isinstance(node, JID): | ||||
|             node = node.bare | ||||
|         if not isinstance(node, JID): | ||||
|             node = JID(node) | ||||
|  | ||||
|         node = node.bare | ||||
|         if node not in self._rosters: | ||||
|             self._rosters[node] = RosterNode(self.xmpp, node, self.db) | ||||
|  | ||||
|   | ||||
| @@ -89,8 +89,11 @@ class RosterNode(object): | ||||
|  | ||||
|         A new item entry will be created if one does not already exist. | ||||
|         """ | ||||
|         if isinstance(key, JID): | ||||
|             key = key.bare | ||||
|         if key is None: | ||||
|             key = JID('') | ||||
|         if not isinstance(key, JID): | ||||
|             key = JID(key) | ||||
|         key = key.bare | ||||
|         if key not in self._jids: | ||||
|             self.add(key, save=True) | ||||
|         return self._jids[key] | ||||
| @@ -101,8 +104,11 @@ class RosterNode(object): | ||||
|  | ||||
|         To remove an item from the server, use the remove() method. | ||||
|         """ | ||||
|         if isinstance(key, JID): | ||||
|             key = key.bare | ||||
|         if key is None: | ||||
|             key = JID('') | ||||
|         if not isinstance(key, JID): | ||||
|             key = JID(key) | ||||
|         key = key.bare | ||||
|         if key in self._jids: | ||||
|             del self._jids[key] | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class Error(ElementBase): | ||||
|     name = 'error' | ||||
|     plugin_attrib = 'error' | ||||
|     interfaces = set(('code', 'condition', 'text', 'type', | ||||
|                       'gone', 'redirect')) | ||||
|                       'gone', 'redirect', 'by')) | ||||
|     sub_interfaces = set(('text',)) | ||||
|     plugin_attrib_map = {} | ||||
|     plugin_tag_map = {} | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza.rootstanza import RootStanza | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
|  | ||||
|  | ||||
| class Message(RootStanza): | ||||
| @@ -54,13 +54,14 @@ class Message(RootStanza): | ||||
|         del_mucnick -- Dummy method to prevent deletion. | ||||
|     """ | ||||
|  | ||||
|     namespace = 'jabber:client' | ||||
|     name = 'message' | ||||
|     interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', | ||||
|                       'mucroom', 'mucnick')) | ||||
|     sub_interfaces = set(('body', 'subject')) | ||||
|     namespace = 'jabber:client' | ||||
|     plugin_attrib = name | ||||
|     types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) | ||||
|     interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject', | ||||
|                       'thread', 'parent_thread', 'mucroom', 'mucnick']) | ||||
|     sub_interfaces = set(['body', 'subject', 'thread']) | ||||
|     lang_interfaces = sub_interfaces | ||||
|     types = set(['normal', 'chat', 'headline', 'error', 'groupchat']) | ||||
|  | ||||
|     def get_type(self): | ||||
|         """ | ||||
| @@ -72,6 +73,31 @@ class Message(RootStanza): | ||||
|         """ | ||||
|         return self._get_attr('type', 'normal') | ||||
|  | ||||
|     def get_parent_thread(self): | ||||
|         """Return the message thread's parent thread.""" | ||||
|         thread = self.xml.find('{%s}thread' % self.namespace) | ||||
|         if thread is not None: | ||||
|             return thread.attrib.get('parent', '') | ||||
|         return '' | ||||
|  | ||||
|     def set_parent_thread(self, value): | ||||
|         """Add or change the message thread's parent thread.""" | ||||
|         thread = self.xml.find('{%s}thread' % self.namespace) | ||||
|         if value: | ||||
|             if thread is None: | ||||
|                 thread = ET.Element('{%s}thread' % self.namespace) | ||||
|                 self.xml.append(thread) | ||||
|             thread.attrib['parent'] = value | ||||
|         else: | ||||
|             if thread is not None and 'parent' in thread.attrib: | ||||
|                 del thread.attrib['parent'] | ||||
|  | ||||
|     def del_parent_thread(self): | ||||
|         """Delete the message thread's parent reference.""" | ||||
|         thread = self.xml.find('{%s}thread' % self.namespace) | ||||
|         if thread is not None and 'parent' in thread.attrib: | ||||
|             del thread.attrib['parent'] | ||||
|  | ||||
|     def chat(self): | ||||
|         """Set the message type to 'chat'.""" | ||||
|         self['type'] = 'chat' | ||||
| @@ -96,10 +122,16 @@ class Message(RootStanza): | ||||
|             clear -- Indicates if existing content should be removed | ||||
|                      before replying. Defaults to True. | ||||
|         """ | ||||
|         thread = self['thread'] | ||||
|         parent = self['parent_thread'] | ||||
|  | ||||
|         StanzaBase.reply(self, clear) | ||||
|         if self['type'] == 'groupchat': | ||||
|             self['to'] = self['to'].bare | ||||
|  | ||||
|         self['thread'] = thread | ||||
|         self['parent_thread'] = parent | ||||
|  | ||||
|         del self['id'] | ||||
|  | ||||
|         if body is not None: | ||||
|   | ||||
| @@ -60,16 +60,17 @@ class Presence(RootStanza): | ||||
|         set_priority -- Set the value of the <priority> element. | ||||
|     """ | ||||
|  | ||||
|     namespace = 'jabber:client' | ||||
|     name = 'presence' | ||||
|     interfaces = set(('type', 'to', 'from', 'id', 'show', | ||||
|                       'status', 'priority')) | ||||
|     sub_interfaces = set(('show', 'status', 'priority')) | ||||
|     namespace = 'jabber:client' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['type', 'to', 'from', 'id', 'show', | ||||
|                       'status', 'priority']) | ||||
|     sub_interfaces = set(['show', 'status', 'priority']) | ||||
|     lang_interfaces = set(['status']) | ||||
|  | ||||
|     types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', | ||||
|                  'subscribed', 'unsubscribe', 'unsubscribed')) | ||||
|     showtypes = set(('dnd', 'chat', 'xa', 'away')) | ||||
|     types = set(['available', 'unavailable', 'error', 'probe', 'subscribe', | ||||
|                  'subscribed', 'unsubscribe', 'unsubscribed']) | ||||
|     showtypes = set(['dnd', 'chat', 'xa', 'away']) | ||||
|  | ||||
|     def exception(self, e): | ||||
|         """ | ||||
|   | ||||
| @@ -8,10 +8,8 @@ | ||||
|  | ||||
| import socket | ||||
| import threading | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| from sleekxmpp.util import Queue | ||||
|  | ||||
|  | ||||
| class TestLiveSocket(object): | ||||
| @@ -39,8 +37,8 @@ class TestLiveSocket(object): | ||||
|         """ | ||||
|         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
|         self.recv_buffer = [] | ||||
|         self.recv_queue = queue.Queue() | ||||
|         self.send_queue = queue.Queue() | ||||
|         self.recv_queue = Queue() | ||||
|         self.send_queue = Queue() | ||||
|         self.send_queue_lock = threading.Lock() | ||||
|         self.recv_queue_lock = threading.Lock() | ||||
|         self.is_live = True | ||||
|   | ||||
| @@ -7,10 +7,8 @@ | ||||
| """ | ||||
|  | ||||
| import socket | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| from sleekxmpp.util import Queue | ||||
|  | ||||
|  | ||||
| class TestSocket(object): | ||||
| @@ -36,8 +34,8 @@ class TestSocket(object): | ||||
|             Same as arguments for socket.socket | ||||
|         """ | ||||
|         self.socket = socket.socket(*args, **kwargs) | ||||
|         self.recv_queue = queue.Queue() | ||||
|         self.send_queue = queue.Queue() | ||||
|         self.recv_queue = Queue() | ||||
|         self.send_queue = Queue() | ||||
|         self.is_live = False | ||||
|         self.disconnected = False | ||||
|  | ||||
|   | ||||
| @@ -8,13 +8,10 @@ | ||||
|  | ||||
| import unittest | ||||
| from xml.parsers.expat import ExpatError | ||||
| try: | ||||
|     import Queue as queue | ||||
| except: | ||||
|     import queue | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import ClientXMPP, ComponentXMPP | ||||
| from sleekxmpp.util import Queue | ||||
| from sleekxmpp.stanza import Message, Iq, Presence | ||||
| from sleekxmpp.test import TestSocket, TestLiveSocket | ||||
| from sleekxmpp.exceptions import XMPPError, IqTimeout, IqError | ||||
| @@ -338,7 +335,7 @@ class SleekTest(unittest.TestCase): | ||||
|  | ||||
|         # We will use this to wait for the session_start event | ||||
|         # for live connections. | ||||
|         skip_queue = queue.Queue() | ||||
|         skip_queue = Queue() | ||||
|  | ||||
|         if socket == 'mock': | ||||
|             self.xmpp.set_socket(TestSocket()) | ||||
|   | ||||
							
								
								
									
										3
									
								
								sleekxmpp/thirdparty/suelta/util.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								sleekxmpp/thirdparty/suelta/util.py
									
									
									
									
										vendored
									
									
								
							| @@ -15,6 +15,9 @@ def bytes(text): | ||||
|     :param text: Unicode text to convert to bytes | ||||
|     :rtype: bytes (Python3), str (Python2.6+) | ||||
|     """ | ||||
|     if text is None: | ||||
|         return b'' | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         import __builtin__ | ||||
|         return __builtin__.bytes(text) | ||||
|   | ||||
							
								
								
									
										23
									
								
								sleekxmpp/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								sleekxmpp/util/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
|     sleekxmpp.util | ||||
|     ~~~~~~~~~~~~~~ | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
|  | ||||
| # ===================================================================== | ||||
| # Standardize import of Queue class: | ||||
|  | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
|  | ||||
| Queue = queue.Queue | ||||
| QueueEmpty = queue.Empty | ||||
							
								
								
									
										158
									
								
								sleekxmpp/util/stringprep_profiles.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								sleekxmpp/util/stringprep_profiles.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
|     sleekxmpp.util.stringprep_profiles | ||||
|     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     This module makes it easier to define profiles of stringprep, | ||||
|     such as nodeprep and resourceprep for JID validation, and | ||||
|     SASLprep for SASL. | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import sys | ||||
| import stringprep | ||||
| import unicodedata | ||||
|  | ||||
|  | ||||
| class StringPrepError(UnicodeError): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def to_unicode(data): | ||||
|     """Ensure that a given string is Unicode, regardless of Python version.""" | ||||
|     if sys.version_info < (3, 0): | ||||
|         return unicode(data) | ||||
|     else: | ||||
|         return str(data) | ||||
|  | ||||
|  | ||||
| def b1_mapping(char): | ||||
|     """Map characters that are commonly mapped to nothing.""" | ||||
|     return '' if stringprep.in_table_b1(char) else None | ||||
|  | ||||
|  | ||||
| def c12_mapping(char): | ||||
|     """Map non-ASCII whitespace to spaces.""" | ||||
|     return ' ' if stringprep.in_table_c12(char) else None | ||||
|  | ||||
|  | ||||
| def map_input(data, tables=None): | ||||
|     """ | ||||
|     Each character in the input stream MUST be checked against | ||||
|     a mapping table. | ||||
|     """ | ||||
|     result = [] | ||||
|     for char in data: | ||||
|         replacement = None | ||||
|  | ||||
|         for mapping in tables: | ||||
|             replacement = mapping(char) | ||||
|             if replacement is not None: | ||||
|                 break | ||||
|  | ||||
|         if replacement is None: | ||||
|             replacement = char | ||||
|         result.append(replacement) | ||||
|     return ''.join(result) | ||||
|  | ||||
|  | ||||
| def normalize(data, nfkc=True): | ||||
|     """ | ||||
|     A profile can specify one of two options for Unicode normalization: | ||||
|         - no normalization | ||||
|         - Unicode normalization with form KC | ||||
|     """ | ||||
|     if nfkc: | ||||
|         data = unicodedata.normalize('NFKC', data) | ||||
|     return data | ||||
|  | ||||
|  | ||||
| def prohibit_output(data, tables=None): | ||||
|     """ | ||||
|     Before the text can be emitted, it MUST be checked for prohibited | ||||
|     code points. | ||||
|     """ | ||||
|     for char in data: | ||||
|         for check in tables: | ||||
|             if check(char): | ||||
|                 raise StringPrepError("Prohibited code point: %s" % char) | ||||
|  | ||||
|  | ||||
| def check_bidi(data): | ||||
|     """ | ||||
|     1) The characters in section 5.8 MUST be prohibited. | ||||
|  | ||||
|     2) If a string contains any RandALCat character, the string MUST NOT | ||||
|        contain any LCat character. | ||||
|  | ||||
|     3) If a string contains any RandALCat character, a RandALCat | ||||
|        character MUST be the first character of the string, and a | ||||
|        RandALCat character MUST be the last character of the string. | ||||
|     """ | ||||
|     if not data: | ||||
|         return data | ||||
|  | ||||
|     has_lcat = False | ||||
|     has_randal = False | ||||
|  | ||||
|     for c in data: | ||||
|         if stringprep.in_table_c8(c): | ||||
|             raise StringPrepError("BIDI violation: seciton 6 (1)") | ||||
|         if stringprep.in_table_d1(c): | ||||
|             has_randal = True | ||||
|         elif stringprep.in_table_d2(c): | ||||
|             has_lcat = True | ||||
|  | ||||
|     if has_randal and has_lcat: | ||||
|         raise StringPrepError("BIDI violation: section 6 (2)") | ||||
|  | ||||
|     first_randal = stringprep.in_table_d1(data[0]) | ||||
|     last_randal = stringprep.in_table_d1(data[-1]) | ||||
|     if has_randal and not (first_randal and last_randal): | ||||
|         raise StringPrepError("BIDI violation: section 6 (3)") | ||||
|  | ||||
|  | ||||
| def create(nfkc=True, bidi=True, mappings=None, | ||||
|            prohibited=None, unassigned=None): | ||||
|     """Create a profile of stringprep. | ||||
|  | ||||
|     :param bool nfkc: | ||||
|         If `True`, perform NFKC Unicode normalization. Defaults to `True`. | ||||
|     :param bool bidi: | ||||
|         If `True`, perform bidirectional text checks. Defaults to `True`. | ||||
|     :param list mappings: | ||||
|         Optional list of functions for mapping characters to | ||||
|         suitable replacements. | ||||
|     :param list prohibited: | ||||
|         Optional list of functions which check for the presence of | ||||
|         prohibited characters. | ||||
|     :param list unassigned: | ||||
|         Optional list of functions for detecting the use of unassigned | ||||
|         code points. | ||||
|  | ||||
|     :raises: StringPrepError | ||||
|     :return: Unicode string of the resulting text passing the | ||||
|              profile's requirements. | ||||
|     """ | ||||
|     def profile(data, query=False): | ||||
|         try: | ||||
|             data = to_unicode(data) | ||||
|         except UnicodeError: | ||||
|             raise StringPrepError | ||||
|  | ||||
|         data = map_input(data, mappings) | ||||
|         data = normalize(data, nfkc) | ||||
|         prohibit_output(data, prohibited) | ||||
|         if bidi: | ||||
|             check_bidi(data) | ||||
|         if query and unassigned: | ||||
|             check_unassigned(data, unassigned) | ||||
|         return data | ||||
|     return profile | ||||
| @@ -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.1.6' | ||||
| __version_info__ = (1, 1, 6, '', 0) | ||||
| __version__ = '1.1.10' | ||||
| __version_info__ = (1, 1, 10, '', 0) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream.jid import JID | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.xmlstream.scheduler import Scheduler | ||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET | ||||
| from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin | ||||
|   | ||||
| @@ -10,11 +10,8 @@ | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| from sleekxmpp.util import Queue, QueueEmpty | ||||
| from sleekxmpp.xmlstream.handler.base import BaseHandler | ||||
|  | ||||
|  | ||||
| @@ -37,7 +34,7 @@ class Waiter(BaseHandler): | ||||
|  | ||||
|     def __init__(self, name, matcher, stream=None): | ||||
|         BaseHandler.__init__(self, name, matcher, stream=stream) | ||||
|         self._payload = queue.Queue() | ||||
|         self._payload = Queue() | ||||
|  | ||||
|     def prerun(self, payload): | ||||
|         """Store the matched stanza when received during processing. | ||||
| @@ -74,7 +71,7 @@ class Waiter(BaseHandler): | ||||
|             try: | ||||
|                 stanza = self._payload.get(True, 1) | ||||
|                 break | ||||
|             except queue.Empty: | ||||
|             except QueueEmpty: | ||||
|                 elapsed_time += 1 | ||||
|                 if elapsed_time >= timeout: | ||||
|                     log.warning("Timed out waiting for %s", self.name) | ||||
|   | ||||
| @@ -1,145 +1,5 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
|     sleekxmpp.xmlstream.jid | ||||
|     ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| import logging | ||||
|  | ||||
|     This module allows for working with Jabber IDs (JIDs) by | ||||
|     providing accessors for the various components of a JID. | ||||
| logging.warning('Deprecated: sleekxmpp.xmlstream.jid is moving to sleekxmpp.jid') | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2011 Nathanael C. Fritz | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
|  | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
|  | ||||
|     Each JID may have three components: a user, a domain, and an optional | ||||
|     resource. For example: user@domain/resource | ||||
|  | ||||
|     When a resource is not used, the JID is called a bare JID. | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     **JID Properties:** | ||||
|         :jid: Alias for ``full``. | ||||
|         :full: The value of the full JID. | ||||
|         :bare: The value of the bare JID. | ||||
|         :user: The username portion of the JID. | ||||
|         :domain: The domain name portion of the JID. | ||||
|         :server: Alias for ``domain``. | ||||
|         :resource: The resource portion of the JID. | ||||
|  | ||||
|     :param string jid: A string of the form ``'[user@]domain[/resource]'``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid): | ||||
|         """Initialize a new JID""" | ||||
|         self.reset(jid) | ||||
|  | ||||
|     def reset(self, jid): | ||||
|         """Start fresh from a new JID string. | ||||
|  | ||||
|         :param string jid: A string of the form ``'[user@]domain[/resource]'``. | ||||
|         """ | ||||
|         if isinstance(jid, JID): | ||||
|             jid = jid.full | ||||
|         self._full = self._jid = jid | ||||
|         self._domain = None | ||||
|         self._resource = None | ||||
|         self._user = None | ||||
|         self._bare = None | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         """Handle getting the JID values, using cache if available. | ||||
|  | ||||
|         :param name: One of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             if self._resource is None and '/' in self._jid: | ||||
|                 self._resource = self._jid.split('/', 1)[-1] | ||||
|             return self._resource or "" | ||||
|         elif name == 'user': | ||||
|             if self._user is None: | ||||
|                 if '@' in self._jid: | ||||
|                     self._user = self._jid.split('@', 1)[0] | ||||
|                 else: | ||||
|                     self._user = self._user | ||||
|             return self._user or "" | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             if self._domain is None: | ||||
|                 self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] | ||||
|             return self._domain or "" | ||||
|         elif name in ('full', 'jid'): | ||||
|             return self._jid or "" | ||||
|         elif name == 'bare': | ||||
|             if self._bare is None: | ||||
|                 self._bare = self._jid.split('/', 1)[0] | ||||
|             return self._bare or "" | ||||
|  | ||||
|     def __setattr__(self, name, value): | ||||
|         """Edit a JID by updating it's individual values, resetting the | ||||
|         generated JID in the end. | ||||
|  | ||||
|         Arguments: | ||||
|             name  -- The name of the JID part. One of: user, domain, | ||||
|                      server, resource, full, jid, or bare. | ||||
|             value -- The new value for the JID part. | ||||
|         """ | ||||
|         if name in ('resource', 'user', 'domain'): | ||||
|             object.__setattr__(self, "_%s" % name, value) | ||||
|             self.regenerate() | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             self.domain = value | ||||
|         elif name in ('full', 'jid'): | ||||
|             self.reset(value) | ||||
|             self.regenerate() | ||||
|         elif name == 'bare': | ||||
|             if '@' in value: | ||||
|                 u, d = value.split('@', 1) | ||||
|                 object.__setattr__(self, "_user", u) | ||||
|                 object.__setattr__(self, "_domain", d) | ||||
|             else: | ||||
|                 object.__setattr__(self, "_user", '') | ||||
|                 object.__setattr__(self, "_domain", value) | ||||
|             self.regenerate() | ||||
|         else: | ||||
|             object.__setattr__(self, name, value) | ||||
|  | ||||
|     def regenerate(self): | ||||
|         """Generate a new JID based on current values, useful after editing.""" | ||||
|         jid = "" | ||||
|         if self.user: | ||||
|             jid = "%s@" % self.user | ||||
|         jid += self.domain | ||||
|         if self.resource: | ||||
|             jid += "/%s" % self.resource | ||||
|         self.reset(jid) | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return self.full | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return self.full | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         """ | ||||
|         Two JIDs are considered equal if they have the same full JID value. | ||||
|         """ | ||||
|         other = JID(other) | ||||
|         return self.full == other.full | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         """Two JIDs are considered unequal if they are not equal.""" | ||||
|         return not self == other | ||||
|  | ||||
|     def __hash__(self): | ||||
|         """Hash a JID based on the string version of its full JID.""" | ||||
|         return hash(self.full) | ||||
| from sleekxmpp.jid import JID | ||||
|   | ||||
| @@ -254,6 +254,7 @@ def get_SRV(host, port, service, proto='tcp', resolver=None): | ||||
|              by SRV priorities and weights. | ||||
|     """ | ||||
|     if resolver is None: | ||||
|         log.warning("DNS: dnspython not found. Can not use SRV lookup.") | ||||
|         return [(host, port)] | ||||
|  | ||||
|     log.debug("DNS: Querying SRV records for %s" % host) | ||||
|   | ||||
| @@ -15,10 +15,9 @@ | ||||
| import time | ||||
| import threading | ||||
| import logging | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
| import itertools | ||||
|  | ||||
| from sleekxmpp.util import Queue, QueueEmpty | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -102,7 +101,7 @@ class Scheduler(object): | ||||
|  | ||||
|     def __init__(self, parentstop=None): | ||||
|         #: A queue for storing tasks | ||||
|         self.addq = queue.Queue() | ||||
|         self.addq = Queue() | ||||
|  | ||||
|         #: A list of tasks in order of execution time. | ||||
|         self.schedule = [] | ||||
| @@ -157,26 +156,32 @@ class Scheduler(object): | ||||
|                               elapsed < wait: | ||||
|                             newtask = self.addq.get(True, 0.1) | ||||
|                             elapsed += 0.1 | ||||
|                 except queue.Empty: | ||||
|                     cleanup = [] | ||||
|                 except QueueEmpty: | ||||
|                     self.schedule_lock.acquire() | ||||
|                     for task in self.schedule: | ||||
|                         if time.time() >= task.next: | ||||
|                             updated = True | ||||
|                             if not task.run(): | ||||
|                                 cleanup.append(task) | ||||
|                     # select only those tasks which are to be executed now | ||||
|                     relevant = itertools.takewhile( | ||||
|                         lambda task: time.time() >= task.next, self.schedule) | ||||
|                     # run the tasks and keep the return value in a tuple | ||||
|                     status = map(lambda task: (task, task.run()), relevant) | ||||
|                     # remove non-repeating tasks | ||||
|                     for task, doRepeat in status: | ||||
|                         if not doRepeat: | ||||
|                             try: | ||||
|                                 self.schedule.remove(task) | ||||
|                             except ValueError: | ||||
|                                 pass | ||||
|                         else: | ||||
|                             break | ||||
|                     for task in cleanup: | ||||
|                         self.schedule.pop(self.schedule.index(task)) | ||||
|                             # only need to resort tasks if a repeated task has | ||||
|                             # been kept in the list. | ||||
|                             updated = True | ||||
|                 else: | ||||
|                     updated = True | ||||
|                     self.schedule_lock.acquire() | ||||
|                     self.schedule.append(newtask) | ||||
|                     if newtask is not None: | ||||
|                         self.schedule.append(newtask) | ||||
|                 finally: | ||||
|                     if updated: | ||||
|                         self.schedule = sorted(self.schedule, | ||||
|                                                key=lambda task: task.next) | ||||
|                         self.schedule.sort(key=lambda task: task.next) | ||||
|                     self.schedule_lock.release() | ||||
|         except KeyboardInterrupt: | ||||
|             self.run = False | ||||
|   | ||||
| @@ -421,12 +421,6 @@ class ElementBase(object): | ||||
|         #: ``'{namespace}elementname'``. | ||||
|         self.tag = self.tag_name() | ||||
|  | ||||
|         if 'lang' not in self.interfaces: | ||||
|             if isinstance(self.interfaces, tuple): | ||||
|                 self.interfaces += ('lang',) | ||||
|             else: | ||||
|                 self.interfaces.add('lang') | ||||
|  | ||||
|         #: A :class:`weakref.weakref` to the parent stanza, if there is one. | ||||
|         #: If not, then :attr:`parent` is ``None``. | ||||
|         self.parent = None | ||||
| @@ -520,8 +514,9 @@ class ElementBase(object): | ||||
|         :param string attrib: The :attr:`plugin_attrib` value of the | ||||
|                               plugin to enable. | ||||
|         """ | ||||
|         if lang is None: | ||||
|             lang = self.get_lang() | ||||
|         default_lang = self.get_lang() | ||||
|         if not lang: | ||||
|             lang = default_lang | ||||
|  | ||||
|         plugin_class = self.plugin_attrib_map[attrib] | ||||
|  | ||||
| @@ -534,7 +529,7 @@ class ElementBase(object): | ||||
|             existing_xml = self.xml.find(plugin_class.tag_name()) | ||||
|  | ||||
|         if existing_xml is not None: | ||||
|             if existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang: | ||||
|             if existing_xml.attrib.get('{%s}lang' % XML_NS, default_lang) != lang: | ||||
|                 existing_xml = None | ||||
|  | ||||
|         plugin = plugin_class(parent=self, xml=existing_xml) | ||||
| @@ -542,7 +537,8 @@ class ElementBase(object): | ||||
|         if plugin.is_extension: | ||||
|             self.plugins[(attrib, None)] = plugin | ||||
|         else: | ||||
|             plugin['lang'] = lang | ||||
|             if lang != default_lang: | ||||
|                 plugin['lang'] = lang | ||||
|             self.plugins[(attrib, lang)] = plugin | ||||
|  | ||||
|         if plugin_class in self.plugin_iterables: | ||||
| @@ -574,6 +570,7 @@ class ElementBase(object): | ||||
|         .. versionadded:: 1.0-Beta1 | ||||
|         """ | ||||
|         values = {} | ||||
|         values['lang'] = self['lang'] | ||||
|         for interface in self.interfaces: | ||||
|             values[interface] = self[interface] | ||||
|             if interface in self.lang_interfaces: | ||||
| @@ -581,7 +578,7 @@ class ElementBase(object): | ||||
|         for plugin, stanza in self.plugins.items(): | ||||
|             lang = stanza['lang'] | ||||
|             if lang: | ||||
|                 values['%s|%s' % (plugin, lang)] = stanza.values | ||||
|                 values['%s|%s' % (plugin[0], lang)] = stanza.values | ||||
|             else: | ||||
|                 values[plugin[0]] = stanza.values | ||||
|         if self.iterables: | ||||
| @@ -629,6 +626,8 @@ class ElementBase(object): | ||||
|                                 sub.values = subdict | ||||
|                                 self.iterables.append(sub) | ||||
|                                 break | ||||
|             elif interface == 'lang': | ||||
|                 self[interface] = value | ||||
|             elif interface in self.interfaces: | ||||
|                 self[full_interface] = value | ||||
|             elif interface in self.plugin_attrib_map: | ||||
| @@ -678,7 +677,7 @@ class ElementBase(object): | ||||
|  | ||||
|         if attrib == 'substanzas': | ||||
|             return self.iterables | ||||
|         elif attrib in self.interfaces: | ||||
|         elif attrib in self.interfaces or attrib == 'lang': | ||||
|             get_method = "get_%s" % attrib.lower() | ||||
|             get_method2 = "get%s" % attrib.title() | ||||
|  | ||||
| @@ -752,7 +751,7 @@ class ElementBase(object): | ||||
|         if lang and attrib in self.lang_interfaces: | ||||
|             kwargs['lang'] = lang | ||||
|  | ||||
|         if attrib in self.interfaces: | ||||
|         if attrib in self.interfaces or attrib == 'lang': | ||||
|             if value is not None: | ||||
|                 set_method = "set_%s" % attrib.lower() | ||||
|                 set_method2 = "set%s" % attrib.title() | ||||
| @@ -838,7 +837,7 @@ class ElementBase(object): | ||||
|         if lang and attrib in self.lang_interfaces: | ||||
|             kwargs['lang'] = lang | ||||
|  | ||||
|         if attrib in self.interfaces: | ||||
|         if attrib in self.interfaces or attrib == 'lang': | ||||
|             del_method = "del_%s" % attrib.lower() | ||||
|             del_method2 = "del%s" % attrib.title() | ||||
|  | ||||
| @@ -973,10 +972,6 @@ class ElementBase(object): | ||||
|         :param keep: Indicates if the element should be kept if its text is | ||||
|                      removed. Defaults to False. | ||||
|         """ | ||||
|         path = self._fix_ns(name, split=True) | ||||
|         element = self.xml.find(name) | ||||
|         parent = self.xml | ||||
|  | ||||
|         default_lang = self.get_lang() | ||||
|         if lang is None: | ||||
|             lang = default_lang | ||||
| @@ -984,32 +979,51 @@ class ElementBase(object): | ||||
|         if not text and not keep: | ||||
|             return self._del_sub(name, lang=lang) | ||||
|  | ||||
|         if element is None: | ||||
|             # We need to add the element. If the provided name was | ||||
|             # an XPath expression, some of the intermediate elements | ||||
|             # may already exist. If so, we want to use those instead | ||||
|             # of generating new elements. | ||||
|             last_xml = self.xml | ||||
|             walked = [] | ||||
|             for ename in path: | ||||
|                 walked.append(ename) | ||||
|                 element = self.xml.find("/".join(walked)) | ||||
|                 if element is None: | ||||
|                     element = ET.Element(ename) | ||||
|                     if lang: | ||||
|                         element.attrib['{%s}lang' % XML_NS] = lang | ||||
|                     last_xml.append(element) | ||||
|                 parent = last_xml | ||||
|                 last_xml = element | ||||
|             element = last_xml | ||||
|         path = self._fix_ns(name, split=True) | ||||
|         name = path[-1] | ||||
|         parent = self.xml | ||||
|  | ||||
|         if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang: | ||||
|         # The first goal is to find the parent of the subelement, or, if | ||||
|         # we can't find that, the closest grandparent element. | ||||
|         missing_path = [] | ||||
|         search_order = path[:-1] | ||||
|         while search_order: | ||||
|             parent = self.xml.find('/'.join(search_order)) | ||||
|             ename = search_order.pop() | ||||
|             if parent is not None: | ||||
|                 break | ||||
|             else: | ||||
|                 missing_path.append(ename) | ||||
|         missing_path.reverse() | ||||
|  | ||||
|         # Find all existing elements that match the desired | ||||
|         # element path (there may be multiples due to different | ||||
|         # languages values). | ||||
|         if parent is not None: | ||||
|             elements = self.xml.findall('/'.join(path)) | ||||
|         else: | ||||
|             parent = self.xml | ||||
|             elements = [] | ||||
|  | ||||
|         # Insert the remaining grandparent elements that don't exist yet. | ||||
|         for ename in missing_path: | ||||
|             element = ET.Element(ename) | ||||
|             if lang: | ||||
|                 element.attrib['{%s}lang' % XML_NS] = lang | ||||
|             parent.append(element) | ||||
|             parent = element | ||||
|  | ||||
|         # Re-use an existing element with the proper language, if one exists. | ||||
|         for element in elements: | ||||
|             elang = element.attrib.get('{%s}lang' % XML_NS, default_lang) | ||||
|             if not lang and elang == default_lang or lang and lang == elang: | ||||
|                 element.text = text | ||||
|                 return element | ||||
|  | ||||
|         # No useable element exists, so create a new one. | ||||
|         element = ET.Element(name) | ||||
|         element.text = text | ||||
|         if lang and lang != default_lang: | ||||
|             element.attrib['{%s}lang' % XML_NS] = lang | ||||
|         parent.append(element) | ||||
|         return element | ||||
|  | ||||
|     def _set_all_sub_text(self, name, values, keep=False, lang=None): | ||||
| @@ -1184,6 +1198,7 @@ class ElementBase(object): | ||||
|         out = [] | ||||
|         out += [x for x in self.interfaces] | ||||
|         out += [x for x in self.loaded_plugins] | ||||
|         out.append('lang') | ||||
|         if self.iterables: | ||||
|             out.append('substanzas') | ||||
|         return out | ||||
| @@ -1263,7 +1278,7 @@ class ElementBase(object): | ||||
|         """ | ||||
|         return "{%s}%s" % (cls.namespace, cls.name) | ||||
|  | ||||
|     def get_lang(self): | ||||
|     def get_lang(self, lang=None): | ||||
|         result = self.xml.attrib.get('{%s}lang' % XML_NS, '') | ||||
|         if not result and self.parent and self.parent(): | ||||
|             return self.parent()['lang'] | ||||
|   | ||||
| @@ -63,9 +63,11 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|  | ||||
|     default_ns = '' | ||||
|     stream_ns = '' | ||||
|     use_cdata = False | ||||
|     if stream: | ||||
|         default_ns = stream.default_ns | ||||
|         stream_ns = stream.stream_ns | ||||
|         use_cdata = stream.use_cdata | ||||
|  | ||||
|     # Output the tag name and derived namespace of the element. | ||||
|     namespace = '' | ||||
| @@ -81,7 +83,7 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|  | ||||
|     # Output escaped attribute values. | ||||
|     for attrib, value in xml.attrib.items(): | ||||
|         value = xml_escape(value) | ||||
|         value = escape(value, use_cdata) | ||||
|         if '}' not in attrib: | ||||
|             output.append(' %s="%s"' % (attrib, value)) | ||||
|         else: | ||||
| @@ -105,24 +107,24 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, | ||||
|         # If there are additional child elements to serialize. | ||||
|         output.append(">") | ||||
|         if xml.text: | ||||
|             output.append(xml_escape(xml.text)) | ||||
|             output.append(escape(xml.text, use_cdata)) | ||||
|         if len(xml): | ||||
|             for child in xml: | ||||
|                 output.append(tostring(child, tag_xmlns, stanza_ns, stream)) | ||||
|         output.append("</%s>" % tag_name) | ||||
|     elif xml.text: | ||||
|         # If we only have text content. | ||||
|         output.append(">%s</%s>" % (xml_escape(xml.text), tag_name)) | ||||
|         output.append(">%s</%s>" % (escape(xml.text, use_cdata), tag_name)) | ||||
|     else: | ||||
|         # Empty element. | ||||
|         output.append(" />") | ||||
|     if xml.tail: | ||||
|         # If there is additional text after the element. | ||||
|         output.append(xml_escape(xml.tail)) | ||||
|         output.append(escape(xml.tail, use_cdata)) | ||||
|     return ''.join(output) | ||||
|  | ||||
|  | ||||
| def xml_escape(text): | ||||
| def escape(text, use_cdata=False): | ||||
|     """Convert special characters in XML to escape sequences. | ||||
|  | ||||
|     :param string text: The XML text to convert. | ||||
| @@ -132,12 +134,24 @@ def xml_escape(text): | ||||
|         if type(text) != types.UnicodeType: | ||||
|             text = unicode(text, 'utf-8', 'ignore') | ||||
|  | ||||
|     text = list(text) | ||||
|     escapes = {'&': '&', | ||||
|                '<': '<', | ||||
|                '>': '>', | ||||
|                "'": ''', | ||||
|                '"': '"'} | ||||
|     for i, c in enumerate(text): | ||||
|         text[i] = escapes.get(c, c) | ||||
|     return ''.join(text) | ||||
|  | ||||
|     if not use_cdata: | ||||
|         text = list(text) | ||||
|         for i, c in enumerate(text): | ||||
|             text[i] = escapes.get(c, c) | ||||
|         return ''.join(text) | ||||
|     else: | ||||
|         escape_needed = False | ||||
|         for c in text: | ||||
|             if c in escapes: | ||||
|                 escape_needed = True | ||||
|                 break | ||||
|         if escape_needed: | ||||
|             escaped = map(lambda x : "<![CDATA[%s]]>" % x, text.split("]]>")) | ||||
|             return "<![CDATA[]]]><![CDATA[]>]]>".join(escaped) | ||||
|         return text | ||||
|   | ||||
| @@ -26,14 +26,11 @@ import time | ||||
| import random | ||||
| import weakref | ||||
| import uuid | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| from xml.parsers.expat import ExpatError | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.util import Queue, QueueEmpty | ||||
| from sleekxmpp.thirdparty.statemachine import StateMachine | ||||
| from sleekxmpp.xmlstream import Scheduler, tostring, cert | ||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET, ElementBase | ||||
| @@ -212,6 +209,13 @@ class XMLStream(object): | ||||
|         #: proxy based on the settings in :attr:`proxy_config`. | ||||
|         self.use_proxy = False | ||||
|  | ||||
|         #: If set to ``True``, attempt to use IPv6. | ||||
|         self.use_ipv6 = True | ||||
|  | ||||
|         #: Use CDATA for escaping instead of XML entities. Defaults | ||||
|         #: to ``False``. | ||||
|         self.use_cdata = False | ||||
|  | ||||
|         #: An optional dictionary of proxy settings. It may provide: | ||||
|         #: :host: The host offering proxy services. | ||||
|         #: :port: The port for the proxy service. | ||||
| @@ -267,10 +271,10 @@ class XMLStream(object): | ||||
|         self.end_session_on_disconnect = True | ||||
|  | ||||
|         #: A queue of stream, custom, and scheduled events to be processed. | ||||
|         self.event_queue = queue.Queue() | ||||
|         self.event_queue = Queue() | ||||
|  | ||||
|         #: A queue of string data to be sent over the stream. | ||||
|         self.send_queue = queue.Queue() | ||||
|         self.send_queue = Queue() | ||||
|         self.send_queue_lock = threading.Lock() | ||||
|         self.send_lock = threading.RLock() | ||||
|  | ||||
| @@ -464,7 +468,7 @@ class XMLStream(object): | ||||
|                 log.debug("No remaining DNS records to try.") | ||||
|                 self.dns_answers = None | ||||
|                 if reattempt: | ||||
|                     self.reconnect_delay = delay | ||||
|                     self.reconnect_delay = None | ||||
|                 return False | ||||
|  | ||||
|         af = Socket.AF_INET | ||||
| @@ -534,8 +538,8 @@ class XMLStream(object): | ||||
|                     try: | ||||
|                         cert.verify(self._expected_server_name, self._der_cert) | ||||
|                     except cert.CertificateError as err: | ||||
|                         log.error(err.message) | ||||
|                         if not self.event_handled('ssl_invalid_cert'): | ||||
|                             log.error(err.message) | ||||
|                             self.disconnect(send_close=False) | ||||
|                         else: | ||||
|                             self.event('ssl_invalid_cert', | ||||
| @@ -825,8 +829,8 @@ class XMLStream(object): | ||||
|             try: | ||||
|                 cert.verify(self._expected_server_name, self._der_cert) | ||||
|             except cert.CertificateError as err: | ||||
|                 log.error(err.message) | ||||
|                 if not self.event_handled('ssl_invalid_cert'): | ||||
|                     log.error(err.message) | ||||
|                     self.disconnect(self.auto_reconnect, send_close=False) | ||||
|                 else: | ||||
|                     self.event('ssl_invalid_cert', pem_cert, direct=True) | ||||
| @@ -951,6 +955,10 @@ class XMLStream(object): | ||||
|         else: | ||||
|             self.__filters[mode].append(handler) | ||||
|  | ||||
|     def del_filter(self, mode, handler): | ||||
|         """Remove an incoming or outgoing filter.""" | ||||
|         self.__filters[mode].remove(handler) | ||||
|  | ||||
|     def add_handler(self, mask, pointer, name=None, disposable=False, | ||||
|                     threaded=False, filter=False, instream=False): | ||||
|         """A shortcut method for registering a handler using XML masks. | ||||
| @@ -1579,7 +1587,7 @@ class XMLStream(object): | ||||
|                 try: | ||||
|                     wait = self.wait_timeout | ||||
|                     event = self.event_queue.get(True, timeout=wait) | ||||
|                 except queue.Empty: | ||||
|                 except QueueEmpty: | ||||
|                     event = None | ||||
|                 if event is None: | ||||
|                     continue | ||||
| @@ -1648,7 +1656,7 @@ class XMLStream(object): | ||||
|                 else: | ||||
|                     try: | ||||
|                         data = self.send_queue.get(True, 1) | ||||
|                     except queue.Empty: | ||||
|                     except QueueEmpty: | ||||
|                         continue | ||||
|                 log.debug("SEND: %s", data) | ||||
|                 enc_data = data.encode('utf-8') | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.xmlstream.jid import JID | ||||
| from sleekxmpp import JID, InvalidJID | ||||
|  | ||||
|  | ||||
| class TestJIDClass(SleekTest): | ||||
| @@ -137,5 +137,146 @@ class TestJIDClass(SleekTest): | ||||
|         self.assertFalse(jid1 == jid2, "Same JIDs are not considered equal") | ||||
|         self.assertTrue(jid1 != jid2, "Same JIDs are considered not equal") | ||||
|  | ||||
|     def testZeroLengthDomain(self): | ||||
|         self.assertRaises(InvalidJID, JID, domain='') | ||||
|         self.assertRaises(InvalidJID, JID, 'user@/resource') | ||||
|  | ||||
|     def testZeroLengthLocalPart(self): | ||||
|         self.assertRaises(InvalidJID, JID, local='', domain='test.com') | ||||
|         self.assertRaises(InvalidJID, JID, '@/test.com') | ||||
|  | ||||
|     def testZeroLengthResource(self): | ||||
|         self.assertRaises(InvalidJID, JID, domain='test.com', resource='') | ||||
|         self.assertRaises(InvalidJID, JID, 'test.com/') | ||||
|  | ||||
|     def test1023LengthDomain(self): | ||||
|         domain = ('a.' * 509) + 'a.com' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|     def test1023LengthLocalPart(self): | ||||
|         local = 'a' * 1023 | ||||
|         jid1 = JID(local=local, domain='test.com') | ||||
|         jid2 = JID('%s@test.com' % local) | ||||
|  | ||||
|     def test1023LengthResource(self): | ||||
|         resource = 'r' * 1023 | ||||
|         jid1 = JID(domain='test.com', resource=resource) | ||||
|         jid2 = JID('test.com/%s' % resource) | ||||
|  | ||||
|     def test1024LengthDomain(self): | ||||
|         domain = ('a.' * 509) + 'aa.com' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|     def test1024LengthLocalPart(self): | ||||
|         local = 'a' * 1024 | ||||
|         self.assertRaises(InvalidJID, JID, local=local, domain='test.com') | ||||
|         self.assertRaises(InvalidJID, JID, '%s@/test.com' % local) | ||||
|  | ||||
|     def test1024LengthResource(self): | ||||
|         resource = 'r' * 1024 | ||||
|         self.assertRaises(InvalidJID, JID, domain='test.com', resource=resource) | ||||
|         self.assertRaises(InvalidJID, JID, 'test.com/%s' % resource) | ||||
|  | ||||
|     def testTooLongDomainLabel(self): | ||||
|         domain = ('a' * 64) + '.com' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|     def testDomainEmptyLabel(self): | ||||
|         domain = 'aaa..bbb.com' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|     def testDomainIPv4(self): | ||||
|         domain = '127.0.0.1' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|     def testDomainIPv6(self): | ||||
|         domain = '[::1]' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|     def testDomainInvalidIPv6NoBrackets(self): | ||||
|         domain = '::1' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|         self.assertEqual(jid1.domain, '[::1]') | ||||
|         self.assertEqual(jid2.domain, '[::1]') | ||||
|  | ||||
|     def testDomainInvalidIPv6MissingBracket(self): | ||||
|         domain = '[::1' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|         self.assertEqual(jid1.domain, '[::1]') | ||||
|         self.assertEqual(jid2.domain, '[::1]') | ||||
|  | ||||
|     def testDomainWithPort(self): | ||||
|         domain = 'example.com:5555' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|     def testDomainWithTrailingDot(self): | ||||
|         domain = 'example.com.' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|         self.assertEqual(jid1.domain, 'example.com') | ||||
|         self.assertEqual(jid2.domain, 'example.com') | ||||
|  | ||||
|     def testDomainWithDashes(self): | ||||
|         domain = 'example.com-' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|         domain = '-example.com' | ||||
|         self.assertRaises(InvalidJID, JID, domain=domain) | ||||
|         self.assertRaises(InvalidJID, JID, 'user@%s/resource' % domain) | ||||
|  | ||||
|     def testACEDomain(self): | ||||
|         domain = 'xn--bcher-kva.ch' | ||||
|         jid1 = JID(domain=domain) | ||||
|         jid2 = JID('user@%s/resource' % domain) | ||||
|  | ||||
|         self.assertEqual(jid1.domain.encode('utf-8'), b'b\xc3\xbccher.ch') | ||||
|         self.assertEqual(jid2.domain.encode('utf-8'), b'b\xc3\xbccher.ch') | ||||
|  | ||||
|     def testJIDEscapeExistingSequences(self): | ||||
|         jid = JID(local='blah\\foo\\20bar', domain='example.com') | ||||
|         self.assertEqual(jid.local, 'blah\\foo\\5c20bar') | ||||
|  | ||||
|     def testJIDEscape(self): | ||||
|         jid = JID(local='here\'s_a_wild_&_/cr%zy/_address_for:<wv>("IMPS")', | ||||
|                   domain='example.com') | ||||
|         self.assertEqual(jid.local, r'here\27s_a_wild_\26_\2fcr%zy\2f_address_for\3a\3cwv\3e(\22IMPS\22)') | ||||
|  | ||||
|     def testJIDUnescape(self): | ||||
|         jid = JID(local='here\'s_a_wild_&_/cr%zy/_address_for:<wv>("IMPS")', | ||||
|                   domain='example.com') | ||||
|         ujid = jid.unescape() | ||||
|         self.assertEqual(ujid.local, 'here\'s_a_wild_&_/cr%zy/_address_for:<wv>("IMPS")') | ||||
|  | ||||
|         jid = JID(local='blah\\foo\\20bar', domain='example.com') | ||||
|         ujid = jid.unescape() | ||||
|         self.assertEqual(ujid.local, 'blah\\foo\\20bar') | ||||
|  | ||||
|     def testStartOrEndWithEscapedSpaces(self): | ||||
|         local = ' foo' | ||||
|         self.assertRaises(InvalidJID, JID, local=local, domain='example.com') | ||||
|         self.assertRaises(InvalidJID, JID, '%s@example.com' % local) | ||||
|  | ||||
|         local = 'bar ' | ||||
|         self.assertRaises(InvalidJID, JID, local=local, domain='example.com') | ||||
|         self.assertRaises(InvalidJID, JID, '%s@example.com' % local) | ||||
|  | ||||
|         # Need more input for these cases. A JID starting with \20 *is* valid | ||||
|         # according to RFC 6122, but is not according to XEP-0106. | ||||
|         #self.assertRaises(InvalidJID, JID, '%s@example.com' % '\\20foo2') | ||||
|         #self.assertRaises(InvalidJID, JID, '%s@example.com' % 'bar2\\20') | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.xmlstream.stanzabase import ElementBase | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
|  | ||||
| class TestElementBase(SleekTest): | ||||
| @@ -760,7 +761,7 @@ class TestElementBase(SleekTest): | ||||
|           <foo xmlns="foo" /> | ||||
|         """) | ||||
|  | ||||
|         self.assertFalse(stanza['bar'],  | ||||
|         self.assertFalse(stanza['bar'], | ||||
|                 "Returned True for missing bool interface element.") | ||||
|  | ||||
|         stanza['bar'] = True | ||||
| @@ -797,7 +798,7 @@ class TestElementBase(SleekTest): | ||||
|             namespace = 'baz' | ||||
|             plugin_attrib = name | ||||
|             plugin_multi_attrib = 'bazs' | ||||
|             | ||||
|  | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True) | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True) | ||||
|  | ||||
| @@ -829,9 +830,9 @@ class TestElementBase(SleekTest): | ||||
|               <baz xmlns="baz" /> | ||||
|             """) | ||||
|  | ||||
|         self.assertEqual(len(bars), 2,  | ||||
|         self.assertEqual(len(bars), 2, | ||||
|                 "Wrong number of <bar /> stanzas: %s" % len(bars)) | ||||
|         self.assertEqual(len(bazs), 2,  | ||||
|         self.assertEqual(len(bazs), 2, | ||||
|                 "Wrong number of <baz /> stanzas: %s" % len(bazs)) | ||||
|  | ||||
|     def testSetMultiAttrib(self): | ||||
| @@ -853,7 +854,7 @@ class TestElementBase(SleekTest): | ||||
|             namespace = 'baz' | ||||
|             plugin_attrib = name | ||||
|             plugin_multi_attrib = 'bazs' | ||||
|             | ||||
|  | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True) | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True) | ||||
|  | ||||
| @@ -906,7 +907,7 @@ class TestElementBase(SleekTest): | ||||
|             namespace = 'baz' | ||||
|             plugin_attrib = name | ||||
|             plugin_multi_attrib = 'bazs' | ||||
|             | ||||
|  | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True) | ||||
|         register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True) | ||||
|  | ||||
| @@ -938,5 +939,313 @@ class TestElementBase(SleekTest): | ||||
|         self.assertEqual(len(stanza['substanzas']), 2, | ||||
|             "Wrong number of substanzas: %s" % len(stanza['substanzas'])) | ||||
|  | ||||
|     def testDefaultLang(self): | ||||
|         """Test setting a normal subinterface when a default language is set""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['lang'] = 'sv' | ||||
|         stanza['test'] = 'hej' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="sv"> | ||||
|             <test>hej</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'hej', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|sv'], 'hej', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|sv']) | ||||
|  | ||||
|     def testSpecifyLangWithDefault(self): | ||||
|         """Test specifying various languages.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['lang'] = 'sv' | ||||
|         stanza['test'] = 'hej' | ||||
|         stanza['test|en'] = 'hi' | ||||
|         stanza['test|es'] = 'hola' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="sv"> | ||||
|             <test>hej</test> | ||||
|             <test xml:lang="en">hi</test> | ||||
|             <test xml:lang="es">hola</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'hej', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|sv'], 'hej', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|sv']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|en'], 'hi', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|en']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|es'], 'hola', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|es']) | ||||
|  | ||||
|     def testSpecifyLangWithNoDefault(self): | ||||
|         """Test specifying various languages.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['test'] = 'hej' | ||||
|         stanza['test|en'] = 'hi' | ||||
|         stanza['test|es'] = 'hola' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test>hej</test> | ||||
|             <test xml:lang="en">hi</test> | ||||
|             <test xml:lang="es">hola</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'hej', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|en'], 'hi', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|en']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|es'], 'hola', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|es']) | ||||
|  | ||||
|     def testModifyLangInterfaceWithDefault(self): | ||||
|         """Test resetting an interface when a default lang is used.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['lang'] = 'es' | ||||
|         stanza['test'] = 'hola' | ||||
|         stanza['test|en'] = 'hi' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="es"> | ||||
|             <test>hola</test> | ||||
|             <test xml:lang="en">hi</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         stanza['test'] = 'adios' | ||||
|         stanza['test|en'] = 'bye' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="es"> | ||||
|             <test>adios</test> | ||||
|             <test xml:lang="en">bye</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'adios', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|es'], 'adios', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|es']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|en'], 'bye', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|en']) | ||||
|  | ||||
|         stanza['test|es'] = 'hola' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="es"> | ||||
|             <test>hola</test> | ||||
|             <test xml:lang="en">bye</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'hola', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|es'], 'hola', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|es']) | ||||
|  | ||||
|     def testModifyLangInterfaceWithNoDefault(self): | ||||
|         """Test resetting an interface when no default lang is used.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['test'] = 'hola' | ||||
|         stanza['test|en'] = 'hi' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test>hola</test> | ||||
|             <test xml:lang="en">hi</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         stanza['test'] = 'adios' | ||||
|         stanza['test|en'] = 'bye' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test>adios</test> | ||||
|             <test xml:lang="en">bye</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'adios', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test']) | ||||
|  | ||||
|         self.assertEqual(stanza['test'], 'adios', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|es']) | ||||
|  | ||||
|         self.assertEqual(stanza['test|en'], 'bye', | ||||
|                 "Incorrect subinterface value: %s" % stanza['test|en']) | ||||
|  | ||||
|     def testDelInterfacesWithDefaultLang(self): | ||||
|         """Test deleting interfaces with a default lang set.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['lang'] = 'en' | ||||
|         stanza['test'] = 'hi' | ||||
|         stanza['test|no'] = 'hej' | ||||
|         stanza['test|fr'] = 'bonjour' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="en"> | ||||
|             <test>hi</test> | ||||
|             <test xml:lang="no">hej</test> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['test'] | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="en"> | ||||
|             <test xml:lang="no">hej</test> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['test|no'] | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" xml:lang="en"> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     def testDelInterfacesWithNoDefaultLang(self): | ||||
|         """Test deleting interfaces with no default lang set.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['test'] = 'hi' | ||||
|         stanza['test|no'] = 'hej' | ||||
|         stanza['test|fr'] = 'bonjour' | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test>hi</test> | ||||
|             <test xml:lang="no">hej</test> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['test'] | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test xml:lang="no">hej</test> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['test|no'] | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     def testStarLang(self): | ||||
|         """Test using interface|*.""" | ||||
|  | ||||
|         class TestStanza(ElementBase): | ||||
|             name = 'foo' | ||||
|             namespace = 'test' | ||||
|             interfaces = set(['test']) | ||||
|             sub_interfaces = interfaces | ||||
|             lang_interfaces = interfaces | ||||
|  | ||||
|         data = OrderedDict() | ||||
|         data['en'] = 'hi' | ||||
|         data['fr'] = 'bonjour' | ||||
|         data['no'] = 'hej' | ||||
|  | ||||
|         stanza = TestStanza() | ||||
|         stanza['test|*'] = data | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <test xml:lang="en">hi</test> | ||||
|             <test xml:lang="fr">bonjour</test> | ||||
|             <test xml:lang="no">hej</test> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         data2 = stanza['test|*'] | ||||
|  | ||||
|         self.assertEqual(data, data2, | ||||
|                 "Did not extract expected language data: %s" % data2) | ||||
|  | ||||
|         del stanza['test|*'] | ||||
|  | ||||
|         self.check(stanza, """ | ||||
|           <foo xmlns="test" /> | ||||
|         """) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.stanza import Message | ||||
| from sleekxmpp.xmlstream.stanzabase import ET, ElementBase | ||||
| from sleekxmpp.xmlstream.tostring import tostring, xml_escape | ||||
| from sleekxmpp.xmlstream.tostring import tostring, escape | ||||
|  | ||||
|  | ||||
| class TestToString(SleekTest): | ||||
| @@ -30,7 +30,7 @@ class TestToString(SleekTest): | ||||
|     def testXMLEscape(self): | ||||
|         """Test escaping XML special characters.""" | ||||
|         original = """<foo bar="baz">'Hi & welcome!'</foo>""" | ||||
|         escaped = xml_escape(original) | ||||
|         escaped = escape(original) | ||||
|         desired = """<foo bar="baz">'Hi""" | ||||
|         desired += """ & welcome!'</foo>""" | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user