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