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"
24
#include <q3ptrlist.h>
26
#include <qfileinfo.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
qlonglong rangeOffset, rangeLength, length;
70
FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent)
80
FileTransfer::FileTransfer(const FileTransfer& other)
81
: QObject(other.parent())
90
if (d->m->isActive(&other))
94
FileTransfer::~FileTransfer()
100
FileTransfer *FileTransfer::copy() const
102
return new FileTransfer(*this);
105
void FileTransfer::reset()
116
d->needStream = false;
121
void FileTransfer::setProxy(const Jid &proxy)
126
void FileTransfer::sendFile(const Jid &to, const QString &fname, qlonglong size, const QString &desc)
128
d->state = Requesting;
134
d->id = d->m->link(this);
136
d->ft = new JT_FT(d->m->client()->rootTask());
137
connect(d->ft, SIGNAL(finished()), SLOT(ft_finished()));
139
list += "http://jabber.org/protocol/bytestreams";
140
d->ft->request(to, d->id, fname, size, desc, list);
144
int FileTransfer::dataSizeNeeded() const
146
int pending = d->c->bytesToWrite();
147
if(pending >= SENDBUFSIZE)
149
qlonglong left = d->length - (d->sent + pending);
150
int size = SENDBUFSIZE - pending;
151
if((qlonglong)size > left)
156
void FileTransfer::writeFileData(const QByteArray &a)
158
int pending = d->c->bytesToWrite();
159
qlonglong left = d->length - (d->sent + pending);
164
if((qlonglong)a.size() > left) {
166
block.resize((uint)left);
173
Jid FileTransfer::peer() const
178
QString FileTransfer::fileName() const
183
qlonglong FileTransfer::fileSize() const
188
QString FileTransfer::description() const
193
bool FileTransfer::rangeSupported() const
195
return d->rangeSupported;
198
qlonglong FileTransfer::offset() const
200
return d->rangeOffset;
203
qlonglong FileTransfer::length() const
208
void FileTransfer::accept(qlonglong offset, qlonglong length)
210
d->state = Connecting;
211
d->rangeOffset = offset;
212
d->rangeLength = length;
217
d->streamType = "http://jabber.org/protocol/bytestreams";
218
d->m->con_accept(this);
221
void FileTransfer::close()
225
if(d->state == WaitingForAccept)
226
d->m->con_reject(this);
227
else if(d->state == Active)
232
S5BConnection *FileTransfer::s5bConnection() const
237
void FileTransfer::ft_finished()
243
d->state = Connecting;
244
d->rangeOffset = ft->rangeOffset();
245
d->length = ft->rangeLength();
247
d->length = d->size - d->rangeOffset;
248
d->streamType = ft->streamType();
249
d->c = d->m->client()->s5bManager()->createConnection();
250
connect(d->c, SIGNAL(connected()), SLOT(s5b_connected()));
251
connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed()));
252
connect(d->c, SIGNAL(bytesWritten(int)), SLOT(s5b_bytesWritten(int)));
253
connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int)));
255
if(d->proxy.isValid())
256
d->c->setProxy(d->proxy);
257
d->c->connectToJid(d->peer, d->id);
262
if(ft->statusCode() == 403)
269
void FileTransfer::takeConnection(S5BConnection *c)
272
connect(d->c, SIGNAL(connected()), SLOT(s5b_connected()));
273
connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed()));
274
connect(d->c, SIGNAL(readyRead()), SLOT(s5b_readyRead()));
275
connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int)));
276
if(d->proxy.isValid())
277
d->c->setProxy(d->proxy);
279
QTimer::singleShot(0, this, SLOT(doAccept()));
282
void FileTransfer::s5b_connected()
288
void FileTransfer::s5b_connectionClosed()
294
void FileTransfer::s5b_readyRead()
296
QByteArray a = d->c->read();
297
qlonglong need = d->length - d->sent;
298
if((qlonglong)a.size() > need)
299
a.resize((uint)need);
301
if(d->sent == d->length)
306
void FileTransfer::s5b_bytesWritten(int x)
309
if(d->sent == d->length)
314
void FileTransfer::s5b_error(int x)
317
if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect)
319
else if(x == S5BConnection::ErrProxy)
325
void FileTransfer::man_waitForAccept(const FTRequest &req)
327
d->state = WaitingForAccept;
330
d->iq_id = req.iq_id;
331
d->fname = req.fname;
334
d->rangeSupported = req.rangeSupported;
337
void FileTransfer::doAccept()
342
//----------------------------------------------------------------------------
343
// FileTransferManager
344
//----------------------------------------------------------------------------
345
class FileTransferManager::Private
349
Q3PtrList<FileTransfer> list, incoming;
353
FileTransferManager::FileTransferManager(Client *client)
359
d->pft = new JT_PushFT(d->client->rootTask());
360
connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &)));
363
FileTransferManager::~FileTransferManager()
365
d->incoming.setAutoDelete(true);
371
Client *FileTransferManager::client() const
376
FileTransfer *FileTransferManager::createTransfer()
378
FileTransfer *ft = new FileTransfer(this);
382
FileTransfer *FileTransferManager::takeIncoming()
384
if(d->incoming.isEmpty())
387
FileTransfer *ft = d->incoming.getFirst();
388
d->incoming.removeRef(ft);
390
// move to active list
395
bool FileTransferManager::isActive(const FileTransfer *ft) const
397
return d->list.contains(ft) > 0;
400
void FileTransferManager::pft_incoming(const FTRequest &req)
403
for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) {
404
if((*it) == "http://jabber.org/protocol/bytestreams") {
410
d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types");
413
if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) {
414
d->pft->respondError(req.from, req.iq_id, 400, "SID in use");
418
FileTransfer *ft = new FileTransfer(this);
419
ft->man_waitForAccept(req);
420
d->incoming.append(ft);
424
void FileTransferManager::s5b_incomingReady(S5BConnection *c)
426
Q3PtrListIterator<FileTransfer> it(d->list);
427
FileTransfer *ft = 0;
428
for(FileTransfer *i; (i = it.current()); ++it) {
429
if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) {
439
ft->takeConnection(c);
442
QString FileTransferManager::link(FileTransfer *ft)
445
return d->client->s5bManager()->genUniqueSID(ft->d->peer);
448
void FileTransferManager::con_accept(FileTransfer *ft)
450
ft->d->needStream = true;
451
d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType);
454
void FileTransferManager::con_reject(FileTransfer *ft)
456
d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined");
459
void FileTransferManager::unlink(FileTransfer *ft)
461
d->list.removeRef(ft);
464
//----------------------------------------------------------------------------
466
//----------------------------------------------------------------------------
472
qlonglong size, rangeOffset, rangeLength;
474
QStringList streamTypes;
477
JT_FT::JT_FT(Task *parent)
488
void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, qlonglong size, const QString &desc, const QStringList &streamTypes)
492
iq = createIQ(doc(), "set", to.full(), id());
493
QDomElement si = doc()->createElement("si");
494
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
495
si.setAttribute("id", _id);
496
si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer");
498
QDomElement file = doc()->createElement("file");
499
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
500
file.setAttribute("name", fname);
501
file.setAttribute("size", QString::number(size));
502
if(!desc.isEmpty()) {
503
QDomElement de = doc()->createElement("desc");
504
de.appendChild(doc()->createTextNode(desc));
505
file.appendChild(de);
507
QDomElement range = doc()->createElement("range");
508
file.appendChild(range);
509
si.appendChild(file);
511
QDomElement feature = doc()->createElement("feature");
512
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
513
QDomElement x = doc()->createElement("x");
514
x.setAttribute("xmlns", "jabber:x:data");
515
x.setAttribute("type", "form");
517
QDomElement field = doc()->createElement("field");
518
field.setAttribute("var", "stream-method");
519
field.setAttribute("type", "list-single");
520
for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) {
521
QDomElement option = doc()->createElement("option");
522
QDomElement value = doc()->createElement("value");
523
value.appendChild(doc()->createTextNode(*it));
524
option.appendChild(value);
525
field.appendChild(option);
528
x.appendChild(field);
529
feature.appendChild(x);
531
si.appendChild(feature);
534
d->streamTypes = streamTypes;
539
qlonglong JT_FT::rangeOffset() const
541
return d->rangeOffset;
544
qlonglong JT_FT::rangeLength() const
546
return d->rangeLength;
549
QString JT_FT::streamType() const
551
return d->streamType;
559
bool JT_FT::take(const QDomElement &x)
561
if(!iqVerify(x, d->to, id()))
564
if(x.attribute("type") == "result") {
565
QDomElement si = firstChildElement(x);
566
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") {
571
QString id = si.attribute("id");
573
qlonglong range_offset = 0;
574
qlonglong range_length = 0;
576
QDomElement file = si.elementsByTagName("file").item(0).toElement();
578
QDomElement range = file.elementsByTagName("range").item(0).toElement();
579
if(!range.isNull()) {
582
if(range.hasAttribute("offset")) {
583
x = range.attribute("offset").toLongLong(&ok);
590
if(range.hasAttribute("length")) {
591
x = range.attribute("length").toLongLong(&ok);
601
if(range_offset > d->size || (range_length > (d->size - range_offset))) {
607
QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
608
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
609
QDomElement x = feature.elementsByTagName("x").item(0).toElement();
610
if(!x.isNull() && x.attribute("type") == "submit") {
611
QDomElement field = x.elementsByTagName("field").item(0).toElement();
612
if(!field.isNull() && field.attribute("var") == "stream-method") {
613
QDomElement value = field.elementsByTagName("value").item(0).toElement();
615
streamtype = value.text();
620
// must be one of the offered streamtypes
622
for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) {
623
if((*it) == streamtype) {
631
d->rangeOffset = range_offset;
632
d->rangeLength = range_length;
633
d->streamType = streamtype;
643
//----------------------------------------------------------------------------
645
//----------------------------------------------------------------------------
646
JT_PushFT::JT_PushFT(Task *parent)
651
JT_PushFT::~JT_PushFT()
655
void JT_PushFT::respondSuccess(const Jid &to, const QString &id, qlonglong rangeOffset, qlonglong rangeLength, const QString &streamType)
657
QDomElement iq = createIQ(doc(), "result", to.full(), id);
658
QDomElement si = doc()->createElement("si");
659
si.setAttribute("xmlns", "http://jabber.org/protocol/si");
661
if(rangeOffset != 0 || rangeLength != 0) {
662
QDomElement file = doc()->createElement("file");
663
file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
664
QDomElement range = doc()->createElement("range");
666
range.setAttribute("offset", QString::number(rangeOffset));
668
range.setAttribute("length", QString::number(rangeLength));
669
file.appendChild(range);
670
si.appendChild(file);
673
QDomElement feature = doc()->createElement("feature");
674
feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
675
QDomElement x = doc()->createElement("x");
676
x.setAttribute("xmlns", "jabber:x:data");
677
x.setAttribute("type", "submit");
679
QDomElement field = doc()->createElement("field");
680
field.setAttribute("var", "stream-method");
681
QDomElement value = doc()->createElement("value");
682
value.appendChild(doc()->createTextNode(streamType));
683
field.appendChild(value);
685
x.appendChild(field);
686
feature.appendChild(x);
688
si.appendChild(feature);
693
void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str)
695
QDomElement iq = createIQ(doc(), "error", to.full(), id);
696
QDomElement err = textTag(doc(), "error", str);
697
err.setAttribute("code", QString::number(code));
702
bool JT_PushFT::take(const QDomElement &e)
704
// must be an iq-set tag
705
if(e.tagName() != "iq")
707
if(e.attribute("type") != "set")
710
QDomElement si = firstChildElement(e);
711
if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si")
713
if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer")
716
Jid from(e.attribute("from"));
717
QString id = si.attribute("id");
719
QDomElement file = si.elementsByTagName("file").item(0).toElement();
723
QString fname = file.attribute("name");
724
if(fname.isEmpty()) {
725
respondError(from, id, 400, "Bad file name");
732
fname = fi.fileName();
736
qlonglong size = file.attribute("size").toLongLong(&ok);
737
if(!ok || size < 0) {
738
respondError(from, id, 400, "Bad file size");
743
QDomElement de = file.elementsByTagName("desc").item(0).toElement();
747
bool rangeSupported = false;
748
QDomElement range = file.elementsByTagName("range").item(0).toElement();
750
rangeSupported = true;
752
QStringList streamTypes;
753
QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
754
if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
755
QDomElement x = feature.elementsByTagName("x").item(0).toElement();
756
if(!x.isNull() /*&& x.attribute("type") == "form"*/) {
757
QDomElement field = x.elementsByTagName("field").item(0).toElement();
758
if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") {
759
QDomNodeList nl = field.elementsByTagName("option");
760
for(int n = 0; n < nl.count(); ++n) {
761
QDomElement e = nl.item(n).toElement();
762
QDomElement value = e.elementsByTagName("value").item(0).toElement();
764
streamTypes += value.text();
772
r.iq_id = e.attribute("id");
777
r.rangeSupported = rangeSupported;
778
r.streamTypes = streamTypes;