Compare commits
	
		
			125 Commits
		
	
	
		
			sleek-1.1.
			...
			1.1.11
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 846f2bac5f | ||
|   | 2229ad8d8e | ||
|   | 61aff9f49a | ||
|   | 67235c4214 | ||
|   | 12e8bb6ddc | ||
|   | 52feabbe76 | ||
|   | a22ca228cc | ||
|   | d0666a5eb6 | ||
|   | 2a4e435228 | ||
|   | c5046b9c91 | ||
|   | 4598031dd2 | ||
|   | 5e9266ba90 | ||
|   | e6c95f0a2a | ||
|   | 63b58edda1 | ||
|   | af9632519c | ||
|   | d367fb938d | ||
|   | 4190027a78 | ||
|   | ef48a8c4d9 | ||
|   | 829b225053 | ||
|   | 747a6e94e6 | ||
|   | cebc798e72 | ||
|   | e2e8c4b5dc | ||
|   | 675c0112ac | ||
|   | 4dd2c15775 | ||
|   | 9f6decdbc1 | ||
|   | 4ea328b9f2 | ||
|   | 098714b3c4 | ||
|   | cf2c94d974 | ||
|   | 657102e938 | ||
|   | 94488fa2ea | ||
|   | ee9c4abd08 | ||
|   | b5b1c932c7 | ||
|   | b8f04983e1 | ||
|   | 90807dd973 | ||
|   | ef974114ea | ||
|   | f6e1fecdf8 | ||
|   | 94e8b2becf | ||
|   | a6ca6701a0 | ||
|   | c4edb9724b | ||
|   | b5c669bdff | ||
|   | e449dce65c | ||
|   | 671f680bb3 | ||
|   | dfff19ffbf | ||
|   | a4abdf9fa6 | ||
|   | 6c57bb0553 | ||
|   | c2ae1ee891 | ||
|   | fb3e6b7e35 | ||
|   | cf28d4586d | ||
|   | f65eb5eeea | ||
|   | 26fa9bd87e | ||
|   | 0016d9a638 | ||
|   | a88b9737ff | ||
|   | 357406d801 | ||
|   | c7ec6a72cd | ||
|   | e68b07dbce | ||
|   | 1ca0c46333 | ||
|   | e510875f64 | ||
|   | 8a03bd72ae | ||
|   | f0e1fc5aad | ||
|   | c6ac64ed2d | ||
|   | 92be051450 | ||
|   | 779c258e27 | ||
|   | f7a710e55b | ||
|   | 814a50e36f | ||
|   | 230465b946 | ||
|   | d11a67702e | ||
|   | 4e12e228cb | ||
|   | 4a94aeba49 | ||
|   | 295d23ccf3 | ||
|   | aebcf6ff82 | ||
|   | 8c2ece3bca | ||
|   | 80a90a6221 | ||
|   | 2324c90232 | ||
|   | 59ff08174f | ||
|   | b84e359770 | ||
|   | 475ccfa8dc | ||
|   | 267c24c8ef | ||
|   | 1383ca19b5 | ||
|   | 4c3ff2abab | ||
|   | 7c6ef18e4f | ||
|   | f8856467d5 | ||
|   | 3bd84b8d27 | ||
|   | bc8b5774ac | ||
|   | 8009b0485e | ||
|   | 8742a56b3e | ||
|   | a792bcdafe | ||
|   | 167d1ce97b | ||
|   | 695cd95657 | ||
|   | 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 | 
| @@ -45,13 +45,11 @@ The latest source code for SleekXMPP may be found on `Github | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Latest Release** | ||||
|     - `1.1.9 <http://github.com/fritzy/SleekXMPP/zipball/1.1.9>`_ | ||||
|     - `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 | ||||
| --------------------- | ||||
|   | ||||
| @@ -69,8 +69,8 @@ use ASCII. We can get Python to use UTF-8 as the default encoding by including: | ||||
| .. code-block:: python | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         reload(sys) | ||||
|         sys.setdefaultencoding('utf8') | ||||
|         from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|         setdefaultencoding('utf8') | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										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): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     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.") | ||||
| @@ -28,8 +28,8 @@ from stanza import Action | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -56,8 +56,8 @@ class ActionBot(sleekxmpp.ClientXMPP): | ||||
|             StanzaPath('iq@type=set/action'), | ||||
|             self._handle_action)) | ||||
|  | ||||
|         self.add_event_handler('custom_action',  | ||||
|                 self._handle_action_event,  | ||||
|         self.add_event_handler('custom_action', | ||||
|                 self._handle_action_event, | ||||
|                 threaded=True) | ||||
|  | ||||
|         register_stanza_plugin(Iq, Action) | ||||
|   | ||||
| @@ -26,8 +26,8 @@ from stanza import Action | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| @@ -22,8 +23,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -83,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         if self.get in self.info_types: | ||||
|             # By using block=True, the result stanza will be | ||||
|             # returned. Execution will block until the reply is | ||||
|             # received. Non-blocking options would be to listen | ||||
|             # for the disco_info event, or passing a handler | ||||
|             # function using the callback parameter. | ||||
|             info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                              node=self.target_node, | ||||
|                                              block=True) | ||||
|         if self.get in self.items_types: | ||||
|             # The same applies from above. Listen for the | ||||
|             # disco_items event or pass a callback function | ||||
|             # if you need to process a non-blocking request. | ||||
|             items = self['xep_0030'].get_items(jid=self.target_jid, | ||||
|                                                node=self.target_node, | ||||
|                                                block=True) | ||||
|         try: | ||||
|             if self.get in self.info_types: | ||||
|                 # By using block=True, the result stanza will be | ||||
|                 # returned. Execution will block until the reply is | ||||
|                 # received. Non-blocking options would be to listen | ||||
|                 # for the disco_info event, or passing a handler | ||||
|                 # function using the callback parameter. | ||||
|                 info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                  node=self.target_node, | ||||
|                                                  block=True) | ||||
|             elif self.get in self.items_types: | ||||
|                 # The same applies from above. Listen for the | ||||
|                 # disco_items event or pass a callback function | ||||
|                 # if you need to process a non-blocking request. | ||||
|                 items = self['xep_0030'].get_items(jid=self.target_jid, | ||||
|                                                    node=self.target_node, | ||||
|                                                    block=True) | ||||
|             else: | ||||
|                 logging.error("Invalid disco request type.") | ||||
|                 return | ||||
|         except IqError as e: | ||||
|             logging.error("Entity returned an error: %s" % e.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             logging.error("No response received.") | ||||
|         else: | ||||
|             logging.error("Invalid disco request type.") | ||||
|             self.disconnect() | ||||
|             return | ||||
|  | ||||
|         header = 'XMPP Service Discovery: %s' % self.target_jid | ||||
|         print(header) | ||||
|         print('-' * len(header)) | ||||
|         if self.target_node != '': | ||||
|             print('Node: %s' % self.target_node) | ||||
|             header = 'XMPP Service Discovery: %s' % self.target_jid | ||||
|             print(header) | ||||
|             print('-' * len(header)) | ||||
|             if self.target_node != '': | ||||
|                 print('Node: %s' % self.target_node) | ||||
|                 print('-' * len(header)) | ||||
|  | ||||
|         if self.get in self.identity_types: | ||||
|             print('Identities:') | ||||
|             for identity in info['disco_info']['identities']: | ||||
|                 print('  - %s' % str(identity)) | ||||
|             if self.get in self.identity_types: | ||||
|                 print('Identities:') | ||||
|                 for identity in info['disco_info']['identities']: | ||||
|                     print('  - %s' % str(identity)) | ||||
|  | ||||
|         if self.get in self.feature_types: | ||||
|             print('Features:') | ||||
|             for feature in info['disco_info']['features']: | ||||
|                 print('  - %s' % feature) | ||||
|             if self.get in self.feature_types: | ||||
|                 print('Features:') | ||||
|                 for feature in info['disco_info']['features']: | ||||
|                     print('  - %s' % feature) | ||||
|  | ||||
|         if self.get in self.items_types: | ||||
|             print('Items:') | ||||
|             for item in items['disco_items']['items']: | ||||
|                 print('  - %s' % str(item)) | ||||
|  | ||||
|         self.disconnect() | ||||
|             if self.get in self.items_types: | ||||
|                 print('Items:') | ||||
|                 for item in items['disco_items']['items']: | ||||
|                     print('  - %s' % str(item)) | ||||
|         finally: | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
							
								
								
									
										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): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     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.") | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -25,8 +25,8 @@ from sleekxmpp.xmlstream import cert | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -85,7 +85,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP): | ||||
|     def stream_opened(self, stream): | ||||
|         # NOTE: IBB streams are bi-directional, so the original sender is | ||||
|         # now the opened stream's receiver. | ||||
|         print('Stream opened: %s from ' % (stream.sid, stream.receiver)) | ||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.receiver)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # or use the ibb_stream_data event. | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,8 @@ from sleekxmpp.xmlstream import ET, tostring | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -14,8 +14,8 @@ from sleekxmpp.xmlstream.handler import Callback | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -24,8 +24,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										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): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     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.") | ||||
| @@ -29,8 +29,8 @@ from sleekxmpp.xmlstream import JID | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										17
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								setup.py
									
									
									
									
									
								
							| @@ -49,6 +49,8 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/roster', | ||||
|                  'sleekxmpp/util', | ||||
|                  'sleekxmpp/util/sasl', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
| @@ -58,11 +60,14 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0009', | ||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0012', | ||||
|                  'sleekxmpp/plugins/xep_0013', | ||||
|                  'sleekxmpp/plugins/xep_0016', | ||||
|                  'sleekxmpp/plugins/xep_0027', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0033', | ||||
|                  'sleekxmpp/plugins/xep_0047', | ||||
|                  'sleekxmpp/plugins/xep_0049', | ||||
|                  'sleekxmpp/plugins/xep_0050', | ||||
|                  'sleekxmpp/plugins/xep_0054', | ||||
|                  'sleekxmpp/plugins/xep_0059', | ||||
| @@ -75,12 +80,14 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0084', | ||||
|                  'sleekxmpp/plugins/xep_0085', | ||||
|                  'sleekxmpp/plugins/xep_0086', | ||||
|                  'sleekxmpp/plugins/xep_0091', | ||||
|                  'sleekxmpp/plugins/xep_0092', | ||||
|                  'sleekxmpp/plugins/xep_0107', | ||||
|                  'sleekxmpp/plugins/xep_0108', | ||||
|                  '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', | ||||
| @@ -93,8 +100,15 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0221', | ||||
|                  'sleekxmpp/plugins/xep_0224', | ||||
|                  'sleekxmpp/plugins/xep_0231', | ||||
|                  'sleekxmpp/plugins/xep_0235', | ||||
|                  'sleekxmpp/plugins/xep_0249', | ||||
|                  'sleekxmpp/plugins/xep_0257', | ||||
|                  'sleekxmpp/plugins/xep_0258', | ||||
|                  'sleekxmpp/plugins/xep_0279', | ||||
|                  'sleekxmpp/plugins/xep_0280', | ||||
|                  'sleekxmpp/plugins/xep_0297', | ||||
|                  'sleekxmpp/plugins/xep_0308', | ||||
|                  'sleekxmpp/plugins/xep_0313', | ||||
|                  'sleekxmpp/features', | ||||
|                  'sleekxmpp/features/feature_mechanisms', | ||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||
| @@ -102,9 +116,8 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/features/feature_bind', | ||||
|                  'sleekxmpp/features/feature_session', | ||||
|                  'sleekxmpp/features/feature_rosterver', | ||||
|                  'sleekxmpp/features/feature_preapproval', | ||||
|                  'sleekxmpp/thirdparty', | ||||
|                  'sleekxmpp/thirdparty/suelta', | ||||
|                  'sleekxmpp/thirdparty/suelta/mechanisms', | ||||
|                  ] | ||||
|  | ||||
| setup( | ||||
|   | ||||
| @@ -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 * | ||||
|   | ||||
| @@ -101,8 +101,10 @@ class APIRegistry(object): | ||||
|  | ||||
|         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 = '' | ||||
|   | ||||
| @@ -43,8 +43,8 @@ log = logging.getLogger(__name__) | ||||
| # In order to make sure that Unicode is handled properly | ||||
| # in Python 2.x, reset the default encoding. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
|  | ||||
|  | ||||
| class BaseXMPP(XMLStream): | ||||
| @@ -68,10 +68,20 @@ class BaseXMPP(XMLStream): | ||||
|         #: An identifier for the stream as given by the server. | ||||
|         self.stream_id = None | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection. | ||||
|         self.boundjid = JID(jid) | ||||
|         #: The JabberID (JID) requested for this connection. | ||||
|         self.requested_jid = JID(jid, cache_lock=True) | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection, | ||||
|         #: as set after session binding. This may even be a | ||||
|         #: different bare JID than what was requested. | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|  | ||||
|         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() | ||||
|  | ||||
| @@ -104,6 +114,17 @@ class BaseXMPP(XMLStream): | ||||
|         #: ``'to'`` and ``'from'`` JIDs of stanzas. | ||||
|         self.is_component = False | ||||
|  | ||||
|         #: Messages may optionally be tagged with ID values. Setting | ||||
|         #: :attr:`use_message_ids` to `True` will assign all outgoing | ||||
|         #: messages an ID. Some plugin features require enabling | ||||
|         #: this option. | ||||
|         self.use_message_ids = False | ||||
|  | ||||
|         #: Presence updates may optionally be tagged with ID values. | ||||
|         #: Setting :attr:`use_message_ids` to `True` will assign all | ||||
|         #: outgoing messages an ID. | ||||
|         self.use_presence_ids = False | ||||
|  | ||||
|         #: The API registry is a way to process callbacks based on | ||||
|         #: JID+node combinations. Each callback in the registry is | ||||
|         #: marked with: | ||||
| @@ -144,6 +165,8 @@ class BaseXMPP(XMLStream): | ||||
|                      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', | ||||
| @@ -189,6 +212,10 @@ class BaseXMPP(XMLStream): | ||||
|         self.stream_version = xml.get('version', '') | ||||
|         self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) | ||||
|  | ||||
|         if not self.is_component and not self.stream_version: | ||||
|             log.warning('Legacy XMPP 0.9 protocol detected.') | ||||
|             self.event('legacy_protocol') | ||||
|  | ||||
|     def process(self, *args, **kwargs): | ||||
|         """Initialize plugins and begin processing the XML stream. | ||||
|  | ||||
| @@ -214,13 +241,6 @@ class BaseXMPP(XMLStream): | ||||
|         - The send queue processor | ||||
|         - The scheduler | ||||
|         """ | ||||
|         if 'xep_0115' in self.plugin: | ||||
|             name = 'xep_0115' | ||||
|             if not hasattr(self.plugin[name], 'post_inited'): | ||||
|                 if hasattr(self.plugin[name], 'post_init'): | ||||
|                     self.plugin[name].post_init() | ||||
|                 self.plugin[name].post_inited = True | ||||
|  | ||||
|         for name in self.plugin: | ||||
|             if not hasattr(self.plugin[name], 'post_inited'): | ||||
|                 if hasattr(self.plugin[name], 'post_init'): | ||||
| @@ -591,7 +611,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 | ||||
| @@ -645,7 +665,7 @@ class BaseXMPP(XMLStream): | ||||
|     def set_jid(self, jid): | ||||
|         """Rip a JID apart and claim it as our own.""" | ||||
|         log.debug("setting jid to %s", jid) | ||||
|         self.boundjid.full = jid | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|  | ||||
|     def getjidresource(self, fulljid): | ||||
|         if '/' in fulljid: | ||||
| @@ -656,6 +676,10 @@ 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() | ||||
| @@ -666,6 +690,15 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         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 | ||||
| @@ -713,6 +746,8 @@ class BaseXMPP(XMLStream): | ||||
|         item = self.roster[pres['to']][pres['from']] | ||||
|         if item['whitelisted']: | ||||
|             item.authorize() | ||||
|             if roster.auto_subscribe: | ||||
|                 item.subscribe() | ||||
|         elif roster.auto_authorize: | ||||
|             item.authorize() | ||||
|             if roster.auto_subscribe: | ||||
|   | ||||
| @@ -64,7 +64,6 @@ class ClientXMPP(BaseXMPP): | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|  | ||||
|         self.set_jid(jid) | ||||
|         self.escape_quotes = escape_quotes | ||||
|         self.plugin_config = plugin_config | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
| @@ -95,7 +94,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.bound = False | ||||
|         self.bindfail = False | ||||
|  | ||||
|         self.add_event_handler('connected', self._handle_connected) | ||||
|         self.add_event_handler('connected', self._reset_connection_state) | ||||
|         self.add_event_handler('session_bind', self._handle_session_bind) | ||||
|  | ||||
|         self.register_stanza(StreamFeatures) | ||||
| @@ -113,9 +112,10 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.register_plugin('feature_starttls') | ||||
|         self.register_plugin('feature_bind') | ||||
|         self.register_plugin('feature_session') | ||||
|         self.register_plugin('feature_rosterver') | ||||
|         self.register_plugin('feature_preapproval') | ||||
|         self.register_plugin('feature_mechanisms', | ||||
|                 pconfig={'use_mech': sasl_mech} if sasl_mech else None) | ||||
|         self.register_plugin('feature_rosterver') | ||||
|  | ||||
|     @property | ||||
|     def password(self): | ||||
| @@ -179,8 +179,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         self._stream_feature_order.remove((order, name)) | ||||
|         self._stream_feature_order.sort() | ||||
|  | ||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], | ||||
|                             block=True, timeout=None, callback=None): | ||||
|     def update_roster(self, jid, **kwargs): | ||||
|         """Add or change a roster item. | ||||
|  | ||||
|         :param jid: The JID of the entry to modify. | ||||
| @@ -201,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) | ||||
|  | ||||
| @@ -243,7 +252,7 @@ class ClientXMPP(BaseXMPP): | ||||
|             self._handle_roster(response) | ||||
|             return response | ||||
|  | ||||
|     def _handle_connected(self, event=None): | ||||
|     def _reset_connection_state(self, event=None): | ||||
|         #TODO: Use stream state here | ||||
|         self.authenticated = False | ||||
|         self.sessionstarted = False | ||||
| @@ -263,6 +272,8 @@ class ClientXMPP(BaseXMPP): | ||||
|                     # Don't continue if the feature requires | ||||
|                     # restarting the XML stream. | ||||
|                     return True | ||||
|         log.debug('Finished processing stream features.') | ||||
|         self.event('stream_negotiated') | ||||
|  | ||||
|     def _handle_roster(self, iq): | ||||
|         """Update the roster after receiving a roster stanza. | ||||
| @@ -277,15 +288,17 @@ class ClientXMPP(BaseXMPP): | ||||
|         if iq['roster']['ver']: | ||||
|             roster.version = iq['roster']['ver'] | ||||
|         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'] | ||||
|             roster[jid]['to'] = item['subscription'] in ['to', 'both'] | ||||
|             roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|  | ||||
|             roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||
|         valid_subscriptions = ('to', 'from', 'both', 'none', 'remove') | ||||
|         for jid, item in items.items(): | ||||
|             if item['subscription'] in valid_subscriptions: | ||||
|                 roster[jid]['name'] = item['name'] | ||||
|                 roster[jid]['groups'] = item['groups'] | ||||
|                 roster[jid]['from'] = item['subscription'] in ('from', 'both') | ||||
|                 roster[jid]['to'] = item['subscription'] in ('to', 'both') | ||||
|                 roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|  | ||||
|                 roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||
|  | ||||
|         self.event("roster_update", iq) | ||||
|         if iq['type'] == 'set': | ||||
|   | ||||
| @@ -158,7 +158,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         self.event("session_bind", self.xmpp.boundjid, direct=True) | ||||
|         self.event("session_bind", self.boundjid, direct=True) | ||||
|         self.event("session_start") | ||||
|  | ||||
|     def _handle_probe(self, pres): | ||||
|   | ||||
| @@ -11,5 +11,6 @@ __all__ = [ | ||||
|     'feature_mechanisms', | ||||
|     'feature_bind', | ||||
|     'feature_session', | ||||
|     'feature_rosterver' | ||||
|     'feature_rosterver', | ||||
|     'feature_preapproval' | ||||
| ] | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.features.feature_bind import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| @@ -48,7 +49,7 @@ class FeatureBind(BasePlugin): | ||||
|             iq['bind']['resource'] = self.xmpp.boundjid.resource | ||||
|         response = iq.send(now=True) | ||||
|  | ||||
|         self.xmpp.set_jid(response['bind']['jid']) | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) | ||||
|         self.xmpp.bound = True | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) | ||||
|         self.xmpp.session_bind_event.set() | ||||
|   | ||||
| @@ -6,12 +6,12 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import ssl | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.thirdparty import suelta | ||||
| from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError | ||||
| from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure | ||||
|  | ||||
| from sleekxmpp.util import sasl | ||||
| from sleekxmpp.util.stringprep_profiles import StringPrepError | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| @@ -29,42 +29,31 @@ class FeatureMechanisms(BasePlugin): | ||||
|     description = 'RFC 6120: Stream Feature: SASL' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'use_mech': None, | ||||
|         'use_mechs': None, | ||||
|         'min_mech': None, | ||||
|         'sasl_callback': None, | ||||
|         'security_callback': None, | ||||
|         'encrypted_plain': True, | ||||
|         'unencrypted_plain': False, | ||||
|         'unencrypted_digest': False, | ||||
|         'unencrypted_cram': False, | ||||
|         'unencrypted_scram': True, | ||||
|         '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: | ||||
|         if not self.use_mech and not self.xmpp.requested_jid.user: | ||||
|             self.use_mech = 'ANONYMOUS' | ||||
|  | ||||
|         def tls_active(): | ||||
|             return 'starttls' in self.xmpp.features | ||||
|         if self.sasl_callback is None: | ||||
|             self.sasl_callback = self._default_credentials | ||||
|  | ||||
|         def basic_callback(mech, values): | ||||
|             creds = self.xmpp.credentials | ||||
|             for value in values: | ||||
|                 if value == 'username': | ||||
|                     values['username'] = self.xmpp.boundjid.user | ||||
|                 elif value == 'password': | ||||
|                     values['password'] = creds['password'] | ||||
|                 elif value == 'email': | ||||
|                     jid = self.xmpp.boundjid.bare | ||||
|                     values['email'] = creds.get('email', jid) | ||||
|                 elif value in creds: | ||||
|                     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.security_callback is None: | ||||
|             self.security_callback = self._default_security | ||||
|  | ||||
|         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, | ||||
|                                 tls_active=tls_active, | ||||
|                                 mech=self.use_mech) | ||||
|  | ||||
|         self.mech_list = set() | ||||
|         self.attempted_mechs = set() | ||||
|  | ||||
| @@ -95,7 +84,52 @@ class FeatureMechanisms(BasePlugin): | ||||
|         self.xmpp.register_feature('mechanisms', | ||||
|                 self._handle_sasl_auth, | ||||
|                 restart=True, | ||||
|                 order=self.config.get('order', 100)) | ||||
|                 order=self.order) | ||||
|  | ||||
|     def _default_credentials(self, required_values, optional_values): | ||||
|         creds = self.xmpp.credentials | ||||
|         result = {} | ||||
|         values = required_values.union(optional_values) | ||||
|         for value in values: | ||||
|             if value == 'username': | ||||
|                 result[value] = self.xmpp.requested_jid.user | ||||
|             elif value == 'password': | ||||
|                 result[value] = creds['password'] | ||||
|             elif value == 'authzid': | ||||
|                 result[value] = creds.get('authzid', '') | ||||
|             elif value == 'email': | ||||
|                 jid = self.xmpp.requested_jid.bare | ||||
|                 result[value] = creds.get('email', jid) | ||||
|             elif value == 'channel_binding': | ||||
|                 if sys.version_info >= (3, 3): | ||||
|                     result[value] = self.xmpp.socket.get_channel_binding() | ||||
|                 else: | ||||
|                     result[value] = None | ||||
|             elif value == 'host': | ||||
|                 result[value] = self.xmpp.requested_jid.domain | ||||
|             elif value == 'realm': | ||||
|                 result[value] = self.xmpp.requested_jid.domain | ||||
|             elif value == 'service-name': | ||||
|                 result[value] = self.xmpp._service_name | ||||
|             elif value == 'service': | ||||
|                 result[value] = 'xmpp' | ||||
|             elif value in creds: | ||||
|                 result[value] = creds[value] | ||||
|         return result | ||||
|  | ||||
|     def _default_security(self, values): | ||||
|         result = {} | ||||
|         for value in values: | ||||
|             if value == 'encrypted': | ||||
|                 if 'starttls' in self.xmpp.features: | ||||
|                     result[value] = True | ||||
|                 elif isinstance(self.xmpp.socket, ssl.SSLSocket): | ||||
|                     result[value] = True | ||||
|                 else: | ||||
|                     result[value] = False | ||||
|             else: | ||||
|                 result[value] = self.config.get(value, False) | ||||
|         return result | ||||
|  | ||||
|     def _handle_sasl_auth(self, features): | ||||
|         """ | ||||
| @@ -109,37 +143,61 @@ class FeatureMechanisms(BasePlugin): | ||||
|             # server has incorrectly offered it again. | ||||
|             return False | ||||
|  | ||||
|         if not self.use_mech: | ||||
|             self.mech_list = set(features['mechanisms']) | ||||
|         else: | ||||
|             self.mech_list = set([self.use_mech]) | ||||
|         enforce_limit = False | ||||
|         limited_mechs = self.use_mechs | ||||
|  | ||||
|         if limited_mechs is None: | ||||
|             limited_mechs = set() | ||||
|         elif limited_mechs and not isinstance(limited_mechs, set): | ||||
|             limited_mechs = set(limited_mechs) | ||||
|             enforce_limit = True | ||||
|  | ||||
|         if self.use_mech: | ||||
|             limited_mechs.add(self.use_mech) | ||||
|             enforce_limit = True | ||||
|  | ||||
|         if enforce_limit: | ||||
|             self.use_mechs = limited_mechs | ||||
|  | ||||
|         self.mech_list = set(features['mechanisms']) | ||||
|  | ||||
|         return self._send_auth() | ||||
|  | ||||
|     def _send_auth(self): | ||||
|         mech_list = self.mech_list - self.attempted_mechs | ||||
|         self.mech = self.sasl.choose_mechanism(mech_list) | ||||
|  | ||||
|         if mech_list and self.mech is not None: | ||||
|             resp = stanza.Auth(self.xmpp) | ||||
|             resp['mechanism'] = self.mech.name | ||||
|             try: | ||||
|                 resp['value'] = self.mech.process() | ||||
|             except SASLCancelled: | ||||
|                 self.attempted_mechs.add(self.mech.name) | ||||
|                 self._send_auth() | ||||
|             except SASLError: | ||||
|                 self.attempted_mechs.add(self.mech.name) | ||||
|                 self._send_auth() | ||||
|             except SASLPrepFailure: | ||||
|                 log.exception("A credential value did not pass SASLprep.") | ||||
|                 self.xmpp.disconnect() | ||||
|             else: | ||||
|                 resp.send(now=True) | ||||
|         else: | ||||
|         try: | ||||
|             self.mech = sasl.choose(mech_list, | ||||
|                                     self.sasl_callback, | ||||
|                                     self.security_callback, | ||||
|                                     limit=self.use_mechs, | ||||
|                                     min_mech=self.min_mech) | ||||
|         except sasl.SASLNoAppropriateMechanism: | ||||
|             log.error("No appropriate login method.") | ||||
|             self.xmpp.event("no_auth", direct=True) | ||||
|             self.attempted_mechs = set() | ||||
|             return self.xmpp.disconnect() | ||||
|  | ||||
|         resp = stanza.Auth(self.xmpp) | ||||
|         resp['mechanism'] = self.mech.name | ||||
|         try: | ||||
|             resp['value'] = self.mech.process() | ||||
|         except sasl.SASLCancelled: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         except StringPrepError: | ||||
|             log.exception("A credential value did not pass SASLprep.") | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def _handle_challenge(self, stanza): | ||||
| @@ -147,20 +205,33 @@ class FeatureMechanisms(BasePlugin): | ||||
|         resp = self.stanza.Response(self.xmpp) | ||||
|         try: | ||||
|             resp['value'] = self.mech.process(stanza['value']) | ||||
|         except SASLCancelled: | ||||
|         except sasl.SASLCancelled: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except SASLError: | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|  | ||||
|     def _handle_success(self, stanza): | ||||
|         """SASL authentication succeeded. Restart the stream.""" | ||||
|         self.attempted_mechs = set() | ||||
|         self.xmpp.authenticated = True | ||||
|         self.xmpp.features.add('mechanisms') | ||||
|         self.xmpp.event('auth_success', stanza, direct=True) | ||||
|         raise RestartStream() | ||||
|         try: | ||||
|             final = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             self.attempted_mechs = set() | ||||
|             self.xmpp.authenticated = True | ||||
|             self.xmpp.features.add('mechanisms') | ||||
|             self.xmpp.event('auth_success', stanza, direct=True) | ||||
|             raise RestartStream() | ||||
|  | ||||
|     def _handle_fail(self, stanza): | ||||
|         """SASL authentication failed. Disconnect and shutdown.""" | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,10 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
| class Success(StanzaBase): | ||||
|  | ||||
| @@ -16,9 +18,21 @@ class Success(StanzaBase): | ||||
|  | ||||
|     name = 'success' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set() | ||||
|     interfaces = set(['value']) | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         StanzaBase.setup(self, xml) | ||||
|         self.xml.tag = self.tag_name() | ||||
|  | ||||
|     def get_value(self): | ||||
|         return base64.b64decode(bytes(self.xml.text)) | ||||
|  | ||||
|     def set_value(self, values): | ||||
|         if values: | ||||
|             self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') | ||||
|         else: | ||||
|             self.xml.text = '=' | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|   | ||||
							
								
								
									
										15
									
								
								sleekxmpp/features/feature_preapproval/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/features/feature_preapproval/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval | ||||
| from sleekxmpp.features.feature_preapproval.stanza import PreApproval | ||||
|  | ||||
|  | ||||
| register_plugin(FeaturePreApproval) | ||||
							
								
								
									
										42
									
								
								sleekxmpp/features/feature_preapproval/preapproval.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/features/feature_preapproval/preapproval.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| """ | ||||
|     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 logging | ||||
|  | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.features.feature_preapproval import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class FeaturePreApproval(BasePlugin): | ||||
|  | ||||
|     name = 'feature_preapproval' | ||||
|     description = 'RFC 6121: Stream Feature: Subscription Pre-Approval' | ||||
|     dependences = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_feature('preapproval', | ||||
|                 self._handle_preapproval, | ||||
|                 restart=False, | ||||
|                 order=9001) | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, stanza.PreApproval) | ||||
|  | ||||
|     def _handle_preapproval(self, features): | ||||
|         """Save notice that the server support subscription pre-approvals. | ||||
|  | ||||
|         Arguments: | ||||
|             features -- The stream features stanza. | ||||
|         """ | ||||
|         log.debug("Server supports subscription pre-approvals.") | ||||
|         self.xmpp.features.add('preapproval') | ||||
							
								
								
									
										17
									
								
								sleekxmpp/features/feature_preapproval/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/features/feature_preapproval/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class PreApproval(ElementBase): | ||||
|  | ||||
|     name = 'sub' | ||||
|     namespace = 'urn:xmpp:features:pre-approval' | ||||
|     interfaces = set() | ||||
|     plugin_attrib = 'preapproval' | ||||
| @@ -54,13 +54,9 @@ class FeatureSTARTTLS(BasePlugin): | ||||
|             return False | ||||
|         elif not self.xmpp.use_tls: | ||||
|             return False | ||||
|         elif self.xmpp.ssl_support: | ||||
|         else: | ||||
|             self.xmpp.send(features['starttls'], now=True) | ||||
|             return True | ||||
|         else: | ||||
|             log.warning("The module tlslite is required to log in" + \ | ||||
|                         " to some servers, and has not been found.") | ||||
|             return False | ||||
|  | ||||
|     def _handle_starttls_proceed(self, proceed): | ||||
|         """Restart the XML stream when TLS is accepted.""" | ||||
|   | ||||
							
								
								
									
										582
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,582 @@ | ||||
| # -*- 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 threading | ||||
| import encodings.idna | ||||
|  | ||||
| from sleekxmpp.util import stringprep_profiles | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
| #: 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': '\\'} | ||||
|  | ||||
| JID_CACHE = OrderedDict() | ||||
| JID_CACHE_LOCK = threading.Lock() | ||||
| JID_CACHE_MAX_SIZE = 1024 | ||||
|  | ||||
| def _cache(key, parts, locked): | ||||
|     JID_CACHE[key] = (parts, locked) | ||||
|     if len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|         with JID_CACHE_LOCK: | ||||
|             while len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|                 found = None | ||||
|                 for key, item in JID_CACHE.iteritems(): | ||||
|                     if not item[1]: # if not locked | ||||
|                         found = key | ||||
|                         break | ||||
|                 if not found: # more than MAX_SIZE locked | ||||
|                     # warn? | ||||
|                     break | ||||
|                 del JID_CACHE[found] | ||||
|  | ||||
| # 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.map_table_b2], | ||||
|     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): | ||||
|         locked = kwargs.get('cache_lock', False) | ||||
|         in_local = kwargs.get('local', None) | ||||
|         in_domain = kwargs.get('domain', None) | ||||
|         in_resource = kwargs.get('resource', None) | ||||
|         parts = None | ||||
|         if in_local or in_domain or in_resource: | ||||
|             parts = (in_local, in_domain, in_resource) | ||||
|  | ||||
|         # only check cache if there is a jid string, or parts, not if there | ||||
|         # are both | ||||
|         self._jid = None | ||||
|         key = None | ||||
|         if (jid is not None) and (parts is None): | ||||
|             if isinstance(jid, JID): | ||||
|                 # it's already good to go, and there are no additions | ||||
|                 self._jid = jid._jid | ||||
|                 return | ||||
|             key = jid | ||||
|             self._jid, locked = JID_CACHE.get(jid, (None, locked)) | ||||
|         elif jid is None and parts is not None: | ||||
|             key = parts | ||||
|             self._jid, locked = JID_CACHE.get(parts, (None, locked)) | ||||
|         if not self._jid: | ||||
|             if not jid: | ||||
|                 parsed_jid = (None, None, None) | ||||
|             elif not isinstance(jid, JID): | ||||
|                 parsed_jid = _parse_jid(jid) | ||||
|             else: | ||||
|                 parsed_jid = jid._jid | ||||
|  | ||||
|             local, domain, resource = parsed_jid | ||||
|  | ||||
|             if 'local' in kwargs: | ||||
|                 local = _escape_node(in_local) | ||||
|             if 'domain' in kwargs: | ||||
|                 domain = _validate_domain(in_domain) | ||||
|             if 'resource' in kwargs: | ||||
|                 resource = _validate_resource(in_resource) | ||||
|  | ||||
|             self._jid = (local, domain, resource) | ||||
|             if key: | ||||
|                 _cache(key, self._jid, locked) | ||||
|  | ||||
|     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 == '_jid': | ||||
|             super(JID, self).__setattr__('_jid', value) | ||||
|         elif 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]) | ||||
|  | ||||
|     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) | ||||
| @@ -18,11 +18,14 @@ __all__ = [ | ||||
|     'xep_0004',  # Data Forms | ||||
|     'xep_0009',  # Jabber-RPC | ||||
|     'xep_0012',  # Last Activity | ||||
|     'xep_0013',  # Flexible Offline Message Retrieval | ||||
|     'xep_0016',  # Privacy Lists | ||||
|     'xep_0027',  # Current Jabber OpenPGP Usage | ||||
|     'xep_0030',  # Service Discovery | ||||
|     'xep_0033',  # Extended Stanza Addresses | ||||
|     'xep_0045',  # Multi-User Chat (Client) | ||||
|     'xep_0047',  # In-Band Bytestreams | ||||
|     'xep_0049',  # Private XML Storage | ||||
|     'xep_0050',  # Ad-hoc Commands | ||||
|     'xep_0054',  # vcard-temp | ||||
|     'xep_0059',  # Result Set Management | ||||
| @@ -35,12 +38,16 @@ __all__ = [ | ||||
|     'xep_0084',  # User Avatar | ||||
|     'xep_0085',  # Chat State Notifications | ||||
|     'xep_0086',  # Legacy Error Codes | ||||
|     'xep_0091',  # Legacy Delayed Delivery | ||||
|     '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 | ||||
| @@ -56,9 +63,17 @@ __all__ = [ | ||||
|     'xep_0223',  # Persistent Storage of Private Data via Pubsub | ||||
|     'xep_0224',  # Attention | ||||
|     'xep_0231',  # Bits of Binary | ||||
|     'xep_0235',  # OAuth Over XMPP | ||||
|     'xep_0242',  # XMPP Client Compliance 2009 | ||||
|     'xep_0249',  # Direct MUC Invitations | ||||
|     'xep_0256',  # Last Activity in Presence | ||||
|     'xep_0257',  # Client Certificate Management for SASL EXTERNAL | ||||
|     'xep_0258',  # Security Labels in XMPP | ||||
|     'xep_0270',  # XMPP Compliance Suites 2010 | ||||
|     'xep_0279',  # Server IP Check | ||||
|     'xep_0280',  # Message Carbons | ||||
|     'xep_0297',  # Stanza Forwarding | ||||
|     'xep_0302',  # XMPP Compliance Suites 2012 | ||||
|     'xep_0308',  # Last Message Correction | ||||
|     'xep_0313',  # Message Archive Management | ||||
| ] | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| @@ -272,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: | ||||
| @@ -279,7 +288,32 @@ 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. | ||||
|   | ||||
| @@ -201,7 +201,8 @@ class Form(ElementBase): | ||||
|         del self['instructions'] | ||||
|         if instructions in [None, '']: | ||||
|             return | ||||
|         instructions = instructions.split('\n') | ||||
|         if not isinstance(instructions, list): | ||||
|             instructions = instructions.split('\n') | ||||
|         for instruction in instructions: | ||||
|             inst = ET.Element('{%s}instructions' % self.namespace) | ||||
|             inst.text = instruction | ||||
|   | ||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0013/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0013/__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 permissio | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0013.stanza import Offline | ||||
| from sleekxmpp.plugins.xep_0013.offline import XEP_0013 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0013) | ||||
							
								
								
									
										134
									
								
								sleekxmpp/plugins/xep_0013/offline.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								sleekxmpp/plugins/xep_0013/offline.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| """ | ||||
|     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 permissio | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.stanza import Message, Iq | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream.handler import Collector | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0013 import stanza | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0013(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0013 Flexible Offline Message Retrieval | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0013' | ||||
|     description = 'XEP-0013: Flexible Offline Message Retrieval' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, stanza.Offline) | ||||
|         register_stanza_plugin(Message, stanza.Offline) | ||||
|  | ||||
|     def get_count(self, **kwargs): | ||||
|         return self.xmpp['xep_0030'].get_info( | ||||
|                 node='http://jabber.org/protocol/offline', | ||||
|                 local=False, | ||||
|                 **kwargs) | ||||
|  | ||||
|     def get_headers(self, **kwargs): | ||||
|         return self.xmpp['xep_0030'].get_items( | ||||
|                 node='http://jabber.org/protocol/offline', | ||||
|                 local=False, | ||||
|                 **kwargs) | ||||
|  | ||||
|     def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         if not isinstance(nodes, (list, set)): | ||||
|             nodes = [nodes] | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         offline = iq['offline'] | ||||
|         for node in nodes: | ||||
|             item = stanza.Item() | ||||
|             item['node'] = node | ||||
|             item['action'] = 'view' | ||||
|             offline.append(item) | ||||
|  | ||||
|         collector = Collector( | ||||
|             'Offline_Results_%s' % iq['id'], | ||||
|             StanzaPath('message/offline')) | ||||
|         self.xmpp.register_handler(collector) | ||||
|  | ||||
|         if not block and callback is not None: | ||||
|             def wrapped_cb(iq): | ||||
|                 results = collector.stop() | ||||
|                 if iq['type'] == 'result': | ||||
|                     iq['offline']['results'] = results | ||||
|                 callback(iq) | ||||
|             return iq.send(block=block, timeout=timeout, callback=wrapped_cb) | ||||
|         else: | ||||
|             try: | ||||
|                 resp = iq.send(block=block, timeout=timeout, callback=callback) | ||||
|                 resp['offline']['results'] = collector.stop() | ||||
|                 return resp | ||||
|             except XMPPError as e: | ||||
|                 collector.stop() | ||||
|                 raise e | ||||
|  | ||||
|     def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         if not isinstance(nodes, (list, set)): | ||||
|             nodes = [nodes] | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         offline = iq['offline'] | ||||
|         for node in nodes: | ||||
|             item = stanza.Item() | ||||
|             item['node'] = node | ||||
|             item['action'] = 'remove' | ||||
|             offline.append(item) | ||||
|  | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def fetch(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['offline']['fetch'] = True | ||||
|  | ||||
|         collector = Collector( | ||||
|             'Offline_Results_%s' % iq['id'], | ||||
|             StanzaPath('message/offline')) | ||||
|         self.xmpp.register_handler(collector) | ||||
|  | ||||
|         if not block and callback is not None: | ||||
|             def wrapped_cb(iq): | ||||
|                 results = collector.stop() | ||||
|                 if iq['type'] == 'result': | ||||
|                     iq['offline']['results'] = results | ||||
|                 callback(iq) | ||||
|             return iq.send(block=block, timeout=timeout, callback=wrapped_cb) | ||||
|         else: | ||||
|             try: | ||||
|                 resp = iq.send(block=block, timeout=timeout, callback=callback) | ||||
|                 resp['offline']['results'] = collector.stop() | ||||
|                 return resp | ||||
|             except XMPPError as e: | ||||
|                 collector.stop() | ||||
|                 raise e | ||||
|  | ||||
|     def purge(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['offline']['purge'] = True | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										53
									
								
								sleekxmpp/plugins/xep_0013/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								sleekxmpp/plugins/xep_0013/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| """ | ||||
|     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 permissio | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Offline(ElementBase): | ||||
|     name = 'offline' | ||||
|     namespace = 'http://jabber.org/protocol/offline' | ||||
|     plugin_attrib = 'offline' | ||||
|     interfaces = set(['fetch', 'purge', 'results']) | ||||
|     bool_interfaces = interfaces | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         ElementBase.setup(self, xml) | ||||
|         self._results = [] | ||||
|  | ||||
|     # The results interface is meant only as an easy | ||||
|     # way to access the set of collected message responses | ||||
|     # from the query. | ||||
|  | ||||
|     def get_results(self): | ||||
|         return self._results | ||||
|  | ||||
|     def set_results(self, values): | ||||
|         self._results = values | ||||
|  | ||||
|     def del_results(self): | ||||
|         self._results = [] | ||||
|  | ||||
|  | ||||
| class Item(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'http://jabber.org/protocol/offline' | ||||
|     plugin_attrib = 'item' | ||||
|     interfaces = set(['action', 'node', 'jid']) | ||||
|  | ||||
|     actions = set(['view', 'remove']) | ||||
|  | ||||
|     def get_jid(self): | ||||
|         return JID(self._get_attr('jid')) | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         self._set_attr('jid', str(value)) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Offline, Item, iterable=True) | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0016/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0016/__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_0016 import stanza | ||||
| from sleekxmpp.plugins.xep_0016.stanza import Privacy | ||||
| from sleekxmpp.plugins.xep_0016.privacy import XEP_0016 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0016) | ||||
							
								
								
									
										110
									
								
								sleekxmpp/plugins/xep_0016/privacy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								sleekxmpp/plugins/xep_0016/privacy.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| """ | ||||
|     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 Iq | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0016 import stanza | ||||
| from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item | ||||
|  | ||||
|  | ||||
| class XEP_0016(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0016' | ||||
|     description = 'XEP-0016: Privacy Lists' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, Privacy) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Privacy.namespace) | ||||
|  | ||||
|     def get_privacy_lists(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('privacy') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_list(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_active(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy'].enable('active') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_default(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy'].enable('default') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def activate(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['active']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def deactivate(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy'].enable('active') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def make_default(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['default']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def remove_default(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy'].enable('default') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def edit_list(self, name, rules, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         priv_list = iq['privacy']['list'] | ||||
|  | ||||
|         if not rules: | ||||
|             rules = [] | ||||
|  | ||||
|         for rule in rules: | ||||
|             if isinstance(rule, Item): | ||||
|                 priv_list.append(rule) | ||||
|                 continue | ||||
|  | ||||
|             priv_list.add_item( | ||||
|                 rule['value'], | ||||
|                 rule['action'], | ||||
|                 rule['order'], | ||||
|                 itype=rule.get('type', None), | ||||
|                 iq=rule.get('iq', None), | ||||
|                 message=rule.get('message', None), | ||||
|                 presence_in=rule.get('presence_in', | ||||
|                     rule.get('presence-in', None)), | ||||
|                 presence_out=rule.get('presence_out', | ||||
|                     rule.get('presence-out', None))) | ||||
|  | ||||
|     def remove_list(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										103
									
								
								sleekxmpp/plugins/xep_0016/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								sleekxmpp/plugins/xep_0016/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Privacy(ElementBase): | ||||
|     name = 'query' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = 'privacy' | ||||
|     interfaces = set() | ||||
|  | ||||
|     def add_list(self, name): | ||||
|         priv_list = List() | ||||
|         priv_list['name'] = name | ||||
|         self.append(priv_list) | ||||
|         return priv_list | ||||
|  | ||||
|  | ||||
| class Active(ElementBase): | ||||
|     name = 'active' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|  | ||||
| class Default(ElementBase): | ||||
|     name = 'default' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|  | ||||
| class List(ElementBase): | ||||
|     name = 'list' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'lists' | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|     def add_item(self, value, action, order, itype=None, iq=False, | ||||
|                  message=False, presence_in=False, presence_out=False): | ||||
|         item = Item() | ||||
|         item.values = {'type': itype, | ||||
|                        'value': value, | ||||
|                        'action': action, | ||||
|                        'order': order, | ||||
|                        'message': message, | ||||
|                        'iq': iq, | ||||
|                        'presence_in': presence_in, | ||||
|                        'presence_out': presence_out} | ||||
|         self.append(item) | ||||
|         return item | ||||
|  | ||||
|  | ||||
| class Item(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['type', 'value', 'action', 'order', 'iq', | ||||
|                       'message', 'presence_in', 'presence_out']) | ||||
|     bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out']) | ||||
|  | ||||
|     type_values = ('', 'jid', 'group', 'subscription') | ||||
|     action_values = ('allow', 'deny') | ||||
|  | ||||
|     def set_type(self, value): | ||||
|         if value and value not in self.type_values: | ||||
|             raise ValueError('Unknown type value: %s' % value) | ||||
|         else: | ||||
|             self._set_attr('type', value) | ||||
|  | ||||
|     def set_action(self, value): | ||||
|         if value not in self.action_values: | ||||
|             raise ValueError('Unknown action value: %s' % value) | ||||
|         else: | ||||
|             self._set_attr('action', value) | ||||
|  | ||||
|     def set_presence_in(self, value): | ||||
|         keep = True if value else False | ||||
|         self._set_sub_text('presence-in', '', keep=keep) | ||||
|  | ||||
|     def get_presence_in(self): | ||||
|         pres = self.xml.find('{%s}presence-in' % self.namespace) | ||||
|         return pres is not None | ||||
|  | ||||
|     def del_presence_in(self): | ||||
|         self._del_sub('{%s}presence-in' % self.namespace) | ||||
|  | ||||
|     def set_presence_out(self, value): | ||||
|         keep = True if value else False | ||||
|         self._set_sub_text('presence-in', '', keep=keep) | ||||
|  | ||||
|     def get_presence_out(self): | ||||
|         pres = self.xml.find('{%s}presence-in' % self.namespace) | ||||
|         return pres is not None | ||||
|  | ||||
|     def del_presence_out(self): | ||||
|         self._del_sub('{%s}presence-in' % self.namespace) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Privacy, Active) | ||||
| register_stanza_plugin(Privacy, Default) | ||||
| register_stanza_plugin(Privacy, List, iterable=True) | ||||
| register_stanza_plugin(List, Item, iterable=True) | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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', | ||||
| @@ -287,7 +288,7 @@ class XEP_0030(BasePlugin): | ||||
|                 'cached': cached} | ||||
|         return self.api['has_identity'](jid, node, ifrom, data) | ||||
|  | ||||
|     def get_info(self, jid=None, node=None, local=False, | ||||
|     def get_info(self, jid=None, node=None, local=None, | ||||
|                        cached=None, **kwargs): | ||||
|         """ | ||||
|         Retrieve the disco#info results from a given JID/node combination. | ||||
| @@ -323,18 +324,21 @@ class XEP_0030(BasePlugin): | ||||
|             callback -- Optional callback to execute when a reply is | ||||
|                         received instead of blocking and waiting for | ||||
|                         the reply. | ||||
|             timeout_callback -- Optional callback to execute when no result  | ||||
|                         has been received in timeout seconds. | ||||
|         """ | ||||
|         if jid is not None and not isinstance(jid, JID): | ||||
|             jid = JID(jid) | ||||
|             if self.xmpp.is_component: | ||||
|                 if jid.domain == self.xmpp.boundjid.domain: | ||||
|                     local = True | ||||
|             else: | ||||
|                 if str(jid) == str(self.xmpp.boundjid): | ||||
|                     local = True | ||||
|             jid = jid.full | ||||
|         elif jid in (None, ''): | ||||
|             local = True | ||||
|         if local is None: | ||||
|             if jid is not None and not isinstance(jid, JID): | ||||
|                 jid = JID(jid) | ||||
|                 if self.xmpp.is_component: | ||||
|                     if jid.domain == self.xmpp.boundjid.domain: | ||||
|                         local = True | ||||
|                 else: | ||||
|                     if str(jid) == str(self.xmpp.boundjid): | ||||
|                         local = True | ||||
|                 jid = jid.full | ||||
|             elif jid in (None, ''): | ||||
|                 local = True | ||||
|  | ||||
|         if local: | ||||
|             log.debug("Looking up local disco#info data " + \ | ||||
| @@ -362,7 +366,8 @@ class XEP_0030(BasePlugin): | ||||
|         iq['disco_info']['node'] = node if node else '' | ||||
|         return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                        block=kwargs.get('block', True), | ||||
|                        callback=kwargs.get('callback', None)) | ||||
|                        callback=kwargs.get('callback', None), | ||||
|                        timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|  | ||||
|     def set_info(self, jid=None, node=None, info=None): | ||||
|         """ | ||||
| @@ -403,8 +408,10 @@ class XEP_0030(BasePlugin): | ||||
|             iterator -- If True, return a result set iterator using | ||||
|                         the XEP-0059 plugin, if the plugin is loaded. | ||||
|                         Otherwise the parameter is ignored. | ||||
|             timeout_callback -- Optional callback to execute when no result  | ||||
|                         has been received in timeout seconds. | ||||
|         """ | ||||
|         if local or jid is None: | ||||
|         if local or local is None and jid is None: | ||||
|             items = self.api['get_items'](jid, node, | ||||
|                     kwargs.get('ifrom', None), | ||||
|                     kwargs) | ||||
| @@ -421,7 +428,8 @@ class XEP_0030(BasePlugin): | ||||
|         else: | ||||
|             return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                            block=kwargs.get('block', True), | ||||
|                            callback=kwargs.get('callback', None)) | ||||
|                            callback=kwargs.get('callback', None), | ||||
|                            timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|  | ||||
|     def set_items(self, jid=None, node=None, **kwargs): | ||||
|         """ | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
| @@ -156,6 +156,7 @@ class XEP_0045(BasePlugin): | ||||
|         entry = pr['muc'].getStanzaValues() | ||||
|         entry['show'] = pr['show'] | ||||
|         entry['status'] = pr['status'] | ||||
|         entry['alt_nick'] = pr['nick'] | ||||
|         if pr['type'] == 'unavailable': | ||||
|             if entry['nick'] in self.rooms[entry['room']]: | ||||
|                 del self.rooms[entry['room']][entry['nick']] | ||||
| @@ -244,11 +245,11 @@ class XEP_0045(BasePlugin): | ||||
|         stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom) | ||||
|         x = ET.Element('{http://jabber.org/protocol/muc}x') | ||||
|         if password: | ||||
|             passelement = ET.Element('password') | ||||
|             passelement = ET.Element('{http://jabber.org/protocol/muc}password') | ||||
|             passelement.text = password | ||||
|             x.append(passelement) | ||||
|         if maxhistory: | ||||
|             history = ET.Element('history') | ||||
|             history = ET.Element('{http://jabber.org/protocol/muc}history') | ||||
|             if maxhistory ==  "0": | ||||
|                 history.attrib['maxchars'] = maxhistory | ||||
|             else: | ||||
| @@ -270,10 +271,10 @@ class XEP_0045(BasePlugin): | ||||
|             iq['from'] = ifrom | ||||
|         iq['to'] = room | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||
|         destroy = ET.Element('destroy') | ||||
|         destroy = ET.Element('{http://jabber.org/protocol/muc#owner}destroy') | ||||
|         if altroom: | ||||
|             destroy.attrib['jid'] = altroom | ||||
|         xreason = ET.Element('reason') | ||||
|         xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason') | ||||
|         xreason.text = reason | ||||
|         destroy.append(xreason) | ||||
|         query.append(destroy) | ||||
| @@ -293,9 +294,9 @@ class XEP_0045(BasePlugin): | ||||
|             raise TypeError | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#admin}query') | ||||
|         if nick is not None: | ||||
|             item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) | ||||
|             item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'nick':nick}) | ||||
|         else: | ||||
|             item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) | ||||
|             item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'jid':jid}) | ||||
|         query.append(item) | ||||
|         iq = self.xmpp.makeIqSet(query) | ||||
|         iq['to'] = room | ||||
| @@ -316,7 +317,7 @@ class XEP_0045(BasePlugin): | ||||
|         x = ET.Element('{http://jabber.org/protocol/muc#user}x') | ||||
|         invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) | ||||
|         if reason: | ||||
|             rxml = ET.Element('reason') | ||||
|             rxml = ET.Element('{http://jabber.org/protocol/muc#user}reason') | ||||
|             rxml.text = reason | ||||
|             invite.append(rxml) | ||||
|         x.append(invite) | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import re | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
|  | ||||
| VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*') | ||||
| @@ -14,7 +14,7 @@ def to_b64(data): | ||||
|  | ||||
|  | ||||
| def from_b64(data): | ||||
|     return bytes(base64.b64decode(bytes(data))).decode('utf-8') | ||||
|     return bytes(base64.b64decode(bytes(data))) | ||||
|  | ||||
|  | ||||
| class Open(ElementBase): | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0049/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0049/__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_0049.stanza import PrivateXML | ||||
| from sleekxmpp.plugins.xep_0049.private_storage import XEP_0049 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0049) | ||||
							
								
								
									
										53
									
								
								sleekxmpp/plugins/xep_0049/private_storage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								sleekxmpp/plugins/xep_0049/private_storage.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| """ | ||||
|     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 | ||||
| from sleekxmpp.plugins.xep_0049 import stanza, PrivateXML | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0049(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0049' | ||||
|     description = 'XEP-0049: Private XML Storage' | ||||
|     dependencies = set([]) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, PrivateXML) | ||||
|  | ||||
|     def register(self, stanza): | ||||
|         register_stanza_plugin(PrivateXML, stanza, iterable=True) | ||||
|  | ||||
|     def store(self, data, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|  | ||||
|         if not isinstance(data, list): | ||||
|             data = [data] | ||||
|  | ||||
|         for elem in data: | ||||
|             iq['private'].append(elem) | ||||
|  | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def retrieve(self, name, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         iq['private'].enable(name) | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										17
									
								
								sleekxmpp/plugins/xep_0049/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/plugins/xep_0049/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
|  | ||||
| class PrivateXML(ElementBase): | ||||
|  | ||||
|     name = 'query' | ||||
|     namespace = 'jabber:iq:private' | ||||
|     plugin_attrib = 'private' | ||||
|     interfaces = 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", | ||||
| @@ -181,12 +187,6 @@ class XEP_0050(BasePlugin): | ||||
|             jid = JID(jid) | ||||
|         item_jid = jid.full | ||||
|  | ||||
|         # Client disco uses only the bare JID | ||||
|         if self.xmpp.is_component: | ||||
|             jid = jid.full | ||||
|         else: | ||||
|             jid = jid.bare | ||||
|  | ||||
|         self.xmpp['xep_0030'].add_identity(category='automation', | ||||
|                                            itype='command-list', | ||||
|                                            name='Ad-Hoc commands', | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| import base64 | ||||
| import datetime as dt | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID | ||||
| from sleekxmpp.plugins import xep_0082 | ||||
|  | ||||
| @@ -542,6 +541,7 @@ register_stanza_plugin(VCardTemp, Logo, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Mailer, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Note, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Nickname, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Org, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Photo, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, ProdID, iterable=True) | ||||
| register_stanza_plugin(VCardTemp, Rev, iterable=True) | ||||
|   | ||||
| @@ -97,8 +97,8 @@ class XEP_0054(BasePlugin): | ||||
|  | ||||
|     def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None, | ||||
|                       callback=None, timeout=None): | ||||
|         self.api['set_vcard'](jid, None, ifrom, vcard) | ||||
|         if self.xmpp.is_component: | ||||
|             self.api['set_vcard'](jid, None, ifrom, vcard) | ||||
|             return | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|   | ||||
| @@ -25,11 +25,14 @@ class ResultIterator(): | ||||
|     An iterator for Result Set Managment | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, query, interface, amount=10, start=None, reverse=False): | ||||
|     def __init__(self, query, interface, results='substanzas', amount=10, | ||||
|                        start=None, reverse=False): | ||||
|         """ | ||||
|         Arguments: | ||||
|            query     -- The template query | ||||
|            interface -- The substanza of the query, for example disco_items | ||||
|            results   -- The query stanza's interface which provides a | ||||
|                         countable list of query results. | ||||
|            amount    -- The max amounts of items to request per iteration | ||||
|            start     -- From which item id to start | ||||
|            reverse   -- If True, page backwards through the results | ||||
| @@ -46,6 +49,7 @@ class ResultIterator(): | ||||
|         self.amount = amount | ||||
|         self.start = start | ||||
|         self.interface = interface | ||||
|         self.results = results | ||||
|         self.reverse = reverse | ||||
|         self._stop = False | ||||
|  | ||||
| @@ -85,7 +89,7 @@ class ResultIterator(): | ||||
|                r[self.interface]['rsm']['first_index']: | ||||
|                 count = int(r[self.interface]['rsm']['count']) | ||||
|                 first = int(r[self.interface]['rsm']['first_index']) | ||||
|                 num_items = len(r[self.interface]['substanzas']) | ||||
|                 num_items = len(r[self.interface][self.results]) | ||||
|                 if first + num_items == count: | ||||
|                     self._stop = True | ||||
|  | ||||
| @@ -123,7 +127,7 @@ class XEP_0059(BasePlugin): | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) | ||||
|  | ||||
|     def iterate(self, stanza, interface): | ||||
|     def iterate(self, stanza, interface, results='substanzas'): | ||||
|         """ | ||||
|         Create a new result set iterator for a given stanza query. | ||||
|  | ||||
| @@ -135,5 +139,7 @@ class XEP_0059(BasePlugin): | ||||
|                          result set management stanza should be | ||||
|                          appended. For example, for disco#items queries | ||||
|                          the interface 'disco_items' should be used. | ||||
|             results   -- The name of the interface containing the | ||||
|                          query results (typically just 'substanzas'). | ||||
|         """ | ||||
|         return ResultIterator(stanza, interface) | ||||
|         return ResultIterator(stanza, interface, results) | ||||
|   | ||||
| @@ -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,8 @@ 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') | ||||
|   | ||||
| @@ -27,10 +27,12 @@ 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) | ||||
|  | ||||
| @@ -38,14 +40,14 @@ class XEP_0077(BasePlugin): | ||||
|             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.config.get('order', 50)) | ||||
|             self.xmpp.unregister_feature('register', self.order) | ||||
|  | ||||
|     def _handle_register_feature(self, features): | ||||
|         if 'mechanisms' in self.xmpp.features: | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import hashlib | ||||
| import random | ||||
| import sys | ||||
|  | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
| @@ -34,23 +35,37 @@ 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) | ||||
|  | ||||
|         self.xmpp.add_event_handler('legacy_protocol', | ||||
|                 self._handle_legacy_protocol) | ||||
|  | ||||
|         register_stanza_plugin(Iq, stanza.IqAuth) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.AuthFeature) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.unregister_feature('auth', self.config.get('order', 15)) | ||||
|         self.xmpp.del_event_handler('legacy_protocol', | ||||
|                 self._handle_legacy_protocol) | ||||
|         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']: | ||||
|             return False | ||||
|         return self.authenticate() | ||||
|  | ||||
|     def _handle_legacy_protocol(self, event): | ||||
|         self.authenticate() | ||||
|  | ||||
|     def authenticate(self): | ||||
|         if self.xmpp.authenticated: | ||||
|             return False | ||||
|  | ||||
| @@ -59,13 +74,13 @@ class XEP_0078(BasePlugin): | ||||
|         # Step 1: Request the auth form | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['to'] = self.xmpp.boundjid.host | ||||
|         iq['auth']['username'] = self.xmpp.boundjid.user | ||||
|         iq['to'] = self.xmpp.requested_jid.host | ||||
|         iq['auth']['username'] = self.xmpp.requested_jid.user | ||||
|  | ||||
|         try: | ||||
|             resp = iq.send(now=True) | ||||
|         except IqError: | ||||
|             log.info("Authentication failed: %s", resp['error']['condition']) | ||||
|         except IqError as err: | ||||
|             log.info("Authentication failed: %s", err.iq['error']['condition']) | ||||
|             self.xmpp.event('failed_auth', direct=True) | ||||
|             self.xmpp.disconnect() | ||||
|             return True | ||||
| @@ -78,13 +93,14 @@ class XEP_0078(BasePlugin): | ||||
|         # Step 2: Fill out auth form for either password or digest auth | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['auth']['username'] = self.xmpp.boundjid.user | ||||
|         iq['auth']['username'] = self.xmpp.requested_jid.user | ||||
|  | ||||
|         # A resource is required, so create a random one if necessary | ||||
|         if self.xmpp.boundjid.resource: | ||||
|             iq['auth']['resource'] = self.xmpp.boundjid.resource | ||||
|         else: | ||||
|             iq['auth']['resource'] = '%s' % random.random() | ||||
|         resource = self.xmpp.requested_jid.resource | ||||
|         if not resource: | ||||
|             resource = uuid.uuid4() | ||||
|  | ||||
|         iq['auth']['resource'] = resource | ||||
|  | ||||
|         if 'digest' in resp['auth']['fields']: | ||||
|             log.debug('Authenticating via jabber:iq:auth Digest') | ||||
| @@ -106,16 +122,22 @@ class XEP_0078(BasePlugin): | ||||
|             result = iq.send(now=True) | ||||
|         except IqError as err: | ||||
|             log.info("Authentication failed") | ||||
|             self.xmpp.disconnect() | ||||
|             self.xmpp.event("failed_auth", direct=True) | ||||
|             self.xmpp.disconnect() | ||||
|         except IqTimeout: | ||||
|             log.info("Authentication failed") | ||||
|             self.xmpp.disconnect() | ||||
|             self.xmpp.event("failed_auth", direct=True) | ||||
|             self.xmpp.disconnect() | ||||
|  | ||||
|         self.xmpp.features.add('auth') | ||||
|  | ||||
|         self.xmpp.authenticated = True | ||||
|  | ||||
|         self.xmpp.boundjid = JID(self.xmpp.requested_jid, | ||||
|                 resource=resource, | ||||
|                 cache_lock=True) | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) | ||||
|  | ||||
|         log.debug("Established Session") | ||||
|         self.xmpp.sessionstarted = True | ||||
|         self.xmpp.session_started_event.set() | ||||
|   | ||||
| @@ -41,6 +41,9 @@ class XEP_0084(BasePlugin): | ||||
|     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, | ||||
| @@ -54,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,17 +69,19 @@ class XEP_0084(BasePlugin): | ||||
|         metadata = MetaData() | ||||
|         if items is None: | ||||
|             items = [] | ||||
|         if not isinstance(items, (list, set)): | ||||
|             items = [items] | ||||
|         for info in items: | ||||
|             metadata.add_info(info['id'], info['type'], info['bytes'], | ||||
|                     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, | ||||
|   | ||||
| @@ -7,8 +7,8 @@ | ||||
| """ | ||||
|  | ||||
| from base64 import b64encode, b64decode | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| @@ -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} | ||||
|   | ||||
| @@ -52,4 +52,5 @@ class XEP_0085(BasePlugin): | ||||
|     def _handle_chat_state(self, msg): | ||||
|         state = msg['chat_state'] | ||||
|         log.debug("Chat State: %s, %s", state, msg['from'].jid) | ||||
|         self.xmpp.event('chatstate', msg) | ||||
|         self.xmpp.event('chatstate_%s' % state, 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) | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0091/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0091/__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_0091 import stanza | ||||
| from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay | ||||
| from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0091) | ||||
							
								
								
									
										29
									
								
								sleekxmpp/plugins/xep_0091/legacy_delay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								sleekxmpp/plugins/xep_0091/legacy_delay.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| """ | ||||
|     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.stanza import Message, Presence | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0091 import stanza | ||||
|  | ||||
|  | ||||
| class XEP_0091(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0091: Legacy Delayed Delivery | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0091' | ||||
|     description = 'XEP-0091: Legacy Delayed Delivery' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, stanza.LegacyDelay) | ||||
|         register_stanza_plugin(Presence, stanza.LegacyDelay) | ||||
							
								
								
									
										46
									
								
								sleekxmpp/plugins/xep_0091/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								sleekxmpp/plugins/xep_0091/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| """ | ||||
|     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 datetime as dt | ||||
|  | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
| from sleekxmpp.plugins import xep_0082 | ||||
|  | ||||
|  | ||||
| class LegacyDelay(ElementBase): | ||||
|  | ||||
|     name = 'x' | ||||
|     namespace = 'jabber:x:delay' | ||||
|     plugin_attrib = 'legacy_delay' | ||||
|     interfaces = set(('from', 'stamp', 'text')) | ||||
|  | ||||
|     def get_from(self): | ||||
|         return JID(self._get_attr('from')) | ||||
|  | ||||
|     def set_from(self, value): | ||||
|         self._set_attr('from', str(value)) | ||||
|  | ||||
|     def get_stamp(self): | ||||
|         timestamp = self._get_attr('stamp') | ||||
|         return xep_0082.parse('%sZ' % timestamp) | ||||
|  | ||||
|     def set_stamp(self, value): | ||||
|         if isinstance(value, dt.datetime): | ||||
|             value = value.astimezone(xep_0082.tzutc) | ||||
|             value = xep_0082.format_datetime(value) | ||||
|         self._set_attr('stamp', value[0:19].replace('-', '')) | ||||
|  | ||||
|     def get_text(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_text(self, value): | ||||
|         self.xml.text = value | ||||
|  | ||||
|     def del_text(self): | ||||
|         self.xml.text = '' | ||||
| @@ -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', | ||||
| @@ -63,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() | ||||
| @@ -88,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) | ||||
| @@ -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 | ||||
| @@ -103,12 +104,17 @@ class XEP_0115(BasePlugin): | ||||
|         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): | ||||
| @@ -137,6 +143,11 @@ class XEP_0115(BasePlugin): | ||||
|         if str(existing_verstring) == str(pres['caps']['ver']): | ||||
|             return | ||||
|  | ||||
|         existing_caps = self.get_caps(verstring=pres['caps']['ver']) | ||||
|         if existing_caps is not None: | ||||
|             self.assign_verstring(pres['from'], pres['caps']['ver']) | ||||
|             return | ||||
|  | ||||
|         if pres['caps']['hash'] not in self.hashes: | ||||
|             try: | ||||
|                 log.debug("Unknown caps hash: %s", pres['caps']['hash']) | ||||
|   | ||||
							
								
								
									
										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,8 +8,11 @@ | ||||
|  | ||||
| import hashlib | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from sleekxmpp import JID | ||||
| from sleekxmpp.stanza import Presence | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| @@ -30,11 +33,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,10 +50,12 @@ 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) | ||||
| @@ -56,56 +64,71 @@ class XEP_0153(BasePlugin): | ||||
|  | ||||
|     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) | ||||
|         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() | ||||
|         try: | ||||
|             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) | ||||
|         except XMPPError: | ||||
|             log.debug('Could not retrieve vCard for %s' % jid) | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     # ================================================================= | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
| @@ -68,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): | ||||
|   | ||||
| @@ -48,7 +48,7 @@ class XEP_0191(BasePlugin): | ||||
|     def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = 'ifrom' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('blocklist') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|   | ||||
| @@ -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', | ||||
| @@ -137,8 +142,8 @@ class XEP_0198(BasePlugin): | ||||
|         if self.xmpp.is_component: | ||||
|             return | ||||
|  | ||||
|         self.xmpp.unregister_feature('sm', self.config.get('order', 10100)) | ||||
|         self.xmpp.unregister_feature('sm', self.config.get('resume_order', 9000)) | ||||
|         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) | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -14,14 +14,17 @@ from sleekxmpp.plugins import xep_0082 | ||||
|  | ||||
| class Delay(ElementBase): | ||||
|  | ||||
|     """ | ||||
|     """ | ||||
|  | ||||
|     name = 'delay' | ||||
|     namespace = 'urn:xmpp:delay' | ||||
|     plugin_attrib = 'delay' | ||||
|     interfaces = set(('from', 'stamp', 'text')) | ||||
|  | ||||
|     def get_from(self): | ||||
|         return JID(self._get_attr('from')) | ||||
|  | ||||
|     def set_from(self, value): | ||||
|         self._set_attr('from', str(value)) | ||||
|  | ||||
|     def get_stamp(self): | ||||
|         timestamp = self._get_attr('stamp') | ||||
|         return xep_0082.parse(timestamp) | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0235/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0235/__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_0235 import stanza | ||||
| from sleekxmpp.plugins.xep_0235.stanza import OAuth | ||||
| from sleekxmpp.plugins.xep_0235.oauth import XEP_0235 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0235) | ||||
							
								
								
									
										32
									
								
								sleekxmpp/plugins/xep_0235/oauth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								sleekxmpp/plugins/xep_0235/oauth.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| """ | ||||
|     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 Message | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.xep_0235 import stanza, OAuth | ||||
|  | ||||
|  | ||||
| class XEP_0235(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0235' | ||||
|     description = 'XEP-0235: OAuth Over XMPP' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, OAuth) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:oauth:0') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:oauth:0') | ||||
							
								
								
									
										80
									
								
								sleekxmpp/plugins/xep_0235/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								sleekxmpp/plugins/xep_0235/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| """ | ||||
|     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 hmac | ||||
| import hashlib | ||||
| import urllib | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, JID | ||||
|  | ||||
|  | ||||
| class OAuth(ElementBase): | ||||
|  | ||||
|     name = 'oauth' | ||||
|     namespace = 'urn:xmpp:oauth:0' | ||||
|     plugin_attrib = 'oauth' | ||||
|     interfaces = set(['oauth_consumer_key', 'oauth_nonce', 'oauth_signature', | ||||
|                       'oauth_signature_method', 'oauth_timestamp', | ||||
|                       'oauth_token', 'oauth_version']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|     def generate_signature(self, stanza, sfrom, sto, consumer_secret, | ||||
|                                  token_secret, method='HMAC-SHA1'): | ||||
|         self['oauth_signature_method'] = method | ||||
|  | ||||
|         request = urllib.quote('%s&%s' % (sfrom, sto), '') | ||||
|         parameters = urllib.quote('&'.join([ | ||||
|             'oauth_consumer_key=%s' % self['oauth_consumer_key'], | ||||
|             'oauth_nonce=%s' % self['oauth_nonce'], | ||||
|             'oauth_signature_method=%s' % self['oauth_signature_method'], | ||||
|             'oauth_timestamp=%s' % self['oauth_timestamp'], | ||||
|             'oauth_token=%s' % self['oauth_token'], | ||||
|             'oauth_version=%s' % self['oauth_version'] | ||||
|         ]), '') | ||||
|  | ||||
|         sigbase = '%s&%s&%s' % (stanza, request, parameters) | ||||
|  | ||||
|         consumer_secret = urllib.quote(consumer_secret, '') | ||||
|         token_secret = urllib.quote(token_secret, '') | ||||
|         key = '%s&%s' % (consumer_secret, token_secret) | ||||
|  | ||||
|         if method == 'HMAC-SHA1': | ||||
|             sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest()) | ||||
|         elif method == 'PLAINTEXT': | ||||
|             sig = key | ||||
|  | ||||
|         self['oauth_signature'] = sig | ||||
|         return sig | ||||
|  | ||||
|     def verify_signature(self, stanza, sfrom, sto, consumer_secret, | ||||
|                                token_secret): | ||||
|         method = self['oauth_signature_method'] | ||||
|  | ||||
|         request = urllib.quote('%s&%s' % (sfrom, sto), '') | ||||
|         parameters = urllib.quote('&'.join([ | ||||
|             'oauth_consumer_key=%s' % self['oauth_consumer_key'], | ||||
|             'oauth_nonce=%s' % self['oauth_nonce'], | ||||
|             'oauth_signature_method=%s' % self['oauth_signature_method'], | ||||
|             'oauth_timestamp=%s' % self['oauth_timestamp'], | ||||
|             'oauth_token=%s' % self['oauth_token'], | ||||
|             'oauth_version=%s' % self['oauth_version'] | ||||
|         ]), '') | ||||
|  | ||||
|         sigbase = '%s&%s&%s' % (stanza, request, parameters) | ||||
|  | ||||
|         consumer_secret = urllib.quote(consumer_secret, '') | ||||
|         token_secret = urllib.quote(token_secret, '') | ||||
|         key = '%s&%s' % (consumer_secret, token_secret) | ||||
|  | ||||
|         if method == 'HMAC-SHA1': | ||||
|             sig = base64.b64encode(hmac.new(key, sigbase, hashlib.sha1).digest()) | ||||
|         elif method == 'PLAINTEXT': | ||||
|             sig = key | ||||
|  | ||||
|         return self['oauth_signature'] == sig | ||||
							
								
								
									
										21
									
								
								sleekxmpp/plugins/xep_0242.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								sleekxmpp/plugins/xep_0242.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_0242(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0242' | ||||
|     description = 'XEP-0242: XMPP Client Compliance 2009' | ||||
|     dependencies = set(['xep_0030', 'xep_0115', 'xep_0054', | ||||
|                         'xep_0045', 'xep_0085', 'xep_0016', | ||||
|                         'xep_0191']) | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0242) | ||||
| @@ -25,10 +25,11 @@ class XEP_0256(BasePlugin): | ||||
|     description = 'XEP-0256: Last Activity in Presence' | ||||
|     dependencies = set(['xep_0012']) | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'auto_last_activity': False | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.auto_last_activity = self.config.get('auto_last_activity', False) | ||||
|  | ||||
|         register_stanza_plugin(Presence, LastActivity) | ||||
|  | ||||
|         self.xmpp.add_filter('out', self._initial_presence_activity) | ||||
|   | ||||
							
								
								
									
										17
									
								
								sleekxmpp/plugins/xep_0257/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/plugins/xep_0257/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| """ | ||||
|     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_0257 import stanza | ||||
| from sleekxmpp.plugins.xep_0257.stanza import Certs, AppendCert | ||||
| from sleekxmpp.plugins.xep_0257.stanza import DisableCert, RevokeCert | ||||
| from sleekxmpp.plugins.xep_0257.client_cert_management import XEP_0257 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0257) | ||||
							
								
								
									
										65
									
								
								sleekxmpp/plugins/xep_0257/client_cert_management.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								sleekxmpp/plugins/xep_0257/client_cert_management.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| """ | ||||
|     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_0257 import stanza, Certs | ||||
| from sleekxmpp.plugins.xep_0257 import AppendCert, DisableCert, RevokeCert | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0257(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0257' | ||||
|     description = 'XEP-0258: Client Certificate Management for SASL EXTERNAL' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, Certs) | ||||
|         register_stanza_plugin(Iq, AppendCert) | ||||
|         register_stanza_plugin(Iq, DisableCert) | ||||
|         register_stanza_plugin(Iq, RevokeCert) | ||||
|  | ||||
|     def get_certs(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('sasl_certs') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def add_cert(self, name, cert, allow_management=True, ifrom=None, | ||||
|                        block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['sasl_cert_append']['name'] = name | ||||
|         iq['sasl_cert_append']['x509cert'] = cert | ||||
|         iq['sasl_cert_append']['cert_management'] = allow_management | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def disable_cert(self, name, ifrom=None, block=True, | ||||
|                            timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['sasl_cert_disable']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def revoke_cert(self, name, ifrom=None, block=True, | ||||
|                            timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['sasl_cert_revoke']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										87
									
								
								sleekxmpp/plugins/xep_0257/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								sleekxmpp/plugins/xep_0257/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| """ | ||||
|     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, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Certs(ElementBase): | ||||
|     name = 'query' | ||||
|     namespace = 'urn:xmpp:saslcert:1' | ||||
|     plugin_attrib = 'sasl_certs' | ||||
|     interfaces = set() | ||||
|  | ||||
|  | ||||
| class CertItem(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'urn:xmpp:saslcert:1' | ||||
|     plugin_attrib = 'item' | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['name', 'x509cert', 'users']) | ||||
|     sub_interfaces = set(['name', 'x509cert']) | ||||
|  | ||||
|     def get_users(self): | ||||
|         resources = self.xml.findall('{%s}users/{%s}resource' % ( | ||||
|             self.namespace, self.namespace)) | ||||
|         return set([res.text for res in resources]) | ||||
|  | ||||
|     def set_users(self, values): | ||||
|         users = self.xml.find('{%s}users' % self.namespace) | ||||
|         if users is None: | ||||
|             users = ET.Element('{%s}users' % self.namespace) | ||||
|             self.xml.append(users) | ||||
|         for resource in values: | ||||
|             res = ET.Element('{%s}resource' % self.namespace) | ||||
|             res.text = resource | ||||
|             users.append(res) | ||||
|  | ||||
|     def del_users(self): | ||||
|         users = self.xml.find('{%s}users' % self.namespace) | ||||
|         if users is not None: | ||||
|             self.xml.remove(users) | ||||
|  | ||||
|  | ||||
| class AppendCert(ElementBase): | ||||
|     name = 'append' | ||||
|     namespace = 'urn:xmpp:saslcert:1' | ||||
|     plugin_attrib = 'sasl_cert_append' | ||||
|     interfaces = set(['name', 'x509cert', 'cert_management']) | ||||
|     sub_interfaces = set(['name', 'x509cert']) | ||||
|  | ||||
|     def get_cert_management(self): | ||||
|         manage = self.xml.find('{%s}no-cert-management' % self.namespace) | ||||
|         return manage is None | ||||
|  | ||||
|     def set_cert_management(self, value): | ||||
|         self.del_cert_management() | ||||
|         if not value: | ||||
|             manage = ET.Element('{%s}no-cert-management' % self.namespace) | ||||
|             self.xml.append(manage) | ||||
|  | ||||
|     def del_cert_management(self): | ||||
|         manage = self.xml.find('{%s}no-cert-management' % self.namespace) | ||||
|         if manage is not None: | ||||
|             self.xml.remove(manage) | ||||
|  | ||||
|  | ||||
| class DisableCert(ElementBase): | ||||
|     name = 'disable' | ||||
|     namespace = 'urn:xmpp:saslcert:1' | ||||
|     plugin_attrib = 'sasl_cert_disable' | ||||
|     interfaces = set(['name']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|  | ||||
| class RevokeCert(ElementBase): | ||||
|     name = 'revoke' | ||||
|     namespace = 'urn:xmpp:saslcert:1' | ||||
|     plugin_attrib = 'sasl_cert_revoke' | ||||
|     interfaces = set(['name']) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Certs, CertItem, iterable=True) | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| from base64 import b64encode, b64decode | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0279/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0279/__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_0279 import stanza | ||||
| from sleekxmpp.plugins.xep_0279.stanza import IPCheck | ||||
| from sleekxmpp.plugins.xep_0279.ipcheck import XEP_0279 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0279) | ||||
							
								
								
									
										39
									
								
								sleekxmpp/plugins/xep_0279/ipcheck.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								sleekxmpp/plugins/xep_0279/ipcheck.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| """ | ||||
|     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_0279 import stanza, IPCheck | ||||
|  | ||||
|  | ||||
| class XEP_0279(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0279' | ||||
|     description = 'XEP-0279: Server IP Check' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, IPCheck) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('urn:xmpp:sic:0') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0') | ||||
|  | ||||
|     def check_ip(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         iq.enable('ip_check') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user