You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp

759 lines
18 KiB

/*
* filetransfer.cpp - File Transfer
* Copyright (C) 2004 Justin Karneges
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "filetransfer.h"
#include <tqtimer.h>
#include <tqptrlist.h>
#include <tqguardedptr.h>
#include <tqfileinfo.h>
#include "xmpp_xmlcommon.h"
#include "s5b.h"
#define SENDBUFSIZE 65536
using namespace XMPP;
// firstChildElement
//
// Get an element's first child element
static TQDomElement firstChildElement(const TQDomElement &e)
{
for(TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
if(n.isElement())
return n.toElement();
}
return TQDomElement();
}
//----------------------------------------------------------------------------
// FileTransfer
//----------------------------------------------------------------------------
class FileTransfer::Private
{
public:
FileTransferManager *m;
JT_FT *ft;
Jid peer;
TQString fname;
TQ_LLONG size;
TQ_LLONG sent;
TQString desc;
bool rangeSupported;
TQ_LLONG rangeOffset, rangeLength, length;
TQString streamType;
bool needStream;
TQString id, iq_id;
S5BConnection *c;
Jid proxy;
int state;
bool sender;
};
FileTransfer::FileTransfer(FileTransferManager *m, TQObject *parent)
:TQObject(parent)
{
d = new Private;
d->m = m;
d->ft = 0;
d->c = 0;
reset();
}
FileTransfer::~FileTransfer()
{
reset();
delete d;
}
void FileTransfer::reset()
{
d->m->unlink(this);
delete d->ft;
d->ft = 0;
delete d->c;
d->c = 0;
d->state = Idle;
d->needStream = false;
d->sent = 0;
d->sender = false;
}
void FileTransfer::setProxy(const Jid &proxy)
{
d->proxy = proxy;
}
void FileTransfer::sendFile(const Jid &to, const TQString &fname, TQ_LLONG size, const TQString &desc)
{
d->state = Requesting;
d->peer = to;
d->fname = fname;
d->size = size;
d->desc = desc;
d->sender = true;
d->id = d->m->link(this);
d->ft = new JT_FT(d->m->client()->rootTask());
connect(d->ft, TQT_SIGNAL(finished()), TQT_SLOT(ft_finished()));
TQStringList list;
list += "http://jabber.org/protocol/bytestreams";
d->ft->request(to, d->id, fname, size, desc, list);
d->ft->go(true);
}
int FileTransfer::dataSizeNeeded() const
{
int pending = d->c->bytesToWrite();
if(pending >= SENDBUFSIZE)
return 0;
TQ_LLONG left = d->length - (d->sent + pending);
int size = SENDBUFSIZE - pending;
if((TQ_LLONG)size > left)
size = (int)left;
return size;
}
void FileTransfer::writeFileData(const TQByteArray &a)
{
int pending = d->c->bytesToWrite();
TQ_LLONG left = d->length - (d->sent + pending);
if(left == 0)
return;
TQByteArray block;
if((TQ_LLONG)a.size() > left) {
block = a.copy();
block.resize((uint)left);
}
else
block = a;
d->c->write(block);
}
Jid FileTransfer::peer() const
{
return d->peer;
}
TQString FileTransfer::fileName() const
{
return d->fname;
}
TQ_LLONG FileTransfer::fileSize() const
{
return d->size;
}
TQString FileTransfer::description() const
{
return d->desc;
}
bool FileTransfer::rangeSupported() const
{
return d->rangeSupported;
}
TQ_LLONG FileTransfer::offset() const
{
return d->rangeOffset;
}
TQ_LLONG FileTransfer::length() const
{
return d->length;
}
void FileTransfer::accept(TQ_LLONG offset, TQ_LLONG length)
{
d->state = Connecting;
d->rangeOffset = offset;
d->rangeLength = length;
if(length > 0)
d->length = length;
else
d->length = d->size;
d->streamType = "http://jabber.org/protocol/bytestreams";
d->m->con_accept(this);
}
void FileTransfer::close()
{
if(d->state == Idle)
return;
if(d->state == WaitingForAccept)
d->m->con_reject(this);
else if(d->state == Active)
d->c->close();
reset();
}
S5BConnection *FileTransfer::s5bConnection() const
{
return d->c;
}
void FileTransfer::ft_finished()
{
JT_FT *ft = d->ft;
d->ft = 0;
if(ft->success()) {
d->state = Connecting;
d->rangeOffset = ft->rangeOffset();
d->length = ft->rangeLength();
if(d->length == 0)
d->length = d->size - d->rangeOffset;
d->streamType = ft->streamType();
d->c = d->m->client()->s5bManager()->createConnection();
connect(d->c, TQT_SIGNAL(connected()), TQT_SLOT(s5b_connected()));
connect(d->c, TQT_SIGNAL(connectionClosed()), TQT_SLOT(s5b_connectionClosed()));
connect(d->c, TQT_SIGNAL(bytesWritten(int)), TQT_SLOT(s5b_bytesWritten(int)));
connect(d->c, TQT_SIGNAL(error(int)), TQT_SLOT(s5b_error(int)));
if(d->proxy.isValid())
d->c->setProxy(d->proxy);
d->c->connectToJid(d->peer, d->id);
accepted();
}
else {
reset();
if(ft->statusCode() == 403)
error(ErrReject);
else
error(ErrNeg);
}
}
void FileTransfer::takeConnection(S5BConnection *c)
{
d->c = c;
connect(d->c, TQT_SIGNAL(connected()), TQT_SLOT(s5b_connected()));
connect(d->c, TQT_SIGNAL(connectionClosed()), TQT_SLOT(s5b_connectionClosed()));
connect(d->c, TQT_SIGNAL(readyRead()), TQT_SLOT(s5b_readyRead()));
connect(d->c, TQT_SIGNAL(error(int)), TQT_SLOT(s5b_error(int)));
if(d->proxy.isValid())
d->c->setProxy(d->proxy);
accepted();
TQTimer::singleShot(0, this, TQT_SLOT(doAccept()));
}
void FileTransfer::s5b_connected()
{
d->state = Active;
connected();
}
void FileTransfer::s5b_connectionClosed()
{
reset();
error(ErrStream);
}
void FileTransfer::s5b_readyRead()
{
TQByteArray a = d->c->read();
TQ_LLONG need = d->length - d->sent;
if((TQ_LLONG)a.size() > need)
a.resize((uint)need);
d->sent += a.size();
if(d->sent == d->length)
reset();
readyRead(a);
}
void FileTransfer::s5b_bytesWritten(int x)
{
d->sent += x;
if(d->sent == d->length)
reset();
bytesWritten(x);
}
void FileTransfer::s5b_error(int x)
{
reset();
if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect)
error(ErrConnect);
else if(x == S5BConnection::ErrProxy)
error(ErrProxy);
else
error(ErrStream);
}
void FileTransfer::man_waitForAccept(const FTRequest &req)
{
d->state = WaitingForAccept;
d->peer = req.from;
d->id = req.id;
d->iq_id = req.iq_id;
d->fname = req.fname;
d->size = req.size;
d->desc = req.desc;
d->rangeSupported = req.rangeSupported;
}
void FileTransfer::doAccept()
{
d->c->accept();
}
//----------------------------------------------------------------------------
// FileTransferManager
//----------------------------------------------------------------------------
class FileTransferManager::Private
{
public:
Client *client;
TQPtrList<FileTransfer> list, incoming;
JT_PushFT *pft;
};
FileTransferManager::FileTransferManager(Client *client)
:TQObject(client)
{
d = new Private;
d->client = client;
d->pft = new JT_PushFT(d->client->rootTask());
connect(d->pft, TQT_SIGNAL(incoming(const FTRequest &)), TQT_SLOT(pft_incoming(const FTRequest &)));
}
FileTransferManager::~FileTransferManager()
{
d->incoming.setAutoDelete(true);
d->incoming.clear();
delete d->pft;
delete d;
}
Client *FileTransferManager::client() const
{
return d->client;
}
FileTransfer *FileTransferManager::createTransfer()
{
FileTransfer *ft = new FileTransfer(this);
return ft;
}
FileTransfer *FileTransferManager::takeIncoming()
{
if(d->incoming.isEmpty())
return 0;
FileTransfer *ft = d->incoming.getFirst();
d->incoming.removeRef(ft);
// move to active list
d->list.append(ft);
return ft;
}
void FileTransferManager::pft_incoming(const FTRequest &req)
{
bool found = false;
for(TQStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) {
if((*it) == "http://jabber.org/protocol/bytestreams") {
found = true;
break;
}
}
if(!found) {
d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types");
return;
}
if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) {
d->pft->respondError(req.from, req.iq_id, 400, "SID in use");
return;
}
FileTransfer *ft = new FileTransfer(this);
ft->man_waitForAccept(req);
d->incoming.append(ft);
incomingReady();
}
void FileTransferManager::s5b_incomingReady(S5BConnection *c)
{
TQPtrListIterator<FileTransfer> it(d->list);
FileTransfer *ft = 0;
for(FileTransfer *i; (i = it.current()); ++it) {
if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) {
ft = i;
break;
}
}
if(!ft) {
c->close();
delete c;
return;
}
ft->takeConnection(c);
}
TQString FileTransferManager::link(FileTransfer *ft)
{
d->list.append(ft);
return d->client->s5bManager()->genUniqueSID(ft->d->peer);
}
void FileTransferManager::con_accept(FileTransfer *ft)
{
ft->d->needStream = true;
d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType);
}
void FileTransferManager::con_reject(FileTransfer *ft)
{
d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined");
}
void FileTransferManager::unlink(FileTransfer *ft)
{
d->list.removeRef(ft);
}
//----------------------------------------------------------------------------
// JT_FT
//----------------------------------------------------------------------------
class JT_FT::Private
{
public:
TQDomElement iq;
Jid to;
TQ_LLONG size, rangeOffset, rangeLength;
TQString streamType;
TQStringList streamTypes;
};
JT_FT::JT_FT(Task *parent)
:Task(parent)
{
d = new Private;
}
JT_FT::~JT_FT()
{
delete d;
}
void JT_FT::request(const Jid &to, const TQString &_id, const TQString &fname, TQ_LLONG size, const TQString &desc, const TQStringList &streamTypes)
{
TQDomElement iq;
d->to = to;
iq = createIQ(doc(), "set", to.full(), id());
TQDomElement si = doc()->createElement("si");
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
si.setAttribute("id", _id);
si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer");
TQDomElement file = doc()->createElement("file");
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
file.setAttribute("name", fname);
file.setAttribute("size", TQString::number(size));
if(!desc.isEmpty()) {
TQDomElement de = doc()->createElement("desc");
de.appendChild(doc()->createTextNode(desc));
file.appendChild(de);
}
TQDomElement range = doc()->createElement("range");
file.appendChild(range);
si.appendChild(file);
TQDomElement feature = doc()->createElement("feature");
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
TQDomElement x = doc()->createElement("x");
x.setAttribute("xmlns", "jabber:x:data");
x.setAttribute("type", "form");
TQDomElement field = doc()->createElement("field");
field.setAttribute("var", "stream-method");
field.setAttribute("type", "list-single");
for(TQStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) {
TQDomElement option = doc()->createElement("option");
TQDomElement value = doc()->createElement("value");
value.appendChild(doc()->createTextNode(*it));
option.appendChild(value);
field.appendChild(option);
}
x.appendChild(field);
feature.appendChild(x);
si.appendChild(feature);
iq.appendChild(si);
d->streamTypes = streamTypes;
d->size = size;
d->iq = iq;
}
TQ_LLONG JT_FT::rangeOffset() const
{
return d->rangeOffset;
}
TQ_LLONG JT_FT::rangeLength() const
{
return d->rangeLength;
}
TQString JT_FT::streamType() const
{
return d->streamType;
}
void JT_FT::onGo()
{
send(d->iq);
}
bool JT_FT::take(const TQDomElement &x)
{
if(!iqVerify(x, d->to, id()))
return false;
if(x.attribute("type") == "result") {
TQDomElement si = firstChildElement(x);
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") {
setError(900, "");
return true;
}
TQString id = si.attribute("id");
TQ_LLONG range_offset = 0;
TQ_LLONG range_length = 0;
TQDomElement file = si.elementsByTagName("file").item(0).toElement();
if(!file.isNull()) {
TQDomElement range = file.elementsByTagName("range").item(0).toElement();
if(!range.isNull()) {
int x;
bool ok;
if(range.hasAttribute("offset")) {
x = range.attribute("offset").toLongLong(&ok);
if(!ok || x < 0) {
setError(900, "");
return true;
}
range_offset = x;
}
if(range.hasAttribute("length")) {
x = range.attribute("length").toLongLong(&ok);
if(!ok || x < 0) {
setError(900, "");
return true;
}
range_length = x;
}
}
}
if(range_offset > d->size || (range_length > (d->size - range_offset))) {
setError(900, "");
return true;
}
TQString streamtype;
TQDomElement feature = si.elementsByTagName("feature").item(0).toElement();
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
TQDomElement x = feature.elementsByTagName("x").item(0).toElement();
if(!x.isNull() && x.attribute("type") == "submit") {
TQDomElement field = x.elementsByTagName("field").item(0).toElement();
if(!field.isNull() && field.attribute("var") == "stream-method") {
TQDomElement value = field.elementsByTagName("value").item(0).toElement();
if(!value.isNull())
streamtype = value.text();
}
}
}
// must be one of the offered streamtypes
bool found = false;
for(TQStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) {
if((*it) == streamtype) {
found = true;
break;
}
}
if(!found)
return true;
d->rangeOffset = range_offset;
d->rangeLength = range_length;
d->streamType = streamtype;
setSuccess();
}
else {
setError(x);
}
return true;
}
//----------------------------------------------------------------------------
// JT_PushFT
//----------------------------------------------------------------------------
JT_PushFT::JT_PushFT(Task *parent)
:Task(parent)
{
}
JT_PushFT::~JT_PushFT()
{
}
void JT_PushFT::respondSuccess(const Jid &to, const TQString &id, TQ_LLONG rangeOffset, TQ_LLONG rangeLength, const TQString &streamType)
{
TQDomElement iq = createIQ(doc(), "result", to.full(), id);
TQDomElement si = doc()->createElement("si");
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
if(rangeOffset != 0 || rangeLength != 0) {
TQDomElement file = doc()->createElement("file");
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
TQDomElement range = doc()->createElement("range");
if(rangeOffset > 0)
range.setAttribute("offset", TQString::number(rangeOffset));
if(rangeLength > 0)
range.setAttribute("length", TQString::number(rangeLength));
file.appendChild(range);
si.appendChild(file);
}
TQDomElement feature = doc()->createElement("feature");
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
TQDomElement x = doc()->createElement("x");
x.setAttribute("xmlns", "jabber:x:data");
x.setAttribute("type", "submit");
TQDomElement field = doc()->createElement("field");
field.setAttribute("var", "stream-method");
TQDomElement value = doc()->createElement("value");
value.appendChild(doc()->createTextNode(streamType));
field.appendChild(value);
x.appendChild(field);
feature.appendChild(x);
si.appendChild(feature);
iq.appendChild(si);
send(iq);
}
void JT_PushFT::respondError(const Jid &to, const TQString &id, int code, const TQString &str)
{
TQDomElement iq = createIQ(doc(), "error", to.full(), id);
TQDomElement err = textTag(doc(), "error", str);
err.setAttribute("code", TQString::number(code));
iq.appendChild(err);
send(iq);
}
bool JT_PushFT::take(const TQDomElement &e)
{
// must be an iq-set tag
if(e.tagName() != "iq")
return false;
if(e.attribute("type") != "set")
return false;
TQDomElement si = firstChildElement(e);
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si")
return false;
if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer")
return false;
Jid from(e.attribute("from"));
TQString id = si.attribute("id");
TQDomElement file = si.elementsByTagName("file").item(0).toElement();
if(file.isNull())
return true;
TQString fname = file.attribute("name");
if(fname.isEmpty()) {
respondError(from, id, 400, "Bad file name");
return true;
}
// ensure kosher
{
TQFileInfo fi(fname);
fname = fi.fileName();
}
bool ok;
TQ_LLONG size = file.attribute("size").toLongLong(&ok);
if(!ok || size < 0) {
respondError(from, id, 400, "Bad file size");
return true;
}
TQString desc;
TQDomElement de = file.elementsByTagName("desc").item(0).toElement();
if(!de.isNull())
desc = de.text();
bool rangeSupported = false;
TQDomElement range = file.elementsByTagName("range").item(0).toElement();
if(!range.isNull())
rangeSupported = true;
TQStringList streamTypes;
TQDomElement feature = si.elementsByTagName("feature").item(0).toElement();
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
TQDomElement x = feature.elementsByTagName("x").item(0).toElement();
if(!x.isNull() /*&& x.attribute("type") == "form"*/) {
TQDomElement field = x.elementsByTagName("field").item(0).toElement();
if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") {
TQDomNodeList nl = field.elementsByTagName("option");
for(uint n = 0; n < nl.count(); ++n) {
TQDomElement e = nl.item(n).toElement();
TQDomElement value = e.elementsByTagName("value").item(0).toElement();
if(!value.isNull())
streamTypes += value.text();
}
}
}
}
FTRequest r;
r.from = from;
r.iq_id = e.attribute("id");
r.id = id;
r.fname = fname;
r.size = size;
r.desc = desc;
r.rangeSupported = rangeSupported;
r.streamTypes = streamTypes;
incoming(r);
return true;
}