client.cpp 29.7 KB
Newer Older
1
/*
2
 * client.cpp - IM Client
3
 * Copyright (C) 2003  Justin Karneges
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
//!  authenticate, manipulate the roster, and send / receive messages and
//!  presence.  It is the centerpiece of this library, and all Tasks must pass
//!  through it.
//!
//!  For convenience, many Tasks are handled internally to Client (such as
//!  JT_Auth).  However, for accessing features beyond the basics provided by
//!  Client, you will need to manually invoke Tasks.  Fortunately, the
//!  process is very simple.
//!
//!  The entire Task system is heavily founded on Qt.  All Tasks have a parent,
//!  except for the root Task, and are considered QObjects.  By using Qt's RTTI
//!  facilities (QObject::sender(), QObject::isA(), etc), you can use a
//!  "fire and forget" approach with Tasks.
//!
//!  \code
//!  #include "client.h"
41
//!  using namespace XMPP;
42 43 44 45 46 47 48 49 50
//!
//!  ...
//!
//!  Client *client;
//!
//!  Session::Session()
//!  {
//!    client = new Client;
//!    connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken()));
51
//!    connect(client, SIGNAL(authFinished(bool,int,QString)), SLOT(authFinished(bool,int,QString)));
52 53 54 55 56 57 58 59 60 61 62 63 64
//!    client->connectToHost("jabber.org");
//!  }
//!
//!  void Session::clientHandshaken()
//!  {
//!    client->authDigest("jabtest", "12345", "Psi");
//!  }
//!
//!  void Session::authFinished(bool success, int, const QString &err)
//!  {
//!    if(success)
//!      printf("Login success!");
//!    else
65
//!      printf("Login failed.  Here's why: %s\n", err.toLatin1());
66 67 68
//!  }
//!  \endcode

69
#include <QObject>
70
#include <QMap>
71 72
#include <QTimer>
#include <QPointer>
73
#include <QList>
74 75 76

#include "im.h"
#include "safedelete.h"
77 78 79 80
#include "xmpp_tasks.h"
#include "xmpp_xmlcommon.h"
#include "s5b.h"
#include "xmpp_ibb.h"
81
#include "xmpp_bitsofbinary.h"
82
#include "filetransfer.h"
83 84
#include "xmpp_caps.h"
#include "protocol.h"
85

86
#ifdef Q_OS_WIN
87 88 89
#define vsnprintf _vsnprintf
#endif

90 91
namespace XMPP
{
92 93 94 95 96 97 98 99 100 101 102 103

//----------------------------------------------------------------------------
// Client
//----------------------------------------------------------------------------
class Client::GroupChat
{
public:
	enum { Connecting, Connected, Closing };
	GroupChat() {}

	Jid j;
	int status;
104
	QString password;
105 106 107 108 109 110 111
};

class Client::ClientPrivate
{
public:
	ClientPrivate() {}

112
	QPointer<ClientStream> stream;
113 114 115 116
	QDomDocument doc;
	int id_seed;
	Task *root;
	QString host, user, pass, resource;
117 118
	QString osName, osVersion, tzname, clientName, clientVersion;
	CapsSpec caps;
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 127

	LiveRoster roster;
	ResourceList resourceList;
128
	CapsManager *capsman;
129
	S5BManager *s5bman;
130
	IBBManager *ibbman;
131
	BoBManager *bobman;
132 133
	FileTransferManager *ftman;
	bool ftEnabled;
134
	QList<GroupChat> groupChatList;
135 136 137 138 139 140 141 142
};


Client::Client(QObject *par)
:QObject(par)
{
	d = new ClientPrivate;
	d->tzoffset = 0;
143
	d->useTzoffset = false;
144
	d->active = false;
145
	d->osName = "N/A";
146 147 148 149 150 151
	d->clientName = "N/A";
	d->clientVersion = "0.0";

	d->id_seed = 0xaaaa;
	d->root = new Task(this, true);

152 153
	d->s5bman = new S5BManager(this);
	connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady()));
154 155 156 157

	d->ibbman = new IBBManager(this);
	connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady()));

158 159
	d->bobman = new BoBManager(this);

160
	d->ftman = 0;
161 162

	d->capsman = new CapsManager(this);
163 164 165 166 167 168
}

Client::~Client()
{
	close(true);

169
	delete d->ftman;
170
	delete d->ibbman;
171
	delete d->s5bman;
172 173
	delete d->root;
	delete d;
174
	//fprintf(stderr, "\tClient::~Client\n");
175 176
}

177
void Client::connectToServer(ClientStream *s, const Jid &j, bool auth)
178
{
179 180 181 182
	d->stream = s;
	//connect(d->stream, SIGNAL(connected()), SLOT(streamConnected()));
	//connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken()));
	connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int)));
183
	//connect(d->stream, SIGNAL(sslCertificateReady(QSSLCert)), SLOT(streamSSLCertificateReady(QSSLCert)));
184 185
	connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead()));
	//connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished()));
186 187
	connect(d->stream, SIGNAL(incomingXml(QString)), SLOT(streamIncomingXml(QString)));
	connect(d->stream, SIGNAL(outgoingXml(QString)), SLOT(streamOutgoingXml(QString)));
188

189
	d->stream->connectToServer(j, auth);
190 191
}

192
void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource)
193
{
194 195 196 197 198
	// TODO
	d->host = host;
	d->user = user;
	d->pass = pass;
	d->resource = _resource;
199

200 201 202
	Status stat;
	stat.setIsAvailable(false);
	d->resourceList += Resource(resource(), stat);
203

204
	JT_PushPresence *pp = new JT_PushPresence(rootTask());
205 206
	connect(pp, SIGNAL(subscription(Jid,QString,QString)), SLOT(ppSubscription(Jid,QString,QString)));
	connect(pp, SIGNAL(presence(Jid,Status)), SLOT(ppPresence(Jid,Status)));
207 208

	JT_PushMessage *pm = new JT_PushMessage(rootTask());
209
	connect(pm, SIGNAL(message(Message)), SLOT(pmMessage(Message)));
210 211

	JT_PushRoster *pr = new JT_PushRoster(rootTask());
212
	connect(pr, SIGNAL(roster(Roster)), SLOT(prRoster(Roster)));
213 214

	new JT_ServInfo(rootTask());
215
	new JT_PongServer(rootTask());
216 217

	d->active = true;
218 219
}

220
void Client::setFileTransferEnabled(bool b)
221
{
222 223 224 225 226 227 228 229 230 231
	if(b) {
		if(!d->ftman)
			d->ftman = new FileTransferManager(this);
	}
	else {
		if(d->ftman) {
			delete d->ftman;
			d->ftman = 0;
		}
	}
232 233
}

234
FileTransferManager *Client::fileTransferManager() const
235
{
236
	return d->ftman;
237 238
}

239
S5BManager *Client::s5bManager() const
240
{
241
	return d->s5bman;
242 243
}

244
IBBManager *Client::ibbManager() const
245
{
246
	return d->ibbman;
247 248
}

249 250 251 252 253
BoBManager *Client::bobManager() const
{
	return d->bobman;
}

254 255 256 257 258
CapsManager *Client::capsManager() const
{
	return d->capsman;
}

259
bool Client::isActive() const
260
{
261
	return d->active;
262 263
}

264 265 266 267 268 269 270 271 272 273 274
QString Client::groupChatPassword(const QString& host, const QString& room) const
{
	Jid jid(room + "@" + host);
	foreach(GroupChat i, d->groupChatList) {
		if(i.j.compare(jid, false)) {
			return i.password;
		}
	}
	return QString();
}

275 276 277
void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s)
{
	Jid jid(room + "@" + host + "/" + nick);
278
	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
Till Gerken's avatar
Till Gerken committed
279
		GroupChat &i = *it;
280
		if(i.j.compare(jid, false)) {
Till Gerken's avatar
Till Gerken committed
281 282
			i.j = jid;

283 284 285 286 287 288
			Status s = _s;
			s.setIsAvailable(true);

			JT_Presence *j = new JT_Presence(rootTask());
			j->pres(jid, s);
			j->go(true);
289

290 291 292 293 294
			break;
		}
	}
}

295
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)
296 297
{
	Jid jid(room + "@" + host + "/" + nick);
298
	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) {
299 300 301 302
		GroupChat &i = *it;
		if(i.j.compare(jid, false)) {
			// if this room is shutting down, then free it up
			if(i.status == GroupChat::Closing)
303
				it = d->groupChatList.erase(it);
304 305 306 307 308 309 310 311 312 313 314
			else
				return false;
		}
		else
			++it;
	}

	debug(QString("Client: Joined: [%1]\n").arg(jid.full()));
	GroupChat i;
	i.j = jid;
	i.status = GroupChat::Connecting;
315
	i.password = password;
316 317 318
	d->groupChatList += i;

	JT_Presence *j = new JT_Presence(rootTask());
319 320
	Status s = _s;
	s.setMUC();
321
	s.setMUCHistory(maxchars, maxstanzas, seconds, since);
322 323
	if (!password.isEmpty()) {
		s.setMUCPassword(password);
324
	}
325
	j->pres(jid,s);
326 327 328 329 330
	j->go(true);

	return true;
}

331 332 333 334
void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s)
{
	Jid jid(room + "@" + host);
	bool found = false;
335
	foreach (const GroupChat &i, d->groupChatList) {
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
		if(i.j.compare(jid, false)) {
			found = true;
			jid = i.j;
			break;
		}
	}
	if(!found)
		return;

	Status s = _s;
	s.setIsAvailable(true);

	JT_Presence *j = new JT_Presence(rootTask());
	j->pres(jid, s);
	j->go(true);
}

353
void Client::groupChatLeave(const QString &host, const QString &room, const QString &statusStr)
354 355
{
	Jid jid(room + "@" + host);
356
	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
357 358 359 360 361 362 363 364 365 366 367
		GroupChat &i = *it;

		if(!i.j.compare(jid, false))
			continue;

		i.status = GroupChat::Closing;
		debug(QString("Client: Leaving: [%1]\n").arg(i.j.full()));

		JT_Presence *j = new JT_Presence(rootTask());
		Status s;
		s.setIsAvailable(false);
368
		s.setStatus(statusStr);
369 370 371 372 373
		j->pres(i.j, s);
		j->go(true);
	}
}

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
void Client::groupChatLeaveAll(const QString &statusStr)
{
	if (d->stream && d->active) {
		for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
			GroupChat &i = *it;
			i.status = GroupChat::Closing;

			JT_Presence *j = new JT_Presence(rootTask());
			Status s;
			s.setIsAvailable(false);
			s.setStatus(statusStr);
			j->pres(i.j, s);
			j->go(true);
		}
	}
}

QString Client::groupChatNick(const QString &host, const QString &room) const
{
	Jid jid(room + "@" + host);
	foreach (const GroupChat &gc, d->groupChatList) {
		if (gc.j.compare(jid, false)) {
			return gc.j.resource();
		}
	}
	return QString();
}

402 403 404 405 406 407 408 409
bool Client::isStreamManagementActive() const {
	ClientStream *cs = qobject_cast<ClientStream*>(d->stream);
	if(cs)
		return cs->isStreamManagementActive();
	return false;
}


410
/*void Client::start()
411
{
412 413 414 415 416 417 418 419 420
	if(d->stream->old()) {
		// old has no activation step
		d->active = true;
		activated();
	}
	else {
		// TODO: IM session
	}
}*/
421 422 423 424

// TODO: fast close
void Client::close(bool)
{
425 426 427 428
	if(d->stream) {
		d->stream->disconnect(this);
		d->stream->close();
		d->stream = 0;
429 430 431 432 433 434 435
	}
	disconnected();
	cleanup();
}

void Client::cleanup()
{
436 437
	d->active = false;
	//d->authed = false;
438 439 440
	d->groupChatList.clear();
}

441
/*void Client::continueAfterCert()
442 443 444 445 446 447 448 449 450 451 452 453
{
	d->stream->continueAfterCert();
}

void Client::streamConnected()
{
	connected();
}

void Client::streamHandshaken()
{
	handshaken();
454
}*/
455

456
void Client::streamError(int)
457
{
458 459
	//StreamError e = err;
	//error(e);
460

461
	//if(!e.isWarning()) {
462 463
		disconnected();
		cleanup();
464
	//}
465 466
}

467
/*void Client::streamSSLCertificateReady(const QSSLCert &cert)
468 469 470 471 472 473 474
{
	sslCertReady(cert);
}

void Client::streamCloseFinished()
{
	closeFinished();
475 476 477 478 479 480 481 482 483 484 485 486 487
}*/

static QDomElement oldStyleNS(const QDomElement &e)
{
	// find closest parent with a namespace
	QDomNode par = e.parentNode();
	while(!par.isNull() && par.namespaceURI().isNull())
		par = par.parentNode();
	bool noShowNS = false;
	if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
		noShowNS = true;

	QDomElement i;
488
	int x;
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
	//if(noShowNS)
		i = e.ownerDocument().createElement(e.tagName());
	//else
	//	i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName());

	// copy attributes
	QDomNamedNodeMap al = e.attributes();
	for(x = 0; x < al.count(); ++x)
		i.setAttributeNode(al.item(x).cloneNode().toAttr());

	if(!noShowNS)
		i.setAttribute("xmlns", e.namespaceURI());

	// copy children
	QDomNodeList nl = e.childNodes();
	for(x = 0; x < nl.count(); ++x) {
		QDomNode n = nl.item(x);
		if(n.isElement())
			i.appendChild(oldStyleNS(n.toElement()));
		else
			i.appendChild(n.cloneNode());
	}
	return i;
512 513
}

514
void Client::streamReadyRead()
515
{
516
	while(d->stream && d->stream->stanzaAvailable()) {
517 518 519 520
		Stanza s = d->stream->read();

		QString out = s.toString();
		debug(QString("Client: incoming: [\n%1]\n").arg(out));
521
		emit xmlIncoming(out);
522 523 524 525 526

		QDomElement x = oldStyleNS(s.element());
		distribute(x);
	}
}
527

528 529 530 531 532
void Client::streamIncomingXml(const QString &s)
{
	QString str = s;
	if(str.at(str.length()-1) != '\n')
		str += '\n';
533
	emit xmlIncoming(str);
534
}
535

536 537 538 539 540
void Client::streamOutgoingXml(const QString &s)
{
	QString str = s;
	if(str.at(str.length()-1) != '\n')
		str += '\n';
541
	emit xmlOutgoing(str);
542 543 544 545
}

void Client::debug(const QString &str)
{
546
	emit debugText(str);
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
}

QString Client::genUniqueId()
{
	QString s;
	s.sprintf("a%x", d->id_seed);
	d->id_seed += 0x10;
	return s;
}

Task *Client::rootTask()
{
	return d->root;
}

QDomDocument *Client::doc() const
{
	return &d->doc;
}

void Client::distribute(const QDomElement &x)
{
	if(x.hasAttribute("from")) {
		Jid j(x.attribute("from"));
		if(!j.isValid()) {
			debug("Client: bad 'from' JID\n");
			return;
		}
	}

577 578 579 580
	if(!rootTask()->take(x) && (x.attribute("type") == "get" || x.attribute("type") == "set") ) {
		debug("Client: Unrecognized IQ.\n");

		// Create reply element
581
		QDomElement reply = createIQ(doc(), "error", x.attribute("from"), x.attribute("id"));
582 583 584 585 586

		// Copy children
		for (QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) {
			reply.appendChild(n.cloneNode());
		}
587

588 589 590 591 592 593 594 595 596 597
		// Add error
		QDomElement error = doc()->createElement("error");
		error.setAttribute("type","cancel");
		reply.appendChild(error);

		QDomElement error_type = doc()->createElement("feature-not-implemented");
		error_type.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-stanzas");
		error.appendChild(error_type);

		send(reply);
598 599 600
	}
}

601
void Client::send(const QDomElement &x, bool want_notify)
602
{
603 604 605 606
	if(!d->stream)
		return;

	//QString out;
607
	//QTextStream ts(&out, IO_WriteOnly);
608 609 610 611 612
	//x.save(ts, 0);

	//QString out = Stream::xmlToString(x);
	//debug(QString("Client: outgoing: [\n%1]\n").arg(out));
	//xmlOutgoing(out);
613

614 615 616 617 618 619 620
	QDomElement e = addCorrectNS(x);
	Stanza s = d->stream->createStanza(e);
	if(s.isNull()) {
		//printf("bad stanza??\n");
		return;
	}
	QString out = s.toString();
621
	//qWarning() << "Out: " << out;
622
	debug(QString("Client: outgoing: [\n%1]\n").arg(out));
623
	emit xmlOutgoing(out);
624

625
	//printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).toLatin1(), Stream::xmlToString(e).toLatin1(), s.toString().toLatin1());
626
	d->stream->write(s, want_notify);
627 628 629 630
}

void Client::send(const QString &str)
{
631 632 633
	if(!d->stream)
		return;

634
	debug(QString("Client: outgoing: [\n%1]\n").arg(str));
635
	emit xmlOutgoing(str);
636
	static_cast<ClientStream*>(d->stream)->writeDirect(str);
637 638 639 640
}

Stream & Client::stream()
{
641
	return *(d->stream.data());
642 643
}

644 645 646 647 648
QString Client::streamBaseNS() const
{
	return d->stream->baseNS();
}

649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
const LiveRoster & Client::roster() const
{
	return d->roster;
}

const ResourceList & Client::resourceList() const
{
	return d->resourceList;
}

QString Client::host() const
{
	return d->host;
}

QString Client::user() const
{
	return d->user;
}

QString Client::pass() const
{
	return d->pass;
}

QString Client::resource() const
{
	return d->resource;
}

Jid Client::jid() const
{
	QString s;
	if(!d->user.isEmpty())
		s += d->user + '@';
	s += d->host;
	if(!d->resource.isEmpty()) {
		s += '/';
		s += d->resource;
	}

	return Jid(s);
}

693
void Client::ppSubscription(const Jid &j, const QString &s, const QString& n)
694
{
695
	emit subscription(j, s, n);
696 697 698 699 700 701 702 703 704
}

void Client::ppPresence(const Jid &j, const Status &s)
{
	if(s.isAvailable())
		debug(QString("Client: %1 is available.\n").arg(j.full()));
	else
		debug(QString("Client: %1 is unavailable.\n").arg(j.full()));

705
	for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
706 707 708 709 710 711 712 713 714 715
		GroupChat &i = *it;

		if(i.j.compare(j, false)) {
			bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false;

			debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us));
			switch(i.status) {
				case GroupChat::Connecting:
					if(us && s.hasError()) {
						Jid j = i.j;
716
						d->groupChatList.erase(it);
717
						emit groupChatError(j, s.errorCode(), s.errorString());
718 719 720 721 722
					}
					else {
						// don't signal success unless it is a non-error presence
						if(!s.hasError()) {
							i.status = GroupChat::Connected;
723
							emit groupChatJoined(i.j);
724
						}
725
						emit groupChatPresence(j, s);
726 727 728
					}
					break;
				case GroupChat::Connected:
729
					emit groupChatPresence(j, s);
730 731 732 733
					break;
				case GroupChat::Closing:
					if(us && !s.isAvailable()) {
						Jid j = i.j;
734
						d->groupChatList.erase(it);
735
						emit groupChatLeft(j);
736 737 738 739 740 741 742 743 744 745 746
					}
					break;
				default:
					break;
			}

			return;
		}
	}

	if(s.hasError()) {
747
		emit presenceError(j, s.errorCode(), s.errorString());
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
		return;
	}

	// is it me?
	if(j.compare(jid(), false)) {
		updateSelfPresence(j, s);
	}
	else {
		// update all relavent roster entries
		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) {
			LiveRosterItem &i = *it;

			if(!i.jid().compare(j, false))
				continue;

			// roster item has its own resource?
			if(!i.jid().resource().isEmpty()) {
				if(i.jid().resource() != j.resource())
					continue;
			}

			updatePresence(&i, j, s);
		}
	}
}

void Client::updateSelfPresence(const Jid &j, const Status &s)
{
	ResourceList::Iterator rit = d->resourceList.find(j.resource());
	bool found = (rit == d->resourceList.end()) ? false: true;

	// unavailable?  remove the resource
	if(!s.isAvailable()) {
		if(found) {
			debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource()));
			(*rit).setStatus(s);
784
			emit resourceUnavailable(j, *rit);
785
			d->resourceList.erase(rit);
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
		}
	}
	// available?  add/update the resource
	else {
		Resource r;
		if(!found) {
			r = Resource(j.resource(), s);
			d->resourceList += r;
			debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource()));
		}
		else {
			(*rit).setStatus(s);
			r = *rit;
			debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource()));
		}

802
		emit resourceAvailable(j, r);
803 804 805 806 807 808 809 810 811 812 813 814 815
	}
}

void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s)
{
	ResourceList::Iterator rit = i->resourceList().find(j.resource());
	bool found = (rit == i->resourceList().end()) ? false: true;

	// unavailable?  remove the resource
	if(!s.isAvailable()) {
		if(found) {
			(*rit).setStatus(s);
			debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
816
			emit resourceUnavailable(j, *rit);
817
			i->resourceList().erase(rit);
818 819
			i->setLastUnavailableStatus(s);
		}
820 821 822 823 824
		else {
			// create the resource just for the purpose of emit
			Resource r = Resource(j.resource(), s);
			i->resourceList() += r;
			rit = i->resourceList().find(j.resource());
825
			emit resourceUnavailable(j, *rit);
826
			i->resourceList().erase(rit);
827 828
			i->setLastUnavailableStatus(s);
		}
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
	}
	// available?  add/update the resource
	else {
		Resource r;
		if(!found) {
			r = Resource(j.resource(), s);
			i->resourceList() += r;
			debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
		}
		else {
			(*rit).setStatus(s);
			r = *rit;
			debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource()));
		}

844
		emit resourceAvailable(j, r);
845 846 847 848 849 850 851
	}
}

void Client::pmMessage(const Message &m)
{
	debug(QString("Client: Message from %1\n").arg(m.from().full()));

852 853 854 855 856 857 858 859 860
	// bits of binary. we can't do this in Message, since it knows nothing about Client
	foreach (const BoBData &b, m.bobDataList()) {
		d->bobman->append(b);
	}

	if (!m.ibbData().data.isEmpty()) {
		d->ibbman->takeIncomingData(m.from(), m.id(), m.ibbData(), Stanza::Message);
	}

861
	if(m.type() == "groupchat") {
862
		for(QList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) {
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882
			const GroupChat &i = *it;

			if(!i.j.compare(m.from(), false))
				continue;

			if(i.status == GroupChat::Connected)
				messageReceived(m);
		}
	}
	else
		messageReceived(m);
}

void Client::prRoster(const Roster &r)
{
	importRoster(r);
}

void Client::rosterRequest()
{
883
	if(!d->active)
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
		return;

	JT_Roster *r = new JT_Roster(rootTask());
	connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished()));
	r->get();
	d->roster.flagAllForDelete(); // mod_groups patch
	r->go(true);
}

void Client::slotRosterRequestFinished()
{
	JT_Roster *r = (JT_Roster *)sender();
	// on success, let's take it
	if(r->success()) {
		//d->roster.flagAllForDelete(); // mod_groups patch

		importRoster(r->roster());

		for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) {
			LiveRosterItem &i = *it;
			if(i.flagForDelete()) {
905
				emit rosterItemRemoved(i);
906
				it = d->roster.erase(it);
907 908 909 910 911 912 913 914 915 916 917 918
			}
			else
				++it;
		}
	}
	else {
		// don't report a disconnect.  Client::error() will do that.
		if(r->statusCode() == Task::ErrDisc)
			return;
	}

	// report success / fail
919
	emit rosterRequestFinished(r->success(), r->statusCode(), r->statusString());
920 921 922 923
}

void Client::importRoster(const Roster &r)
{
924
	emit beginImportRoster();
925 926 927
	for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) {
		importRosterItem(*it);
	}
928
	emit endImportRoster();
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
}

void Client::importRosterItem(const RosterItem &item)
{
	QString substr;
	switch(item.subscription().type()) {
		case Subscription::Both:
			substr = "<-->";  break;
		case Subscription::From:
			substr = "  ->";  break;
		case Subscription::To:
			substr = "<-  ";  break;
		case Subscription::Remove:
			substr = "xxxx";  break;
		case Subscription::None:
		default:
			substr = "----";  break;
	}

	QString dstr, str;
949
	str.sprintf("  %s %-32s", qPrintable(substr), qPrintable(item.jid().full()));
950 951 952 953 954 955 956 957
	if(!item.name().isEmpty())
		str += QString(" [") + item.name() + "]";
	str += '\n';

	// Remove
	if(item.subscription().type() == Subscription::Remove) {
		LiveRoster::Iterator it = d->roster.find(item.jid());
		if(it != d->roster.end()) {
958
			emit rosterItemRemoved(*it);
959
			d->roster.erase(it);
960 961 962 963 964 965 966 967 968 969
		}
		dstr = "Client: (Removed) ";
	}
	// Add/Update
	else {
		LiveRoster::Iterator it = d->roster.find(item.jid());
		if(it != d->roster.end()) {
			LiveRosterItem &i = *it;
			i.setFlagForDelete(false);
			i.setRosterItem(item);
970
			emit rosterItemUpdated(i);
971 972 973 974 975 976 977
			dstr = "Client: (Updated) ";
                }
		else {
			LiveRosterItem i(item);
			d->roster += i;

			// signal it
978
			emit rosterItemAdded(i);
979 980 981 982 983 984 985
			dstr = "Client: (Added)   ";
		}
	}

	debug(dstr + str);
}

986
void Client::sendMessage(const Message &m, bool want_notify)
987
{
988
	JT_Message *j = new JT_Message(rootTask(), m, want_notify);
989 990 991
	j->go(true);
}

992
void Client::sendSubscription(const Jid &jid, const QString &type, const QString& nick)
993 994
{
	JT_Presence *j = new JT_Presence(rootTask());
995
	j->sub(jid, type, nick);
996 997 998 999 1000
	j->go(true);
}

void Client::setPresence(const Status &s)
{
1001 1002 1003 1004 1005 1006
	if (d->capsman->isEnabled()) {
		if (d->caps.version().isEmpty() && !d->caps.node().isEmpty()) {
			d->caps = CapsSpec(makeDiscoResult(d->caps.node())); /* recompute caps hash */
		}
	}

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
	JT_Presence *j = new JT_Presence(rootTask());
	j->pres(s);
	j->go(true);

	// update our resourceList
	ppPresence(jid(), s);
	//ResourceList::Iterator rit = d->resourceList.find(resource());
	//Resource &r = *rit;
	//r.setStatus(s);
}

QString Client::OSName() const
{
1020 1021 1022 1023 1024 1025
	return d->osName;
}

QString Client::OSVersion() const
{
	return d->osVersion;
1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
}

QString Client::timeZone() const
{
	return d->tzname;
}

int Client::timeZoneOffset() const
{
	return d->tzoffset;
}

1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
/**
  \brief Returns true if Client is using old, manual time zone conversions.

  By default, conversions between UTC and local time are done automatically by Qt.
  In this mode, manualTimeZoneOffset() returns true,
  and timeZoneOffset() always retuns 0 (so you shouldn't use that function).

  However, if you call setTimeZone(), Client instance switches to old mode
  and uses given time zone offset for all calculations.
  */
bool Client::manualTimeZoneOffset() const
{
	return d->useTzoffset;
}

1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
QString Client::clientName() const
{
	return d->clientName;
}

QString Client::clientVersion() const
{
	return d->clientVersion;
}

1063
CapsSpec Client::caps() const
1064
{
1065
	return d->caps;
1066 1067
}

1068
CapsSpec Client::serverCaps() const
1069
{
1070 1071 1072 1073 1074 1075 1076
	const StreamFeatures &f = d->stream->streamFeatures();
	if (!(f.capsAlgo.isEmpty() || f.capsNode.isEmpty() || f.capsVersion.isEmpty())) {
		if (CapsSpec::cryptoMap().contains(f.capsAlgo)) {
			return CapsSpec(f.capsNode, CapsSpec::cryptoMap().value(f.capsAlgo), f.capsVersion);
		}
	}
	return CapsSpec();
1077 1078
}

1079
void Client::setOSName(const QString &name)
1080
{
1081
	d->osName = name;
1082 1083
}

1084
void Client::setOSVersion(const QString &version)
1085
{
1086
	d->osVersion = version;
1087 1088 1089 1090 1091 1092
}

void Client::setTimeZone(const QString &name, int offset)
{
	d->tzname = name;
	d->tzoffset = offset;
1093
	d->useTzoffset = true;
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
}

void Client::setClientName(const QString &s)
{
	d->clientName = s;
}

void Client::setClientVersion(const QString &s)
{
	d->clientVersion = s;
}

1106
void Client::setCaps(const CapsSpec &s)
1107
{
1108
	d->caps = s;
1109 1110
}

1111
DiscoItem::Identity Client::identity() const
1112 1113 1114 1115
{
	return d->identity;
}

1116
void Client::setIdentity(const DiscoItem::Identity &identity)
1117
{
1118 1119 1120
	if (!(d->identity == identity)) {
		d->caps.resetVersion();
	}
1121 1122 1123
	d->identity = identity;
}

1124 1125
void Client::setFeatures(const Features& f)
{
1126 1127 1128
	if (!(d->features == f)) {
		d->caps.resetVersion();
	}
1129 1130 1131 1132 1133 1134 1135 1136
	d->features = f;
}

const Features& Client::features() const
{
	return d->features;
}

1137
DiscoItem Client::makeDiscoResult(const QString &node) const
1138
{
1139 1140 1141 1142 1143 1144
	DiscoItem item;
	item.setNode(node);
	DiscoItem::Identity id = identity();
	if (id.category.isEmpty() || id.type.isEmpty()) {
		id.category = "client";
		id.type = "pc";
1145
	}
1146
	item.setIdentities(id);
1147

1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
	Features features;

	if (d->ftman) {
		features.addFeature("http://jabber.org/protocol/bytestreams");
		features.addFeature("http://jabber.org/protocol/ibb");
		features.addFeature("http://jabber.org/protocol/si");
		features.addFeature("http://jabber.org/protocol/si/profile/file-transfer");
	}
	features.addFeature("http://jabber.org/protocol/disco#info");
	features.addFeature("jabber:x:data");
	features.addFeature("urn:xmpp:bob");
	features.addFeature("urn:xmpp:ping");
	features.addFeature("urn:xmpp:time");

	// Client-specific features
	foreach (const QString & i, d->features.list()) {
		features.addFeature(i);
1165 1166
	}

1167
	item.setFeatures(features);
1168

1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207