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