support for xep-300
This commit is contained in:
		
							parent
							
								
									76bc866a98
								
							
						
					
					
						commit
						376777091e
					
				
					 5 changed files with 161 additions and 14 deletions
				
			
		| 
						 | 
				
			
			@ -208,7 +208,9 @@ gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE,
 | 
			
		|||
        'jabber:iq:gateway', xmpp.NS_LAST, xmpp.NS_PRIVACY, xmpp.NS_PRIVATE,
 | 
			
		||||
        xmpp.NS_REGISTER, xmpp.NS_VERSION, xmpp.NS_DATA, xmpp.NS_ENCRYPTED, 'msglog',
 | 
			
		||||
        'sslc2s', 'stringprep', xmpp.NS_PING, xmpp.NS_TIME_REVISED, xmpp.NS_SSN,
 | 
			
		||||
        xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL]
 | 
			
		||||
        xmpp.NS_MOOD, xmpp.NS_ACTIVITY, xmpp.NS_NICK, xmpp.NS_ROSTERX, xmpp.NS_SECLABEL,
 | 
			
		||||
        xmpp.NS_HASHES, xmpp.NS_HASHES_MD5, xmpp.NS_HASHES_SHA1,
 | 
			
		||||
        xmpp.NS_HASHES_SHA256, xmpp.NS_HASHES_SHA512]
 | 
			
		||||
 | 
			
		||||
# Optional features gajim supports per account
 | 
			
		||||
gajim_optional_features = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -151,10 +151,24 @@ class ConnectionJingle(object):
 | 
			
		|||
        file_props['sid'] = jingle.sid
 | 
			
		||||
        c = JingleFileTransfer(jingle, file_props=file_props,
 | 
			
		||||
                               use_security=use_security)
 | 
			
		||||
        c.hash_algo = self.__hash_support(contact) 
 | 
			
		||||
        jingle.add_content('file' + helpers.get_random_string_16(), c)
 | 
			
		||||
        jingle.start_session()
 | 
			
		||||
        return c.transport.sid
 | 
			
		||||
 | 
			
		||||
    def __hash_support(self, contact):
 | 
			
		||||
        
 | 
			
		||||
        if contact.supports(xmpp.NS_HASHES):
 | 
			
		||||
            if contact.supports(xmpp.NS_HASHES_MD5):
 | 
			
		||||
                return 'md5'
 | 
			
		||||
            elif contact.supports(xmpp.NS_HASHES_SHA1):
 | 
			
		||||
                return 'sha-1'
 | 
			
		||||
            elif contact.supports(xmpp.NS_HASHES_SHA256):
 | 
			
		||||
                return 'sha-256'
 | 
			
		||||
            elif contact.supports(xmpp.NS_HASHES_SHA512):
 | 
			
		||||
                return 'sha-512'
 | 
			
		||||
            
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def iter_jingle_sessions(self, jid, sid=None, media=None):
 | 
			
		||||
        if sid:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,7 +26,7 @@ from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5, Jingl
 | 
			
		|||
from common import helpers
 | 
			
		||||
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
 | 
			
		||||
from common.connection_handlers_events import FileRequestReceivedEvent
 | 
			
		||||
 | 
			
		||||
import threading
 | 
			
		||||
import logging
 | 
			
		||||
log = logging.getLogger('gajim.c.jingle_ft')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +56,11 @@ class JingleFileTransfer(JingleContent):
 | 
			
		|||
 | 
			
		||||
        # events we might be interested in
 | 
			
		||||
        self.callbacks['session-initiate'] += [self.__on_session_initiate]
 | 
			
		||||
        self.callbacks['session-initiate-sent'] += [self.__on_session_initiate_sent]
 | 
			
		||||
        self.callbacks['content-add'] += [self.__on_session_initiate]
 | 
			
		||||
        self.callbacks['session-accept'] += [self.__on_session_accept]
 | 
			
		||||
        self.callbacks['session-terminate'] += [self.__on_session_terminate]        
 | 
			
		||||
        self.callbacks['session-info'] += [self.__on_session_info]
 | 
			
		||||
        self.callbacks['transport-accept'] += [self.__on_transport_accept]
 | 
			
		||||
        self.callbacks['transport-replace'] += [self.__on_transport_replace]
 | 
			
		||||
        self.callbacks['session-accept-sent'] += [self.__transport_setup]
 | 
			
		||||
| 
						 | 
				
			
			@ -100,10 +102,34 @@ class JingleFileTransfer(JingleContent):
 | 
			
		|||
        self.media = 'file'
 | 
			
		||||
        self.nominated_cand = {}
 | 
			
		||||
        
 | 
			
		||||
        # Hash algorithm that we are using to calculate the integrity of the 
 | 
			
		||||
        # file. Could be 'md5', 'sha-1', etc...
 | 
			
		||||
        self.hash_algo = None
 | 
			
		||||
 | 
			
		||||
    def __on_session_initiate(self, stanza, content, error, action):
 | 
			
		||||
        gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
 | 
			
		||||
            conn=self.session.connection, stanza=stanza, jingle_content=content,
 | 
			
		||||
            FT_content=self))
 | 
			
		||||
    def __on_session_initiate_sent(self, stanza, content, error, action):
 | 
			
		||||
        # Calculate file_hash in a new thread
 | 
			
		||||
        self.hashThread = threading.Thread(target=self.__calcHash)
 | 
			
		||||
        self.hashThread.start()
 | 
			
		||||
        
 | 
			
		||||
    def __calcHash(self):
 | 
			
		||||
        if self.hash_algo == None:
 | 
			
		||||
            return
 | 
			
		||||
        try:
 | 
			
		||||
            file = open(self.file_props['file-name'], 'r')
 | 
			
		||||
        except:
 | 
			
		||||
            return
 | 
			
		||||
        h = xmpp.Hashes()
 | 
			
		||||
        h.calculateHash(self.hash_algo, file)
 | 
			
		||||
        checksum = xmpp.Node(tag='checksum',  
 | 
			
		||||
                             payload=[xmpp.Node(tag='file', payload=[h])])
 | 
			
		||||
        checksum.setNamespace(xmpp.NS_JINGLE_FILE_TRANSFER)
 | 
			
		||||
        # Send hash in a session info
 | 
			
		||||
        self.session.__session_info(checksum )
 | 
			
		||||
    
 | 
			
		||||
        
 | 
			
		||||
    def __on_session_accept(self, stanza, content, error, action):
 | 
			
		||||
        log.info("__on_session_accept")
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +178,9 @@ class JingleFileTransfer(JingleContent):
 | 
			
		|||
    def __on_session_terminate(self, stanza, content, error, action):
 | 
			
		||||
        log.info("__on_session_terminate")
 | 
			
		||||
 | 
			
		||||
    def __on_session_info(self, stanza, content, error, action):
 | 
			
		||||
        pass
 | 
			
		||||
        
 | 
			
		||||
    def __on_transport_accept(self, stanza, content, error, action):
 | 
			
		||||
        log.info("__on_transport_accept")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -164,8 +193,6 @@ class JingleFileTransfer(JingleContent):
 | 
			
		|||
    def __on_transport_info(self, stanza, content, error, action):
 | 
			
		||||
        log.info("__on_transport_info")
 | 
			
		||||
 | 
			
		||||
        #if not self.weinitiate: # proxy activated from initiator
 | 
			
		||||
        #    return
 | 
			
		||||
        if content.getTag('transport').getTag('candidate-error'):
 | 
			
		||||
            self.nominated_cand['peer-cand'] = False
 | 
			
		||||
            if self.state == STATE_CAND_SENT_PENDING_REPLY:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,7 +98,7 @@ class JingleSession(object):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
        self.accepted = True # is this session accepted by user
 | 
			
		||||
 | 
			
		||||
        self.file_hash = None
 | 
			
		||||
        # callbacks to call on proper contents
 | 
			
		||||
        # use .prepend() to add new callbacks, especially when you're going
 | 
			
		||||
        # to send error instead of ack
 | 
			
		||||
| 
						 | 
				
			
			@ -127,6 +127,7 @@ class JingleSession(object):
 | 
			
		|||
                'iq-error':             [self.__on_error],
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def collect_iq_id(self, iq_id):
 | 
			
		||||
        if iq_id is not None:
 | 
			
		||||
            self.iq_ids.append(iq_id)
 | 
			
		||||
| 
						 | 
				
			
			@ -416,10 +417,21 @@ class JingleSession(object):
 | 
			
		|||
    def __on_session_info(self, stanza, jingle, error, action):
 | 
			
		||||
        # TODO: ringing, active, (un)hold, (un)mute
 | 
			
		||||
        payload = jingle.getPayload()
 | 
			
		||||
        if payload:
 | 
			
		||||
        for p in payload:
 | 
			
		||||
            if p.getName() == 'checksum':
 | 
			
		||||
                hashes = p.getTag('file').getTag(name='hashes', 
 | 
			
		||||
                                        namespace=xmpp.NS_HASHES)
 | 
			
		||||
                for hash in hashes.getChildren():
 | 
			
		||||
                    algo = hash.getAttr('algo')
 | 
			
		||||
                    if algo in xmpp.Hashes.supported:
 | 
			
		||||
                        data = hash.getData()
 | 
			
		||||
                        self.file_hash = data
 | 
			
		||||
                        print data
 | 
			
		||||
                        raise xmpp.NodeProcessed
 | 
			
		||||
        self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info', type_='modify')
 | 
			
		||||
        raise xmpp.NodeProcessed
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def __on_content_remove(self, stanza, jingle, error, action):
 | 
			
		||||
        for content in jingle.iterTags('content'):
 | 
			
		||||
            creator = content['creator']
 | 
			
		||||
| 
						 | 
				
			
			@ -705,6 +717,13 @@ class JingleSession(object):
 | 
			
		|||
            jingle.addChild(node=payload)
 | 
			
		||||
        self.connection.connection.send(stanza)
 | 
			
		||||
        
 | 
			
		||||
    def _JingleFileTransfer__session_info(self, p):
 | 
			
		||||
        # For some strange reason when I call
 | 
			
		||||
        # self.session.__session_info(h) from the jingleFileTransfer object
 | 
			
		||||
        # within a thread, this method gets called instead. Even though, it
 | 
			
		||||
        # isn't being called explicitly.
 | 
			
		||||
        self.__session_info(p)
 | 
			
		||||
 | 
			
		||||
    def _session_terminate(self, reason=None):
 | 
			
		||||
        assert self.state != JingleStates.ended
 | 
			
		||||
        stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ sub- stanzas) handling routines
 | 
			
		|||
from simplexml import Node, NodeBuilder
 | 
			
		||||
import time
 | 
			
		||||
import string
 | 
			
		||||
import hashlib
 | 
			
		||||
 | 
			
		||||
def ascii_upper(s):
 | 
			
		||||
    trans_table = string.maketrans(string.ascii_lowercase,
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +90,7 @@ NS_JINGLE_ERRORS  = 'urn:xmpp:jingle:errors:1'                        # XEP-0166
 | 
			
		|||
NS_JINGLE_RTP     = 'urn:xmpp:jingle:apps:rtp:1'                      # XEP-0167
 | 
			
		||||
NS_JINGLE_RTP_AUDIO = 'urn:xmpp:jingle:apps:rtp:audio'                # XEP-0167
 | 
			
		||||
NS_JINGLE_RTP_VIDEO = 'urn:xmpp:jingle:apps:rtp:video'                # XEP-0167
 | 
			
		||||
NS_JINGLE_FILE_TRANSFER='urn:xmpp:jingle:apps:file-transfer:1'        # XEP-0234
 | 
			
		||||
NS_JINGLE_FILE_TRANSFER ='urn:xmpp:jingle:apps:file-transfer:1'        # XEP-0234
 | 
			
		||||
NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0'                      # XTLS: EXPERIMENTAL security layer of jingle
 | 
			
		||||
NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1'            # XEP-0177
 | 
			
		||||
NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1'            # XEP-0176
 | 
			
		||||
| 
						 | 
				
			
			@ -160,6 +161,11 @@ NS_PUBKEY_PUBKEY  = 'urn:xmpp:pubkey:2'
 | 
			
		|||
NS_PUBKEY_REVOKE  = 'urn:xmpp:revoke:2'
 | 
			
		||||
NS_PUBKEY_ATTEST  = 'urn:xmpp:attest:2'
 | 
			
		||||
NS_STREAM_MGMT    = 'urn:xmpp:sm:2'                                   # XEP-198
 | 
			
		||||
NS_HASHES         = 'urn:xmpp:hashes:0'                               # XEP-300
 | 
			
		||||
NS_HASHES_MD5     = 'urn:xmpp:hash-function-textual-names:md5'
 | 
			
		||||
NS_HASHES_SHA1    = 'urn:xmpp:hash-function-textual-names:sha-1'
 | 
			
		||||
NS_HASHES_SHA256  = 'urn:xmpp:hash-function-textual-names:sha-256'
 | 
			
		||||
NS_HASHES_SHA512  = 'urn:xmpp:hash-function-textual-names:sha-512'
 | 
			
		||||
                 
 | 
			
		||||
xmpp_stream_error_conditions = '''
 | 
			
		||||
bad-format --  --  -- The entity has sent XML that cannot be processed.
 | 
			
		||||
| 
						 | 
				
			
			@ -1031,6 +1037,84 @@ class Iq(Protocol):
 | 
			
		|||
        iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
 | 
			
		||||
        return iq
 | 
			
		||||
    
 | 
			
		||||
class Hashes(Node): 
 | 
			
		||||
    """
 | 
			
		||||
    Hash elements for various XEPs as defined in XEP-300
 | 
			
		||||
    """
 | 
			
		||||
    
 | 
			
		||||
    """
 | 
			
		||||
    RECOMENDED HASH USE:
 | 
			
		||||
    Algorithm     Support
 | 
			
		||||
    MD2           MUST NOT
 | 
			
		||||
    MD4           MUST NOT
 | 
			
		||||
    MD5           MAY
 | 
			
		||||
    SHA-1         MUST
 | 
			
		||||
    SHA-256       MUST
 | 
			
		||||
    SHA-512       SHOULD
 | 
			
		||||
    """
 | 
			
		||||
    
 | 
			
		||||
    supported = ('md5', 'sha-1', 'sha-256', 'sha-512')
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, nsp=NS_HASHES):
 | 
			
		||||
        Node.__init__(self, None, {}, [], None, None,False, None)
 | 
			
		||||
        self.setNamespace(nsp)
 | 
			
		||||
        self.setName('hashes')
 | 
			
		||||
    
 | 
			
		||||
    def calculateHash(self, algo, file_string):
 | 
			
		||||
        """
 | 
			
		||||
        Calculate the hash and add it. It is preferable doing it here
 | 
			
		||||
        instead of doing it all over the place in Gajim.
 | 
			
		||||
        """
 | 
			
		||||
        hl = None
 | 
			
		||||
        hash = None
 | 
			
		||||
        
 | 
			
		||||
        # file_string can be a string or a file
 | 
			
		||||
        if type(file_string) == str: # if it is a string
 | 
			
		||||
            if algo == 'md5':
 | 
			
		||||
                hl = hashlib.md5()
 | 
			
		||||
            elif algo == 'sha-1':
 | 
			
		||||
                hl = hashlib.sha1()
 | 
			
		||||
            elif algo == 'sha-256':
 | 
			
		||||
                hl = hashlib.sha256()
 | 
			
		||||
            elif algo == 'sha-512':
 | 
			
		||||
                hl = hashlib.sha512()
 | 
			
		||||
                
 | 
			
		||||
            if hl == None:
 | 
			
		||||
                # Raise exception
 | 
			
		||||
                raise Exception('Hash algorithm not supported')
 | 
			
		||||
            else:
 | 
			
		||||
                hl.update(file_string)
 | 
			
		||||
                hash = hl.hexdigest()
 | 
			
		||||
        else: # if it is a file
 | 
			
		||||
                
 | 
			
		||||
            if algo == 'md5':
 | 
			
		||||
                hl = hashlib.md5()
 | 
			
		||||
            elif algo == 'sha-1':
 | 
			
		||||
                hl = hashlib.sha1()
 | 
			
		||||
            elif algo == 'sha-256':
 | 
			
		||||
                hl = hashlib.sha256()
 | 
			
		||||
            elif algo == 'sha-512':
 | 
			
		||||
                hl = hashlib.sha512()
 | 
			
		||||
                
 | 
			
		||||
            if hl == None:
 | 
			
		||||
                # Raise exception
 | 
			
		||||
                raise Exception('Hash algorithm not supported')
 | 
			
		||||
            else:
 | 
			
		||||
                for line in file_string:
 | 
			
		||||
                    hl.update(line)
 | 
			
		||||
                hash = hl.hexdigest()
 | 
			
		||||
                
 | 
			
		||||
        self.addHash(hash, algo)
 | 
			
		||||
            
 | 
			
		||||
    def addHash(self, hash, algo):
 | 
			
		||||
        """
 | 
			
		||||
        More than one hash can be added. Although it is permitted, it should
 | 
			
		||||
        not be done for big files because it could slow down Gajim.
 | 
			
		||||
        """
 | 
			
		||||
        attrs = {}
 | 
			
		||||
        attrs['algo'] = algo 
 | 
			
		||||
        self.addChild('hash', attrs, [hash])
 | 
			
		||||
     
 | 
			
		||||
class Acks(Node):
 | 
			
		||||
    """
 | 
			
		||||
    Acknowledgement elements for Stream Management
 | 
			
		||||
| 
						 | 
				
			
			@ -1413,3 +1497,4 @@ class DataForm(Node):
 | 
			
		|||
        Simple dictionary interface for setting datafields values by their names
 | 
			
		||||
        """
 | 
			
		||||
        return self.setField(name).setValue(val)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue