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 
hasStream() const655 bool Client::hasStream() const
656 {
657 	return !!d->stream;
658 }
659 
stream()660 Stream & Client::stream()
661 {
662 	return *(d->stream.data());
663 }
664 
streamBaseNS() const665 QString Client::streamBaseNS() const
666 {
667 	return d->stream->baseNS();
668 }
669 
roster() const670 const LiveRoster & Client::roster() const
671 {
672 	return d->roster;
673 }
674 
resourceList() const675 const ResourceList & Client::resourceList() const
676 {
677 	return d->resourceList;
678 }
679 
host() const680 QString Client::host() const
681 {
682 	return d->host;
683 }
684 
user() const685 QString Client::user() const
686 {
687 	return d->user;
688 }
689 
pass() const690 QString Client::pass() const
691 {
692 	return d->pass;
693 }
694 
resource() const695 QString Client::resource() const
696 {
697 	return d->resource;
698 }
699 
jid() const700 Jid Client::jid() const
701 {
702 	QString s;
703 	if(!d->user.isEmpty())
704 		s += d->user + '@';
705 	s += d->host;
706 	if(!d->resource.isEmpty()) {
707 		s += '/';
708 		s += d->resource;
709 	}
710 
711 	return Jid(s);
712 }
713 
ppSubscription(const Jid & j,const QString & s,const QString & n)714 void Client::ppSubscription(const Jid &j, const QString &s, const QString& n)
715 {
716 	emit subscription(j, s, n);
717 }
718 
ppPresence(const Jid & j,const Status & s)719 void Client::ppPresence(const Jid &j, const Status &s)
720 {
721 	if(s.isAvailable())
722 		debug(QString("Client: %1 is available.\n").arg(j.full()));
723 	else
724 		debug(QString("Client: %1 is unavailable.\n").arg(j.full()));
725 
726 	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
727 		GroupChat &i = *it;
728 
729 		if(i.j.compare(j, false)) {
730 			bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false;
731 
732 			debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us));
733 			switch(i.status) {
734 				case GroupChat::Connecting:
735 					if(us && s.hasError()) {
736 						Jid j = i.j;
737 						d->groupChatList.erase(it);
738 						emit groupChatError(j, s.errorCode(), s.errorString());
739 					}
740 					else {
741 						// don't signal success unless it is a non-error presence
742 						if(!s.hasError()) {
743 							i.status = GroupChat::Connected;
744 							emit groupChatJoined(i.j);
745 						}
746 						emit groupChatPresence(j, s);
747 					}
748 					break;
749 				case GroupChat::Connected:
750 					emit groupChatPresence(j, s);
751 					break;
752 				case GroupChat::Closing:
753 					if(us && !s.isAvailable()) {
754 						Jid j = i.j;
755 						d->groupChatList.erase(it);
756 						emit groupChatLeft(j);
757 					}
758 					break;
759 				default:
760 					break;
761 			}
762 
763 			return;
764 		}
765 	}
766 
767 	if(s.hasError()) {
768 		emit presenceError(j, s.errorCode(), s.errorString());
769 		return;
770 	}
771 
772 	// is it me?
773 	if(j.compare(jid(), false)) {
774 		updateSelfPresence(j, s);
775 	}
776 	else {
777 		// update all relavent roster entries
778 		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) {
779 			LiveRosterItem &i = *it;
780 
781 			if(!i.jid().compare(j, false))
782 				continue;
783 
784 			// roster item has its own resource?
785 			if(!i.jid().resource().isEmpty()) {
786 				if(i.jid().resource() != j.resource())
787 					continue;
788 			}
789 
790 			updatePresence(&i, j, s);
791 		}
792 	}
793 }
794 
updateSelfPresence(const Jid & j,const Status & s)795 void Client::updateSelfPresence(const Jid &j, const Status &s)
796 {
797 	ResourceList::Iterator rit = d->resourceList.find(j.resource());
798 	bool found = (rit == d->resourceList.end()) ? false: true;
799 
800 	// unavailable?  remove the resource
801 	if(!s.isAvailable()) {
802 		if(found) {
803 			debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource()));
804 			(*rit).setStatus(s);
805 			emit resourceUnavailable(j, *rit);
806 			d->resourceList.erase(rit);
807 		}
808 	}
809 	// available?  add/update the resource
810 	else {
811 		Resource r;
812 		if(!found) {
813 			r = Resource(j.resource(), s);
814 			d->resourceList += r;
815 			debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource()));
816 		}
817 		else {
818 			(*rit).setStatus(s);
819 			r = *rit;
820 			debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource()));
821 		}
822 
823 		emit resourceAvailable(j, r);
824 	}
825 }
826 
updatePresence(LiveRosterItem * i,const Jid & j,const Status & s)827 void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s)
828 {
829 	ResourceList::Iterator rit = i->resourceList().find(j.resource());
830 	bool found = (rit == i->resourceList().end()) ? false: true;
831 
832 	// unavailable?  remove the resource
833 	if(!s.isAvailable()) {
834 		if(found) {
835 			(*rit).setStatus(s);
836 			debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
837 			emit resourceUnavailable(j, *rit);
838 			i->resourceList().erase(rit);
839 			i->setLastUnavailableStatus(s);
840 		}
841 		else {
842 			// create the resource just for the purpose of emit
843 			Resource r = Resource(j.resource(), s);
844 			i->resourceList() += r;
845 			rit = i->resourceList().find(j.resource());
846 			emit resourceUnavailable(j, *rit);
847 			i->resourceList().erase(rit);
848 			i->setLastUnavailableStatus(s);
849 		}
850 	}
851 	// available?  add/update the resource
852 	else {
853 		Resource r;
854 		if(!found) {
855 			r = Resource(j.resource(), s);
856 			i->resourceList() += r;
857 			debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
858 		}
859 		else {
860 			(*rit).setStatus(s);
861 			r = *rit;
862 			debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
863 		}
864 
865 		emit resourceAvailable(j, r);
866 	}
867 }
868 
pmMessage(const Message & m)869 void Client::pmMessage(const Message &m)
870 {
871 	debug(QString("Client: Message from %1\n").arg(m.from().full()));
872 
873 	// bits of binary. we can't do this in Message, since it knows nothing about Client
874 	foreach (const BoBData &b, m.bobDataList()) {
875 		d->bobman->append(b);
876 	}
877 
878 	if (!m.ibbData().data.isEmpty()) {
879 		d->ibbman->takeIncomingData(m.from(), m.id(), m.ibbData(), Stanza::Message);
880 	}
881 
882 	if(m.type() == "groupchat") {
883 		for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
884 			const GroupChat &i = *it;
885 
886 			if(!i.j.compare(m.from(), false))
887 				continue;
888 
889 			if(i.status == GroupChat::Connected)
890 				messageReceived(m);
891 		}
892 	}
893 	else
894 		messageReceived(m);
895 }
896 
prRoster(const Roster & r)897 void Client::prRoster(const Roster &r)
898 {
899 	importRoster(r);
900 }
901 
rosterRequest()902 void Client::rosterRequest()
903 {
904 	if(!d->active)
905 		return;
906 
907 	JT_Roster *r = new JT_Roster(rootTask());
908 	connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished()));
909 	r->get();
910 	d->roster.flagAllForDelete(); // mod_groups patch
911 	r->go(true);
912 }
913 
slotRosterRequestFinished()914 void Client::slotRosterRequestFinished()
915 {
916 	JT_Roster *r = (JT_Roster *)sender();
917 	// on success, let's take it
918 	if(r->success()) {
919 		//d->roster.flagAllForDelete(); // mod_groups patch
920 
921 		importRoster(r->roster());
922 
923 		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) {
924 			LiveRosterItem &i = *it;
925 			if(i.flagForDelete()) {
926 				emit rosterItemRemoved(i);
927 				it = d->roster.erase(it);
928 			}
929 			else
930 				++it;
931 		}
932 	}
933 	else {
934 		// don't report a disconnect.  Client::error() will do that.
935 		if(r->statusCode() == Task::ErrDisc)
936 			return;
937 	}
938 
939 	// report success / fail
940 	emit rosterRequestFinished(r->success(), r->statusCode(), r->statusString());
941 }
942 
importRoster(const Roster & r)943 void Client::importRoster(const Roster &r)
944 {
945 	emit beginImportRoster();
946 	for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) {
947 		importRosterItem(*it);
948 	}
949 	emit endImportRoster();
950 }
951 
importRosterItem(const RosterItem & item)952 void Client::importRosterItem(const RosterItem &item)
953 {
954 	QString substr;
955 	switch(item.subscription().type()) {
956 		case Subscription::Both:
957 			substr = "<-->";  break;
958 		case Subscription::From:
959 			substr = "  ->";  break;
960 		case Subscription::To:
961 			substr = "<-  ";  break;
962 		case Subscription::Remove:
963 			substr = "xxxx";  break;
964 		case Subscription::None:
965 		default:
966 			substr = "----";  break;
967 	}
968 
969 	QString dstr, str;
970 	str.sprintf("  %s %-32s", qPrintable(substr), qPrintable(item.jid().full()));
971 	if(!item.name().isEmpty())
972 		str += QString(" [") + item.name() + "]";
973 	str += '\n';
974 
975 	// Remove
976 	if(item.subscription().type() == Subscription::Remove) {
977 		LiveRoster::Iterator it = d->roster.find(item.jid());
978 		if(it != d->roster.end()) {
979 			emit rosterItemRemoved(*it);
980 			d->roster.erase(it);
981 		}
982 		dstr = "Client: (Removed) ";
983 	}
984 	// Add/Update
985 	else {
986 		LiveRoster::Iterator it = d->roster.find(item.jid());
987 		if(it != d->roster.end()) {
988 			LiveRosterItem &i = *it;
989 			i.setFlagForDelete(false);
990 			i.setRosterItem(item);
991 			emit rosterItemUpdated(i);
992 			dstr = "Client: (Updated) ";
993                 }
994 		else {
995 			LiveRosterItem i(item);
996 			d->roster += i;
997 
998 			// signal it
999 			emit rosterItemAdded(i);
1000 			dstr = "Client: (Added)   ";
1001 		}
1002 	}
1003 
1004 	debug(dstr + str);
1005 }
1006 
sendMessage(const Message & m)1007 void Client::sendMessage(const Message &m)
1008 {
1009 	JT_Message *j = new JT_Message(rootTask(), m);
1010 	j->go(true);
1011 }
1012 
sendSubscription(const Jid & jid,const QString & type,const QString & nick)1013 void Client::sendSubscription(const Jid &jid, const QString &type, const QString& nick)
1014 {
1015 	JT_Presence *j = new JT_Presence(rootTask());
1016 	j->sub(jid, type, nick);
1017 	j->go(true);
1018 }
1019 
setPresence(const Status & s)1020 void Client::setPresence(const Status &s)
1021 {
1022 	if (d->capsman->isEnabled()) {
1023 		if (d->caps.version().isEmpty() && !d->caps.node().isEmpty()) {
1024 			d->caps = CapsSpec(makeDiscoResult(d->caps.node())); /* recompute caps hash */
1025 		}
1026 	}
1027 
1028 	JT_Presence *j = new JT_Presence(rootTask());
1029 	j->pres(s);
1030 	j->go(true);
1031 
1032 	// update our resourceList
1033 	ppPresence(jid(), s);
1034 	//ResourceList::Iterator rit = d->resourceList.find(resource());
1035 	//Resource &r = *rit;
1036 	//r.setStatus(s);
1037 }
1038 
OSName() const1039 QString Client::OSName() const
1040 {
1041 	return d->osName;
1042 }
1043 
OSVersion() const1044 QString Client::OSVersion() const
1045 {
1046 	return d->osVersion;
1047 }
1048 
timeZone() const1049 QString Client::timeZone() const
1050 {
1051 	return d->tzname;
1052 }
1053 
timeZoneOffset() const1054 int Client::timeZoneOffset() const
1055 {
1056 	return d->tzoffset;
1057 }
1058 
1059 /**
1060   \brief Returns true if Client is using old, manual time zone conversions.
1061 
1062   By default, conversions between UTC and local time are done automatically by Qt.
1063   In this mode, manualTimeZoneOffset() returns true,
1064   and timeZoneOffset() always retuns 0 (so you shouldn't use that function).
1065 
1066   However, if you call setTimeZone(), Client instance switches to old mode
1067   and uses given time zone offset for all calculations.
1068   */
manualTimeZoneOffset() const1069 bool Client::manualTimeZoneOffset() const
1070 {
1071 	return d->useTzoffset;
1072 }
1073 
clientName() const1074 QString Client::clientName() const
1075 {
1076 	return d->clientName;
1077 }
1078 
clientVersion() const1079 QString Client::clientVersion() const
1080 {
1081 	return d->clientVersion;
1082 }
1083 
caps() const1084 CapsSpec Client::caps() const
1085 {
1086 	return d->caps;
1087 }
1088 
serverCaps() const1089 CapsSpec Client::serverCaps() const
1090 {
1091 	return d->serverCaps;
1092 }
1093 
setOSName(const QString & name)1094 void Client::setOSName(const QString &name)
1095 {
1096 	d->osName = name;
1097 }
1098 
setOSVersion(const QString & version)1099 void Client::setOSVersion(const QString &version)
1100 {
1101 	d->osVersion = version;
1102 }
1103 
setTimeZone(const QString & name,int offset)1104 void Client::setTimeZone(const QString &name, int offset)
1105 {
1106 	d->tzname = name;
1107 	d->tzoffset = offset;
1108 	d->useTzoffset = true;
1109 }
1110 
setClientName(const QString & s)1111 void Client::setClientName(const QString &s)
1112 {
1113 	d->clientName = s;
1114 }
1115 
setClientVersion(const QString & s)1116 void Client::setClientVersion(const QString &s)
1117 {
1118 	d->clientVersion = s;
1119 }
1120 
setCaps(const CapsSpec & s)1121 void Client::setCaps(const CapsSpec &s)
1122 {
1123 	d->caps = s;
1124 }
1125 
identity() const1126 DiscoItem::Identity Client::identity() const
1127 {
1128 	return d->identity;
1129 }
1130 
setIdentity(const DiscoItem::Identity & identity)1131 void Client::setIdentity(const DiscoItem::Identity &identity)
1132 {
1133 	if (!(d->identity == identity)) {
1134 		d->caps.resetVersion();
1135 	}
1136 	d->identity = identity;
1137 }
1138 
setFeatures(const Features & f)1139 void Client::setFeatures(const Features& f)
1140 {
1141 	if (!(d->features == f)) {
1142 		d->caps.resetVersion();
1143 	}
1144 	d->features = f;
1145 }
1146 
features() const1147 const Features& Client::features() const
1148 {
1149 	return d->features;
1150 }
1151 
makeDiscoResult(const QString & node) const1152 DiscoItem Client::makeDiscoResult(const QString &node) const
1153 {
1154 	DiscoItem item;
1155 	item.setNode(node);
1156 	DiscoItem::Identity id = identity();
1157 	if (id.category.isEmpty() || id.type.isEmpty()) {
1158 		id.category = "client";
1159 		id.type = "pc";
1160 	}
1161 	item.setIdentities(id);
1162 
1163 	Features features;
1164 
1165 	if (d->ftman) {
1166 		features.addFeature("http://jabber.org/protocol/bytestreams");
1167 		features.addFeature("http://jabber.org/protocol/ibb");
1168 		features.addFeature("http://jabber.org/protocol/si");
1169 		features.addFeature("http://jabber.org/protocol/si/profile/file-transfer");
1170 	}
1171 	features.addFeature("http://jabber.org/protocol/disco#info");
1172 	features.addFeature("jabber:x:data");
1173 	features.addFeature("urn:xmpp:bob");
1174 	features.addFeature("urn:xmpp:ping");
1175 	features.addFeature("urn:xmpp:time");
1176 	features.addFeature("urn:xmpp:message-correct:0");
1177 
1178 	// Client-specific features
1179 	foreach (const QString & i, d->features.list()) {
1180 		features.addFeature(i);
1181 	}
1182 
1183 	item.setFeatures(features);
1184 
1185 	// xep-0232 Software Information
1186 	XData si;
1187 	XData::FieldList si_fields;
1188 
1189 	XData::Field si_type_field;
1190 	si_type_field.setType(XData::Field::Field_Hidden);
1191 	si_type_field.setVar("FORM_TYPE");
1192 	si_type_field.setValue(QStringList(QLatin1String("urn:xmpp:dataforms:softwareinfo")));
1193 	si_fields.append(si_type_field);
1194 
1195 	XData::Field software_field;
1196 	software_field.setType(XData::Field::Field_TextSingle);
1197 	software_field.setVar("software");
1198 	software_field.setValue(QStringList(d->clientName));
1199 	si_fields.append(software_field);
1200 
1201 	XData::Field software_v_field;
1202 	software_v_field.setType(XData::Field::Field_TextSingle);
1203 	software_v_field.setVar("software_version");
1204 	software_v_field.setValue(QStringList(d->clientVersion));
1205 	si_fields.append(software_v_field);
1206 
1207 	XData::Field os_field;
1208 	os_field.setType(XData::Field::Field_TextSingle);
1209 	os_field.setVar("os");
1210 	os_field.setValue(QStringList(d->osName));
1211 	si_fields.append(os_field);
1212 
1213 	XData::Field os_v_field;
1214 	os_v_field.setType(XData::Field::Field_TextSingle);
1215 	os_v_field.setVar("os_version");
1216 	os_v_field.setValue(QStringList(d->osVersion));
1217 	si_fields.append(os_v_field);
1218 
1219 	si.setType(XData::Data_Result);
1220 	si.setFields(si_fields);
1221 
1222 	item.setExtensions(QList<XData>() << si);
1223 
1224 	return item;
1225 }
1226 
s5b_incomingReady()1227 void Client::s5b_incomingReady()
1228 {
1229 	handleIncoming(d->s5bman->takeIncoming());
1230 }
1231 
ibb_incomingReady()1232 void Client::ibb_incomingReady()
1233 {
1234 	handleIncoming(d->ibbman->takeIncoming());
1235 }
1236 
handleIncoming(BSConnection * c)1237 void Client::handleIncoming(BSConnection *c)
1238 {
1239 	if(!c)
1240 		return;
1241 	if(!d->ftman) {
1242 		c->close();
1243 		c->deleteLater();
1244 		return;
1245 	}
1246 	d->ftman->stream_incomingReady(c);
1247 }
1248 
handleSMAckResponse(int h)1249 void Client::handleSMAckResponse(int h) {
1250 	qDebug() << "handleSMAckResponse: h = " << h;
1251 }
1252 
1253 //---------------------------------------------------------------------------
1254 // LiveRosterItem
1255 //---------------------------------------------------------------------------
LiveRosterItem(const Jid & jid)1256 LiveRosterItem::LiveRosterItem(const Jid &jid)
1257 :RosterItem(jid)
1258 {
1259 	setFlagForDelete(false);
1260 }
1261 
LiveRosterItem(const RosterItem & i)1262 LiveRosterItem::LiveRosterItem(const RosterItem &i)
1263 {
1264 	setRosterItem(i);
1265 	setFlagForDelete(false);
1266 }
1267 
~LiveRosterItem()1268 LiveRosterItem::~LiveRosterItem()
1269 {
1270 }
1271 
setRosterItem(const RosterItem & i)1272 void LiveRosterItem::setRosterItem(const RosterItem &i)
1273 {
1274 	setJid(i.jid());
1275 	setName(i.name());
1276 	setGroups(i.groups());
1277 	setSubscription(i.subscription());
1278 	setAsk(i.ask());
1279 	setIsPush(i.isPush());
1280 }
1281 
resourceList()1282 ResourceList & LiveRosterItem::resourceList()
1283 {
1284 	return v_resourceList;
1285 }
1286 
priority()1287 ResourceList::Iterator LiveRosterItem::priority()
1288 {
1289 	return v_resourceList.priority();
1290 }
1291 
resourceList() const1292 const ResourceList & LiveRosterItem::resourceList() const
1293 {
1294 	return v_resourceList;
1295 }
1296 
priority() const1297 ResourceList::ConstIterator LiveRosterItem::priority() const
1298 {
1299 	return v_resourceList.priority();
1300 }
1301 
isAvailable() const1302 bool LiveRosterItem::isAvailable() const
1303 {
1304 	if(v_resourceList.count() > 0)
1305 		return true;
1306 	return false;
1307 }
1308 
lastUnavailableStatus() const1309 const Status & LiveRosterItem::lastUnavailableStatus() const
1310 {
1311 	return v_lastUnavailableStatus;
1312 }
1313 
flagForDelete() const1314 bool LiveRosterItem::flagForDelete() const
1315 {
1316 	return v_flagForDelete;
1317 }
1318 
setLastUnavailableStatus(const Status & s)1319 void LiveRosterItem::setLastUnavailableStatus(const Status &s)
1320 {
1321 	v_lastUnavailableStatus = s;
1322 }
1323 
setFlagForDelete(bool b)1324 void LiveRosterItem::setFlagForDelete(bool b)
1325 {
1326 	v_flagForDelete = b;
1327 }
1328 
1329 //---------------------------------------------------------------------------
1330 // LiveRoster
1331 //---------------------------------------------------------------------------
LiveRoster()1332 LiveRoster::LiveRoster()
1333 :QList<LiveRosterItem>()
1334 {
1335 }
1336 
~LiveRoster()1337 LiveRoster::~LiveRoster()
1338 {
1339 }
1340 
flagAllForDelete()1341 void LiveRoster::flagAllForDelete()
1342 {
1343 	for(Iterator it = begin(); it != end(); ++it)
1344 		(*it).setFlagForDelete(true);
1345 }
1346 
find(const Jid & j,bool compareRes)1347 LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes)
1348 {
1349 	Iterator it;
1350 	for(it = begin(); it != end(); ++it) {
1351 		if((*it).jid().compare(j, compareRes))
1352 			break;
1353 	}
1354 	return it;
1355 }
1356 
find(const Jid & j,bool compareRes) const1357 LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const
1358 {
1359 	ConstIterator it;
1360 	for(it = begin(); it != end(); ++it) {
1361 		if((*it).jid().compare(j, compareRes))
1362 			break;
1363 	}
1364 	return it;
1365 }
1366 
1367 }
1368