# -*- coding: utf-8 -*-
# xmppony
# filetransfer.py
# Copyright (c) 2009 Anaƫl Verrier
# Copyright (c) 2003-2009 Alexey "Snake" Nezhdanov
# This program 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.
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
This module contains IBB class that is the simple implementation of JEP-0047.
Note that this is just a transport for data. You have to negotiate data transfer before
(via StreamInitiation most probably). Unfortunately SI is not implemented yet.
"""
import base64
import os
import socket
from dispatcher import PlugIn
import features
from protocol import (ERR_BAD_REQUEST, ERR_ITEM_NOT_FOUND,
ERR_UNEXPECTED_REQUEST, Error, ErrorNode, Iq, JID, Node,
NodeProcessed, NS_AMP, NS_BYTESTREAMS, NS_IBB,
NS_PROFILE_FT, NS_PROFILE_TREE_FT, NS_STANZAS, Protocol)
from si import makeKey, SI
import socks5
#Events name
SEND_SUCCESS = 'SUCCESSFULL SEND'
RECEIVE_SUCCESS = 'SUCCESSFULL RECEIVE'
SEND_ERROR = 'ERROR ON SEND'
RECEIVE_ERROR = 'ERROR ON RECEIVE'
class IBB(PlugIn):
""" IBB used to transfer small-sized data chunk over estabilished xmpp connection.
Data is split into small blocks (by default 3000 bytes each), encoded as base 64
and sent to another entity that compiles these blocks back into the data chunk.
This is very inefficiend but should work under any circumstances. Note that
using IBB normally should be the last resort.
"""
def __init__(self):
""" Initialise internal variables. """
PlugIn.__init__(self)
self.DBG_LINE = 'ibb'
self._exported_methods = [self.OpenStream]
self._streams = dict()
self._ampnode = Node(
NS_AMP + ' amp',
payload=[Node('rule', {'condition': 'deliver-at',
'value': 'stored', 'action': 'error'}),
Node('rule', {'condition': 'match-resource',
'value': 'exact', 'action': 'error'})])
def plugin(self, owner):
""" Register handlers for receiving incoming datastreams. Used internally. """
self._owner.RegisterHandlerOnce('iq', self.StreamOpenReplyHandler) # Move to StreamOpen and specify stanza id
self._owner.RegisterHandler('iq', self.IqHandler, ns=NS_IBB)
self._owner.RegisterHandler('message', self.ReceiveHandler, ns=NS_IBB)
def IqHandler(self, conn, stanza):
""" Handles streams state change. Used internally. """
typ = stanza.getType()
self.DEBUG('IqHandler called typ->%s' % typ, 'info')
if typ == 'set' and stanza.getTag('open', namespace=NS_IBB):
self.StreamOpenHandler(conn, stanza)
elif typ == 'set' and stanza.getTag('close', namespace=NS_IBB):
self.StreamCloseHandler(conn, stanza)
elif typ == 'result':
self.StreamCommitHandler(conn, stanza)
elif typ == 'error':
self.StreamOpenReplyHandler(conn, stanza)
else:
conn.send(Error(stanza, ERR_BAD_REQUEST))
raise NodeProcessed
def StreamOpenHandler(self, conn, stanza):
""" Handles opening of new incoming stream. Used internally. """
"""
"""
err = None
sid = stanza.getTagAttr('open', 'sid')
blocksize = stanza.getTagAttr('open', 'block-size')
self.DEBUG('StreamOpenHandler called sid->%s blocksize->%s' %
(sid, blocksize), 'info')
try:
blocksize = int(blocksize)
except:
err = ERR_BAD_REQUEST
if not sid or not blocksize:
err = ERR_BAD_REQUEST
elif sid in self._streams.keys():
err = ERR_UNEXPECTED_REQUEST
if err:
rep = Error(stanza, err)
else:
self.DEBUG("Opening stream: id %s, block-size %s" %
(sid, blocksize), 'info')
rep = Protocol('iq', stanza.getFrom(), 'result', stanza.getTo(),
{'id': stanza.getID()})
self._streams[sid] = {'direction': '<' + str(stanza.getFrom()),
'block-size': blocksize,
'fp': open('/tmp/xmpp_file_' + sid, 'w'),
'seq': 0,
'syn_id': stanza.getID()}
conn.send(rep)
def OpenStream(self, sid, to, fp, blocksize=3000):
""" Start new stream. You should provide stream id 'sid', the endpoind jid 'to',
the file object containing info for send 'fp'. Also the desired blocksize can be specified.
Take into account that recommended stanza size is 4k and IBB uses base64 encoding
that increases size of data by 1/3."""
if sid in self._streams.keys():
return
if not JID(to).getResource():
return
self._streams[sid] = {'direction': '|>' + to, 'block-size': blocksize,
'fp': fp, 'seq': 0}
self._owner.RegisterCycleHandler(self.SendHandler)
syn = Protocol('iq', to, 'set',
payload=[Node(NS_IBB + ' open',
{'sid': sid,
'block-size': blocksize})])
self._owner.send(syn)
self._streams[sid]['syn_id'] = syn.getID()
return self._streams[sid]
def SendHandler(self, conn):
""" Send next portion of data if it is time to do it. Used internally. """
self.DEBUG('SendHandler called', 'info')
for sid in self._streams:
stream = self._streams[sid]
if stream['direction'][:2] == '|>':
#cont = 1
pass
elif stream['direction'][0] == '>':
chunk = stream['fp'].read(stream['block-size'])
if chunk:
datanode = Node(NS_IBB + ' data',
{'sid': sid, 'seq': stream['seq']},
base64.encodestring(chunk))
stream['seq'] += 1
if stream['seq'] == 65536:
stream['seq'] = 0
conn.send(Protocol('message', stream['direction'][1:],
payload=[datanode, self._ampnode]))
else:
# notify the other side about stream closing
conn.send(Protocol('iq', stream['direction'][1:], 'set',
payload=[Node(NS_IBB + ' close',
{'sid': sid})]))
# notify the local user about sucessfull send
conn.Event(self.DBG_LINE, 'SUCCESSFULL SEND', stream)
# delete the local stream
del self._streams[sid]
self._owner.UnregisterCycleHandler(self.SendHandler)
"""
qANQR1DBwU4DX7jmYZnncmUQB/9KuKBddzQH+tZ1ZywKK0yHKnq57kWq+RFtQdCJ
WpdWpR0uQsuJe7+vh3NWn59/gTc5MDlX8dS9p0ovStmNcyLhxVgmqS8ZKhsblVeu
IpQ0JgavABqibJolc3BKrVtVV1igKiX/N7Pi8RtY1K18toaMDhdEfhBRzO/XB0+P
AQhYlRjNacGcslkhXqNjK5Va4tuOAPy2n1Q8UUrHbUd0g+xJ9Bm0G0LZXyvCWyKH
kuNEHFQiLuCY6Iv0myq6iX6tjuHehZlFSh80b5BVV9tNLwNR5Eqz1klxMhoghJOA
"""
def ReceiveHandler(self, conn, stanza):
""" Receive next portion of incoming datastream and store it write
it to temporary file. Used internally.
"""
sid = stanza.getTagAttr('data', 'sid')
seq = stanza.getTagAttr('data', 'seq')
data = stanza.getTagData('data')
self.DEBUG('ReceiveHandler called sid->%s seq->%s' % (sid, seq), 'info')
try:
seq = int(seq)
data = base64.decodestring(data)
except:
seq = ''
data = ''
err = None
if not sid in self._streams:
err = ERR_ITEM_NOT_FOUND
else:
stream = self._streams[sid]
if not data:
err = ERR_BAD_REQUEST
elif seq != stream['seq']:
err = ERR_UNEXPECTED_REQUEST
else:
self.DEBUG('Successfull receive sid->%s %s+%s bytes' %
(sid, stream['fp'].tell(), len(data)), 'ok')
stream['seq'] += 1
stream['fp'].write(data)
if err:
self.DEBUG('Error on receive: %s' % err, 'error')
conn.send(Error(Iq(to=stanza.getFrom(), frm=stanza.getTo(),
payload=[Node(NS_IBB + ' close')]), err,
reply=0))
def StreamCloseHandler(self, conn, stanza):
""" Handle stream closure due to all data transmitted.
Raise xmpppy event specifying successfull data receive. """
sid = stanza.getTagAttr('close', 'sid')
self.DEBUG('StreamCloseHandler called sid->%s' % sid, 'info')
if sid in self._streams:
conn.send(stanza.buildReply('result'))
conn.Event(self.DBG_LINE, 'SUCCESSFULL RECEIVE', self._streams[sid])
del self._streams[sid]
else:
conn.send(Error(stanza, ERR_ITEM_NOT_FOUND))
def StreamBrokenHandler(self, conn, stanza):
""" Handle stream closure due to all some error while receiving data.
Raise xmpppy event specifying unsuccessfull data receive. """
syn_id = stanza.getID()
self.DEBUG('StreamBrokenHandler called syn_id->%s' % syn_id, 'info')
for sid in self._streams:
stream = self._streams[sid]
if stream['syn_id'] == syn_id:
if stream['direction'][0] == '<':
conn.Event(self.DBG_LINE, 'ERROR ON RECEIVE', stream)
else:
conn.Event(self.DBG_LINE, 'ERROR ON SEND', stream)
del self._streams[sid]
def StreamOpenReplyHandler(self, conn, stanza):
""" Handle remote side reply about is it agree or not to receive our datastream.
Used internally. Raises xmpppy event specfiying if the data transfer
is agreed upon."""
syn_id = stanza.getID()
self.DEBUG('StreamOpenReplyHandler called syn_id->%s' % syn_id, 'info')
for sid in self._streams:
stream = self._streams[sid]
if stream['syn_id'] == syn_id:
if stanza.getType() == 'error':
if stream['direction'][0] == '<':
conn.Event(self.DBG_LINE, 'ERROR ON RECEIVE', stream)
else:
conn.Event(self.DBG_LINE, 'ERROR ON SEND', stream)
del self._streams[sid]
elif stanza.getType() == 'result':
if stream['direction'][0] == '|':
stream['direction'] = stream['direction'][1:]
conn.Event(self.DBG_LINE, 'STREAM COMMITTED', stream)
else:
conn.send(Error(stanza, ERR_UNEXPECTED_REQUEST))
class S5B(PlugIn):
"""Socks5 Bytestream (JEP-0065) can be used to send and receive big files, it is the preferred method for file transfer
This class implement jabber part of JEP-0065, the socks5 part is in the socks5.py module.
The method that you may need to use are :
addProxy : to add a proxy to the list of streamhosts if you aleady know his jid, ip and port
seekProxy : search a server for proxy.
addStreamHost : to add a manually a streamhost.
You shouldn't send a file using only this class, you should prefer the SIFileTransfer class.
"""
def __init__(self):
PlugIn.__init__(self)
self.DBG_LINE = 's5b'
self._exported_methods = [self.addStreamHost,
self.SetS5BFileDestination,
self.addProxy, self.seekProxy]
self.streamHosts = dict()
self.fileToSend = dict()
self._fileDestination = dict()
self.SOCKS5 = socks5.SOCKSv5(self)
def plugin(self, owner):
"""Initialise some variables and set up handlers."""
self.ownJID = '%(user)s@%(server)s/%(resource)s' % {
'user': owner._User,
'server': owner._Server[0],
'resource': owner._Resource}
owner.RegisterHandler('iq', self.ConnectionAckCB, ns=NS_BYTESTREAMS,
typ='result')
owner.RegisterHandler('iq', self.StreamHostCB, typ='set',
ns=NS_BYTESTREAMS)
def plugout(self):
"""Close every socket.
Clear all data"""
self.SOCKS5.closeSockets()
del self.streamHosts
del self.fileToSend
del self._fileDestination
del self.SOCKS5
def addProxy(self, jid):
"""Discover information about the proxy65 given in argument
and add it to the list of known streamhosts
Return False if the jid doesn't support bytestreams
"""
#verify that the jid support bytestream
info = features.discoverInfo(self._owner, jid)
if (info[1].count(NS_BYTESTREAMS) == 0 or
info[0][0]['category'] != 'proxy' or
info[0][0]['type'] != 'bytestreams'):
return False
self.DEBUG('%s is a proxy65' % jid, 'info')
#Get ip and port
iq = Iq(typ='get', to=jid, queryNS=NS_BYTESTREAMS)
rec = self._owner.SendAndWaitForResponse(iq)
try:
for streamhost in rec.getQueryChildren():
self.addStreamHost(jid, 'proxy', ip=streamhost.getAttr('host'),
port=streamhost.getAttr('port'),
zeroconf=streamhost.getAttr('zeroconf'))
return True
except:
return False
def seekProxy(self, server=None):
"""Seek for bytestreams proxy on the server, if no server is specified, use the current server.
Return True if we found at least one proxy."""
if server is None:
server = self._owner._Server[0]
if server.count('@'):
self.DEBUG('%s is not a valid server' % server, 'error')
return False
items = features.discoverItems(self._owner, server)
found = False
for item in items:
if self.addProxy(item['jid']):
found = True
return found
def SetS5BFileDestination(self, key, path, offset, length):
""" Set the destination of the file obtained with the stream identified by the Stream ID : sid
offset and length have the same meaning as in SIFileTransfer.AcceptStream
Usualy called by SIFileTransfer"""
self._fileDestination[key] = {'path': path, 'offset': offset,
'length': length}
def ConnectionAckCB(self, conn, iq):
"""Handler for when the target acknowledges SOCKS5 connection
Used internally"""
try:
jidUsed = iq.getTag('query', namespace=NS_BYTESTREAMS).getTag('streamhost-used').getAttr('jid')
except:
jidUsed = None
key = None
for keyt in self.fileToSend:
if (self.fileToSend[keyt]['id'] == iq.getID() and
self.fileToSend[keyt]['to'] == iq.getFrom()):
key = keyt
del keyt
break
if key and jidUsed:
fil = self.fileToSend[key]
#Check if the stream used is local or is a proxy
stream = self.streamHosts[jidUsed]
if stream['type'] == 'self':
self.SOCKS5.activate(key, fil['file'], fil['offset'],
fil['length'])
elif stream['type'] == 'proxy':
if self.SOCKS5.connectTo(key, stream['host'],
int(stream['port'])):
reply = self.SOCKS5.sendRequest(key, socks5.CMD_CONNECT,
socks5.ADDR_DOMAINNAME, key,
0)
if reply is not None and reply == (key, 0):
iq = Iq(typ='set', to=jidUsed, queryNS=NS_BYTESTREAMS,
payload=[Node('activate', payload=[fil['to']])])
iq.getTag('query').setAttr('sid', fil['sid'])
self._owner.SendAndCallForResponse(
iq, self.proxyActivatedHandler, args={'fil':fil,
'key':key})
else:
self.eventSendError(key)
else:
self.eventSendError(key)
del self.fileToSend[key]
else:
self._owner.send(Error(iq, ERR_UNEXPECTED_REQUEST))
raise NodeProcessed
def proxyActivatedHandler(self, conn, iq, fil, key):
"""Called when we received the activation ack from the proxy
Used internally"""
if iq and iq.getAttr('type') == 'result':
self.SOCKS5.sendToProxy(key, fil['file'], fil['offset'],
fil['length'])
raise NodeProcessed
def StreamHostCB(self, conn, iq):
"""Called when we receive the list of streamhosts
Used internally"""
self.DEBUG('Received a list of streamhost', 'info')
query = iq.getTag('query', namespace=NS_BYTESTREAMS)
if query is not None:
sid = query.getAttr('sid')
else:
sid = None
id_ = iq.getID()
err = None
rep = None
key = makeKey(sid, iq.getFrom().__str__(), iq.getTo().__str__())
if not sid:
err = ERR_BAD_REQUEST
elif not key in self._fileDestination:
err = ERR_UNEXPECTED_REQUEST
s = False
if query.getAttr('mode') == 'udp':
self.DEBUG('UDP bytestreams not implemented', 'error')
elif not err:
for streamhost in query.getTags('streamhost'):
#We try to connect to each streamhost in the given order
if (not streamhost.getAttr('host') or
not streamhost.getAttr('port')):
pass
s = self.SOCKS5.connectTo(key, streamhost.getAttr('host'),
int(streamhost.getAttr('port')))
if s:
self.DEBUG('Connected to streamhost %s' %
streamhost.getAttr('jid'), 'info')
self.SOCKS5.sendRequest(key, socks5.CMD_CONNECT,
socks5.ADDR_DOMAINNAME, key, 0)
self.DEBUG('Waiting for data', 'info')
fil = self._fileDestination[key]
self.SOCKS5.threadWaitForData(key, fil['path'],
fil['offset'], fil['length'])
self.sendStreamhostACK(iq.getFrom(),
streamhost.getAttr('jid'), id_)
break
if not err and not s:
self.DEBUG('No suitable streamhost', 'error')
rep = Protocol('iq', to=iq.getFrom(), typ='error',
payload=[ErrorNode('item-not-found', '404',
'cancel')])
rep.getTag('error').getTag('item-not-found').setAttr('xmlns',
NS_STANZAS)
rep.setID(id_)
if err:
rep = Error(iq, err)
if rep:
self._owner.send(rep)
if err or not s:
self.eventReceiveError(key)
raise NodeProcessed
def sendStreamhostACK(self, to, jid, id_):
"""Send the stream used to the sender.
Used internally."""
iq = Iq(typ='result', to=to, queryNS=NS_BYTESTREAMS,
payload=[Node('streamhost-used', attrs={'jid': jid})])
iq.setID(id_)
self._owner.send(iq)
def addStreamHost(self, jid, type_='self', ip='', port=0, zeroconf=''):
"""Add another streamhost, for example a S5B proxy
type can be 'self' or 'proxy'
self mean that the Streamhost is us, and proxy is a ... proxy
We can't have more than one 'self' streamhost.
"""
if type_ == 'self':
for host in self.streamHosts:
if type_ == 'self':
return False
self.DEBUG('Added streamhost type : %s, jid : %s' % (type_, jid),
'info')
host = {'jid': jid, 'type': type_}
if ip != '':
host['host'] = ip
if port != 0:
host['port'] = port
if type_ == 'self':
port = self.SOCKS5.listenTCP(ip, port)
host['port'] = port
if zeroconf != '' and zeroconf is not None:
host['zeroconf'] = zeroconf
self.streamHosts[jid] = host
def OpenBytestream(self, sid, to, fp, offset, length):
"""Send the previously opened file fp to the JID designed by to with the Stream ID : sid"""
iq = Iq(typ='set', queryNS=NS_BYTESTREAMS, to=to)
query = iq.getTag('query')
query.setAttr('sid', sid)
query.setAttr('mode', 'tcp')
key = makeKey(sid, self.ownJID, to)
self.SOCKS5.authorise(key)
for hostJID in self.streamHosts:
host = self.streamHosts[hostJID]
typ = host['type']
del host['type']
query.addChild('streamhost', attrs=host)
host['type'] = typ
#time.sleep(0.1)
self._owner.send(iq)
self.fileToSend[key] = {'file': fp, 'id': iq.getID(), 'to': to,
'offset': offset, 'length': length, 'sid': sid}
def eventSendSuccess(self, key):
"""raise an xmpppy event SEND_SUCCESS.
The data is the key of stream
Used internally (called by socks5.py"""
self._owner.Dispatcher.Event(NS_BYTESTREAMS, SEND_SUCCESS, key)
def eventSendError(self, key):
"""raise an xmpppy event SEND_ERROR.
The data is the key of stream
Used internally (called by socks5.py"""
self._owner.Dispatcher.Event(NS_BYTESTREAMS, SEND_ERROR, key)
def eventReceiveSuccess(self, key):
"""raise an xmpppy event RECEIVE_SUCCESS.
The data is the key of stream
Used internally (called by socks5.py"""
self._owner.Dispatcher.Event(NS_BYTESTREAMS, RECEIVE_SUCCESS, key)
def eventReceiveError(self, key):
"""raise an xmpppy event RECEIVE_ERROR.
The data is the key of stream
Used internally (called by socks5.py"""
self._owner.Dispatcher.Event(NS_BYTESTREAMS, RECEIVE_ERROR, key)
class SIFileTransfer(SI):
"""Implement sending files with stream inititiation (JEP-0096)
To send a file you must call the sendSIFile method
To receive a file you must handle SI_REQUEST events in the NS_SI realm (cf SIOfferCB method)
"""
def __init__(self, localip='', port=0):
SI.__init__(self, NS_PROFILE_FT)
if localip == '':
self.localip = socket.gethostbyname(socket.gethostname())
else:
self.localip = localip
self.port = port
self.authorisedStream = dict() #contain keys and path of streams to auto-accept (used by tree transfer)
self._exported_methods.append(self.sendSIFile)
def plugin(self, owner):
SI.plugin(self, owner)
self.ownJID = '%(user)s@%(server)s/%(resource)s' % {
'user': owner._User,
'server': owner._Server[0],
'resource': owner._Resource}
try:
S5B().PlugIn(owner)
owner.addStreamHost(self.ownJID, ip=self.localip, port=self.port)
self.streamMethod.append(NS_BYTESTREAMS)
except None:
pass
try:
IBB().PlugIn(owner)
self.streamMethod.append(NS_IBB)
except:
pass
def plugout(self):
if self.streamMethod.count(NS_IBB):
self._owner.IBB.PlugOut()
if self.streamMethod.count(NS_BYTESTREAMS):
self._owner.S5B.PlugOut()
def authoriseStream(self, key, path, method=None):
"""Auto authorise the stream identified by key. The method is the default stream method to use."""
self.authorisedStream[key] = {'path': path, 'method': method}
def siOffered(self, conn, info):
"""If a stream is authorised accept it otherwise we raise an event"""
if self.authorisedStream.has_key(info['key']):
self.AcceptStream(info['key'],
self.authorisedStream[info['key']]['path'])
del self.authorisedStream[info['key']]
else:
SI.siOffered(self, conn, info)
def selectStreamMethod(self, conn, iq, key=None):
"""If the stream was negociated with tree-transfer, use the method previously found."""
if (key in self.authorisedStream and
self.authorisedStream[key]['method'] is not None):
return self.authorisedStream[key]['method'], True
else:
return SI.selectStreamMethod(self, conn, iq, key)
def parseInfo(self, node):
"""Parse the file node (cf JEP-0096)
retrun a dictionnary containing :
'filename' : the name of the file offered
'filesize' : the size of the file (in bytes)
'desc' : a description of the file
'range' : a boolean, if True the peer accept ranged transfer
"""
filename = node.getAttr('name')
filesize = node.getAttr('size')
desc = node.getTagData('desc')
if node.getTag('range'):
rang = True
else:
rang = False
if desc is None:
desc = ''
return {'filename': filename, 'filesize': filesize, 'desc': desc,
'range': rang}
def AcceptStream(self, key, path, offset=None, length=None, cont=False):
"""Accept an incomming SI, path is the emplacement where to download the file
length specify the number of bytes to retrieve and offset specify where to start in the file.
ex : offset=128,length=256 tell that you whish to retrieve only 256 bytes starting at the 128th bytes in the file.
If cont is set to True, we'll ask only for the remaining of the file, in this case offset attribute is overidden (and lenght shouldn't be set)
"""
rang = None
if self.streamsOfferedInfo[key]['info']['range']:
rang = Node('range')
if cont:
if os.path.exists(path):
offset = os.path.getsize(path)
if offset and rang:
rang.setAttr('offset', offset)
else:
offset = 0
if length and rang:
rang.setAttr('length', length)
else:
#download only the filesize given by the sender
length = int(
self.streamsOfferedInfo[key]['info']['filesize']) - offset
self.streamsOfferedInfo[key]['offset'] = offset
self.streamsOfferedInfo[key]['length'] = length
node = Node('file', attrs={'xmlns': NS_PROFILE_FT}, payload=[rang])
method = SI.AcceptStream(self, key, node)
if method == NS_IBB:
self._owner.SetIBBFileDestination(key, path, offset, length)
elif method == NS_BYTESTREAMS:
self._owner.SetS5BFileDestination(key, path, offset, length)
def openStream(self, method, streamInfo, profileData):
"""Open the stream
Is called by responseHandler method
Used internally"""
sid = None
offset = None
length = None
try:
offset = int(profileData.getTag('range').getAttr('offset'))
length = int(profileData.getTag('range').getAttr('length'))
except:
pass
file_ = streamInfo['file']
size = streamInfo['filesize']
sid = streamInfo['sid']
to = streamInfo['to']
if method == 'http://jabber.org/protocol/ibb':
fp = open(file_)
self._owner.OpenStream(sid=sid, to=to, fp=fp)
elif method == 'http://jabber.org/protocol/bytestreams':
if offset is None:
offset = 0
if length is None:
length = size - offset
fp = open(file_)
self._owner.S5B.OpenBytestream(sid=sid, to=to, fp=fp, offset=offset,
length=length)
def sendSIFile(self, to, file_, desc='', sid=None, method=None):
"""Send a file
to:target
file:path of the file
desc:description of the file
Return an unique key generated from the stream id, the jid of the sender and the jid of the target
"""
fileSize = os.path.getsize(file_)
fileName = file_[file_.rfind('/') + 1:]
node = Node('file', attrs={'xmlns': NS_PROFILE_FT, 'name': fileName,
'size': fileSize},
payload=[Node('desc', payload=[desc]), Node('range')])
fileInfo = {'file':file_, 'filesize': fileSize}
return SI.sendSI(self, to, node, fileInfo, sid, method)
class SITreeTransfer(SI):
def __init__(self, siFT):
"""siFT is an SIFileTransfer instance"""
SI.__init__(self, NS_PROFILE_TREE_FT)
self._exported_methods.append(self.sendSITree)
self.siFT = siFT
self.streamMethod = self.siFT.streamMethod
def plugin(self, owner):
SI.plugin(self, owner)
self.ownJID = '%(user)s@%(server)s/%(resource)s' % {
'user': owner._User,
'server': owner._Server[0],
'resource': owner._Resource}
def parseInfo(self, node):
info = dict()
if node is None:
return dict()
if node.has_attr('numfiles'):
info['numfiles'] = node.getAttr('numfiles')
if node.has_attr('size'):
info['size'] = node.getAttr('size')
info['tree'] = self.parseDirectory(node)
return info
def parseDirectory(self, node):
"""Parse recursivly a directory node
return a dictionnary containing the tree
"""
info = dict()
for di in node.getTags('directory'):
info[di.getAttr('name')] = self.parseDirectory(di)
for fil in node.getTags('file'):
info[fil.getAttr('name')] = fil.getAttr('sid')
return info
def AcceptStream(self, key, path):
"""Accept an incomming SI Tree Transfer, path is the emplacement where to download the tree (it must be a directory)"""
tree = self.streamsOfferedInfo[key]['info']['tree']
fro = self.streamsOfferedInfo[key]['from']
method = SI.AcceptStream(self, key)
self.createDirectory(path, tree, fro, method)
def createDirectory(self, path, tree, fro, method=None):
for dirname in tree:
if isinstance(tree[dirname], dict):
dirpath = path + os.path.sep + dirname
if not os.path.exists(dirpath):
os.mkdir(dirpath)
self.createDirectory(dirpath, tree[dirname], fro, method)
elif isinstance(tree[dirname], unicode):
key = makeKey(tree[dirname], fro, self.ownJID)
self.siFT.authoriseStream(key, path + os.path.sep + dirname,
method)
def openStream(self, method, streamInfo, profileData):
"""Open the stream
Is called by responseHandler method
Used internally"""
filelist = streamInfo['filelist']
#sid = streamInfo['sid']
to = streamInfo['to']
if self.streamMethod.count(method) == 0:
self._owner.send(Error(streamInfo['iq'], ERR_BAD_REQUEST))
return
for fileName in filelist:
self.siFT.sendSIFile(to, fileName[0], sid=fileName[1],
method=method)
def sendSITree(self, to, path):
"""Send a directory
to:target
path:path of the directory
Return an unique key generated from the stream id, the jid of the sender and the jid of the target
"""
path = os.path.realpath(path)
dirSize, numFiles, dirNode, fileList = self.walkDirectory(
path, node=Node('directory',
attrs={'name': os.path.basename(path)}))
node = Node('tree', attrs={'xmlns': NS_PROFILE_TREE_FT,
'numfiles': numFiles, 'dirsize': dirSize},
payload=[dirNode])
return SI.sendSI(self, to, node, {'filelist': fileList})
def walkDirectory(self, path, size=0, numFiles=0, node=None, fileList=[]):
"""Return the size of the directory, the number of files, the directory node and a liste of file to send"""
for fileName in os.listdir(path):
fullPath = os.path.join(path, fileName)
if os.path.isdir(fullPath):
newNode = Node('directory', attrs={'name': fileName})
size, numFiles, newNode, fileList = self.walkDirectory(
fullPath, size, numFiles, newNode)
if os.path.isfile(fullPath):
numFiles = numFiles + 1
size = size + os.path.getsize(fullPath)
fileList.append([fullPath, 'tree' + str(self.ID)])
newNode = Node('file', attrs={'name': fileName,
'sid': 'tree' + str(self.ID)})
self.ID = self.ID + 1
if node is None:
node = newNode
else:
node.addChild(node = newNode)
return size, numFiles, node, fileList