2
* filetransfer.cpp - File Transfer
3
* Copyright (C) 2004 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
#include"filetransfer.h"
25
#include<qguardedptr.h>
27
#include"xmpp_xmlcommon.h"
30
#define SENDBUFSIZE 65536
36
// Get an element's first child element
37
static QDomElement firstChildElement(const QDomElement &e)
39
for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
46
//----------------------------------------------------------------------------
48
//----------------------------------------------------------------------------
49
class FileTransfer::Private
52
FileTransferManager *m;
60
Q_LLONG rangeOffset, rangeLength, length;
70
FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent)
80
FileTransfer::~FileTransfer()
86
void FileTransfer::reset()
97
d->needStream = false;
102
void FileTransfer::setProxy(const Jid &proxy)
107
void FileTransfer::sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc)
109
d->state = Requesting;
115
d->id = d->m->link(this);
117
d->ft = new JT_FT(d->m->client()->rootTask());
118
connect(d->ft, SIGNAL(finished()), SLOT(ft_finished()));
120
list += "http://jabber.org/protocol/bytestreams";
121
d->ft->request(to, d->id, fname, size, desc, list);
125
int FileTransfer::dataSizeNeeded() const
127
int pending = d->c->bytesToWrite();
128
if(pending >= SENDBUFSIZE)
130
Q_LLONG left = d->length - (d->sent + pending);
131
int size = SENDBUFSIZE - pending;
132
if((Q_LLONG)size > left)
137
void FileTransfer::writeFileData(const QByteArray &a)
139
int pending = d->c->bytesToWrite();
140
Q_LLONG left = d->length - (d->sent + pending);
145
if((Q_LLONG)a.size() > left) {
147
block.resize((uint)left);
154
Jid FileTransfer::peer() const
159
QString FileTransfer::fileName() const
164
Q_LLONG FileTransfer::fileSize() const
169
QString FileTransfer::description() const
174
bool FileTransfer::rangeSupported() const
176
return d->rangeSupported;
179
Q_LLONG FileTransfer::offset() const
181
return d->rangeOffset;
184
Q_LLONG FileTransfer::length() const
189
void FileTransfer::accept(Q_LLONG offset, Q_LLONG length)
191
d->state = Connecting;
192
d->rangeOffset = offset;
193
d->rangeLength = length;
198
d->streamType = "http://jabber.org/protocol/bytestreams";
199
d->m->con_accept(this);
202
void FileTransfer::close()
206
if(d->state == WaitingForAccept)
207
d->m->con_reject(this);
208
else if(d->state == Active)
213
S5BConnection *FileTransfer::s5bConnection() const
218
void FileTransfer::ft_finished()
224
d->state = Connecting;
225
d->rangeOffset = ft->rangeOffset();
226
d->length = ft->rangeLength();
228
d->length = d->size - d->rangeOffset;
229
d->streamType = ft->streamType();
230
d->c = d->m->client()->s5bManager()->createConnection();
231
connect(d->c, SIGNAL(connected()), SLOT(s5b_connected()));
232
connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed()));
233
connect(d->c, SIGNAL(bytesWritten(int)), SLOT(s5b_bytesWritten(int)));
234
connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int)));
236
if(d->proxy.isValid())
237
d->c->setProxy(d->proxy);
238
d->c->connectToJid(d->peer, d->id);
243
if(ft->statusCode() == 403)
250
void FileTransfer::takeConnection(S5BConnection *c)
253
connect(d->c, SIGNAL(connected()), SLOT(s5b_connected()));
254
connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed()));
255
connect(d->c, SIGNAL(readyRead()), SLOT(s5b_readyRead()));
256
connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int)));
257
if(d->proxy.isValid())
258
d->c->setProxy(d->proxy);
260
QTimer::singleShot(0, this, SLOT(doAccept()));
263
void FileTransfer::s5b_connected()
269
void FileTransfer::s5b_connectionClosed()
275
void FileTransfer::s5b_readyRead()
277
QByteArray a = d->c->read();
278
Q_LLONG need = d->length - d->sent;
279
if((Q_LLONG)a.size() > need)
280
a.resize((uint)need);
282
if(d->sent == d->length)
287
void FileTransfer::s5b_bytesWritten(int x)
290
if(d->sent == d->length)
295
void FileTransfer::s5b_error(int x)
298
if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect)
300
else if(x == S5BConnection::ErrProxy)
306
void FileTransfer::man_waitForAccept(const FTRequest &req)
308
d->state = WaitingForAccept;
311
d->iq_id = req.iq_id;
312
d->fname = req.fname;
315
d->rangeSupported = req.rangeSupported;
318
void FileTransfer::doAccept()
323
//----------------------------------------------------------------------------
324
// FileTransferManager
325
//----------------------------------------------------------------------------
326
class FileTransferManager::Private
330
QPtrList<FileTransfer> list, incoming;
334
FileTransferManager::FileTransferManager(Client *client)
340
d->pft = new JT_PushFT(d->client->rootTask());
341
connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &)));
344
FileTransferManager::~FileTransferManager()
346
d->incoming.setAutoDelete(true);
352
Client *FileTransferManager::client() const
357
FileTransfer *FileTransferManager::createTransfer()
359
FileTransfer *ft = new FileTransfer(this);
363
FileTransfer *FileTransferManager::takeIncoming()
365
if(d->incoming.isEmpty())
368
FileTransfer *ft = d->incoming.getFirst();
369
d->incoming.removeRef(ft);
371
// move to active list
376
void FileTransferManager::pft_incoming(const FTRequest &req)
379
for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) {
380
if((*it) == "http://jabber.org/protocol/bytestreams") {
386
d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types");
389
if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) {
390
d->pft->respondError(req.from, req.iq_id, 400, "SID in use");
394
FileTransfer *ft = new FileTransfer(this);
395
ft->man_waitForAccept(req);
396
d->incoming.append(ft);
400
void FileTransferManager::s5b_incomingReady(S5BConnection *c)
402
QPtrListIterator<FileTransfer> it(d->list);
403
FileTransfer *ft = 0;
404
for(FileTransfer *i; (i = it.current()); ++it) {
405
if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) {
415
ft->takeConnection(c);
418
QString FileTransferManager::link(FileTransfer *ft)
421
return d->client->s5bManager()->genUniqueSID(ft->d->peer);
424
void FileTransferManager::con_accept(FileTransfer *ft)
426
ft->d->needStream = true;
427
d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType);
430
void FileTransferManager::con_reject(FileTransfer *ft)
432
d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined");
435
void FileTransferManager::unlink(FileTransfer *ft)
437
d->list.removeRef(ft);
440
//----------------------------------------------------------------------------
442
//----------------------------------------------------------------------------
448
Q_LLONG size, rangeOffset, rangeLength;
450
QStringList streamTypes;
453
JT_FT::JT_FT(Task *parent)
464
void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes)
468
iq = createIQ(doc(), "set", to.full(), id());
469
QDomElement si = doc()->createElement("si");
470
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
471
si.setAttribute("id", _id);
472
si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer");
474
QDomElement file = doc()->createElement("file");
475
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
476
file.setAttribute("name", fname);
477
file.setAttribute("size", QString::number(size));
478
if(!desc.isEmpty()) {
479
QDomElement de = doc()->createElement("desc");
480
de.appendChild(doc()->createTextNode(desc));
481
file.appendChild(de);
483
QDomElement range = doc()->createElement("range");
484
file.appendChild(range);
485
si.appendChild(file);
487
QDomElement feature = doc()->createElement("feature");
488
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
489
QDomElement x = doc()->createElement("x");
490
x.setAttribute("xmlns", "jabber:x:data");
491
x.setAttribute("type", "form");
493
QDomElement field = doc()->createElement("field");
494
field.setAttribute("var", "stream-method");
495
field.setAttribute("type", "list-single");
496
for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) {
497
QDomElement option = doc()->createElement("option");
498
QDomElement value = doc()->createElement("value");
499
value.appendChild(doc()->createTextNode(*it));
500
option.appendChild(value);
501
field.appendChild(option);
504
x.appendChild(field);
505
feature.appendChild(x);
507
si.appendChild(feature);
510
d->streamTypes = streamTypes;
515
Q_LLONG JT_FT::rangeOffset() const
517
return d->rangeOffset;
520
Q_LLONG JT_FT::rangeLength() const
522
return d->rangeLength;
525
QString JT_FT::streamType() const
527
return d->streamType;
535
bool JT_FT::take(const QDomElement &x)
537
if(!iqVerify(x, d->to, id()))
540
if(x.attribute("type") == "result") {
541
QDomElement si = firstChildElement(x);
542
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") {
547
QString id = si.attribute("id");
549
Q_LLONG range_offset = 0;
550
Q_LLONG range_length = 0;
552
QDomElement file = si.elementsByTagName("file").item(0).toElement();
554
QDomElement range = file.elementsByTagName("range").item(0).toElement();
555
if(!range.isNull()) {
558
if(range.hasAttribute("offset")) {
559
#if QT_VERSION >= 0x030200
560
x = range.attribute("offset").toLongLong(&ok);
562
x = range.attribute("offset").toLong(&ok);
570
if(range.hasAttribute("length")) {
571
#if QT_VERSION >= 0x030200
572
x = range.attribute("length").toLongLong(&ok);
574
x = range.attribute("length").toLong(&ok);
585
if(range_offset > d->size || (range_length > (d->size - range_offset))) {
591
QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
592
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
593
QDomElement x = feature.elementsByTagName("x").item(0).toElement();
594
if(!x.isNull() && x.attribute("type") == "submit") {
595
QDomElement field = x.elementsByTagName("field").item(0).toElement();
596
if(!field.isNull() && field.attribute("var") == "stream-method") {
597
QDomElement value = field.elementsByTagName("value").item(0).toElement();
599
streamtype = value.text();
604
// must be one of the offered streamtypes
606
for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) {
607
if((*it) == streamtype) {
615
d->rangeOffset = range_offset;
616
d->rangeLength = range_length;
617
d->streamType = streamtype;
627
//----------------------------------------------------------------------------
629
//----------------------------------------------------------------------------
630
JT_PushFT::JT_PushFT(Task *parent)
635
JT_PushFT::~JT_PushFT()
639
void JT_PushFT::respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType)
641
QDomElement iq = createIQ(doc(), "result", to.full(), id);
642
QDomElement si = doc()->createElement("si");
643
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
645
if(rangeOffset != 0 || rangeLength != 0) {
646
QDomElement file = doc()->createElement("file");
647
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
648
QDomElement range = doc()->createElement("range");
650
range.setAttribute("offset", QString::number(rangeOffset));
652
range.setAttribute("length", QString::number(rangeLength));
653
file.appendChild(range);
654
si.appendChild(file);
657
QDomElement feature = doc()->createElement("feature");
658
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
659
QDomElement x = doc()->createElement("x");
660
x.setAttribute("xmlns", "jabber:x:data");
661
x.setAttribute("type", "submit");
663
QDomElement field = doc()->createElement("field");
664
field.setAttribute("var", "stream-method");
665
QDomElement value = doc()->createElement("value");
666
value.appendChild(doc()->createTextNode(streamType));
667
field.appendChild(value);
669
x.appendChild(field);
670
feature.appendChild(x);
672
si.appendChild(feature);
677
void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str)
679
QDomElement iq = createIQ(doc(), "error", to.full(), id);
680
QDomElement err = textTag(doc(), "error", str);
681
err.setAttribute("code", QString::number(code));
686
bool JT_PushFT::take(const QDomElement &e)
688
// must be an iq-set tag
689
if(e.tagName() != "iq")
691
if(e.attribute("type") != "set")
694
QDomElement si = firstChildElement(e);
695
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si")
697
if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer")
700
Jid from(e.attribute("from"));
701
QString id = si.attribute("id");
703
QDomElement file = si.elementsByTagName("file").item(0).toElement();
707
QString fname = file.attribute("name");
708
if(fname.isEmpty()) {
709
respondError(from, id, 400, "Bad file name");
716
fname = fi.fileName();
720
#if QT_VERSION >= 0x030200
721
Q_LLONG size = file.attribute("size").toLongLong(&ok);
723
Q_LLONG size = file.attribute("size").toLong(&ok);
725
if(!ok || size < 0) {
726
respondError(from, id, 400, "Bad file size");
731
QDomElement de = file.elementsByTagName("desc").item(0).toElement();
735
bool rangeSupported = false;
736
QDomElement range = file.elementsByTagName("range").item(0).toElement();
738
rangeSupported = true;
740
QStringList streamTypes;
741
QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
742
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
743
QDomElement x = feature.elementsByTagName("x").item(0).toElement();
744
if(!x.isNull() /*&& x.attribute("type") == "form"*/) {
745
QDomElement field = x.elementsByTagName("field").item(0).toElement();
746
if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") {
747
QDomNodeList nl = field.elementsByTagName("option");
748
for(uint n = 0; n < nl.count(); ++n) {
749
QDomElement e = nl.item(n).toElement();
750
QDomElement value = e.elementsByTagName("value").item(0).toElement();
752
streamTypes += value.text();
760
r.iq_id = e.attribute("id");
765
r.rangeSupported = rangeSupported;
766
r.streamTypes = streamTypes;