source: psi/trunk/iris/jabber/filetransfer.cpp@ 43

Last change on this file since 43 was 2, checked in by dmik, 19 years ago

Imported original Psi 0.10 sources from Affinix

File size: 17.8 KB
Line 
1/*
2 * filetransfer.cpp - File Transfer
3 * Copyright (C) 2004 Justin Karneges
4 *
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.
9 *
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.
14 *
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
18 *
19 */
20
21#include"filetransfer.h"
22
23#include<qtimer.h>
24#include<qptrlist.h>
25#include<qguardedptr.h>
26#include<qfileinfo.h>
27#include"xmpp_xmlcommon.h"
28#include"s5b.h"
29
30#define SENDBUFSIZE 65536
31
32using namespace XMPP;
33
34// firstChildElement
35//
36// Get an element's first child element
37static QDomElement firstChildElement(const QDomElement &e)
38{
39 for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
40 if(n.isElement())
41 return n.toElement();
42 }
43 return QDomElement();
44}
45
46//----------------------------------------------------------------------------
47// FileTransfer
48//----------------------------------------------------------------------------
49class FileTransfer::Private
50{
51public:
52 FileTransferManager *m;
53 JT_FT *ft;
54 Jid peer;
55 QString fname;
56 Q_LLONG size;
57 Q_LLONG sent;
58 QString desc;
59 bool rangeSupported;
60 Q_LLONG rangeOffset, rangeLength, length;
61 QString streamType;
62 bool needStream;
63 QString id, iq_id;
64 S5BConnection *c;
65 Jid proxy;
66 int state;
67 bool sender;
68};
69
70FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent)
71:QObject(parent)
72{
73 d = new Private;
74 d->m = m;
75 d->ft = 0;
76 d->c = 0;
77 reset();
78}
79
80FileTransfer::~FileTransfer()
81{
82 reset();
83 delete d;
84}
85
86void FileTransfer::reset()
87{
88 d->m->unlink(this);
89
90 delete d->ft;
91 d->ft = 0;
92
93 delete d->c;
94 d->c = 0;
95
96 d->state = Idle;
97 d->needStream = false;
98 d->sent = 0;
99 d->sender = false;
100}
101
102void FileTransfer::setProxy(const Jid &proxy)
103{
104 d->proxy = proxy;
105}
106
107void FileTransfer::sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc)
108{
109 d->state = Requesting;
110 d->peer = to;
111 d->fname = fname;
112 d->size = size;
113 d->desc = desc;
114 d->sender = true;
115 d->id = d->m->link(this);
116
117 d->ft = new JT_FT(d->m->client()->rootTask());
118 connect(d->ft, SIGNAL(finished()), SLOT(ft_finished()));
119 QStringList list;
120 list += "http://jabber.org/protocol/bytestreams";
121 d->ft->request(to, d->id, fname, size, desc, list);
122 d->ft->go(true);
123}
124
125int FileTransfer::dataSizeNeeded() const
126{
127 int pending = d->c->bytesToWrite();
128 if(pending >= SENDBUFSIZE)
129 return 0;
130 Q_LLONG left = d->length - (d->sent + pending);
131 int size = SENDBUFSIZE - pending;
132 if((Q_LLONG)size > left)
133 size = (int)left;
134 return size;
135}
136
137void FileTransfer::writeFileData(const QByteArray &a)
138{
139 int pending = d->c->bytesToWrite();
140 Q_LLONG left = d->length - (d->sent + pending);
141 if(left == 0)
142 return;
143
144 QByteArray block;
145 if((Q_LLONG)a.size() > left) {
146 block = a.copy();
147 block.resize((uint)left);
148 }
149 else
150 block = a;
151 d->c->write(block);
152}
153
154Jid FileTransfer::peer() const
155{
156 return d->peer;
157}
158
159QString FileTransfer::fileName() const
160{
161 return d->fname;
162}
163
164Q_LLONG FileTransfer::fileSize() const
165{
166 return d->size;
167}
168
169QString FileTransfer::description() const
170{
171 return d->desc;
172}
173
174bool FileTransfer::rangeSupported() const
175{
176 return d->rangeSupported;
177}
178
179Q_LLONG FileTransfer::offset() const
180{
181 return d->rangeOffset;
182}
183
184Q_LLONG FileTransfer::length() const
185{
186 return d->length;
187}
188
189void FileTransfer::accept(Q_LLONG offset, Q_LLONG length)
190{
191 d->state = Connecting;
192 d->rangeOffset = offset;
193 d->rangeLength = length;
194 if(length > 0)
195 d->length = length;
196 else
197 d->length = d->size;
198 d->streamType = "http://jabber.org/protocol/bytestreams";
199 d->m->con_accept(this);
200}
201
202void FileTransfer::close()
203{
204 if(d->state == Idle)
205 return;
206 if(d->state == WaitingForAccept)
207 d->m->con_reject(this);
208 else if(d->state == Active)
209 d->c->close();
210 reset();
211}
212
213S5BConnection *FileTransfer::s5bConnection() const
214{
215 return d->c;
216}
217
218void FileTransfer::ft_finished()
219{
220 JT_FT *ft = d->ft;
221 d->ft = 0;
222
223 if(ft->success()) {
224 d->state = Connecting;
225 d->rangeOffset = ft->rangeOffset();
226 d->length = ft->rangeLength();
227 if(d->length == 0)
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)));
235
236 if(d->proxy.isValid())
237 d->c->setProxy(d->proxy);
238 d->c->connectToJid(d->peer, d->id);
239 accepted();
240 }
241 else {
242 reset();
243 if(ft->statusCode() == 403)
244 error(ErrReject);
245 else
246 error(ErrNeg);
247 }
248}
249
250void FileTransfer::takeConnection(S5BConnection *c)
251{
252 d->c = 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);
259 accepted();
260 QTimer::singleShot(0, this, SLOT(doAccept()));
261}
262
263void FileTransfer::s5b_connected()
264{
265 d->state = Active;
266 connected();
267}
268
269void FileTransfer::s5b_connectionClosed()
270{
271 reset();
272 error(ErrStream);
273}
274
275void FileTransfer::s5b_readyRead()
276{
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);
281 d->sent += a.size();
282 if(d->sent == d->length)
283 reset();
284 readyRead(a);
285}
286
287void FileTransfer::s5b_bytesWritten(int x)
288{
289 d->sent += x;
290 if(d->sent == d->length)
291 reset();
292 bytesWritten(x);
293}
294
295void FileTransfer::s5b_error(int x)
296{
297 reset();
298 if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect)
299 error(ErrConnect);
300 else if(x == S5BConnection::ErrProxy)
301 error(ErrProxy);
302 else
303 error(ErrStream);
304}
305
306void FileTransfer::man_waitForAccept(const FTRequest &req)
307{
308 d->state = WaitingForAccept;
309 d->peer = req.from;
310 d->id = req.id;
311 d->iq_id = req.iq_id;
312 d->fname = req.fname;
313 d->size = req.size;
314 d->desc = req.desc;
315 d->rangeSupported = req.rangeSupported;
316}
317
318void FileTransfer::doAccept()
319{
320 d->c->accept();
321}
322
323//----------------------------------------------------------------------------
324// FileTransferManager
325//----------------------------------------------------------------------------
326class FileTransferManager::Private
327{
328public:
329 Client *client;
330 QPtrList<FileTransfer> list, incoming;
331 JT_PushFT *pft;
332};
333
334FileTransferManager::FileTransferManager(Client *client)
335:QObject(client)
336{
337 d = new Private;
338 d->client = client;
339
340 d->pft = new JT_PushFT(d->client->rootTask());
341 connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &)));
342}
343
344FileTransferManager::~FileTransferManager()
345{
346 d->incoming.setAutoDelete(true);
347 d->incoming.clear();
348 delete d->pft;
349 delete d;
350}
351
352Client *FileTransferManager::client() const
353{
354 return d->client;
355}
356
357FileTransfer *FileTransferManager::createTransfer()
358{
359 FileTransfer *ft = new FileTransfer(this);
360 return ft;
361}
362
363FileTransfer *FileTransferManager::takeIncoming()
364{
365 if(d->incoming.isEmpty())
366 return 0;
367
368 FileTransfer *ft = d->incoming.getFirst();
369 d->incoming.removeRef(ft);
370
371 // move to active list
372 d->list.append(ft);
373 return ft;
374}
375
376void FileTransferManager::pft_incoming(const FTRequest &req)
377{
378 bool found = false;
379 for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) {
380 if((*it) == "http://jabber.org/protocol/bytestreams") {
381 found = true;
382 break;
383 }
384 }
385 if(!found) {
386 d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types");
387 return;
388 }
389 if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) {
390 d->pft->respondError(req.from, req.iq_id, 400, "SID in use");
391 return;
392 }
393
394 FileTransfer *ft = new FileTransfer(this);
395 ft->man_waitForAccept(req);
396 d->incoming.append(ft);
397 incomingReady();
398}
399
400void FileTransferManager::s5b_incomingReady(S5BConnection *c)
401{
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()) {
406 ft = i;
407 break;
408 }
409 }
410 if(!ft) {
411 c->close();
412 delete c;
413 return;
414 }
415 ft->takeConnection(c);
416}
417
418QString FileTransferManager::link(FileTransfer *ft)
419{
420 d->list.append(ft);
421 return d->client->s5bManager()->genUniqueSID(ft->d->peer);
422}
423
424void FileTransferManager::con_accept(FileTransfer *ft)
425{
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);
428}
429
430void FileTransferManager::con_reject(FileTransfer *ft)
431{
432 d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined");
433}
434
435void FileTransferManager::unlink(FileTransfer *ft)
436{
437 d->list.removeRef(ft);
438}
439
440//----------------------------------------------------------------------------
441// JT_FT
442//----------------------------------------------------------------------------
443class JT_FT::Private
444{
445public:
446 QDomElement iq;
447 Jid to;
448 Q_LLONG size, rangeOffset, rangeLength;
449 QString streamType;
450 QStringList streamTypes;
451};
452
453JT_FT::JT_FT(Task *parent)
454:Task(parent)
455{
456 d = new Private;
457}
458
459JT_FT::~JT_FT()
460{
461 delete d;
462}
463
464void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes)
465{
466 QDomElement iq;
467 d->to = to;
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");
473
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);
482 }
483 QDomElement range = doc()->createElement("range");
484 file.appendChild(range);
485 si.appendChild(file);
486
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");
492
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);
502 }
503
504 x.appendChild(field);
505 feature.appendChild(x);
506
507 si.appendChild(feature);
508 iq.appendChild(si);
509
510 d->streamTypes = streamTypes;
511 d->size = size;
512 d->iq = iq;
513}
514
515Q_LLONG JT_FT::rangeOffset() const
516{
517 return d->rangeOffset;
518}
519
520Q_LLONG JT_FT::rangeLength() const
521{
522 return d->rangeLength;
523}
524
525QString JT_FT::streamType() const
526{
527 return d->streamType;
528}
529
530void JT_FT::onGo()
531{
532 send(d->iq);
533}
534
535bool JT_FT::take(const QDomElement &x)
536{
537 if(!iqVerify(x, d->to, id()))
538 return false;
539
540 if(x.attribute("type") == "result") {
541 QDomElement si = firstChildElement(x);
542 if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") {
543 setError(900, "");
544 return true;
545 }
546
547 QString id = si.attribute("id");
548
549 Q_LLONG range_offset = 0;
550 Q_LLONG range_length = 0;
551
552 QDomElement file = si.elementsByTagName("file").item(0).toElement();
553 if(!file.isNull()) {
554 QDomElement range = file.elementsByTagName("range").item(0).toElement();
555 if(!range.isNull()) {
556 int x;
557 bool ok;
558 if(range.hasAttribute("offset")) {
559#if QT_VERSION >= 0x030200
560 x = range.attribute("offset").toLongLong(&ok);
561#else
562 x = range.attribute("offset").toLong(&ok);
563#endif
564 if(!ok || x < 0) {
565 setError(900, "");
566 return true;
567 }
568 range_offset = x;
569 }
570 if(range.hasAttribute("length")) {
571#if QT_VERSION >= 0x030200
572 x = range.attribute("length").toLongLong(&ok);
573#else
574 x = range.attribute("length").toLong(&ok);
575#endif
576 if(!ok || x < 0) {
577 setError(900, "");
578 return true;
579 }
580 range_length = x;
581 }
582 }
583 }
584
585 if(range_offset > d->size || (range_length > (d->size - range_offset))) {
586 setError(900, "");
587 return true;
588 }
589
590 QString streamtype;
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();
598 if(!value.isNull())
599 streamtype = value.text();
600 }
601 }
602 }
603
604 // must be one of the offered streamtypes
605 bool found = false;
606 for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) {
607 if((*it) == streamtype) {
608 found = true;
609 break;
610 }
611 }
612 if(!found)
613 return true;
614
615 d->rangeOffset = range_offset;
616 d->rangeLength = range_length;
617 d->streamType = streamtype;
618 setSuccess();
619 }
620 else {
621 setError(x);
622 }
623
624 return true;
625}
626
627//----------------------------------------------------------------------------
628// JT_PushFT
629//----------------------------------------------------------------------------
630JT_PushFT::JT_PushFT(Task *parent)
631:Task(parent)
632{
633}
634
635JT_PushFT::~JT_PushFT()
636{
637}
638
639void JT_PushFT::respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType)
640{
641 QDomElement iq = createIQ(doc(), "result", to.full(), id);
642 QDomElement si = doc()->createElement("si");
643 si.setAttribute("xmlns", "http://jabber.org/protocol/si");
644
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");
649 if(rangeOffset > 0)
650 range.setAttribute("offset", QString::number(rangeOffset));
651 if(rangeLength > 0)
652 range.setAttribute("length", QString::number(rangeLength));
653 file.appendChild(range);
654 si.appendChild(file);
655 }
656
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");
662
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);
668
669 x.appendChild(field);
670 feature.appendChild(x);
671
672 si.appendChild(feature);
673 iq.appendChild(si);
674 send(iq);
675}
676
677void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str)
678{
679 QDomElement iq = createIQ(doc(), "error", to.full(), id);
680 QDomElement err = textTag(doc(), "error", str);
681 err.setAttribute("code", QString::number(code));
682 iq.appendChild(err);
683 send(iq);
684}
685
686bool JT_PushFT::take(const QDomElement &e)
687{
688 // must be an iq-set tag
689 if(e.tagName() != "iq")
690 return false;
691 if(e.attribute("type") != "set")
692 return false;
693
694 QDomElement si = firstChildElement(e);
695 if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si")
696 return false;
697 if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer")
698 return false;
699
700 Jid from(e.attribute("from"));
701 QString id = si.attribute("id");
702
703 QDomElement file = si.elementsByTagName("file").item(0).toElement();
704 if(file.isNull())
705 return true;
706
707 QString fname = file.attribute("name");
708 if(fname.isEmpty()) {
709 respondError(from, id, 400, "Bad file name");
710 return true;
711 }
712
713 // ensure kosher
714 {
715 QFileInfo fi(fname);
716 fname = fi.fileName();
717 }
718
719 bool ok;
720#if QT_VERSION >= 0x030200
721 Q_LLONG size = file.attribute("size").toLongLong(&ok);
722#else
723 Q_LLONG size = file.attribute("size").toLong(&ok);
724#endif
725 if(!ok || size < 0) {
726 respondError(from, id, 400, "Bad file size");
727 return true;
728 }
729
730 QString desc;
731 QDomElement de = file.elementsByTagName("desc").item(0).toElement();
732 if(!de.isNull())
733 desc = de.text();
734
735 bool rangeSupported = false;
736 QDomElement range = file.elementsByTagName("range").item(0).toElement();
737 if(!range.isNull())
738 rangeSupported = true;
739
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();
751 if(!value.isNull())
752 streamTypes += value.text();
753 }
754 }
755 }
756 }
757
758 FTRequest r;
759 r.from = from;
760 r.iq_id = e.attribute("id");
761 r.id = id;
762 r.fname = fname;
763 r.size = size;
764 r.desc = desc;
765 r.rangeSupported = rangeSupported;
766 r.streamTypes = streamTypes;
767
768 incoming(r);
769 return true;
770}
Note: See TracBrowser for help on using the repository browser.