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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21 #include "filetransfer.h"
22
23 #include <QList>
24 #include <QTimer>
25 #include <QPointer>
26 #include <QFileInfo>
27 #include <QSet>
28 #include "xmpp_xmlcommon.h"
29 #include "s5b.h"
30 #include "xmpp_ibb.h"
31
32 #define SENDBUFSIZE 65536
33
34 using namespace XMPP;
35
36 // firstChildElement
37 //
38 // Get an element's first child element
firstChildElement(const QDomElement & e)39 static QDomElement firstChildElement(const QDomElement &e)
40 {
41 for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
42 if(n.isElement())
43 return n.toElement();
44 }
45 return QDomElement();
46 }
47
48 //----------------------------------------------------------------------------
49 // FileTransfer
50 //----------------------------------------------------------------------------
51 class FileTransfer::Private
52 {
53 public:
54 FileTransferManager *m;
55 JT_FT *ft;
56 Jid peer;
57 QString fname;
58 qlonglong size;
59 qlonglong sent;
60 QString desc;
61 bool rangeSupported;
62 qlonglong rangeOffset, rangeLength, length;
63 QString streamType;
64 FTThumbnail thumbnail;
65 bool needStream;
66 QString id, iq_id;
67 BSConnection *c;
68 Jid proxy;
69 int state;
70 bool sender;
71 };
72
FileTransfer(FileTransferManager * m,QObject * parent)73 FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent)
74 :QObject(parent)
75 {
76 d = new Private;
77 d->m = m;
78 d->ft = 0;
79 d->c = 0;
80 reset();
81 }
82
FileTransfer(const FileTransfer & other)83 FileTransfer::FileTransfer(const FileTransfer& other)
84 : QObject(other.parent())
85 {
86 d = new Private;
87 *d = *other.d;
88 d->m = other.d->m;
89 d->ft = 0;
90 d->c = 0;
91 reset();
92
93 if (d->m->isActive(&other))
94 d->m->link(this);
95 }
96
~FileTransfer()97 FileTransfer::~FileTransfer()
98 {
99 reset();
100 delete d;
101 }
102
copy() const103 FileTransfer *FileTransfer::copy() const
104 {
105 return new FileTransfer(*this);
106 }
107
reset()108 void FileTransfer::reset()
109 {
110 d->m->unlink(this);
111
112 delete d->ft;
113 d->ft = 0;
114
115 if (d->c) {
116 d->c->disconnect(this);
117 d->c->manager()->deleteConnection(d->c, d->state == Active && !d->sender ?
118 3000 : 0);
119 d->c = 0;
120 }
121
122 d->state = Idle;
123 d->needStream = false;
124 d->sent = 0;
125 d->sender = false;
126 }
127
setProxy(const Jid & proxy)128 void FileTransfer::setProxy(const Jid &proxy)
129 {
130 d->proxy = proxy;
131 }
132
sendFile(const Jid & to,const QString & fname,qlonglong size,const QString & desc,const FTThumbnail & thumb)133 void FileTransfer::sendFile(const Jid &to, const QString &fname, qlonglong size,
134 const QString &desc, const FTThumbnail &thumb)
135 {
136 d->state = Requesting;
137 d->peer = to;
138 d->fname = fname;
139 d->size = size;
140 d->desc = desc;
141 d->sender = true;
142 d->id = d->m->link(this);
143
144 d->ft = new JT_FT(d->m->client()->rootTask());
145 connect(d->ft, SIGNAL(finished()), SLOT(ft_finished()));
146 d->ft->request(to, d->id, fname, size, desc, d->m->streamPriority(), thumb);
147 d->ft->go(true);
148 }
149
dataSizeNeeded() const150 int FileTransfer::dataSizeNeeded() const
151 {
152 int pending = d->c->bytesToWrite();
153 if(pending >= SENDBUFSIZE)
154 return 0;
155 qlonglong left = d->length - (d->sent + pending);
156 int size = SENDBUFSIZE - pending;
157 if((qlonglong)size > left)
158 size = (int)left;
159 return size;
160 }
161
writeFileData(const QByteArray & a)162 void FileTransfer::writeFileData(const QByteArray &a)
163 {
164 int pending = d->c->bytesToWrite();
165 qlonglong left = d->length - (d->sent + pending);
166 if(left == 0)
167 return;
168
169 QByteArray block;
170 if((qlonglong)a.size() > left) {
171 block = a;
172 block.resize((uint)left);
173 }
174 else
175 block = a;
176 d->c->write(block);
177 }
178
thumbnail() const179 const FTThumbnail &FileTransfer::thumbnail() const
180 {
181 return d->thumbnail;
182 }
183
peer() const184 Jid FileTransfer::peer() const
185 {
186 return d->peer;
187 }
188
fileName() const189 QString FileTransfer::fileName() const
190 {
191 return d->fname;
192 }
193
fileSize() const194 qlonglong FileTransfer::fileSize() const
195 {
196 return d->size;
197 }
198
description() const199 QString FileTransfer::description() const
200 {
201 return d->desc;
202 }
203
rangeSupported() const204 bool FileTransfer::rangeSupported() const
205 {
206 return d->rangeSupported;
207 }
208
offset() const209 qlonglong FileTransfer::offset() const
210 {
211 return d->rangeOffset;
212 }
213
length() const214 qlonglong FileTransfer::length() const
215 {
216 return d->length;
217 }
218
accept(qlonglong offset,qlonglong length)219 void FileTransfer::accept(qlonglong offset, qlonglong length)
220 {
221 d->state = Connecting;
222 d->rangeOffset = offset;
223 d->rangeLength = length;
224 if(length > 0)
225 d->length = length;
226 else
227 d->length = d->size;
228 d->m->con_accept(this);
229 }
230
close()231 void FileTransfer::close()
232 {
233 if(d->state == Idle)
234 return;
235 if(d->state == WaitingForAccept)
236 d->m->con_reject(this);
237 else if(d->state == Active)
238 d->c->close();
239 reset();
240 }
241
bsConnection() const242 BSConnection *FileTransfer::bsConnection() const
243 {
244 return d->c;
245 }
246
247 // file transfer request accepted or error happened
ft_finished()248 void FileTransfer::ft_finished()
249 {
250 JT_FT *ft = d->ft;
251 d->ft = 0;
252
253 if(ft->success()) {
254 d->state = Connecting;
255 d->rangeOffset = ft->rangeOffset();
256 d->length = ft->rangeLength();
257 if(d->length == 0)
258 d->length = d->size - d->rangeOffset;
259 d->streamType = ft->streamType();
260 BytestreamManager *streamManager = d->m->streamManager(d->streamType);
261 if (streamManager) {
262 d->c = streamManager->createConnection();
263 if (dynamic_cast<S5BManager*>(streamManager) && d->proxy.isValid()) {
264 ((S5BConnection*)(d->c))->setProxy(d->proxy);
265 }
266 connect(d->c, SIGNAL(connected()), SLOT(stream_connected()));
267 connect(d->c, SIGNAL(connectionClosed()), SLOT(stream_connectionClosed()));
268 connect(d->c, SIGNAL(bytesWritten(qint64)), SLOT(stream_bytesWritten(qint64)));
269 connect(d->c, SIGNAL(error(int)), SLOT(stream_error(int)));
270
271 d->c->connectToJid(d->peer, d->id);
272 accepted();
273 }
274 else {
275 emit error(Err400);
276 reset();
277 }
278 }
279 else {
280 if(ft->statusCode() == 403)
281 emit error(ErrReject);
282 else if(ft->statusCode() == 400)
283 emit error(Err400);
284 else
285 emit error(ErrNeg);
286 reset();
287 }
288 }
289
takeConnection(BSConnection * c)290 void FileTransfer::takeConnection(BSConnection *c)
291 {
292 d->c = c;
293 connect(d->c, SIGNAL(connected()), SLOT(stream_connected()));
294 connect(d->c, SIGNAL(connectionClosed()), SLOT(stream_connectionClosed()));
295 connect(d->c, SIGNAL(readyRead()), SLOT(stream_readyRead()));
296 connect(d->c, SIGNAL(error(int)), SLOT(stream_error(int)));
297
298 S5BConnection *s5b = dynamic_cast<S5BConnection*>(c);
299 if(s5b && d->proxy.isValid())
300 s5b->setProxy(d->proxy);
301 accepted();
302 QTimer::singleShot(0, this, SLOT(doAccept()));
303 }
304
stream_connected()305 void FileTransfer::stream_connected()
306 {
307 d->state = Active;
308 emit connected();
309 }
310
stream_connectionClosed()311 void FileTransfer::stream_connectionClosed()
312 {
313 bool err = (d->sent != d->length);
314 reset();
315 if (err)
316 emit error(ErrStream);
317 }
318
stream_readyRead()319 void FileTransfer::stream_readyRead()
320 {
321 QByteArray a = d->c->readAll();
322 qlonglong need = d->length - d->sent;
323 if((qlonglong)a.size() > need)
324 a.resize((uint)need);
325 d->sent += a.size();
326 // if(d->sent == d->length) // we close it in stream_connectionClosed. at least for ibb
327 // reset(); // in other words we wait for another party to close the connection
328 readyRead(a);
329 }
330
stream_bytesWritten(qint64 x)331 void FileTransfer::stream_bytesWritten(qint64 x)
332 {
333 d->sent += x;
334 if(d->sent == d->length)
335 reset();
336 emit bytesWritten(x);
337 }
338
stream_error(int x)339 void FileTransfer::stream_error(int x)
340 {
341 reset();
342 if(x == BSConnection::ErrRefused || x == BSConnection::ErrConnect)
343 error(ErrConnect);
344 else if(x == BSConnection::ErrProxy)
345 error(ErrProxy);
346 else
347 error(ErrStream);
348 }
349
man_waitForAccept(const FTRequest & req,const QString & streamType)350 void FileTransfer::man_waitForAccept(const FTRequest &req, const QString &streamType)
351 {
352 d->state = WaitingForAccept;
353 d->peer = req.from;
354 d->id = req.id;
355 d->iq_id = req.iq_id;
356 d->fname = req.fname;
357 d->size = req.size;
358 d->desc = req.desc;
359 d->rangeSupported = req.rangeSupported;
360 d->streamType = streamType;
361 d->thumbnail = req.thumbnail;
362 }
363
doAccept()364 void FileTransfer::doAccept()
365 {
366 d->c->accept();
367 }
368
369 //----------------------------------------------------------------------------
370 // FileTransferManager
371 //----------------------------------------------------------------------------
372 class FileTransferManager::Private
373 {
374 public:
375 Client *client;
376 QList<FileTransfer*> list, incoming;
377 QStringList streamPriority;
378 QHash<QString, BytestreamManager*> streamMap;
379 QSet<QString> disabledStreamTypes;
380 JT_PushFT *pft;
381 };
382
FileTransferManager(Client * client)383 FileTransferManager::FileTransferManager(Client *client)
384 :QObject(client)
385 {
386 d = new Private;
387 d->client = client;
388 if (client->s5bManager()) {
389 d->streamPriority.append(S5BManager::ns());
390 d->streamMap[S5BManager::ns()] = client->s5bManager();
391 }
392 if (client->ibbManager()) {
393 d->streamPriority.append(IBBManager::ns());
394 d->streamMap[IBBManager::ns()] = client->ibbManager();
395 }
396
397 d->pft = new JT_PushFT(d->client->rootTask());
398 connect(d->pft, SIGNAL(incoming(FTRequest)), SLOT(pft_incoming(FTRequest)));
399 }
400
~FileTransferManager()401 FileTransferManager::~FileTransferManager()
402 {
403 while (!d->incoming.isEmpty()) {
404 delete d->incoming.takeFirst();
405 }
406 delete d->pft;
407 delete d;
408 }
409
client() const410 Client *FileTransferManager::client() const
411 {
412 return d->client;
413 }
414
createTransfer()415 FileTransfer *FileTransferManager::createTransfer()
416 {
417 FileTransfer *ft = new FileTransfer(this);
418 return ft;
419 }
420
takeIncoming()421 FileTransfer *FileTransferManager::takeIncoming()
422 {
423 if(d->incoming.isEmpty())
424 return 0;
425
426 FileTransfer *ft = d->incoming.takeFirst();
427
428 // move to active list
429 d->list.append(ft);
430 return ft;
431 }
432
isActive(const FileTransfer * ft) const433 bool FileTransferManager::isActive(const FileTransfer *ft) const
434 {
435 return d->list.contains(const_cast<FileTransfer*>(ft));
436 }
437
setDisabled(const QString & ns,bool state)438 void FileTransferManager::setDisabled(const QString &ns, bool state)
439 {
440 if (state) {
441 d->disabledStreamTypes.insert(ns);
442 }
443 else {
444 d->disabledStreamTypes.remove(ns);
445 }
446 }
447
pft_incoming(const FTRequest & req)448 void FileTransferManager::pft_incoming(const FTRequest &req)
449 {
450 QString streamType;
451 foreach(const QString& ns, d->streamPriority) {
452 if(req.streamTypes.contains(ns)) {
453 BytestreamManager *manager = streamManager(ns);
454 if (manager && manager->isAcceptableSID(req.from, req.id)) {
455 streamType = ns;
456 break;
457 }
458 }
459 }
460
461 if(streamType.isEmpty()) {
462 d->pft->respondError(req.from, req.iq_id, Stanza::Error::NotAcceptable,
463 "No valid stream types");
464 return;
465 }
466
467 FileTransfer *ft = new FileTransfer(this);
468 ft->man_waitForAccept(req, streamType);
469 d->incoming.append(ft);
470 incomingReady();
471 }
472
streamManager(const QString & ns) const473 BytestreamManager* FileTransferManager::streamManager(const QString &ns) const
474 {
475 if (d->disabledStreamTypes.contains(ns)) {
476 return 0;
477 }
478 return d->streamMap.value(ns);
479 }
480
streamPriority() const481 QStringList FileTransferManager::streamPriority() const
482 {
483 QStringList ret;
484 foreach (const QString &ns, d->streamPriority) {
485 if (!d->disabledStreamTypes.contains(ns)) {
486 ret.append(ns);
487 }
488 }
489 return ret;
490 }
491
stream_incomingReady(BSConnection * c)492 void FileTransferManager::stream_incomingReady(BSConnection *c)
493 {
494 foreach(FileTransfer* ft, d->list) {
495 if(ft->d->needStream && ft->d->peer.compare(c->peer()) && ft->d->id == c->sid()) {
496 ft->takeConnection(c);
497 return;
498 }
499 }
500 c->close();
501 delete c;
502 }
503
link(FileTransfer * ft)504 QString FileTransferManager::link(FileTransfer *ft)
505 {
506 QString id;
507 bool found;
508 do {
509 found = false;
510 id = QString("ft_%1").arg(qrand() & 0xffff, 4, 16, QChar('0'));
511 foreach (FileTransfer* ft, d->list) {
512 if (ft->d->peer.compare(ft->d->peer) && ft->d->id == id) {
513 found = true;
514 break;
515 }
516 }
517 } while(found);
518 d->list.append(ft);
519 return id;
520 }
521
con_accept(FileTransfer * ft)522 void FileTransferManager::con_accept(FileTransfer *ft)
523 {
524 ft->d->needStream = true;
525 d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType);
526 }
527
con_reject(FileTransfer * ft)528 void FileTransferManager::con_reject(FileTransfer *ft)
529 {
530 d->pft->respondError(ft->d->peer, ft->d->iq_id, Stanza::Error::Forbidden, "Declined");
531 }
532
unlink(FileTransfer * ft)533 void FileTransferManager::unlink(FileTransfer *ft)
534 {
535 d->list.removeAll(ft);
536 }
537
538 //----------------------------------------------------------------------------
539 // JT_FT
540 //----------------------------------------------------------------------------
541 class JT_FT::Private
542 {
543 public:
544 QDomElement iq;
545 Jid to;
546 qlonglong size, rangeOffset, rangeLength;
547 QString streamType;
548 QStringList streamTypes;
549 };
550
JT_FT(Task * parent)551 JT_FT::JT_FT(Task *parent)
552 :Task(parent)
553 {
554 d = new Private;
555 }
556
~JT_FT()557 JT_FT::~JT_FT()
558 {
559 delete d;
560 }
561
request(const Jid & to,const QString & _id,const QString & fname,qlonglong size,const QString & desc,const QStringList & streamTypes,const FTThumbnail & thumb)562 void JT_FT::request(const Jid &to, const QString &_id, const QString &fname,
563 qlonglong size, const QString &desc,
564 const QStringList &streamTypes, const FTThumbnail &thumb)
565 {
566 QDomElement iq;
567 d->to = to;
568 iq = createIQ(doc(), "set", to.full(), id());
569 QDomElement si = doc()->createElement("si");
570 si.setAttribute("xmlns", "http://jabber.org/protocol/si");
571 si.setAttribute("id", _id);
572 si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer");
573
574 QDomElement file = doc()->createElement("file");
575 file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
576 file.setAttribute("name", fname);
577 file.setAttribute("size", QString::number(size));
578 if(!desc.isEmpty()) {
579 QDomElement de = doc()->createElement("desc");
580 de.appendChild(doc()->createTextNode(desc));
581 file.appendChild(de);
582 }
583 QDomElement range = doc()->createElement("range");
584 file.appendChild(range);
585
586 if (!thumb.data.isEmpty()) {
587 BoBData data = client()->bobManager()->append(thumb.data, thumb.mimeType);
588 QDomElement thel = doc()->createElement("thumbnail");
589 thel.setAttribute("xmlns", "urn:xmpp:thumbs:0");
590 thel.setAttribute("cid", data.cid());
591 thel.setAttribute("mime-type", thumb.mimeType);
592 if (thumb.width && thumb.height) {
593 thel.setAttribute("width", thumb.width);
594 thel.setAttribute("height", thumb.height);
595 }
596 file.appendChild(thel);
597 }
598
599 si.appendChild(file);
600
601 QDomElement feature = doc()->createElement("feature");
602 feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
603 QDomElement x = doc()->createElement("x");
604 x.setAttribute("xmlns", "jabber:x:data");
605 x.setAttribute("type", "form");
606
607 QDomElement field = doc()->createElement("field");
608 field.setAttribute("var", "stream-method");
609 field.setAttribute("type", "list-single");
610 for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) {
611 QDomElement option = doc()->createElement("option");
612 QDomElement value = doc()->createElement("value");
613 value.appendChild(doc()->createTextNode(*it));
614 option.appendChild(value);
615 field.appendChild(option);
616 }
617
618 x.appendChild(field);
619 feature.appendChild(x);
620
621 si.appendChild(feature);
622 iq.appendChild(si);
623
624 d->streamTypes = streamTypes;
625 d->size = size;
626 d->iq = iq;
627 }
628
rangeOffset() const629 qlonglong JT_FT::rangeOffset() const
630 {
631 return d->rangeOffset;
632 }
633
rangeLength() const634 qlonglong JT_FT::rangeLength() const
635 {
636 return d->rangeLength;
637 }
638
streamType() const639 QString JT_FT::streamType() const
640 {
641 return d->streamType;
642 }
643
onGo()644 void JT_FT::onGo()
645 {
646 send(d->iq);
647 }
648
take(const QDomElement & x)649 bool JT_FT::take(const QDomElement &x)
650 {
651 if(!iqVerify(x, d->to, id()))
652 return false;
653
654 if(x.attribute("type") == "result") {
655 QDomElement si = firstChildElement(x);
656 if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") {
657 setError(900, "");
658 return true;
659 }
660
661 QString id = si.attribute("id");
662
663 qlonglong range_offset = 0;
664 qlonglong range_length = 0;
665
666 QDomElement file = si.elementsByTagName("file").item(0).toElement();
667 if(!file.isNull()) {
668 QDomElement range = file.elementsByTagName("range").item(0).toElement();
669 if(!range.isNull()) {
670 qlonglong x;
671 bool ok;
672 if(range.hasAttribute("offset")) {
673 x = range.attribute("offset").toLongLong(&ok);
674 if(!ok || x < 0) {
675 setError(900, "");
676 return true;
677 }
678 range_offset = x;
679 }
680 if(range.hasAttribute("length")) {
681 x = range.attribute("length").toLongLong(&ok);
682 if(!ok || x < 0) {
683 setError(900, "");
684 return true;
685 }
686 range_length = x;
687 }
688 }
689 }
690
691 if(range_offset > d->size || (range_length > (d->size - range_offset))) {
692 setError(900, "");
693 return true;
694 }
695
696 QString streamtype;
697 QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
698 if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
699 QDomElement x = feature.elementsByTagName("x").item(0).toElement();
700 if(!x.isNull() && x.attribute("type") == "submit") {
701 QDomElement field = x.elementsByTagName("field").item(0).toElement();
702 if(!field.isNull() && field.attribute("var") == "stream-method") {
703 QDomElement value = field.elementsByTagName("value").item(0).toElement();
704 if(!value.isNull())
705 streamtype = value.text();
706 }
707 }
708 }
709
710 // must be one of the offered streamtypes
711 if (!d->streamTypes.contains(streamtype)) {
712 return true;
713 }
714
715 d->rangeOffset = range_offset;
716 d->rangeLength = range_length;
717 d->streamType = streamtype;
718 setSuccess();
719 }
720 else {
721 setError(x);
722 }
723
724 return true;
725 }
726
727 //----------------------------------------------------------------------------
728 // JT_PushFT
729 //----------------------------------------------------------------------------
JT_PushFT(Task * parent)730 JT_PushFT::JT_PushFT(Task *parent)
731 :Task(parent)
732 {
733 }
734
~JT_PushFT()735 JT_PushFT::~JT_PushFT()
736 {
737 }
738
respondSuccess(const Jid & to,const QString & id,qlonglong rangeOffset,qlonglong rangeLength,const QString & streamType)739 void JT_PushFT::respondSuccess(const Jid &to, const QString &id, qlonglong rangeOffset, qlonglong rangeLength, const QString &streamType)
740 {
741 QDomElement iq = createIQ(doc(), "result", to.full(), id);
742 QDomElement si = doc()->createElement("si");
743 si.setAttribute("xmlns", "http://jabber.org/protocol/si");
744
745 if(rangeOffset != 0 || rangeLength != 0) {
746 QDomElement file = doc()->createElement("file");
747 file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer");
748 QDomElement range = doc()->createElement("range");
749 if(rangeOffset > 0)
750 range.setAttribute("offset", QString::number(rangeOffset));
751 if(rangeLength > 0)
752 range.setAttribute("length", QString::number(rangeLength));
753 file.appendChild(range);
754 si.appendChild(file);
755 }
756
757 QDomElement feature = doc()->createElement("feature");
758 feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg");
759 QDomElement x = doc()->createElement("x");
760 x.setAttribute("xmlns", "jabber:x:data");
761 x.setAttribute("type", "submit");
762
763 QDomElement field = doc()->createElement("field");
764 field.setAttribute("var", "stream-method");
765 QDomElement value = doc()->createElement("value");
766 value.appendChild(doc()->createTextNode(streamType));
767 field.appendChild(value);
768
769 x.appendChild(field);
770 feature.appendChild(x);
771
772 si.appendChild(feature);
773 iq.appendChild(si);
774 send(iq);
775 }
776
respondError(const Jid & to,const QString & id,Stanza::Error::ErrorCond cond,const QString & str)777 void JT_PushFT::respondError(const Jid &to, const QString &id,
778 Stanza::Error::ErrorCond cond, const QString &str)
779 {
780 QDomElement iq = createIQ(doc(), "error", to.full(), id);
781 Stanza::Error error(Stanza::Error::Cancel, cond, str);
782 iq.appendChild(error.toXml(*client()->doc(), client()->stream().baseNS()));
783 send(iq);
784 }
785
take(const QDomElement & e)786 bool JT_PushFT::take(const QDomElement &e)
787 {
788 // must be an iq-set tag
789 if(e.tagName() != "iq")
790 return false;
791 if(e.attribute("type") != "set")
792 return false;
793
794 QDomElement si = firstChildElement(e);
795 if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si")
796 return false;
797 if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer")
798 return false;
799
800 Jid from(e.attribute("from"));
801 QString id = si.attribute("id");
802
803 QDomElement file = si.elementsByTagName("file").item(0).toElement();
804 if(file.isNull())
805 return true;
806
807 QString fname = file.attribute("name");
808 if(fname.isEmpty()) {
809 respondError(from, id, Stanza::Error::BadRequest, "Bad file name");
810 return true;
811 }
812
813 // ensure kosher
814 {
815 QFileInfo fi(fname);
816 fname = fi.fileName();
817 }
818
819 bool ok;
820 qlonglong size = file.attribute("size").toLongLong(&ok);
821 if(!ok || size < 0) {
822 respondError(from, id, Stanza::Error::BadRequest, "Bad file size");
823 return true;
824 }
825
826 QString desc;
827 QDomElement de = file.elementsByTagName("desc").item(0).toElement();
828 if(!de.isNull())
829 desc = de.text();
830
831 bool rangeSupported = false;
832 QDomElement range = file.elementsByTagName("range").item(0).toElement();
833 if(!range.isNull())
834 rangeSupported = true;
835
836 QStringList streamTypes;
837 QDomElement feature = si.elementsByTagName("feature").item(0).toElement();
838 if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") {
839 QDomElement x = feature.elementsByTagName("x").item(0).toElement();
840 if(!x.isNull() /*&& x.attribute("type") == "form"*/) {
841 QDomElement field = x.elementsByTagName("field").item(0).toElement();
842 if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") {
843 QDomNodeList nl = field.elementsByTagName("option");
844 for(int n = 0; n < nl.count(); ++n) {
845 QDomElement e = nl.item(n).toElement();
846 QDomElement value = e.elementsByTagName("value").item(0).toElement();
847 if(!value.isNull())
848 streamTypes += value.text();
849 }
850 }
851 }
852 }
853
854 FTThumbnail thumb;
855 QDomElement thel = file.elementsByTagName("thumbnail").item(0).toElement();
856 if(!thel.isNull() && thel.attribute("xmlns") == QLatin1String("urn:xmpp:thumbs:0")) {
857 thumb.data = thel.attribute("cid").toUtf8();
858 thumb.mimeType = thel.attribute("mime-type");
859 thumb.width = thel.attribute("width").toUInt();
860 thumb.height = thel.attribute("height").toUInt();
861 }
862
863 FTRequest r;
864 r.from = from;
865 r.iq_id = e.attribute("id");
866 r.id = id;
867 r.fname = fname;
868 r.size = size;
869 r.desc = desc;
870 r.rangeSupported = rangeSupported;
871 r.streamTypes = streamTypes;
872 r.thumbnail = thumb;
873
874 emit incoming(r);
875 return true;
876 }
877