240 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			240 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								# This file is part of Gajim.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Gajim is free software; you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
								# it under the terms of the GNU General Public License as published
							 | 
						||
| 
								 | 
							
								# by the Free Software Foundation; version 3 only.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Gajim is distributed in the hope that it will be useful,
							 | 
						||
| 
								 | 
							
								# but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
								# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
							 | 
						||
| 
								 | 
							
								# GNU General Public License for more details.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# You should have received a copy of the GNU General Public License
							 | 
						||
| 
								 | 
							
								# along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# XEP-0047: In-Band Bytestreams
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import nbxmpp
							 | 
						||
| 
								 | 
							
								from nbxmpp.namespaces import Namespace
							 | 
						||
| 
								 | 
							
								from nbxmpp.protocol import NodeProcessed
							 | 
						||
| 
								 | 
							
								from nbxmpp.structs import StanzaHandler
							 | 
						||
| 
								 | 
							
								from nbxmpp.errors import StanzaError
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from gajim.common import app
							 | 
						||
| 
								 | 
							
								from gajim.common.helpers import to_user_string
							 | 
						||
| 
								 | 
							
								from gajim.common.modules.base import BaseModule
							 | 
						||
| 
								 | 
							
								from gajim.common.file_props import FilesProp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class IBB(BaseModule):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _nbxmpp_extends = 'IBB'
							 | 
						||
| 
								 | 
							
								    _nbxmpp_methods = [
							 | 
						||
| 
								 | 
							
								        'send_open',
							 | 
						||
| 
								 | 
							
								        'send_close',
							 | 
						||
| 
								 | 
							
								        'send_data',
							 | 
						||
| 
								 | 
							
								        'send_reply',
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, con):
							 | 
						||
| 
								 | 
							
								        BaseModule.__init__(self, con)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.handlers = [
							 | 
						||
| 
								 | 
							
								            StanzaHandler(name='iq',
							 | 
						||
| 
								 | 
							
								                          callback=self._ibb_received,
							 | 
						||
| 
								 | 
							
								                          ns=Namespace.IBB),
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _ibb_received(self, _con, stanza, properties):
							 | 
						||
| 
								 | 
							
								        if not properties.is_ibb:
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if properties.ibb.type == 'data':
							 | 
						||
| 
								 | 
							
								            self._log.info('Data received, sid: %s, seq: %s',
							 | 
						||
| 
								 | 
							
								                           properties.ibb.sid, properties.ibb.seq)
							 | 
						||
| 
								 | 
							
								            file_props = FilesProp.getFilePropByTransportSid(self._account,
							 | 
						||
| 
								 | 
							
								                                                             properties.ibb.sid)
							 | 
						||
| 
								 | 
							
								            if not file_props:
							 | 
						||
| 
								 | 
							
								                self.send_reply(stanza, nbxmpp.ERR_ITEM_NOT_FOUND)
							 | 
						||
| 
								 | 
							
								                raise NodeProcessed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if file_props.connected:
							 | 
						||
| 
								 | 
							
								                self._on_data_received(stanza, file_props, properties)
							 | 
						||
| 
								 | 
							
								                self.send_reply(stanza)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif properties.ibb.type == 'open':
							 | 
						||
| 
								 | 
							
								            self._log.info('Open received, sid: %s, blocksize: %s',
							 | 
						||
| 
								 | 
							
								                           properties.ibb.sid, properties.ibb.block_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            file_props = FilesProp.getFilePropByTransportSid(self._account,
							 | 
						||
| 
								 | 
							
								                                                             properties.ibb.sid)
							 | 
						||
| 
								 | 
							
								            if not file_props:
							 | 
						||
| 
								 | 
							
								                self.send_reply(stanza, nbxmpp.ERR_ITEM_NOT_FOUND)
							 | 
						||
| 
								 | 
							
								                raise NodeProcessed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            file_props.block_size = properties.ibb.block_size
							 | 
						||
| 
								 | 
							
								            file_props.direction = '<'
							 | 
						||
| 
								 | 
							
								            file_props.seq = 0
							 | 
						||
| 
								 | 
							
								            file_props.received_len = 0
							 | 
						||
| 
								 | 
							
								            file_props.last_time = time.time()
							 | 
						||
| 
								 | 
							
								            file_props.error = 0
							 | 
						||
| 
								 | 
							
								            file_props.paused = False
							 | 
						||
| 
								 | 
							
								            file_props.connected = True
							 | 
						||
| 
								 | 
							
								            file_props.completed = False
							 | 
						||
| 
								 | 
							
								            file_props.disconnect_cb = None
							 | 
						||
| 
								 | 
							
								            file_props.continue_cb = None
							 | 
						||
| 
								 | 
							
								            file_props.syn_id = stanza.getID()
							 | 
						||
| 
								 | 
							
								            file_props.fp = open(file_props.file_name, 'wb')
							 | 
						||
| 
								 | 
							
								            self.send_reply(stanza)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        elif properties.ibb.type == 'close':
							 | 
						||
| 
								 | 
							
								            self._log.info('Close received, sid: %s', properties.ibb.sid)
							 | 
						||
| 
								 | 
							
								            file_props = FilesProp.getFilePropByTransportSid(self._account,
							 | 
						||
| 
								 | 
							
								                                                             properties.ibb.sid)
							 | 
						||
| 
								 | 
							
								            if not file_props:
							 | 
						||
| 
								 | 
							
								                self.send_reply(stanza, nbxmpp.ERR_ITEM_NOT_FOUND)
							 | 
						||
| 
								 | 
							
								                raise NodeProcessed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self.send_reply(stanza)
							 | 
						||
| 
								 | 
							
								            file_props.fp.close()
							 | 
						||
| 
								 | 
							
								            file_props.completed = file_props.received_len >= file_props.size
							 | 
						||
| 
								 | 
							
								            if not file_props.completed:
							 | 
						||
| 
								 | 
							
								                file_props.error = -1
							 | 
						||
| 
								 | 
							
								            app.socks5queue.complete_transfer_cb(self._account, file_props)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        raise NodeProcessed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _on_data_received(self, stanza, file_props, properties):
							 | 
						||
| 
								 | 
							
								        ibb = properties.ibb
							 | 
						||
| 
								 | 
							
								        if ibb.seq != file_props.seq:
							 | 
						||
| 
								 | 
							
								            self.send_reply(stanza, nbxmpp.ERR_UNEXPECTED_REQUEST)
							 | 
						||
| 
								 | 
							
								            self.send_close(file_props)
							 | 
						||
| 
								 | 
							
								            raise NodeProcessed
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self._log.debug('Data received: sid: %s, %s+%s bytes',
							 | 
						||
| 
								 | 
							
								                        ibb.sid, file_props.fp.tell(), len(ibb.data))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        file_props.seq += 1
							 | 
						||
| 
								 | 
							
								        file_props.started = True
							 | 
						||
| 
								 | 
							
								        file_props.fp.write(ibb.data)
							 | 
						||
| 
								 | 
							
								        current_time = time.time()
							 | 
						||
| 
								 | 
							
								        file_props.elapsed_time += current_time - file_props.last_time
							 | 
						||
| 
								 | 
							
								        file_props.last_time = current_time
							 | 
						||
| 
								 | 
							
								        file_props.received_len += len(ibb.data)
							 | 
						||
| 
								 | 
							
								        app.socks5queue.progress_transfer_cb(self._account, file_props)
							 | 
						||
| 
								 | 
							
								        if file_props.received_len >= file_props.size:
							 | 
						||
| 
								 | 
							
								            file_props.completed = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def send_open(self, to, sid, fp):
							 | 
						||
| 
								 | 
							
								        self._log.info('Send open to %s, sid: %s', to, sid)
							 | 
						||
| 
								 | 
							
								        file_props = FilesProp.getFilePropBySid(sid)
							 | 
						||
| 
								 | 
							
								        file_props.direction = '>'
							 | 
						||
| 
								 | 
							
								        file_props.block_size = 4096
							 | 
						||
| 
								 | 
							
								        file_props.fp = fp
							 | 
						||
| 
								 | 
							
								        file_props.seq = -1
							 | 
						||
| 
								 | 
							
								        file_props.error = 0
							 | 
						||
| 
								 | 
							
								        file_props.paused = False
							 | 
						||
| 
								 | 
							
								        file_props.received_len = 0
							 | 
						||
| 
								 | 
							
								        file_props.last_time = time.time()
							 | 
						||
| 
								 | 
							
								        file_props.connected = True
							 | 
						||
| 
								 | 
							
								        file_props.completed = False
							 | 
						||
| 
								 | 
							
								        file_props.disconnect_cb = None
							 | 
						||
| 
								 | 
							
								        file_props.continue_cb = None
							 | 
						||
| 
								 | 
							
								        self._nbxmpp('IBB').send_open(to,
							 | 
						||
| 
								 | 
							
								                                      file_props.transport_sid,
							 | 
						||
| 
								 | 
							
								                                      4096,
							 | 
						||
| 
								 | 
							
								                                      callback=self._on_open_result,
							 | 
						||
| 
								 | 
							
								                                      user_data=file_props)
							 | 
						||
| 
								 | 
							
								        return file_props
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _on_open_result(self, task):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            task.finish()
							 | 
						||
| 
								 | 
							
								        except StanzaError as error:
							 | 
						||
| 
								 | 
							
								            app.socks5queue.error_cb('Error', to_user_string(error))
							 | 
						||
| 
								 | 
							
								            self._log.warning(error)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        file_props = task.get_user_data()
							 | 
						||
| 
								 | 
							
								        self.send_data(file_props)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def send_close(self, file_props):
							 | 
						||
| 
								 | 
							
								        file_props.connected = False
							 | 
						||
| 
								 | 
							
								        file_props.fp.close()
							 | 
						||
| 
								 | 
							
								        file_props.stopped = True
							 | 
						||
| 
								 | 
							
								        to = file_props.receiver
							 | 
						||
| 
								 | 
							
								        if file_props.direction == '<':
							 | 
						||
| 
								 | 
							
								            to = file_props.sender
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self._log.info('Send close to %s, sid: %s',
							 | 
						||
| 
								 | 
							
								                       to, file_props.transport_sid)
							 | 
						||
| 
								 | 
							
								        self._nbxmpp('IBB').send_close(to, file_props.transport_sid,
							 | 
						||
| 
								 | 
							
								                                       callback=self._on_close_result)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if file_props.completed:
							 | 
						||
| 
								 | 
							
								            app.socks5queue.complete_transfer_cb(self._account, file_props)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            if file_props.type_ == 's':
							 | 
						||
| 
								 | 
							
								                peerjid = file_props.receiver
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                peerjid = file_props.sender
							 | 
						||
| 
								 | 
							
								            session = self._con.get_module('Jingle').get_jingle_session(
							 | 
						||
| 
								 | 
							
								                peerjid, file_props.sid, 'file')
							 | 
						||
| 
								 | 
							
								            # According to the xep, the initiator also cancels
							 | 
						||
| 
								 | 
							
								            # the jingle session if there are no more files to send using IBB
							 | 
						||
| 
								 | 
							
								            if session.weinitiate:
							 | 
						||
| 
								 | 
							
								                session.cancel_session()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _on_close_result(self, task):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            task.finish()
							 | 
						||
| 
								 | 
							
								        except StanzaError as error:
							 | 
						||
| 
								 | 
							
								            app.socks5queue.error_cb('Error', to_user_string(error))
							 | 
						||
| 
								 | 
							
								            self._log.warning(error)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def send_data(self, file_props):
							 | 
						||
| 
								 | 
							
								        if file_props.completed:
							 | 
						||
| 
								 | 
							
								            self.send_close(file_props)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        chunk = file_props.fp.read(file_props.block_size)
							 | 
						||
| 
								 | 
							
								        if chunk:
							 | 
						||
| 
								 | 
							
								            file_props.seq += 1
							 | 
						||
| 
								 | 
							
								            file_props.started = True
							 | 
						||
| 
								 | 
							
								            if file_props.seq == 65536:
							 | 
						||
| 
								 | 
							
								                file_props.seq = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            self._log.info('Send data to %s, sid: %s',
							 | 
						||
| 
								 | 
							
								                           file_props.receiver, file_props.transport_sid)
							 | 
						||
| 
								 | 
							
								            self._nbxmpp('IBB').send_data(file_props.receiver,
							 | 
						||
| 
								 | 
							
								                                          file_props.transport_sid,
							 | 
						||
| 
								 | 
							
								                                          file_props.seq,
							 | 
						||
| 
								 | 
							
								                                          chunk,
							 | 
						||
| 
								 | 
							
								                                          callback=self._on_data_result,
							 | 
						||
| 
								 | 
							
								                                          user_data=file_props)
							 | 
						||
| 
								 | 
							
								            current_time = time.time()
							 | 
						||
| 
								 | 
							
								            file_props.elapsed_time += current_time - file_props.last_time
							 | 
						||
| 
								 | 
							
								            file_props.last_time = current_time
							 | 
						||
| 
								 | 
							
								            file_props.received_len += len(chunk)
							 | 
						||
| 
								 | 
							
								            if file_props.size == file_props.received_len:
							 | 
						||
| 
								 | 
							
								                file_props.completed = True
							 | 
						||
| 
								 | 
							
								            app.socks5queue.progress_transfer_cb(self._account, file_props)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _on_data_result(self, task):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            task.finish()
							 | 
						||
| 
								 | 
							
								        except StanzaError as error:
							 | 
						||
| 
								 | 
							
								            app.socks5queue.error_cb('Error', to_user_string(error))
							 | 
						||
| 
								 | 
							
								            self._log.warning(error)
							 | 
						||
| 
								 | 
							
								            return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        file_props = task.get_user_data()
							 | 
						||
| 
								 | 
							
								        self.send_data(file_props)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_instance(*args, **kwargs):
							 | 
						||
| 
								 | 
							
								    return IBB(*args, **kwargs), 'IBB'
							 |