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