1 /*
2  * client.cpp - IM Client
3  * Copyright (C) 2003  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 //! \class XMPP::Client client.h
22 //! \brief Communicates with the XMPP network.  Start here.
23 //!
24 //!  Client controls an active XMPP connection.  It allows you to connect,
25 //!  authenticate, manipulate the roster, and send / receive messages and
26 //!  presence.  It is the centerpiece of this library, and all Tasks must pass
27 //!  through it.
28 //!
29 //!  For convenience, many Tasks are handled internally to Client (such as
30 //!  JT_Auth).  However, for accessing features beyond the basics provided by
31 //!  Client, you will need to manually invoke Tasks.  Fortunately, the
32 //!  process is very simple.
33 //!
34 //!  The entire Task system is heavily founded on Qt.  All Tasks have a parent,
35 //!  except for the root Task, and are considered QObjects.  By using Qt's RTTI
36 //!  facilities (QObject::sender(), QObject::isA(), etc), you can use a
37 //!  "fire and forget" approach with Tasks.
38 //!
39 //!  \code
40 //!  #include "client.h"
41 //!  using namespace XMPP;
42 //!
43 //!  ...
44 //!
45 //!  Client *client;
46 //!
47 //!  Session::Session()
48 //!  {
49 //!    client = new Client;
50 //!    connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken()));
51 //!    connect(client, SIGNAL(authFinished(bool,int,QString)), SLOT(authFinished(bool,int,QString)));
52 //!    client->connectToHost("jabber.org");
53 //!  }
54 //!
55 //!  void Session::clientHandshaken()
56 //!  {
57 //!    client->authDigest("jabtest", "12345", "Psi");
58 //!  }
59 //!
60 //!  void Session::authFinished(bool success, int, const QString &err)
61 //!  {
62 //!    if(success)
63 //!      printf("Login success!");
64 //!    else
65 //!      printf("Login failed.  Here's why: %s\n", err.toLatin1());
66 //!  }
67 //!  \endcode
68 
69 #include <QObject>
70 #include <QMap>
71 #include <QTimer>
72 #include <QPointer>
73 #include <QList>
74 
75 #include "im.h"
76 #include "safedelete.h"
77 #include "xmpp_tasks.h"
78 #include "xmpp_xmlcommon.h"
79 #include "s5b.h"
80 #include "xmpp_ibb.h"
81 #include "xmpp_bitsofbinary.h"
82 #include "filetransfer.h"
83 #include "xmpp_caps.h"
84 #include "protocol.h"
85 
86 #ifdef Q_OS_WIN
87 #define vsnprintf _vsnprintf
88 #endif
89 
90 namespace XMPP
91 {
92 
93 //----------------------------------------------------------------------------
94 // Client
95 //----------------------------------------------------------------------------
96 class Client::GroupChat
97 {
98 public:
99 	enum { Connecting, Connected, Closing };
GroupChat()100 	GroupChat() {}
101 
102 	Jid j;
103 	int status;
104 	QString password;
105 };
106 
107 class Client::ClientPrivate
108 {
109 public:
ClientPrivate()110 	ClientPrivate() {}
111 
112 	QPointer<ClientStream> stream;
113 	QDomDocument doc;
114 	int id_seed;
115 	Task *root;
116 	QString host, user, pass, resource;
117 	QString osName, osVersion, tzname, clientName, clientVersion;
118 	CapsSpec caps, serverCaps;
119 	DiscoItem::Identity identity;
120 	Features features;
121 	QMap<QString,Features> extension_features;
122 	int tzoffset;
123 	bool useTzoffset;	// manual tzoffset is old way of doing utc<->local translations
124 	bool active;
125 
126 	LiveRoster roster;
127 	ResourceList resourceList;
128 	CapsManager *capsman;
129 	S5BManager *s5bman;
130 	IBBManager *ibbman;
131 	BoBManager *bobman;
132 	FileTransferManager *ftman;
133 	bool ftEnabled;
134 	QList<GroupChat> groupChatList;
135 };
136 
137 
Client(QObject * par)138 Client::Client(QObject *par)
139 :QObject(par)
140 {
141 	d = new ClientPrivate;
142 	d->tzoffset = 0;
143 	d->useTzoffset = false;
144 	d->active = false;
145 	d->osName = "N/A";
146 	d->clientName = "N/A";
147 	d->clientVersion = "0.0";
148 
149 	d->id_seed = 0xaaaa;
150 	d->root = new Task(this, true);
151 
152 	d->s5bman = new S5BManager(this);
153 	connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady()));
154 
155 	d->ibbman = new IBBManager(this);
156 	connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady()));
157 
158 	d->bobman = new BoBManager(this);
159 
160 	d->ftman = 0;
161 
162 	d->capsman = new CapsManager(this);
163 }
164 
~Client()165 Client::~Client()
166 {
167 	//fprintf(stderr, "\tClient::~Client\n");
168 	//fflush(stderr);
169 
170 	close(true);
171 
172 	delete d->ftman;
173 	delete d->ibbman;
174 	delete d->s5bman;
175 	delete d->root;
176 	delete d;
177 	//fprintf(stderr, "\tClient::~Client\n");
178 }
179 
connectToServer(ClientStream * s,const Jid & j,bool auth)180 void Client::connectToServer(ClientStream *s, const Jid &j, bool auth)
181 {
182 	d->stream = s;
183 	//connect(d->stream, SIGNAL(connected()), SLOT(streamConnected()));
184 	//connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken()));
185 	connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int)));
186 	//connect(d->stream, SIGNAL(sslCertificateReady(QSSLCert)), SLOT(streamSSLCertificateReady(QSSLCert)));
187 	connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead()));
188 	//connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished()));
189 	connect(d->stream, SIGNAL(incomingXml(QString)), SLOT(streamIncomingXml(QString)));
190 	connect(d->stream, SIGNAL(outgoingXml(QString)), SLOT(streamOutgoingXml(QString)));
191 	connect(d->stream, SIGNAL(haveUnhandledFeatures()), SLOT(parseUnhandledStreamFeatures()));
192 
193 	d->stream->connectToServer(j, auth);
194 }
195 
start(const QString & host,const QString & user,const QString & pass,const QString & _resource)196 void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource)
197 {
198 	// TODO
199 	d->host = host;
200 	d->user = user;
201 	d->pass = pass;
202 	d->resource = _resource;
203 
204 	Status stat;
205 	stat.setIsAvailable(false);
206 	d->resourceList += Resource(resource(), stat);
207 
208 	JT_PushPresence *pp = new JT_PushPresence(rootTask());
209 	connect(pp, SIGNAL(subscription(Jid,QString,QString)), SLOT(ppSubscription(Jid,QString,QString)));
210 	connect(pp, SIGNAL(presence(Jid,Status)), SLOT(ppPresence(Jid,Status)));
211 
212 	JT_PushMessage *pm = new JT_PushMessage(rootTask());
213 	connect(pm, SIGNAL(message(Message)), SLOT(pmMessage(Message)));
214 
215 	JT_PushRoster *pr = new JT_PushRoster(rootTask());
216 	connect(pr, SIGNAL(roster(Roster)), SLOT(prRoster(Roster)));
217 
218 	new JT_ServInfo(rootTask());
219 	new JT_PongServer(rootTask());
220 
221 	d->active = true;
222 }
223 
setFileTransferEnabled(bool b)224 void Client::setFileTransferEnabled(bool b)
225 {
226 	if(b) {
227 		if(!d->ftman)
228 			d->ftman = new FileTransferManager(this);
229 	}
230 	else {
231 		if(d->ftman) {
232 			delete d->ftman;
233 			d->ftman = 0;
234 		}
235 	}
236 }
237 
fileTransferManager() const238 FileTransferManager *Client::fileTransferManager() const
239 {
240 	return d->ftman;
241 }
242 
s5bManager() const243 S5BManager *Client::s5bManager() const
244 {
245 	return d->s5bman;
246 }
247 
ibbManager() const248 IBBManager *Client::ibbManager() const
249 {
250 	return d->ibbman;
251 }
252 
bobManager() const253 BoBManager *Client::bobManager() const
254 {
255 	return d->bobman;
256 }
257 
capsManager() const258 CapsManager *Client::capsManager() const
259 {
260 	return d->capsman;
261 }
262 
isActive() const263 bool Client::isActive() const
264 {
265 	return d->active;
266 }
267 
groupChatPassword(const QString & host,const QString & room) const268 QString Client::groupChatPassword(const QString& host, const QString& room) const
269 {
270 	Jid jid(room + "@" + host);
271 	foreach(GroupChat i, d->groupChatList) {
272 		if(i.j.compare(jid, false)) {
273 			return i.password;
274 		}
275 	}
276 	return QString();
277 }
278 
groupChatChangeNick(const QString & host,const QString & room,const QString & nick,const Status & _s)279 void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s)
280 {
281 	Jid jid(room + "@" + host + "/" + nick);
282 	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
283 		GroupChat &i = *it;
284 		if(i.j.compare(jid, false)) {
285 			i.j = jid;
286 
287 			Status s = _s;
288 			s.setIsAvailable(true);
289 
290 			JT_Presence *j = new JT_Presence(rootTask());
291 			j->pres(jid, s);
292 			j->go(true);
293 
294 			break;
295 		}
296 	}
297 }
298 
groupChatJoin(const QString & host,const QString & room,const QString & nick,const QString & password,int maxchars,int maxstanzas,int seconds,const QDateTime & since,const Status & _s)299 bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString& password, int maxchars, int maxstanzas, int seconds, const QDateTime &since, const Status& _s)
300 {
301 	Jid jid(room + "@" + host + "/" + nick);
302 	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) {
303 		GroupChat &i = *it;
304 		if(i.j.compare(jid, false)) {
305 			// if this room is shutting down, then free it up
306 			if(i.status == GroupChat::Closing)
307 				it = d->groupChatList.erase(it);
308 			else
309 				return false;
310 		}
311 		else
312 			++it;
313 	}
314 
315 	debug(QString("Client: Joined: [%1]\n").arg(jid.full()));
316 	GroupChat i;
317 	i.j = jid;
318 	i.status = GroupChat::Connecting;
319 	i.password = password;
320 	d->groupChatList += i;
321 
322 	JT_Presence *j = new JT_Presence(rootTask());
323 	Status s = _s;
324 	s.setMUC();
325 	s.setMUCHistory(maxchars, maxstanzas, seconds, since);
326 	if (!password.isEmpty()) {
327 		s.setMUCPassword(password);
328 	}
329 	j->pres(jid,s);
330 	j->go(true);
331 
332 	return true;
333 }
334 
groupChatSetStatus(const QString & host,const QString & room,const Status & _s)335 void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s)
336 {
337 	Jid jid(room + "@" + host);
338 	bool found = false;
339 	foreach (const GroupChat &i, d->groupChatList) {
340 		if(i.j.compare(jid, false)) {
341 			found = true;
342 			jid = i.j;
343 			break;
344 		}
345 	}
346 	if(!found)
347 		return;
348 
349 	Status s = _s;
350 	s.setIsAvailable(true);
351 
352 	JT_Presence *j = new JT_Presence(rootTask());
353 	j->pres(jid, s);
354 	j->go(true);
355 }
356 
groupChatLeave(const QString & host,const QString & room,const QString & statusStr)357 void Client::groupChatLeave(const QString &host, const QString &room, const QString &statusStr)
358 {
359 	Jid jid(room + "@" + host);
360 	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
361 		GroupChat &i = *it;
362 
363 		if(!i.j.compare(jid, false))
364 			continue;
365 
366 		i.status = GroupChat::Closing;
367 		debug(QString("Client: Leaving: [%1]\n").arg(i.j.full()));
368 
369 		JT_Presence *j = new JT_Presence(rootTask());
370 		Status s;
371 		s.setIsAvailable(false);
372 		s.setStatus(statusStr);
373 		j->pres(i.j, s);
374 		j->go(true);
375 	}
376 }
377 
groupChatLeaveAll(const QString & statusStr)378 void Client::groupChatLeaveAll(const QString &statusStr)
379 {
380 	if (d->stream && d->active) {
381 		for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
382 			GroupChat &i = *it;
383 			i.status = GroupChat::Closing;
384 
385 			JT_Presence *j = new JT_Presence(rootTask());
386 			Status s;
387 			s.setIsAvailable(false);
388 			s.setStatus(statusStr);
389 			j->pres(i.j, s);
390 			j->go(true);
391 		}
392 	}
393 }
394 
groupChatNick(const QString & host,const QString & room) const395 QString Client::groupChatNick(const QString &host, const QString &room) const
396 {
397 	Jid jid(room + "@" + host);
398 	foreach (const GroupChat &gc, d->groupChatList) {
399 		if (gc.j.compare(jid, false)) {
400 			return gc.j.resource();
401 		}
402 	}
403 	return QString();
404 }
405 
406 /*void Client::start()
407 {
408 	if(d->stream->old()) {
409 		// old has no activation step
410 		d->active = true;
411 		activated();
412 	}
413 	else {
414 		// TODO: IM session
415 	}
416 }*/
417 
418 // TODO: fast close
close(bool)419 void Client::close(bool)
420 {
421 	//fprintf(stderr, "\tClient::close\n");
422 	//fflush(stderr);
423 
424 	if(d->stream) {
425 		d->stream->disconnect(this);
426 		d->stream->close();
427 		d->stream = 0;
428 	}
429 	disconnected();
430 	cleanup();
431 }
432 
cleanup()433 void Client::cleanup()
434 {
435 	d->active = false;
436 	//d->authed = false;
437 	d->groupChatList.clear();
438 }
439 
440 /*void Client::continueAfterCert()
441 {
442 	d->stream->continueAfterCert();
443 }
444 
445 void Client::streamConnected()
446 {
447 	connected();
448 }
449 
450 void Client::streamHandshaken()
451 {
452 	handshaken();
453 }*/
454 
streamError(int)455 void Client::streamError(int)
456 {
457 	//StreamError e = err;
458 	//error(e);
459 
460 	//if(!e.isWarning()) {
461 		disconnected();
462 		cleanup();
463 	//}
464 }
465 
466 /*void Client::streamSSLCertificateReady(const QSSLCert &cert)
467 {
468 	sslCertReady(cert);
469 }
470 
471 void Client::streamCloseFinished()
472 {
473 	closeFinished();
474 }*/
475 
oldStyleNS(const QDomElement & e)476 static QDomElement oldStyleNS(const QDomElement &e)
477 {
478 	// find closest parent with a namespace
479 	QDomNode par = e.parentNode();
480 	while(!par.isNull() && par.namespaceURI().isNull())
481 		par = par.parentNode();
482 	bool noShowNS = false;
483 	if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
484 		noShowNS = true;
485 
486 	QDomElement i;
487 	int x;
488 	//if(noShowNS)
489 		i = e.ownerDocument().createElement(e.tagName());
490 	//else
491 	//	i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName());
492 
493 	// copy attributes
494 	QDomNamedNodeMap al = e.attributes();
495 	for(x = 0; x < al.count(); ++x)
496 		i.setAttributeNode(al.item(x).cloneNode().toAttr());
497 
498 	if(!noShowNS)
499 		i.setAttribute("xmlns", e.namespaceURI());
500 
501 	// copy children
502 	QDomNodeList nl = e.childNodes();
503 	for(x = 0; x < nl.count(); ++x) {
504 		QDomNode n = nl.item(x);
505 		if(n.isElement())
506 			i.appendChild(oldStyleNS(n.toElement()));
507 		else
508 			i.appendChild(n.cloneNode());
509 	}
510 	return i;
511 }
512 
streamReadyRead()513 void Client::streamReadyRead()
514 {
515 	//fprintf(stderr, "\tClientStream::streamReadyRead\n");
516 	//fflush(stderr);
517 
518 	while(d->stream && d->stream->stanzaAvailable()) {
519 		Stanza s = d->stream->read();
520 
521 		QString out = s.toString();
522 		debug(QString("Client: incoming: [\n%1]\n").arg(out));
523 		emit xmlIncoming(out);
524 
525 		QDomElement x = oldStyleNS(s.element());
526 		distribute(x);
527 	}
528 }
529 
streamIncomingXml(const QString & s)530 void Client::streamIncomingXml(const QString &s)
531 {
532 	QString str = s;
533 	if(str.at(str.length()-1) != '\n')
534 		str += '\n';
535 	emit xmlIncoming(str);
536 }
537 
streamOutgoingXml(const QString & s)538 void Client::streamOutgoingXml(const QString &s)
539 {
540 	QString str = s;
541 	if(str.at(str.length()-1) != '\n')
542 		str += '\n';
543 	emit xmlOutgoing(str);
544 }
545 
parseUnhandledStreamFeatures()546 void Client::parseUnhandledStreamFeatures()
547 {
548 	QList<QDomElement> nl = d->stream->unhandledFeatures();
549 	foreach (const QDomElement &e, nl) {
550 		if (e.localName() == "c" && e.namespaceURI() == NS_CAPS) {
551 			d->serverCaps = CapsSpec::fromXml(e);
552 			if (d->capsman->isEnabled()) {
553 				d->capsman->updateCaps(Jid(d->stream->jid().domain()), d->serverCaps);
554 			}
555 		}
556 	}
557 }
558 
debug(const QString & str)559 void Client::debug(const QString &str)
560 {
561 	emit debugText(str);
562 }
563 
genUniqueId()564 QString Client::genUniqueId()
565 {
566 	QString s;
567 	s.sprintf("a%x", d->id_seed);
568 	d->id_seed += 0x10;
569 	return s;
570 }
571 
rootTask()572 Task *Client::rootTask()
573 {
574 	return d->root;
575 }
576 
doc() const577 QDomDocument *Client::doc() const
578 {
579 	return &d->doc;
580 }
581 
distribute(const QDomElement & x)582 void Client::distribute(const QDomElement &x)
583 {
584 	if(x.hasAttribute("from")) {
585 		Jid j(x.attribute("from"));
586 		if(!j.isValid()) {
587 			debug("Client: bad 'from' JID\n");
588 			return;
589 		}
590 	}
591 
592 	if(!rootTask()->take(x) && (x.attribute("type") == "get" || x.attribute("type") == "set") ) {
593 		debug("Client: Unrecognized IQ.\n");
594 
595 		// Create reply element
596 		QDomElement reply = createIQ(doc(), "error", x.attribute("from"), x.attribute("id"));
597 
598 		// Copy children
599 		for (QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) {
600 			reply.appendChild(n.cloneNode());
601 		}
602 
603 		// Add error
604 		QDomElement error = doc()->createElement("error");
605 		error.setAttribute("type","cancel");
606 		reply.appendChild(error);
607 
608 		QDomElement error_type = doc()->createElement("feature-not-implemented");
609 		error_type.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-stanzas");
610 		error.appendChild(error_type);
611 
612 		send(reply);
613 	}
614 }
615 
send(const QDomElement & x)616 void Client::send(const QDomElement &x)
617 {
618 	if(!d->stream)
619 		return;
620 
621 	//QString out;
622 	//QTextStream ts(&out, IO_WriteOnly);
623 	//x.save(ts, 0);
624 
625 	//QString out = Stream::xmlToString(x);
626 	//debug(QString("Client: outgoing: [\n%1]\n").arg(out));
627 	//xmlOutgoing(out);
628 
629 	QDomElement e = addCorrectNS(x);
630 	Stanza s = d->stream->createStanza(e);
631 	if(s.isNull()) {
632 		//printf("bad stanza??\n");
633 		return;
634 	}
635 	emit stanzaElementOutgoing(e);
636 	QString out = s.toString();
637 	//qWarning() << "Out: " << out;
638 	debug(QString("Client: outgoing: [\n%1]\n").arg(out));
639 	emit xmlOutgoing(out);
640 
641 	//printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).toLatin1(), Stream::xmlToString(e).toLatin1(), s.toString().toLatin1());
642 	d->stream->write(s);
643 }
644 
send(const QString & str)645 void Client::send(const QString &str)
646 {
647 	if(!d->stream)
648 		return;
649 
650 	debug(QString("Client: outgoing: [\n%1]\n").arg(str));
651 	emit xmlOutgoing(str);
652 	static_cast<ClientStream*>(d->stream)->writeDirect(str);
653 }
654 
stream()655 Stream & Client::stream()
656 {
657 	return *(d->stream.data());
658 }
659 
streamBaseNS() const660 QString Client::streamBaseNS() const
661 {
662 	return d->stream->baseNS();
663 }
664 
roster() const665 const LiveRoster & Client::roster() const
666 {
667 	return d->roster;
668 }
669 
resourceList() const670 const ResourceList & Client::resourceList() const
671 {
672 	return d->resourceList;
673 }
674 
host() const675 QString Client::host() const
676 {
677 	return d->host;
678 }
679 
user() const680 QString Client::user() const
681 {
682 	return d->user;
683 }
684 
pass() const685 QString Client::pass() const
686 {
687 	return d->pass;
688 }
689 
resource() const690 QString Client::resource() const
691 {
692 	return d->resource;
693 }
694 
jid() const695 Jid Client::jid() const
696 {
697 	QString s;
698 	if(!d->user.isEmpty())
699 		s += d->user + '@';
700 	s += d->host;
701 	if(!d->resource.isEmpty()) {
702 		s += '/';
703 		s += d->resource;
704 	}
705 
706 	return Jid(s);
707 }
708 
ppSubscription(const Jid & j,const QString & s,const QString & n)709 void Client::ppSubscription(const Jid &j, const QString &s, const QString& n)
710 {
711 	emit subscription(j, s, n);
712 }
713 
ppPresence(const Jid & j,const Status & s)714 void Client::ppPresence(const Jid &j, const Status &s)
715 {
716 	if(s.isAvailable())
717 		debug(QString("Client: %1 is available.\n").arg(j.full()));
718 	else
719 		debug(QString("Client: %1 is unavailable.\n").arg(j.full()));
720 
721 	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
722 		GroupChat &i = *it;
723 
724 		if(i.j.compare(j, false)) {
725 			bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false;
726 
727 			debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us));
728 			switch(i.status) {
729 				case GroupChat::Connecting:
730 					if(us && s.hasError()) {
731 						Jid j = i.j;
732 						d->groupChatList.erase(it);
733 						emit groupChatError(j, s.errorCode(), s.errorString());
734 					}
735 					else {
736 						// don't signal success unless it is a non-error presence
737 						if(!s.hasError()) {
738 							i.status = GroupChat::Connected;
739 							emit groupChatJoined(i.j);
740 						}
741 						emit groupChatPresence(j, s);
742 					}
743 					break;
744 				case GroupChat::Connected:
745 					emit groupChatPresence(j, s);
746 					break;
747 				case GroupChat::Closing:
748 					if(us && !s.isAvailable()) {
749 						Jid j = i.j;
750 						d->groupChatList.erase(it);
751 						emit groupChatLeft(j);
752 					}
753 					break;
754 				default:
755 					break;
756 			}
757 
758 			return;
759 		}
760 	}
761 
762 	if(s.hasError()) {
763 		emit presenceError(j, s.errorCode(), s.errorString());
764 		return;
765 	}
766 
767 	// is it me?
768 	if(j.compare(jid(), false)) {
769 		updateSelfPresence(j, s);
770 	}
771 	else {
772 		// update all relavent roster entries
773 		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) {
774 			LiveRosterItem &i = *it;
775 
776 			if(!i.jid().compare(j, false))
777 				continue;
778 
779 			// roster item has its own resource?
780 			if(!i.jid().resource().isEmpty()) {
781 				if(i.jid().resource() != j.resource())
782 					continue;
783 			}
784 
785 			updatePresence(&i, j, s);
786 		}
787 	}
788 }
789 
updateSelfPresence(const Jid & j,const Status & s)790 void Client::updateSelfPresence(const Jid &j, const Status &s)
791 {
792 	ResourceList::Iterator rit = d->resourceList.find(j.resource());
793 	bool found = (rit == d->resourceList.end()) ? false: true;
794 
795 	// unavailable?  remove the resource
796 	if(!s.isAvailable()) {
797 		if(found) {
798 			debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource()));
799 			(*rit).setStatus(s);
800 			emit resourceUnavailable(j, *rit);
801 			d->resourceList.erase(rit);
802 		}
803 	}
804 	// available?  add/update the resource
805 	else {
806 		Resource r;
807 		if(!found) {
808 			r = Resource(j.resource(), s);
809 			d->resourceList += r;
810 			debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource()));
811 		}
812 		else {
813 			(*rit).setStatus(s);
814 			r = *rit;
815 			debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource()));
816 		}
817 
818 		emit resourceAvailable(j, r);
819 	}
820 }
821 
updatePresence(LiveRosterItem * i,const Jid & j,const Status & s)822 void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s)
823 {
824 	ResourceList::Iterator rit = i->resourceList().find(j.resource());
825 	bool found = (rit == i->resourceList().end()) ? false: true;
826 
827 	// unavailable?  remove the resource
828 	if(!s.isAvailable()) {
829 		if(found) {
830 			(*rit).setStatus(s);
831 			debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
832 			emit resourceUnavailable(j, *rit);
833 			i->resourceList().erase(rit);
834 			i->setLastUnavailableStatus(s);
835 		}
836 		else {
837 			// create the resource just for the purpose of emit
838 			Resource r = Resource(j.resource(), s);
839 			i->resourceList() += r;
840 			rit = i->resourceList().find(j.resource());
841 			emit resourceUnavailable(j, *rit);
842 			i->resourceList().erase(rit);
843 			i->setLastUnavailableStatus(s);
844 		}
845 	}
846 	// available?  add/update the resource
847 	else {
848 		Resource r;
849 		if(!found) {
850 			r = Resource(j.resource(), s);
851 			i->resourceList() += r;
852 			debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
853 		}
854 		else {
855 			(*rit).setStatus(s);
856 			r = *rit;
857 			debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
858 		}
859 
860 		emit resourceAvailable(j, r);
861 	}
862 }
863 
pmMessage(const Message & m)864 void Client::pmMessage(const Message &m)
865 {
866 	debug(QString("Client: Message from %1\n").arg(m.from().full()));
867 
868 	// bits of binary. we can't do this in Message, since it knows nothing about Client
869 	foreach (const BoBData &b, m.bobDataList()) {
870 		d->bobman->append(b);
871 	}
872 
873 	if (!m.ibbData().data.isEmpty()) {
874 		d->ibbman->takeIncomingData(m.from(), m.id(), m.ibbData(), Stanza::Message);
875 	}
876 
877 	if(m.type() == "groupchat") {
878 		for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
879 			const GroupChat &i = *it;
880 
881 			if(!i.j.compare(m.from(), false))
882 				continue;
883 
884 			if(i.status == GroupChat::Connected)
885 				messageReceived(m);
886 		}
887 	}
888 	else
889 		messageReceived(m);
890 }
891 
prRoster(const Roster & r)892 void Client::prRoster(const Roster &r)
893 {
894 	importRoster(r);
895 }
896 
rosterRequest()897 void Client::rosterRequest()
898 {
899 	if(!d->active)
900 		return;
901 
902 	JT_Roster *r = new JT_Roster(rootTask());
903 	connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished()));
904 	r->get();
905 	d->roster.flagAllForDelete(); // mod_groups patch
906 	r->go(true);
907 }
908 
slotRosterRequestFinished()909 void Client::slotRosterRequestFinished()
910 {
911 	JT_Roster *r = (JT_Roster *)sender();
912 	// on success, let's take it
913 	if(r->success()) {
914 		//d->roster.flagAllForDelete(); // mod_groups patch
915 
916 		importRoster(r->roster());
917 
918 		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) {
919 			LiveRosterItem &i = *it;
920 			if(i.flagForDelete()) {
921 				emit rosterItemRemoved(i);
922 				it = d->roster.erase(it);
923 			}
924 			else
925 				++it;
926 		}
927 	}
928 	else {
929 		// don't report a disconnect.  Client::error() will do that.
930 		if(r->statusCode() == Task::ErrDisc)
931 			return;
932 	}
933 
934 	// report success / fail
935 	emit rosterRequestFinished(r->success(), r->statusCode(), r->statusString());
936 }
937 
importRoster(const Roster & r)938 void Client::importRoster(const Roster &r)
939 {
940 	emit beginImportRoster();
941 	for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) {
942 		importRosterItem(*it);
943 	}
944 	emit endImportRoster();
945 }
946 
importRosterItem(const RosterItem & item)947 void Client::importRosterItem(const RosterItem &item)
948 {
949 	QString substr;
950 	switch(item.subscription().type()) {
951 		case Subscription::Both:
952 			substr = "<-->";  break;
953 		case Subscription::From:
954 			substr = "  ->";  break;
955 		case Subscription::To:
956 			substr = "<-  ";  break;
957 		case Subscription::Remove:
958 			substr = "xxxx";  break;
959 		case Subscription::None:
960 		default:
961 			substr = "----";  break;
962 	}
963 
964 	QString dstr, str;
965 	str.sprintf("  %s %-32s", qPrintable(substr), qPrintable(item.jid().full()));
966 	if(!item.name().isEmpty())
967 		str += QString(" [") + item.name() + "]";
968 	str += '\n';
969 
970 	// Remove
971 	if(item.subscription().type() == Subscription::Remove) {
972 		LiveRoster::Iterator it = d->roster.find(item.jid());
973 		if(it != d->roster.end()) {
974 			emit rosterItemRemoved(*it);
975 			d->roster.erase(it);
976 		}
977 		dstr = "Client: (Removed) ";
978 	}
979 	// Add/Update
980 	else {
981 		LiveRoster::Iterator it = d->roster.find(item.jid());
982 		if(it != d->roster.end()) {
983 			LiveRosterItem &i = *it;
984 			i.setFlagForDelete(false);
985 			i.setRosterItem(item);
986 			emit rosterItemUpdated(i);
987 			dstr = "Client: (Updated) ";
988                 }
989 		else {
990 			LiveRosterItem i(item);
991 			d->roster += i;
992 
993 			// signal it
994 			emit rosterItemAdded(i);
995 			dstr = "Client: (Added)   ";
996 		}
997 	}
998 
999 	debug(dstr + str);
1000 }
1001 
sendMessage(const Message & m)1002 void Client::sendMessage(const Message &m)
1003 {
1004 	JT_Message *j = new JT_Message(rootTask(), m);
1005 	j->go(true);
1006 }
1007 
sendSubscription(const Jid & jid,const QString & type,const QString & nick)1008 void Client::sendSubscription(const Jid &jid, const QString &type, const QString& nick)
1009 {
1010 	JT_Presence *j = new JT_Presence(rootTask());
1011 	j->sub(jid, type, nick);
1012 	j->go(true);
1013 }
1014 
setPresence(const Status & s)1015 void Client::setPresence(const Status &s)
1016 {
1017 	if (d->capsman->isEnabled()) {
1018 		if (d->caps.version().isEmpty() && !d->caps.node().isEmpty()) {
1019 			d->caps = CapsSpec(makeDiscoResult(d->caps.node())); /* recompute caps hash */
1020 		}
1021 	}
1022 
1023 	JT_Presence *j = new JT_Presence(rootTask());
1024 	j->pres(s);
1025 	j->go(true);
1026 
1027 	// update our resourceList
1028 	ppPresence(jid(), s);
1029 	//ResourceList::Iterator rit = d->resourceList.find(resource());
1030 	//Resource &r = *rit;
1031 	//r.setStatus(s);
1032 }
1033 
OSName() const1034 QString Client::OSName() const
1035 {
1036 	return d->osName;
1037 }
1038 
OSVersion() const1039 QString Client::OSVersion() const
1040 {
1041 	return d->osVersion;
1042 }
1043 
timeZone() const1044 QString Client::timeZone() const
1045 {
1046 	return d->tzname;
1047 }
1048 
timeZoneOffset() const1049 int Client::timeZoneOffset() const
1050 {
1051 	return d->tzoffset;
1052 }
1053 
1054 /**
1055   \brief Returns true if Client is using old, manual time zone conversions.
1056 
1057   By default, conversions between UTC and local time are done automatically by Qt.
1058   In this mode, manualTimeZoneOffset() returns true,
1059   and timeZoneOffset() always retuns 0 (so you shouldn't use that function).
1060 
1061   However, if you call setTimeZone(), Client instance switches to old mode
1062   and uses given time zone offset for all calculations.
1063   */
manualTimeZoneOffset() const1064 bool Client::manualTimeZoneOffset() const
1065 {
1066 	return d->useTzoffset;
1067 }
1068 
clientName() const1069 QString Client::clientName() const
1070 {
1071 	return d->clientName;
1072 }
1073 
clientVersion() const1074 QString Client::clientVersion() const
1075 {
1076 	return d->clientVersion;
1077 }
1078 
caps() const1079 CapsSpec Client::caps() const
1080 {
1081 	return d->caps;
1082 }
1083 
serverCaps() const1084 CapsSpec Client::serverCaps() const
1085 {
1086 	return d->serverCaps;
1087 }
1088 
setOSName(const QString & name)1089 void Client::setOSName(const QString &name)
1090 {
1091 	d->osName = name;
1092 }
1093 
setOSVersion(const QString & version)1094 void Client::setOSVersion(const QString &version)
1095 {
1096 	d->osVersion = version;
1097 }
1098 
setTimeZone(const QString & name,int offset)1099 void Client::setTimeZone(const QString &name, int offset)
1100 {
1101 	d->tzname = name;
1102 	d->tzoffset = offset;
1103 	d->useTzoffset = true;
1104 }
1105 
setClientName(const QString & s)1106 void Client::setClientName(const QString &s)
1107 {
1108 	d->clientName = s;
1109 }
1110 
setClientVersion(const QString & s)1111 void Client::setClientVersion(const QString &s)
1112 {
1113 	d->clientVersion = s;
1114 }
1115 
setCaps(const CapsSpec & s)1116 void Client::setCaps(const CapsSpec &s)
1117 {
1118 	d->caps = s;
1119 }
1120 
identity() const1121 DiscoItem::Identity Client::identity() const
1122 {
1123 	return d->identity;
1124 }
1125 
setIdentity(const DiscoItem::Identity & identity)1126 void Client::setIdentity(const DiscoItem::Identity &identity)
1127 {
1128 	if (!(d->identity == identity)) {
1129 		d->caps.resetVersion();
1130 	}
1131 	d->identity = identity;
1132 }
1133 
setFeatures(const Features & f)1134 void Client::setFeatures(const Features& f)
1135 {
1136 	if (!(d->features == f)) {
1137 		d->caps.resetVersion();
1138 	}
1139 	d->features = f;
1140 }
1141 
features() const1142 const Features& Client::features() const
1143 {
1144 	return d->features;
1145 }
1146 
makeDiscoResult(const QString & node) const1147 DiscoItem Client::makeDiscoResult(const QString &node) const
1148 {
1149 	DiscoItem item;
1150 	item.setNode(node);
1151 	DiscoItem::Identity id = identity();
1152 	if (id.category.isEmpty() || id.type.isEmpty()) {
1153 		id.category = "client";
1154 		id.type = "pc";
1155 	}
1156 	item.setIdentities(id);
1157 
1158 	Features features;
1159 
1160 	if (d->ftman) {
1161 		features.addFeature("http://jabber.org/protocol/bytestreams");
1162 		features.addFeature("http://jabber.org/protocol/ibb");
1163 		features.addFeature("http://jabber.org/protocol/si");
1164 		features.addFeature("http://jabber.org/protocol/si/profile/file-transfer");
1165 	}
1166 	features.addFeature("http://jabber.org/protocol/disco#info");
1167 	features.addFeature("jabber:x:data");
1168 	features.addFeature("urn:xmpp:bob");
1169 	features.addFeature("urn:xmpp:ping");
1170 	features.addFeature("urn:xmpp:time");
1171 	features.addFeature("urn:xmpp:message-correct:0");
1172 
1173 	// Client-specific features
1174 	foreach (const QString & i, d->features.list()) {
1175 		features.addFeature(i);
1176 	}
1177 
1178 	item.setFeatures(features);
1179 
1180 	// xep-0232 Software Information
1181 	XData si;
1182 	XData::FieldList si_fields;
1183 
1184 	XData::Field si_type_field;
1185 	si_type_field.setType(XData::Field::Field_Hidden);
1186 	si_type_field.setVar("FORM_TYPE");
1187 	si_type_field.setValue(QStringList(QLatin1String("urn:xmpp:dataforms:softwareinfo")));
1188 	si_fields.append(si_type_field);
1189 
1190 	XData::Field software_field;
1191 	software_field.setType(XData::Field::Field_TextSingle);
1192 	software_field.setVar("software");
1193 	software_field.setValue(QStringList(d->clientName));
1194 	si_fields.append(software_field);
1195 
1196 	XData::Field software_v_field;
1197 	software_v_field.setType(XData::Field::Field_TextSingle);
1198 	software_v_field.setVar("software_version");
1199 	software_v_field.setValue(QStringList(d->clientVersion));
1200 	si_fields.append(software_v_field);
1201 
1202 	XData::Field os_field;
1203 	os_field.setType(XData::Field::Field_TextSingle);
1204 	os_field.setVar("os");
1205 	os_field.setValue(QStringList(d->osName));
1206 	si_fields.append(os_field);
1207 
1208 	XData::Field os_v_field;
1209 	os_v_field.setType(XData::Field::Field_TextSingle);
1210 	os_v_field.setVar("os_version");
1211 	os_v_field.setValue(QStringList(d->osVersion));
1212 	si_fields.append(os_v_field);
1213 
1214 	si.setType(XData::Data_Result);
1215 	si.setFields(si_fields);
1216 
1217 	item.setExtensions(QList<XData>() << si);
1218 
1219 	return item;
1220 }
1221 
s5b_incomingReady()1222 void Client::s5b_incomingReady()
1223 {
1224 	handleIncoming(d->s5bman->takeIncoming());
1225 }
1226 
ibb_incomingReady()1227 void Client::ibb_incomingReady()
1228 {
1229 	handleIncoming(d->ibbman->takeIncoming());
1230 }
1231 
handleIncoming(BSConnection * c)1232 void Client::handleIncoming(BSConnection *c)
1233 {
1234 	if(!c)
1235 		return;
1236 	if(!d->ftman) {
1237 		c->close();
1238 		c->deleteLater();
1239 		return;
1240 	}
1241 	d->ftman->stream_incomingReady(c);
1242 }
1243 
handleSMAckResponse(int h)1244 void Client::handleSMAckResponse(int h) {
1245 	qDebug() << "handleSMAckResponse: h = " << h;
1246 }
1247 
1248 //---------------------------------------------------------------------------
1249 // LiveRosterItem
1250 //---------------------------------------------------------------------------
LiveRosterItem(const Jid & jid)1251 LiveRosterItem::LiveRosterItem(const Jid &jid)
1252 :RosterItem(jid)
1253 {
1254 	setFlagForDelete(false);
1255 }
1256 
LiveRosterItem(const RosterItem & i)1257 LiveRosterItem::LiveRosterItem(const RosterItem &i)
1258 {
1259 	setRosterItem(i);
1260 	setFlagForDelete(false);
1261 }
1262 
~LiveRosterItem()1263 LiveRosterItem::~LiveRosterItem()
1264 {
1265 }
1266 
setRosterItem(const RosterItem & i)1267 void LiveRosterItem::setRosterItem(const RosterItem &i)
1268 {
1269 	setJid(i.jid());
1270 	setName(i.name());
1271 	setGroups(i.groups());
1272 	setSubscription(i.subscription());
1273 	setAsk(i.ask());
1274 	setIsPush(i.isPush());
1275 }
1276 
resourceList()1277 ResourceList & LiveRosterItem::resourceList()
1278 {
1279 	return v_resourceList;
1280 }
1281 
priority()1282 ResourceList::Iterator LiveRosterItem::priority()
1283 {
1284 	return v_resourceList.priority();
1285 }
1286 
resourceList() const1287 const ResourceList & LiveRosterItem::resourceList() const
1288 {
1289 	return v_resourceList;
1290 }
1291 
priority() const1292 ResourceList::ConstIterator LiveRosterItem::priority() const
1293 {
1294 	return v_resourceList.priority();
1295 }
1296 
isAvailable() const1297 bool LiveRosterItem::isAvailable() const
1298 {
1299 	if(v_resourceList.count() > 0)
1300 		return true;
1301 	return false;
1302 }
1303 
lastUnavailableStatus() const1304 const Status & LiveRosterItem::lastUnavailableStatus() const
1305 {
1306 	return v_lastUnavailableStatus;
1307 }
1308 
flagForDelete() const1309 bool LiveRosterItem::flagForDelete() const
1310 {
1311 	return v_flagForDelete;
1312 }
1313 
setLastUnavailableStatus(const Status & s)1314 void LiveRosterItem::setLastUnavailableStatus(const Status &s)
1315 {
1316 	v_lastUnavailableStatus = s;
1317 }
1318 
setFlagForDelete(bool b)1319 void LiveRosterItem::setFlagForDelete(bool b)
1320 {
1321 	v_flagForDelete = b;
1322 }
1323 
1324 //---------------------------------------------------------------------------
1325 // LiveRoster
1326 //---------------------------------------------------------------------------
LiveRoster()1327 LiveRoster::LiveRoster()
1328 :QList<LiveRosterItem>()
1329 {
1330 }
1331 
~LiveRoster()1332 LiveRoster::~LiveRoster()
1333 {
1334 }
1335 
flagAllForDelete()1336 void LiveRoster::flagAllForDelete()
1337 {
1338 	for(Iterator it = begin(); it != end(); ++it)
1339 		(*it).setFlagForDelete(true);
1340 }
1341 
find(const Jid & j,bool compareRes)1342 LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes)
1343 {
1344 	Iterator it;
1345 	for(it = begin(); it != end(); ++it) {
1346 		if((*it).jid().compare(j, compareRes))
1347 			break;
1348 	}
1349 	return it;
1350 }
1351 
find(const Jid & j,bool compareRes) const1352 LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const
1353 {
1354 	ConstIterator it;
1355 	for(it = begin(); it != end(); ++it) {
1356 		if((*it).jid().compare(j, compareRes))
1357 			break;
1358 	}
1359 	return it;
1360 }
1361 
1362 }
1363 
1364 #include "moc_xmpp_client.cpp"
1365