1 /*
2     oscaraccount.cpp  -  Oscar Account Class
3 
4     Copyright (c) 2002 by Tom Linsky <twl6@po.cwru.edu>
5     Copyright (c) 2002 by Chris TenHarmsel <tenharmsel@staticmethod.net>
6     Copyright (c) 2004 by Matt Rogers <mattr@kde.org>
7     Copyright (c) 2008 by Roman Jarosz <kedgedev@centrum.cz>
8     Kopete    (c) 2002-2008 by the Kopete developers  <kopete-devel@kde.org>
9 
10     *************************************************************************
11     *                                                                       *
12     * This program is free software; you can redistribute it and/or modify  *
13     * it under the terms of the GNU General Public License as published by  *
14     * the Free Software Foundation; either version 2 of the License, or     *
15     * (at your option) any later version.                                   *
16     *                                                                       *
17     *************************************************************************
18 */
19 
20 #include "oscaraccount.h"
21 
22 #include "kopetepassword.h"
23 #include "kopeteprotocol.h"
24 #include "kopetemetacontact.h"
25 #include "kopetecontactlist.h"
26 #include "kopeteidentity.h"
27 #include "kopetegroup.h"
28 #include "kopeteuiglobal.h"
29 #include "kopetecontact.h"
30 #include "kopetechatsession.h"
31 
32 #include <assert.h>
33 
34 #include <qapplication.h>
35 #include <qregexp.h>
36 #include <qtimer.h>
37 #include <qtextcodec.h>
38 #include <qimage.h>
39 #include <qfile.h>
40 #include <qdom.h>
41 #include <QHash>
42 #include <QSslSocket>
43 #include <QNetworkProxy>
44 #include <QTextDocument> // Qt::escape
45 #include <QAbstractButton>
46 
47 #include <kdebug.h>
48 #include <kconfig.h>
49 #include <KLocalizedString>
50 #include <kcodecs.h>
51 #include <kmessagebox.h>
52 #include <kdialog.h>
53 #include <knotification.h>
54 
55 #include <kprotocolmanager.h>
56 
57 #include "client.h"
58 #include "connection.h"
59 #include "oscartypeclasses.h"
60 #include "oscarmessage.h"
61 #include "oscarutils.h"
62 #include "oscarclientstream.h"
63 #include "contactmanager.h"
64 #include "oscarlistnonservercontacts.h"
65 #include "kopetetransfermanager.h"
66 #include "kopeteversion.h"
67 #include "oscarversionupdater.h"
68 #include "filetransferhandler.h"
69 #include "chatroomhandler.h"
70 #include "nscainfoevent.h"
71 #include "oscarpresence.h"
72 #include "oscarprotocol.h"
73 #include "oscarstatusmanager.h"
74 #include <kopetesockettimeoutwatcher.h>
75 #include <QStandardPaths>
76 
77 using namespace Oscar;
78 
79 class OscarAccountPrivate : public Client::CodecProvider
80 {
81 	// Backreference
82 	OscarAccount& account;
83 public:
OscarAccountPrivate(OscarAccount & a)84 	OscarAccountPrivate( OscarAccount& a ): account( a ) {}
85 
86 	//The liboscar hook for the account
87 	Client* engine;
88 
89 	quint32 ssiLastModTime;
90 
91 	//contacts waiting on SSI add ack and their metacontact
92 	QMap<QString, Kopete::MetaContact*> addContactMap;
93 
94 	//contacts waiting on their group to be added
95 	QMap<QString, QString> contactAddQueue;
96 	QMap<QString, QString> contactChangeQueue;
97 	QMap<uint, FileTransferHandler*> fileTransferHandlerMap;
98 
99     OscarListNonServerContacts* olnscDialog;
100 
101 	unsigned int versionUpdaterStamp;
102 	bool versionAlreadyUpdated;
103 
104 	bool buddyIconDirty;
105 
codecForContact(const QString & contactName) const106 	QTextCodec* codecForContact( const QString& contactName ) const Q_DECL_OVERRIDE
107 	{
108 		return account.contactCodec( Oscar::normalize( contactName ) );
109 	}
110 
codecForAccount() const111 	QTextCodec* codecForAccount() const Q_DECL_OVERRIDE
112 	{
113 		return account.defaultCodec();
114 	}
115 };
116 
OscarAccount(Kopete::Protocol * parent,const QString & accountID,bool isICQ)117 OscarAccount::OscarAccount(Kopete::Protocol *parent, const QString &accountID, bool isICQ)
118 : Kopete::PasswordedAccount( parent, accountID, false )
119 {
120 	kDebug(OSCAR_GEN_DEBUG) << " accountID='" << accountID <<
121 		"', isICQ=" << isICQ << endl;
122 
123 	d = new OscarAccountPrivate( *this );
124 	d->engine = new Client( this );
125 	QObject::connect( d->engine, SIGNAL(createClientStream(ClientStream**)), this, SLOT(createClientStream(ClientStream**)) );
126 	d->engine->setIsIcq( isICQ );
127 	// Set version capability
128 	// last 4 bytes determine version
129 	// first number, major version
130 	// second number,  minor version
131 	// third number, point version 100+
132 	// fourth number,  point version 0-99
133 	QByteArray kg( "Kopete ICQ     ", 16 );
134 	kg[12] = KOPETE_VERSION_MAJOR;
135 	kg[13] = KOPETE_VERSION_MINOR;
136 	kg[14] = KOPETE_VERSION_RELEASE / 100;
137 	kg[15] = KOPETE_VERSION_RELEASE % 100;
138 	d->engine->setVersionCap( kg );
139 
140 	d->versionAlreadyUpdated = false;
141 	d->buddyIconDirty = false;
142 	d->versionUpdaterStamp = OscarVersionUpdater::self()->stamp();
143 	if ( isICQ )
144 		d->engine->setVersion( OscarVersionUpdater::self()->getICQVersion() );
145 	else
146 		d->engine->setVersion( OscarVersionUpdater::self()->getAIMVersion() );
147 
148 	d->engine->setCodecProvider( d );
149     d->olnscDialog = nullptr;
150     QObject::connect( d->engine, SIGNAL(loggedIn()), this, SLOT(loginActions()) );
151 	QObject::connect( d->engine, SIGNAL(messageReceived(Oscar::Message)),
152 	                  this, SLOT(messageReceived(Oscar::Message)) );
153 	QObject::connect( d->engine, SIGNAL(socketError(int,QString)),
154 	                  this, SLOT(slotSocketError(int,QString)) );
155 	QObject::connect( d->engine, SIGNAL(taskError(Oscar::SNAC,int,bool)),
156 	                  this, SLOT(slotTaskError(Oscar::SNAC,int,bool)) );
157 	QObject::connect( d->engine, SIGNAL(userStartedTyping(QString)),
158 	                  this, SLOT(userStartedTyping(QString)) );
159 	QObject::connect( d->engine, SIGNAL(userStoppedTyping(QString)),
160 	                  this, SLOT(userStoppedTyping(QString)) );
161 	QObject::connect( d->engine, SIGNAL(iconNeedsUploading()),
162 	                  this, SLOT(slotSendBuddyIcon()) );
163 	QObject::connect( d->engine, SIGNAL(incomingFileTransfer(FileTransferHandler*)),
164 	                  this, SLOT(incomingFileTransfer(FileTransferHandler*)) );
165 	QObject::connect( d->engine, SIGNAL(chatroomRequest(ChatRoomHandler*)),
166 	                  this, SLOT(chatroomRequest(ChatRoomHandler*)) );
167 
168 	Kopete::TransferManager *tm = Kopete::TransferManager::transferManager();
169 	QObject::connect( tm, SIGNAL(refused(Kopete::FileTransferInfo)),
170 	                  this, SLOT(fileTransferRefused(Kopete::FileTransferInfo)) );
171 	QObject::connect( tm, SIGNAL(accepted(Kopete::Transfer*,QString)),
172 	                  this, SLOT(fileTransferAccept(Kopete::Transfer*,QString)) );
173 }
174 
~OscarAccount()175 OscarAccount::~OscarAccount()
176 {
177 	OscarAccount::disconnect();
178 	delete d;
179 }
180 
engine()181 Client* OscarAccount::engine()
182 {
183 	return d->engine;
184 }
185 
logOff(Kopete::Account::DisconnectReason reason)186 void OscarAccount::logOff( Kopete::Account::DisconnectReason reason )
187 {
188 	kDebug(OSCAR_GEN_DEBUG) << "accountId='" << accountId() << "'";
189 	//disconnect the signals
190 	Kopete::ContactList* kcl = Kopete::ContactList::self();
191 	QObject::disconnect( kcl, SIGNAL(groupRenamed(Kopete::Group*,QString)),
192 	                     this, SLOT(kopeteGroupRenamed(Kopete::Group*,QString)) );
193 	QObject::disconnect( kcl, SIGNAL(groupRemoved(Kopete::Group*)),
194 	                     this, SLOT(kopeteGroupRemoved(Kopete::Group*)) );
195 	QObject::disconnect( d->engine->ssiManager(), SIGNAL(contactAdded(OContact)),
196 	                     this, SLOT(ssiContactAdded(OContact)) );
197 	QObject::disconnect( d->engine->ssiManager(), SIGNAL(groupAdded(OContact)),
198 	                     this, SLOT(ssiGroupAdded(OContact)) );
199 	QObject::disconnect( d->engine->ssiManager(), SIGNAL(groupUpdated(OContact)),
200 	                     this, SLOT(ssiGroupUpdated(OContact)) );
201 	QObject::disconnect( d->engine->ssiManager(), SIGNAL(contactUpdated(OContact)),
202 	                     this, SLOT(ssiContactUpdated(OContact)) );
203 
204 	d->engine->close();
205 	OscarProtocol* p = dynamic_cast<OscarProtocol*>(protocol());
206 	if ( myself() && p && p->statusManager() )
207 		myself()->setOnlineStatus( p->statusManager()->onlineStatusOf( Oscar::Presence( Oscar::Presence::Offline ) ) );
208 
209 	d->contactAddQueue.clear();
210 	d->contactChangeQueue.clear();
211 
212 	disconnected( reason );
213 }
214 
disconnect()215 void OscarAccount::disconnect()
216 {
217 	logOff( Kopete::Account::Manual );
218 }
219 
passwordWasWrong()220 bool OscarAccount::passwordWasWrong()
221 {
222 	return password().isWrong();
223 }
224 
setIdentity(Kopete::Identity * ident)225 bool OscarAccount::setIdentity( Kopete::Identity *ident )
226 {
227 	if ( !Kopete::PasswordedAccount::setIdentity( ident ) )
228 		return false;
229 
230 	QObject::connect( ident, SIGNAL(propertyChanged(Kopete::PropertyContainer*,QString,QVariant,QVariant)),
231 	                  this, SLOT(slotIdentityPropertyChanged(Kopete::PropertyContainer*,QString,QVariant,QVariant)) );
232 
233 	QString photoPath = ident->property( Kopete::Global::Properties::self()->photo() ).value().toString();
234 	updateBuddyIcon( photoPath );
235 	return true;
236 }
237 
loginActions()238 void OscarAccount::loginActions()
239 {
240     password().setWrong( false );
241     kDebug(OSCAR_GEN_DEBUG) << "processing SSI list";
242     processSSIList();
243 
244 	//start a chat nav connection
245 	if ( !engine()->isIcq() )
246 	{
247 		kDebug(OSCAR_GEN_DEBUG) << "sending request for chat nav service";
248 		d->engine->requestServerRedirect( 0x000D );
249 	}
250 
251 	kDebug(OSCAR_RAW_DEBUG) << "sending request for icon service";
252 	d->engine->connectToIconServer();
253 
254 	if ( d->buddyIconDirty )
255 		updateBuddyIconInSSI();
256 }
257 
processSSIList()258 void OscarAccount::processSSIList()
259 {
260 	//disconnect signals so we don't attempt to add things to SSI!
261 	Kopete::ContactList* kcl = Kopete::ContactList::self();
262 	QObject::disconnect( kcl, SIGNAL(groupRenamed(Kopete::Group*,QString)),
263 	                     this, SLOT(kopeteGroupRenamed(Kopete::Group*,QString)) );
264 	QObject::disconnect( kcl, SIGNAL(groupRemoved(Kopete::Group*)),
265 	                     this, SLOT(kopeteGroupRemoved(Kopete::Group*)) );
266 
267 	kDebug(OSCAR_RAW_DEBUG) ;
268 
269 	ContactManager* listManager = d->engine->ssiManager();
270 
271     //first add groups
272 	QList<OContact> groupList = listManager->groupList();
273 	QList<OContact>::const_iterator git = groupList.constBegin();
274 	QList<OContact>::const_iterator listEnd = groupList.constEnd();
275 	//the protocol dictates that there is at least one group that has contacts
276 	//so i don't have to check for an empty group list
277 
278 	kDebug(OSCAR_GEN_DEBUG) << "Adding " << groupList.count() << " groups to contact list";
279 	for( ; git != listEnd; ++git )
280 	{ //add all the groups.
281 		if ( ( *git ).name() == "Buddies" ) continue;
282 		kDebug( OSCAR_GEN_DEBUG ) << "Adding SSI group'" << ( *git ).name()
283 			<< "' to the kopete contact list" << endl;
284 		kcl->findGroup( ( *git ).name() );
285 	}
286 
287 	//then add contacts
288 	QList<OContact> contactList = listManager->contactList();
289 	QList<OContact>::const_iterator bit = contactList.constBegin();
290 	QList<OContact>::const_iterator blistEnd = contactList.constEnd();
291 	kDebug(OSCAR_GEN_DEBUG) << "Adding " << contactList.count() << " contacts to contact list";
292 	for ( ; bit != blistEnd; ++bit )
293 	{
294 		OContact groupForAdd = listManager->findGroup( ( *bit ).gid() );
295 		Kopete::Group* group;
296 		if ( groupForAdd.isValid() && groupForAdd.name() != "Buddies" )
297 			group = kcl->findGroup( groupForAdd.name() ); //add if not present
298 		else
299 			group = Kopete::Group::topLevel();
300 
301 		kDebug( OSCAR_GEN_DEBUG ) << "Adding contact '" << ( *bit ).name() << "' to kopete list in group " <<
302 			group->displayName() << endl;
303 		OscarContact* oc = dynamic_cast<OscarContact*>( contacts().value( ( *bit ).name() ) );
304 		if ( oc )
305 		{
306 			OContact item = ( *bit );
307 			oc->setSSIItem( item );
308 
309 			//only synchronizes group if metacontact is a member of
310 			//a single group
311 			if ( oc->metaContact()->groups().size() == 1 )
312 			{
313 				Kopete::Group* oldGrp = oc->metaContact()->groups().first();
314 				if ( oldGrp->displayName() != group->displayName() &&
315 				     oc->metaContact()->contacts().count() == 1 )
316 				{
317 					oc->metaContact()->moveToGroup( oldGrp, group );
318 				}
319 			}
320 		}
321 		else
322 			addContact( ( *bit ).name(), QString(), group, Kopete::Account::DontChangeKABC );
323 	}
324 
325 	QObject::connect( kcl, SIGNAL(groupRenamed(Kopete::Group*,QString)),
326 	                  this, SLOT(kopeteGroupRenamed(Kopete::Group*,QString)) );
327 	QObject::connect( kcl, SIGNAL(groupRemoved(Kopete::Group*)),
328 	                  this, SLOT(kopeteGroupRemoved(Kopete::Group*)) );
329 	QObject::connect( listManager, SIGNAL(contactAdded(OContact)),
330 	                  this, SLOT(ssiContactAdded(OContact)) );
331 	QObject::connect( listManager, SIGNAL(groupAdded(OContact)),
332 	                  this, SLOT(ssiGroupAdded(OContact)) );
333 	QObject::connect( listManager, SIGNAL(groupUpdated(OContact)),
334 	                  this, SLOT(ssiGroupUpdated(OContact)) );
335 	QObject::connect( listManager, SIGNAL(contactUpdated(OContact)),
336 	                  this, SLOT(ssiContactUpdated(OContact)) );
337 
338 	// TODO: Synchronize groups.
339 	// Currently groups that have been removed from the server do not get
340 	// removed from the client's list.  The problem is that a group can hold
341 	// contacts from other protocols.  Perhaps groups should store which
342 	// protocols are using it.  Asking the user for which account to create
343 	// a group, similar to how contact addition, could work.
344 
345     const QHash<QString, Kopete::Contact*> &nonServerContacts = contacts();
346     QHash<QString, Kopete::Contact*>::ConstIterator it = nonServerContacts.constBegin();
347     QStringList nonServerContactList;
348     for ( ; it != nonServerContacts.constEnd(); ++it )
349     {
350         const OscarContact* oc = dynamic_cast<const OscarContact*>( ( *it ) );
351         if ( !oc )
352             continue;
353         kDebug(OSCAR_GEN_DEBUG) << oc->contactId() << " contact ssi type: " << oc->ssiItem().type();
354         if ( !oc->isOnServer() )
355             nonServerContactList.append( ( *it )->contactId() );
356     }
357     kDebug(OSCAR_GEN_DEBUG) << "the following contacts are not on the server side list"
358                              << nonServerContactList << endl;
359 	bool showMissingContactsDialog = configGroup()->readEntry(QString::fromLatin1("ShowMissingContactsDialog"), true);
360     if ( !nonServerContactList.isEmpty() && showMissingContactsDialog )
361     {
362         d->olnscDialog = new OscarListNonServerContacts( Kopete::UI::Global::mainWidget() );
363         QObject::connect( d->olnscDialog, SIGNAL(closing()),
364                           this, SLOT(nonServerAddContactDialogClosed()) );
365         d->olnscDialog->addContacts( nonServerContactList );
366         d->olnscDialog->show();
367     }
368 }
369 
nonServerAddContactDialogClosed()370 void OscarAccount::nonServerAddContactDialogClosed()
371 {
372 	if ( !d->olnscDialog )
373 		return;
374 
375 	if ( d->olnscDialog->result() == KDialog::Yes )
376 	{
377 		NonServerContactsAddInfoEvent *event = new NonServerContactsAddInfoEvent( d->engine->ssiManager(), engine()->isIcq(), this );
378 		event->sendEvent();
379 
380 		//start adding contacts
381 		kDebug(OSCAR_GEN_DEBUG) << "adding non server contacts to the contact list";
382 		//get the contact list. get the OscarContact object, then the group
383 		//check if the group is on ssi, if not, add it
384 		//if so, add the contact.
385 		QStringList offliners = d->olnscDialog->nonServerContactList();
386 		QStringList::iterator it, itEnd = offliners.end();
387 		for ( it = offliners.begin(); it != itEnd; ++it )
388 		{
389 			OscarContact* oc = dynamic_cast<OscarContact*>( contacts().value( ( *it ) ) );
390 			if ( !oc )
391 			{
392 				kDebug(OSCAR_GEN_DEBUG) << "no OscarContact object available for" << ( *it );
393 				continue;
394 			}
395 
396 			Kopete::MetaContact* mc = oc->metaContact();
397 			if ( !mc )
398 			{
399 				kDebug(OSCAR_GEN_DEBUG) << "no metacontact object available for" << oc->contactId();
400 				continue;
401 			}
402 
403 			Kopete::Group* group = mc->groups().first();
404 			if ( !group )
405 			{
406 				kDebug(OSCAR_GEN_DEBUG) << "no metacontact object available for" << oc->contactId();
407 				continue;
408 			}
409 
410 			event->addContact( *it );
411 			addContactToSSI( ( *it ), group->displayName(), true );
412 		}
413 	}
414 	else if ( d->olnscDialog->result() == KDialog::No )
415 	{
416 		//remove contacts
417 		kDebug( OSCAR_GEN_DEBUG ) << "removing non server contacts from the "
418 			                         "contact list";
419 		Kopete::ContactList* kcl = Kopete::ContactList::self();
420 		QStringList offliners = d->olnscDialog->nonServerContactList();
421 		QStringList::iterator it, itEnd = offliners.end();
422 		for ( it = offliners.begin(); it != itEnd; ++it )
423 		{
424 			OscarContact* oc = dynamic_cast<OscarContact*>( contacts().value( (*it) ) );
425 			if ( !oc )
426 			{
427 				kDebug( OSCAR_GEN_DEBUG ) << "no OscarContact object available "
428 				                             "for" << ( *it ) << endl;
429 				continue;
430 			}
431 
432 			Kopete::MetaContact* mc = oc->metaContact();
433 			if ( !mc )
434 			{
435 				kDebug( OSCAR_GEN_DEBUG ) << "no metacontact object available "
436 				                             "for" << ( oc->contactId() )
437 				                             << endl;
438 				continue;
439 			}
440 
441 			if ( oc->metaContact()->contacts().count() <= 1 )
442 			{
443 				kcl->removeMetaContact( oc->metaContact() );
444 			}
445 			else
446 			{
447 				kDebug( OSCAR_GEN_DEBUG ) << oc->contactId() << " metacontact "
448 			                                 "contains multiple contacts.";
449 			}
450 		}
451 	}
452 
453 	bool showOnce = d->olnscDialog->onlyShowOnce();
454 	configGroup()->writeEntry( QString::fromLatin1("ShowMissingContactsDialog") , !showOnce);
455 	configGroup()->sync();
456 
457     d->olnscDialog->deleteLater();
458     d->olnscDialog = nullptr;
459 }
460 
chatroomRequest(ChatRoomHandler * handler)461 void OscarAccount::chatroomRequest( ChatRoomHandler* handler )
462 {
463 	KGuiItem buttonYes( KStandardGuiItem::yes() );
464 	KGuiItem buttonNo( KStandardGuiItem::no() );
465 	buttonYes.setText( i18nc( "@action:button filter-yes", "%1", KStandardGuiItem::yes().text() ) );
466 	buttonNo.setText( i18nc( "@action:button filter-no", "%1", KStandardGuiItem::no().text() ) );
467 	i18nc( "@action:button post-filter", "." );
468 
469 	QMessageBox *dialog = new QMessageBox( QMessageBox::Question, i18n( "Chat Room Invitation" ),
470 	                                       ( handler->contact() + ": " + handler->invite() ),
471 	                                       QMessageBox::Yes | QMessageBox::No, NULL );
472 	dialog->setObjectName( "questionYesNoCancel" );
473 	dialog->setDefaultButton(QMessageBox::Yes);
474 	dialog->setModal( false );
475 
476 	QObject::connect( dialog->button(QMessageBox::Yes), SIGNAL(clicked()),
477 	                  handler, SLOT(accept()) );
478 	QObject::connect( dialog->button(QMessageBox::No), SIGNAL(clicked()),
479 	                  handler, SLOT(reject()) );
480 	QObject::connect( handler, SIGNAL(joinChatRoom(QString,int)),
481 	                  engine(), SLOT(joinChatRoom(QString,int)) );
482 
483 	dialog->show();
484 	dialog->raise();
485 	dialog->activateWindow();
486 }
487 
incomingFileTransfer(FileTransferHandler * ftHandler)488 void OscarAccount::incomingFileTransfer( FileTransferHandler* ftHandler )
489 {
490 	QString sender = Oscar::normalize( ftHandler->contact() );
491 	if ( !contacts().value( sender ) )
492 	{
493 		kDebug(OSCAR_RAW_DEBUG) << "Adding '" << sender << "' as temporary contact";
494 		addContact( sender, QString(), 0,  Kopete::Account::Temporary );
495 	}
496 	Kopete::Contact * ct = contacts().value( sender );
497 
498 	// Fill the fileNameList with empty filenames if we have more files so the kopete transfer knows about them
499 	QStringList fileNameList;
500 	fileNameList << ftHandler->fileName();
501 	for ( int i = 1; i < ftHandler->fileCount(); i++ )
502 		fileNameList << "";
503 
504 	Kopete::TransferManager* tm = Kopete::TransferManager::transferManager();
505 	uint ftId = tm->askIncomingTransfer( ct, fileNameList, ftHandler->totalSize(), ftHandler->description(),
506 	                                     ftHandler->internalId(), QPixmap() );
507 	QObject::connect( ftHandler, SIGNAL(destroyed(QObject*)), this, SLOT(fileTransferDestroyed(QObject*)) );
508 	QObject::connect( ftHandler, SIGNAL(transferCancelled()), this, SLOT(fileTransferCancelled()) );
509 
510 	d->fileTransferHandlerMap.insert( ftId, ftHandler );
511 }
512 
fileTransferDestroyed(QObject * object)513 void OscarAccount::fileTransferDestroyed( QObject* object )
514 {
515 	uint key = d->fileTransferHandlerMap.key( (FileTransferHandler*)object, 0 );
516 	if ( key > 0 )
517 		d->fileTransferHandlerMap.remove( key );
518 	else
519 		kDebug(OSCAR_GEN_DEBUG) << "FileTransferHandler not in the map!!!";
520 }
521 
fileTransferCancelled()522 void OscarAccount::fileTransferCancelled()
523 {
524 	FileTransferHandler* ftHandler = qobject_cast<FileTransferHandler*>(sender());
525 	if ( !ftHandler )
526 		return;
527 
528 	uint key = d->fileTransferHandlerMap.key( ftHandler, 0 );
529 	if ( key == 0 )
530 	{
531 		kDebug(OSCAR_GEN_DEBUG) << "FileTransferHandler not in the map!!!";
532 		return;
533 	}
534 
535 	QObject::disconnect( ftHandler, SIGNAL(transferCancelled()), this, SLOT(fileTransferCancelled()) );
536 	Kopete::TransferManager::transferManager()->cancelIncomingTransfer( key );
537 }
538 
fileTransferRefused(const Kopete::FileTransferInfo & info)539 void OscarAccount::fileTransferRefused( const Kopete::FileTransferInfo& info )
540 {
541 	FileTransferHandler* ftHandler = d->fileTransferHandlerMap.value( info.transferId(), 0 );
542 	if ( !ftHandler )
543 		return;
544 
545 	QObject::disconnect( ftHandler, SIGNAL(transferCancelled()), this, SLOT(fileTransferCancelled()) );
546 	ftHandler->cancel();
547 }
548 
fileTransferAccept(Kopete::Transfer * transfer,const QString & fileName)549 void OscarAccount::fileTransferAccept( Kopete::Transfer* transfer, const QString& fileName )
550 {
551 	FileTransferHandler* ftHandler = d->fileTransferHandlerMap.value( transfer->info().transferId(), 0 );
552 	if ( !ftHandler )
553 		return;
554 
555 	QObject::disconnect( ftHandler, SIGNAL(transferCancelled()), this, SLOT(fileTransferCancelled()) );
556 
557 	QObject::connect( transfer, SIGNAL(transferCanceled()), ftHandler, SLOT(cancel()) );
558 	QObject::connect( ftHandler, SIGNAL(transferCancelled()), transfer, SLOT(slotCancelled()) );
559 	QObject::connect( ftHandler, SIGNAL(transferError(int,QString)), transfer, SLOT(slotError(int,QString)) );
560 	QObject::connect( ftHandler, SIGNAL(transferProcessed(uint)), transfer, SLOT(slotProcessed(uint)) );
561 	QObject::connect( ftHandler, SIGNAL(transferFinished()), transfer, SLOT(slotComplete()) );
562 	QObject::connect( ftHandler, SIGNAL(transferNextFile(QString,QString)),
563 	                  transfer, SLOT(slotNextFile(QString,QString)) );
564 
565 	if ( transfer->info().saveToDirectory() )
566 		ftHandler->save( fileName );
567 	else
568 		ftHandler->saveAs( QStringList() << fileName );
569 }
570 
kopeteGroupRemoved(Kopete::Group * group)571 void OscarAccount::kopeteGroupRemoved( Kopete::Group* group )
572 {
573 	if ( isConnected() && group->displayName() != "Buddies" )
574 		d->engine->removeGroup( group->displayName() );
575 }
576 
kopeteGroupAdded(Kopete::Group * group)577 void OscarAccount::kopeteGroupAdded( Kopete::Group* group )
578 {
579 	if ( isConnected() )
580 		d->engine->addGroup( group->displayName() );
581 }
582 
kopeteGroupRenamed(Kopete::Group * group,const QString & oldName)583 void OscarAccount::kopeteGroupRenamed( Kopete::Group* group, const QString& oldName )
584 {
585 	if ( isConnected() && oldName != "Buddies" )
586 		d->engine->renameGroup( oldName, group->displayName() );
587 }
588 
messageReceived(const Oscar::Message & message)589 void OscarAccount::messageReceived( const Oscar::Message& message )
590 {
591 	//the message isn't for us somehow
592 	if ( Oscar::normalize( message.receiver() ) != Oscar::normalize( accountId() ) )
593 	{
594 		kDebug(OSCAR_RAW_DEBUG) << "got a message but we're not the receiver: "
595 			<< message.textArray() << endl;
596 		return;
597 	}
598 
599 	/* Logic behind this:
600 	 * If we don't have the contact yet, create it as a temporary
601 	 * Create the message manager
602 	 * Get the sanitized message back
603 	 * Append to the chat window
604 	 */
605 	QString sender = Oscar::normalize( message.sender() );
606 	if ( !contacts().value( sender ) )
607 	{
608 		kDebug(OSCAR_RAW_DEBUG) << "Adding '" << sender << "' as temporary contact";
609 		addContact( sender, QString(), 0,  Kopete::Account::Temporary );
610 	}
611 
612 	OscarContact* ocSender = static_cast<OscarContact *> ( contacts().value( sender ) ); //should exist now
613 
614 	if ( !ocSender )
615 	{
616 		kWarning(OSCAR_RAW_DEBUG) << "Temporary contact creation failed for '"
617 			<< sender << "'! Discarding message: " << message.textArray() << endl;
618 		return;
619 	}
620 	else
621 	{
622 		if ( message.hasProperty( Oscar::Message::WWP ) )
623 			ocSender->setNickName( i18n("ICQ Web Express") );
624 		if ( message.hasProperty( Oscar::Message::EMail ) )
625 			ocSender->setNickName( i18n("ICQ Email Express") );
626 	}
627 
628 	Kopete::ChatSession* chatSession = ocSender->manager( Kopete::Contact::CanCreate );
629 	chatSession->receivedTypingMsg( ocSender, false ); //person is done typing
630 
631 	//decode message
632 	QString realText( message.text( contactCodec( ocSender ) ) );
633 
634 	//sanitize;
635 	QString sanitizedMsg = sanitizedMessage( realText );
636 
637 	Kopete::ContactPtrList me;
638 	me.append( myself() );
639 	Kopete::Message chatMessage( ocSender, me );
640 	chatMessage.setHtmlBody( sanitizedMsg );
641 	chatMessage.setTimestamp( message.timestamp() );
642 	chatMessage.setDirection( Kopete::Message::Inbound );
643 
644 	chatSession->appendMessage( chatMessage );
645 }
646 
sanitizedMessage(const QString & message) const647 QString OscarAccount::sanitizedMessage( const QString& message ) const
648 {
649 	QDomDocument doc;
650 	QString domError;
651 	int errLine = 0, errCol = 0;
652 
653 	QString msg = addQuotesAroundAttributes(message);
654 	msg = makeWellFormedXML( msg ); // Official AIM client send crap so we have to sort it out.
655 	msg.replace( "<BR>", "<BR/>", Qt::CaseInsensitive );
656 
657 	doc.setContent( msg, false, &domError, &errLine, &errCol );
658 	if ( !domError.isEmpty() ) //error parsing, do nothing
659 	{
660 		kDebug(OSCAR_AIM_DEBUG) << "error from dom document conversion: " << domError << "line:" << errLine << "col:" << errCol;
661 
662 		// HACK: for trillian which sends totaly mangled html (there are not ended font tags)
663 		if ( message.indexOf( QRegExp( "[\\s]*<[\\s]*HTML[\\s]*>[\\s]*<[\\s]*BODY", Qt::CaseInsensitive ) ) != 0 )
664 			return sanitizedPlainMessage( message );
665 		else
666 			return message;
667 	}
668 	else
669 	{
670 		kDebug(OSCAR_AIM_DEBUG) << "conversion to dom document successful."
671 			<< "looking for font tags" << endl;
672 		QList<QDomNode> fontTagList = getElementsByTagNameCI( doc, "FONT" );
673 		if ( fontTagList.count() == 0 )
674 		{
675 			if ( message.indexOf( QRegExp( "[\\s]*<[\\s]*HTML[\\s]*>[\\s]*<[\\s]*BODY", Qt::CaseInsensitive ) ) != 0 )
676 			{
677 				kDebug(OSCAR_AIM_DEBUG) << "No html tags found. Returning normal message";
678 				return sanitizedPlainMessage( message );
679 			}
680 		}
681 		else
682 		{
683 			kDebug(OSCAR_AIM_DEBUG) << "Found font tags. Attempting replacement";
684 			uint numFontTags = fontTagList.count();
685 			for ( uint i = 0; i < numFontTags; i++ )
686 			{
687 				QDomNode fontNode = fontTagList.at(i);
688 				QDomElement fontEl;
689 				if ( !fontNode.isNull() && fontNode.isElement() )
690 					fontEl = fontNode.toElement();
691 				else
692 					continue;
693 				if ( fontEl.hasAttribute( "BACK" ) )
694 				{
695 					QString backgroundColor = fontEl.attribute( "BACK" );
696 					backgroundColor.insert( 0, "background-color: " );
697 					backgroundColor.append( ';' );
698 					fontEl.setAttribute( "style", backgroundColor );
699 					fontEl.removeAttribute( "BACK" );
700 				}
701 			}
702 		}
703 	}
704 	kDebug(OSCAR_AIM_DEBUG) << "sanitized message is " << doc.toString();
705 	return doc.toString();
706 }
707 
setServerAddress(const QString & server)708 void OscarAccount::setServerAddress(const QString &server)
709 {
710 	configGroup()->writeEntry( QString::fromLatin1( "Server" ), server );
711 }
712 
setServerPort(int port)713 void OscarAccount::setServerPort(int port)
714 {
715 	if (port<=0)
716 		port=5190;
717 
718 	configGroup()->writeEntry( QString::fromLatin1( "Port" ), port);
719 }
720 
setServerEncrypted(bool encrypted)721 void OscarAccount::setServerEncrypted( bool encrypted )
722 {
723 	configGroup()->writeEntry( QString::fromLatin1( "Encrypted" ), encrypted);
724 }
725 
setProxyServerSocks5(bool enable)726 void OscarAccount::setProxyServerSocks5( bool enable )
727 {
728 	configGroup()->writeEntry( QString::fromLatin1( "ProxySocks5" ), enable );
729 }
730 
setProxyServerAddress(const QString & server)731 void OscarAccount::setProxyServerAddress(const QString &server)
732 {
733 	configGroup()->writeEntry( QString::fromLatin1( "ProxyServer" ), server );
734 }
735 
setProxyServerPort(int port)736 void OscarAccount::setProxyServerPort(int port)
737 {
738 	configGroup()->writeEntry( QString::fromLatin1( "ProxyPort" ), port);
739 }
740 
setProxyServerEnabled(bool enable)741 void OscarAccount::setProxyServerEnabled(bool enable)
742 {
743 	configGroup()->writeEntry( QString::fromLatin1( "ProxyEnable" ), enable);
744 }
745 
defaultCodec() const746 QTextCodec* OscarAccount::defaultCodec() const
747 {
748 	QTextCodec* codec = QTextCodec::codecForMib( configGroup()->readEntry( "DefaultEncoding", 4 ) );
749 
750 	if ( codec )
751 		return codec;
752 	else
753 		return QTextCodec::codecForMib( 4 );
754 }
755 
contactCodec(const OscarContact * contact) const756 QTextCodec* OscarAccount::contactCodec( const OscarContact* contact ) const
757 {
758 	if ( contact )
759 		return contact->contactCodec();
760 	else
761 		return defaultCodec();
762 }
763 
contactCodec(const QString & contactName) const764 QTextCodec* OscarAccount::contactCodec( const QString& contactName ) const
765 {
766 	// XXX  Need const_cast because Kopete::Account::contacts()
767 	// XXX  method is not const for some strange reason.
768 	OscarContact* contact = static_cast<OscarContact *> ( const_cast<OscarAccount *>(this)->contacts().value( contactName ) );
769 	return contactCodec( contact );
770 }
771 
updateBuddyIcon(const QString & path)772 void OscarAccount::updateBuddyIcon( const QString &path )
773 {
774 	myself()->removeProperty( Kopete::Global::Properties::self()->photo() );
775 
776 	if ( !path.isEmpty() )
777 	{
778 		QImage image( path );
779 		if ( image.isNull() )
780 			return;
781 
782 		const QSize size = ( d->engine->isIcq() ) ? QSize( 52, 64 ) : QSize( 48, 48 );
783 
784 		image = image.scaled( size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation );
785 		if( image.width() > size.width())
786 			image = image.copy( ( image.width() - size.width() ) / 2, 0, size.width(), image.height() );
787 
788 		if( image.height() > size.height())
789 			image = image.copy( 0, ( image.height() - size.height() ) / 2, image.width(), size.height() );
790 
791 		QString newlocation( QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + "oscarpictures/" + accountId() + ".jpg" ) ;
792 
793 		kDebug(OSCAR_RAW_DEBUG) << "Saving buddy icon: " << newlocation;
794 		if ( !image.save( newlocation, "JPEG" ) )
795 			return;
796 
797 		myself()->setProperty( Kopete::Global::Properties::self()->photo() , newlocation );
798 	}
799 
800 	d->buddyIconDirty = true;
801 	updateBuddyIconInSSI();
802 }
803 
addContactToSSI(const QString & contactName,const QString & groupName,bool autoAddGroup)804 bool OscarAccount::addContactToSSI( const QString& contactName, const QString& groupName, bool autoAddGroup )
805 {
806 	ContactManager* listManager = d->engine->ssiManager();
807 	if ( !listManager->findGroup( groupName ) )
808 	{
809 		if ( !autoAddGroup )
810 			return false;
811 
812 		kDebug(OSCAR_GEN_DEBUG) << "adding non-existent group "
813 			<< groupName << endl;
814 
815 		d->contactAddQueue[Oscar::normalize( contactName )] = groupName;
816 		d->engine->addGroup( groupName );
817 	}
818 	else
819 	{
820 		d->engine->addContact( contactName, groupName );
821 	}
822 
823 	return true;
824 }
825 
changeContactGroupInSSI(const QString & contact,const QString & newGroupName,bool autoAddGroup)826 bool OscarAccount::changeContactGroupInSSI( const QString& contact, const QString& newGroupName, bool autoAddGroup )
827 {
828 	ContactManager* listManager = d->engine->ssiManager();
829 	if ( !listManager->findGroup( newGroupName ) )
830 	{
831 		if ( !autoAddGroup )
832 			return false;
833 
834 		kDebug(OSCAR_GEN_DEBUG) << "adding non-existent group "
835 				<< newGroupName << endl;
836 
837 		d->contactChangeQueue[Oscar::normalize( contact )] = newGroupName;
838 		d->engine->addGroup( newGroupName );
839 	}
840 	else
841 	{
842 		d->engine->changeContactGroup( contact, newGroupName );
843 	}
844 
845 	return true;
846 }
847 
createContact(const QString & contactId,Kopete::MetaContact * parentContact)848 bool OscarAccount::createContact(const QString &contactId,
849 	Kopete::MetaContact *parentContact)
850 {
851 	/* We're not even online or connecting
852 	 * (when getting server contacts), so don't bother
853 	 */
854 	if ( !engine()->isActive() )
855 	{
856 		kDebug(OSCAR_GEN_DEBUG) << "Can't add contact, we are offline!";
857 		return false;
858 	}
859 
860 	/* Logic for SSI additions
861 	If the contact is temporary, no SSI addition at all. Just create the contact and be done with it
862 	If the contact is not temporary, we need to do the following:
863 		1. Check if contact already exists in the SSI manager, if so, just create the contact
864 		2. If contact doesn't exist:
865 		2.a. create group on SSI if needed
866 		2.b. create contact on SSI
867 		2.c. create kopete contact
868 	 */
869 
870 	QList<TLV> dummyList;
871 	if ( parentContact->isTemporary() )
872 	{
873 		OContact tempItem( contactId, 0, 0, 0xFFFF, dummyList, 0 );
874 		return createNewContact( contactId, parentContact, tempItem );
875 	}
876 
877 	OContact ssiItem = d->engine->ssiManager()->findContact( contactId );
878 	if ( ssiItem )
879 	{
880 		kDebug(OSCAR_GEN_DEBUG) << "Have new SSI entry. Finding contact";
881 		if ( contacts().value( ssiItem.name() ) )
882 		{
883 			kDebug(OSCAR_GEN_DEBUG) << "Found contact in list. Updating SSI item";
884 			OscarContact* oc = static_cast<OscarContact*>( contacts().value( ssiItem.name() ) );
885 			oc->setSSIItem( ssiItem );
886 			return true;
887 		}
888 		else
889 		{
890 			kDebug(OSCAR_GEN_DEBUG) << "Didn't find contact in list, creating new contact";
891 			return createNewContact( contactId, parentContact, ssiItem );
892 		}
893 	}
894 	else
895 	{ //new contact, check temporary, if temporary, don't add to SSI. otherwise, add.
896 		kDebug(OSCAR_GEN_DEBUG) << "New contact '" << contactId << "' not in SSI."
897 			<< " Creating new contact" << endl;
898 
899 		kDebug(OSCAR_GEN_DEBUG) << "Adding " << contactId << " to server side list";
900 
901 		QString groupName;
902 		Kopete::GroupList kopeteGroups = parentContact->groups(); //get the group list
903 
904 		if ( kopeteGroups.isEmpty() || kopeteGroups.first() == Kopete::Group::topLevel() )
905 		{
906 			kDebug(OSCAR_GEN_DEBUG) << "Contact with NO group. " << "Adding to group 'Buddies'";
907 			groupName = "Buddies";
908 		}
909 		else
910 		{
911 				//apparently kopeteGroups.first() can be invalid. Attempt to prevent
912 				//crashes in SSIData::findGroup(const QString& name)
913 			groupName = kopeteGroups.first() ? kopeteGroups.first()->displayName() : "Buddies";
914 
915 			kDebug(OSCAR_GEN_DEBUG) << "Contact with group." << " No. of groups = " << kopeteGroups.count() <<
916 				" Name of first group = " << groupName << endl;
917 		}
918 
919 		if( groupName.isEmpty() )
920 		{ // emergency exit, should never occur
921 			kWarning(OSCAR_GEN_DEBUG) << "Could not add contact because no groupname was given";
922 			return false;
923 		}
924 
925 		d->addContactMap[Oscar::normalize( contactId )] = parentContact;
926 		addContactToSSI( Oscar::normalize( contactId ), groupName, true );
927 		return true;
928 	}
929 }
930 
updateVersionUpdaterStamp()931 void OscarAccount::updateVersionUpdaterStamp()
932 {
933 	d->versionUpdaterStamp = OscarVersionUpdater::self()->stamp();
934 }
935 
ssiContactAdded(const OContact & item)936 void OscarAccount::ssiContactAdded( const OContact& item )
937 {
938 	QString normalizedName = Oscar::normalize( item.name() );
939 	if ( contacts().value( item.name() ) )
940 	{
941 		kDebug(OSCAR_GEN_DEBUG) << "Received confirmation from server. modifying " << item.name();
942 		OscarContact* oc = static_cast<OscarContact*>( contacts().value( item.name() ) );
943 		oc->setSSIItem( item );
944 		// To be safe remove contact from addContactMap
945 		d->addContactMap.remove( normalizedName );
946 	}
947 	else if ( d->addContactMap.contains( normalizedName ) )
948 	{
949 		kDebug(OSCAR_GEN_DEBUG) << "Received confirmation from server. adding " << item.name()
950 			<< " to the contact list" << endl;
951 		OscarContact* oc = createNewContact( item.name(), d->addContactMap[normalizedName], item );
952 		d->addContactMap.remove( normalizedName );
953 		if ( oc && oc->ssiItem().waitingAuth() )
954 			QTimer::singleShot( 1, oc, SLOT(requestAuthorization()) );
955 	}
956 	else
957 		kDebug(OSCAR_GEN_DEBUG) << "Got addition for contact we weren't waiting on";
958 }
959 
ssiGroupAdded(const OContact & item)960 void OscarAccount::ssiGroupAdded( const OContact& item )
961 {
962 	//check the contact add queue for any contacts matching the
963 	//group name we just added
964 	kDebug(OSCAR_GEN_DEBUG) << "Looking for contacts to be added in group " << item.name();
965 	QMap<QString,QString>::iterator it;
966 	for ( it = d->contactAddQueue.begin(); it != d->contactAddQueue.end(); ++it )
967 	{
968 		if ( Oscar::normalize( it.value() ) == Oscar::normalize( item.name() ) )
969 		{
970 			kDebug(OSCAR_GEN_DEBUG) << "starting delayed add of contact '" << it.key()
971 				<< "' to group " << item.name() << endl;
972 
973 			d->engine->addContact( Oscar::normalize( it.key() ), item.name() );
974 			d->contactAddQueue.erase( it );
975 		}
976 	}
977 
978 	for ( it = d->contactChangeQueue.begin(); it != d->contactChangeQueue.end(); ++it )
979 	{
980 		if ( Oscar::normalize( it.value() ) == Oscar::normalize( item.name() ) )
981 		{
982 			kDebug(OSCAR_GEN_DEBUG) << "starting delayed change of contact '" << it.key()
983 				<< "' to group " << item.name() << endl;
984 
985 			d->engine->changeContactGroup( it.key(),  item.name() );
986 			d->contactChangeQueue.erase( it );
987 		}
988 	}
989 }
990 
ssiContactUpdated(const OContact & item)991 void OscarAccount::ssiContactUpdated( const OContact& item )
992 {
993 	Kopete::Contact* contact = contacts().value( item.name() );
994 	if ( !contact )
995 		return;
996 
997 	kDebug(OSCAR_RAW_DEBUG) << "Updating SSI Item";
998 	OscarContact* oc = static_cast<OscarContact*>( contact );
999 	oc->setSSIItem( item );
1000 }
1001 
userStartedTyping(const QString & contact)1002 void OscarAccount::userStartedTyping( const QString & contact )
1003 {
1004 	Kopete::Contact * ct = contacts().value( Oscar::normalize( contact ) );
1005 	if ( ct )
1006 	{
1007 		OscarContact * oc = static_cast<OscarContact *>( ct );
1008 		oc->startedTyping();
1009 	}
1010 }
1011 
userStoppedTyping(const QString & contact)1012 void OscarAccount::userStoppedTyping( const QString & contact )
1013 {
1014 	Kopete::Contact * ct = contacts().value( Oscar::normalize( contact ) );
1015 	if ( ct )
1016 	{
1017 		OscarContact * oc = static_cast<OscarContact *>( ct );
1018 		oc->stoppedTyping();
1019 	}
1020 }
1021 
slotSocketError(int errCode,const QString & errString)1022 void OscarAccount::slotSocketError( int errCode, const QString& errString )
1023 {
1024 	Q_UNUSED( errCode );
1025 
1026 	if ( !isBusy() )
1027 		KNotification::event( QLatin1String("connection_error"), i18nc( "account has been disconnected", "Kopete: %1 disconnected", accountId() ),
1028 	                      errString,
1029 	                      myself()->onlineStatus().protocolIcon(KIconLoader::SizeMedium),
1030 	                      Kopete::UI::Global::mainWidget() );
1031 
1032 	logOff( Kopete::Account::ConnectionReset );
1033 }
1034 
slotTaskError(const Oscar::SNAC & s,int code,bool fatal)1035 void OscarAccount::slotTaskError( const Oscar::SNAC& s, int code, bool fatal )
1036 {
1037 	kDebug(OSCAR_GEN_DEBUG) << "error received from task";
1038 	kDebug(OSCAR_GEN_DEBUG) << "service: " << s.family
1039 		<< " subtype: " << s.subtype << " code: " << code << endl;
1040 
1041 	QString message;
1042 	if ( s.family == 0 && s.subtype == 0 )
1043 	{
1044 		message = getFLAPErrorMessage( code );
1045 		if ( !isBusy() )
1046 			KNotification::event( QLatin1String("connection_error"), i18nc( "account has been disconnected", "Kopete: %1 disconnected", accountId() ),
1047 		                      message, myself()->onlineStatus().protocolIcon(KIconLoader::SizeMedium),
1048 		                      Kopete::UI::Global::mainWidget() );
1049 		switch ( code )
1050 		{
1051 		case 0x0000:
1052 			logOff( Kopete::Account::Unknown );
1053 			break;
1054 		case 0x0004:
1055 		case 0x0005:
1056 			logOff( Kopete::Account::BadPassword );
1057 			break;
1058 		case 0x0007:
1059 		case 0x0008:
1060 		case 0x0009:
1061 		case 0x0011:
1062 			logOff( Kopete::Account::BadUserName );
1063 			break;
1064 		case 0x001B:
1065 		case 0x001C:
1066 			OscarVersionUpdater::self()->update( d->versionUpdaterStamp );
1067 			if ( !d->versionAlreadyUpdated )
1068 			{
1069 				logOff( Kopete::Account::Unknown );
1070 				d->versionAlreadyUpdated = true;
1071 			}
1072 			else
1073 			{
1074 				logOff( Kopete::Account::Manual );
1075 			}
1076 			break;
1077 		default:
1078 			logOff( Kopete::Account::Manual );
1079 		}
1080 		return;
1081 	}
1082 	if ( !fatal )
1083 		message = i18n("There was an error in the protocol handling; it was not fatal, so you will not be disconnected.");
1084 	else
1085 		message = i18n("There was an error in the protocol handling; automatic reconnection occurring.");
1086 
1087 	if ( !isBusy() )
1088 		KNotification::event( QLatin1String("server_error"), i18n("Kopete: OSCAR Protocol error"), message, myself()->onlineStatus().protocolIcon(KIconLoader::SizeMedium),
1089 	                      Kopete::UI::Global::mainWidget() );
1090 	if ( fatal )
1091 		logOff( Kopete::Account::ConnectionReset );
1092 }
1093 
updateBuddyIconInSSI()1094 void OscarAccount::updateBuddyIconInSSI()
1095 {
1096 	if ( !engine()->isActive() )
1097 		return;
1098 
1099 	QString photoPath = myself()->property( Kopete::Global::Properties::self()->photo() ).value().toString();
1100 
1101 	ContactManager* ssi = engine()->ssiManager();
1102 	OContact item = ssi->findItemForIconByRef( 1 );
1103 
1104 	if ( photoPath.isEmpty() )
1105 	{
1106 		if ( item )
1107 		{
1108 			kDebug(OSCAR_GEN_DEBUG) << "Removing icon hash item from ssi";
1109 			OContact s(item);
1110 
1111 			//remove hash and alias
1112 			QList<TLV> tList( item.tlvList() );
1113 			TLV t = Oscar::findTLV( tList, 0x00D5 );
1114 			if ( t )
1115 				tList.removeAll( t );
1116 
1117 			t = Oscar::findTLV( tList, 0x0131 );
1118 			if ( t )
1119 				tList.removeAll( t );
1120 
1121 			item.setTLVList( tList );
1122 			//s is old, item is new. modification will occur
1123 			engine()->modifyContactItem( s, item );
1124 		}
1125 	}
1126 	else
1127 	{
1128 		QFile iconFile( photoPath );
1129 		iconFile.open( QIODevice::ReadOnly );
1130 
1131 		QCryptographicHash iconHash( QCryptographicHash::Md5 );
1132 		iconHash.addData( &iconFile );
1133 		kDebug(OSCAR_GEN_DEBUG) << "hash is :" << iconHash.result().toHex();
1134 
1135 		QByteArray iconTLVData;
1136 		iconTLVData.resize( 18 );
1137 		iconTLVData[0] = ( d->engine->isIcq() ) ? 0x01 : 0x00;
1138 		iconTLVData[1] = 0x10;
1139 		memcpy( iconTLVData.data() + 2, iconHash.result(), 16 );
1140 
1141 		QList<Oscar::TLV> tList;
1142 		tList.append( TLV( 0x00D5, iconTLVData.size(), iconTLVData ) );
1143 		tList.append( TLV( 0x0131, 0, 0 ) );
1144 
1145 		//find old item, create updated item
1146 		if ( !item )
1147 		{
1148 			kDebug(OSCAR_GEN_DEBUG) << "no existing icon hash item in ssi. creating new";
1149 
1150 			OContact s( "1", 0, ssi->nextContactId(), ROSTER_BUDDYICONS, tList );
1151 
1152 			//item is a non-valid ssi item, so the function will add an item
1153 			kDebug(OSCAR_GEN_DEBUG) << "setting new icon item";
1154 			engine()->modifyContactItem( item, s );
1155 		}
1156 		else
1157 		{ //found an item
1158 			OContact s(item);
1159 
1160 			if ( Oscar::updateTLVs( s, tList ) == true )
1161 			{
1162 				kDebug(OSCAR_GEN_DEBUG) << "modifying old item in ssi.";
1163 
1164 				//s is old, item is new. modification will occur
1165 				engine()->modifyContactItem( item, s );
1166 			}
1167 			else
1168 			{
1169 				kDebug(OSCAR_GEN_DEBUG) << "not updating, item is the same.";
1170 			}
1171 		}
1172 
1173 		iconFile.close();
1174 	}
1175 
1176 	d->buddyIconDirty = false;
1177 }
1178 
slotSendBuddyIcon()1179 void OscarAccount::slotSendBuddyIcon()
1180 {
1181 	//need to disconnect because we could end up with many connections
1182 	QObject::disconnect( engine(), SIGNAL(iconServerConnected()), this, SLOT(slotSendBuddyIcon()) );
1183 	QString photoPath = myself()->property( Kopete::Global::Properties::self()->photo() ).value().toString();
1184 	if ( photoPath.isEmpty() )
1185 		return;
1186 
1187 	kDebug(OSCAR_RAW_DEBUG) << photoPath;
1188 	QFile iconFile( photoPath );
1189 
1190 	if ( iconFile.open( QIODevice::ReadOnly ) )
1191 	{
1192 		if ( !engine()->hasIconConnection() )
1193 		{
1194 			//will send icon when we connect to icon server
1195 			QObject::connect( engine(), SIGNAL(iconServerConnected()),
1196 			                  this, SLOT(slotSendBuddyIcon()) );
1197 
1198 			engine()->connectToIconServer();
1199 			return;
1200 		}
1201 		QByteArray imageData = iconFile.readAll();
1202 		engine()->sendBuddyIcon( imageData );
1203 	}
1204 }
1205 
slotIdentityPropertyChanged(Kopete::PropertyContainer *,const QString & key,const QVariant &,const QVariant & newValue)1206 void OscarAccount::slotIdentityPropertyChanged( Kopete::PropertyContainer*, const QString &key,
1207                                                 const QVariant&, const QVariant &newValue )
1208 {
1209 	kDebug(OSCAR_GEN_DEBUG) << "Identity property changed";
1210 	if ( key == Kopete::Global::Properties::self()->photo().key() )
1211 	{
1212 		updateBuddyIcon( newValue.toString() );
1213 	}
1214 }
1215 
slotGoOffline()1216 void OscarAccount::slotGoOffline()
1217 {
1218 }
1219 
slotGoOnline()1220 void OscarAccount::slotGoOnline()
1221 {
1222 }
1223 
getFLAPErrorMessage(int code)1224 QString OscarAccount::getFLAPErrorMessage( int code )
1225 {
1226 	bool isICQ = d->engine->isIcq();
1227 	QString acctType = isICQ ? i18n("ICQ") : i18n("AIM");
1228 	QString acctDescription = isICQ ? i18nc("ICQ user id", "UIN") : i18nc("AIM user id", "screen name");
1229 	QString reason;
1230 	//FLAP errors are always fatal
1231 	//negative codes are things added by liboscar developers
1232 	//to indicate generic errors in the task
1233 	switch ( code )
1234 	{
1235 	case 0x0001:
1236 		if ( isConnected() ) // multiple logins (on same UIN)
1237 		{
1238 			reason = i18n( "You have logged in more than once with the same %1," \
1239 			               " account %2 is now disconnected.",
1240 				  acctDescription, accountId() );
1241 		}
1242 		else // error while logging in
1243 		{
1244 			reason = i18n( "Sign on failed because either your %1 or " \
1245 			               "password are invalid. Please check your settings for account %2.",
1246 				  acctDescription, accountId() );
1247 
1248 		}
1249 		break;
1250 	case 0x0002: // Service temporarily unavailable
1251 	case 0x0014: // Reservation map error
1252 		reason = i18n("The %1 service is temporarily unavailable. Please try again later.",
1253 			  acctType );
1254 		break;
1255 	case 0x0004: // Incorrect nick or password, re-enter
1256 	case 0x0005: // Mismatch nick or password, re-enter
1257 		reason = i18n("Could not sign on to %1 with account %2 because the " \
1258 		              "password was incorrect.", acctType, accountId() );
1259 		break;
1260 	case 0x0007: // non-existent ICQ#
1261 	case 0x0008: // non-existent ICQ#
1262 		reason = i18n("Could not sign on to %1 with nonexistent account %2.",
1263 			  acctType, accountId() );
1264 		break;
1265 	case 0x0009: // Expired account
1266 		reason = i18n("Sign on to %1 failed because your account %2 expired.",
1267 			  acctType, accountId() );
1268 		break;
1269 	case 0x0011: // Suspended account
1270 		reason = i18n("Sign on to %1 failed because your account %2 is " \
1271 		              "currently suspended.", acctType, accountId() );
1272 		break;
1273 	case 0x0015: // too many clients from same IP
1274 	case 0x0016: // too many clients from same IP
1275 	case 0x0017: // too many clients from same IP (reservation)
1276 		reason = i18n("Could not sign on to %1 as there are too many clients" \
1277 		              " from the same computer.", acctType );
1278 		break;
1279 	case 0x0018: // rate exceeded (turboing)
1280 		if ( isConnected() )
1281 		{
1282 			reason = i18n("Account %1 was blocked on the %2 server for" \
1283 							" sending messages too quickly." \
1284 							" Wait ten minutes and try again." \
1285 							" If you continue to try, you will" \
1286 							" need to wait even longer.",
1287 				  accountId(), acctType );
1288 		}
1289 		else
1290 		{
1291 			reason = i18n("Account %1 was blocked on the %2 server for" \
1292 							" reconnecting too quickly." \
1293 							" Wait ten minutes and try again." \
1294 							" If you continue to try, you will" \
1295 							" need to wait even longer.",
1296 				  accountId(), acctType) ;
1297 		}
1298 		break;
1299 	case 0x001B:
1300 	case 0x001C:
1301 		if ( !d->versionAlreadyUpdated )
1302 		{
1303 			reason = i18n("Sign on to %1 with your account %2 failed.",
1304 			              acctType, accountId() );
1305 		}
1306 		else
1307 		{
1308 			reason = i18n( "The %1 server thinks the client you are using is " \
1309 			               "too old. Please report this as a bug at https://bugs.kde.org",
1310 			               acctType );
1311 		}
1312 		break;
1313 	case 0x0022: // Account suspended because of your age (age < 13)
1314 		reason = i18n("Account %1 was disabled on the %2 server because " \
1315 		              "of your age (under than 13).",
1316 			  accountId(), acctType );
1317 		break;
1318 	default:
1319 		if ( !isConnected() )
1320 		{
1321 			reason = i18n("Sign on to %1 with your account %2 failed.",
1322 				  acctType, accountId() );
1323 		}
1324 		break;
1325 	}
1326 	return reason;
1327 }
1328 
makeWellFormedXML(const QString & message) const1329 QString OscarAccount::makeWellFormedXML( const QString& message ) const
1330 {
1331 	// QList<QPair<tagName, data>>, if tagName isn't empty then data is <tag ....>
1332 	// otherwise data is normal text which is between tags.
1333 	QList< QPair<QString, QString> > tagsAndText;
1334 
1335 	QRegExp tagRegExp( QString::fromLatin1("<([/]?[\\w]+).*>") );
1336 	tagRegExp.setMinimal( true );
1337 	int index = 0;
1338 
1339 	while ( tagRegExp.indexIn( message, index ) != -1 )
1340 	{
1341 		if ( index < tagRegExp.pos() )
1342 		{
1343 			// Append text which was between tags
1344 			QPair<QString, QString> pair;
1345 			pair.second = message.mid( index, tagRegExp.pos() - index );
1346 			tagsAndText.append( pair );
1347 		}
1348 
1349 		// Add tag
1350 		QPair<QString, QString> pair;
1351 		pair.first = tagRegExp.cap( 1 );
1352 		pair.second = message.mid( tagRegExp.pos(), tagRegExp.matchedLength() );
1353 		tagsAndText.append( pair );
1354 		index = tagRegExp.pos() + tagRegExp.matchedLength();
1355 	}
1356 
1357 	if ( index < message.length() )
1358 	{
1359 		// Add text which was at end
1360 		QPair<QString, QString> pair;
1361 		pair.second = message.mid( index, message.length() - index );
1362 		tagsAndText.append( pair );
1363 	}
1364 
1365 	// Make the list well-formed
1366 	QStack<QString> openTags;
1367 	const int tagsAndTextCount = tagsAndText.count();
1368 	for ( int i = 0; i < tagsAndTextCount; ++i )
1369 	{
1370 		QPair<QString, QString> pair = tagsAndText.at( i );
1371 		if ( pair.first.isEmpty() ) // We don't move text
1372 			continue;
1373 
1374 		bool endTag = pair.first.startsWith( "/" );
1375 		if ( !endTag )
1376 		{
1377 			openTags.push( pair.first );
1378 		}
1379 		else if ( !openTags.isEmpty() )
1380 		{
1381 			QString desiredTag = "/" + openTags.pop();
1382 			if ( pair.first != desiredTag )
1383 			{
1384 				// Find desired end tag and insert it into correct position
1385 				for ( int j = i + 1; j < tagsAndTextCount; ++j )
1386 				{
1387 					QPair<QString, QString> pair2 = tagsAndText.at( j );
1388 					if ( pair2.first.isEmpty() )
1389 					{
1390 						// Text is between desired tag so we can't move it
1391 						qWarning() << "Can't make well-formed XML!";
1392 						return message;
1393 					}
1394 
1395 					if ( pair2.first == desiredTag )
1396 					{
1397 						// Move tag to correct position
1398 						tagsAndText.removeAt( j );
1399 						tagsAndText.insert( i, pair2 );
1400 						break;
1401 					}
1402 				}
1403 			}
1404 		}
1405 	}
1406 
1407 	QString wellFormedMessage;
1408 	for ( int i = 0; i < tagsAndTextCount; ++i )
1409 		wellFormedMessage += tagsAndText.at( i ).second;
1410 
1411 	return wellFormedMessage;
1412 }
1413 
addQuotesAroundAttributes(QString message) const1414 QString OscarAccount::addQuotesAroundAttributes( QString message ) const
1415 {
1416 	int sIndex = 0;
1417 	int eIndex = 0;
1418 	int searchIndex = 0;
1419 
1420 	QRegExp attrRegExp( "[\\d\\w]*=[^\"'/>\\s]+" );
1421 	QString attrValue( "\"%1\"" );
1422 
1423 	sIndex = message.indexOf( "<", eIndex );
1424 	eIndex = message.indexOf( ">", sIndex );
1425 
1426 	if ( sIndex == -1 || eIndex == -1 )
1427 		return message;
1428 
1429 	while ( attrRegExp.indexIn( message, searchIndex ) != -1 )
1430 	{
1431 		int startReplace = message.indexOf( "=", attrRegExp.pos() ) + 1;
1432 		int replaceLength = attrRegExp.pos() + attrRegExp.matchedLength() - startReplace;
1433 
1434 		while ( eIndex != -1 && sIndex != -1 && startReplace + replaceLength > eIndex )
1435 		{
1436 			sIndex = message.indexOf( "<", eIndex );
1437 			eIndex = message.indexOf( ">", sIndex );
1438 		}
1439 
1440 		if ( sIndex == -1 || eIndex == -1 )
1441 			return message;
1442 
1443 		searchIndex = attrRegExp.pos() + attrRegExp.matchedLength();
1444 		if ( startReplace <= sIndex )
1445 			continue;
1446 
1447 		QString replaceText = attrValue.arg( message.mid( startReplace, replaceLength ) );
1448 		message.replace( startReplace, replaceLength, replaceText );
1449 
1450 		searchIndex += 2;
1451 		eIndex += 2;
1452 	}
1453 
1454 	return message;
1455 }
1456 
sanitizedPlainMessage(const QString & message) const1457 QString OscarAccount::sanitizedPlainMessage( const QString& message ) const
1458 {
1459 	// FIXME: messages from AIM to ICQ shouldn't be escaped, we need to redesign this
1460 	QString sanitizedMsg = (d->engine->isIcq()) ? message.toHtmlEscaped() : message;
1461 	sanitizedMsg.replace( QRegExp(QString::fromLatin1("[\r]?[\n]")), QString::fromLatin1("<br />") );
1462 	return sanitizedMsg;
1463 }
1464 
getElementsByTagNameCI(const QDomNode & node,const QString & tagName) const1465 QList<QDomNode> OscarAccount::getElementsByTagNameCI( const QDomNode& node, const QString& tagName ) const
1466 {
1467 	QList<QDomNode> nodeList;
1468 
1469 	QDomNode childNode = node.firstChild();
1470 	while ( !childNode.isNull() )
1471 	{
1472 		nodeList.append( getElementsByTagNameCI( childNode, tagName ) );
1473 		if ( childNode.isElement() && childNode.nodeName().compare( tagName, Qt::CaseInsensitive ) == 0 )
1474 			nodeList.append( childNode );
1475 
1476 		childNode = childNode.nextSibling();
1477 	}
1478 	return nodeList;
1479 }
1480 
createClientStream(ClientStream ** clientStream)1481 void OscarAccount::createClientStream( ClientStream **clientStream )
1482 {
1483 	QSslSocket* tcpSocket = new QSslSocket();
1484 
1485 	if (configGroup()->readEntry( QString::fromLatin1( "ProxyEnable" ), false))
1486 	{
1487 		QString proxyExp=configGroup()->readEntry( QString::fromLatin1( "ProxyServer" ), QString() );
1488 		int proxyPort=configGroup()->readEntry( QString::fromLatin1( "ProxyPort" ), 0 );
1489 		bool proxySocks5=configGroup()->readEntry( QString::fromLatin1( "ProxySocks5" ), false );
1490 		tcpSocket->setProxy(QNetworkProxy(proxySocks5 ? QNetworkProxy::Socks5Proxy : QNetworkProxy::HttpProxy, proxyExp, proxyPort));
1491 	}
1492 	else
1493 	{
1494 		const QString &proxyUrl = KProtocolManager::proxyForUrl( KUrl( "http:" ) );
1495 		if (!proxyUrl.isEmpty() && proxyUrl != QLatin1String( "DIRECT" ))
1496 		{
1497 			const KUrl url( proxyUrl );
1498 			QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
1499 			if (url.protocol() == QLatin1String( "http" ))
1500 				proxyType = QNetworkProxy::HttpProxy;
1501 			else if (url.protocol() == QLatin1String( "socks" ))
1502 				proxyType = QNetworkProxy::Socks5Proxy;
1503 			if (proxyType != QNetworkProxy::NoProxy)
1504 				tcpSocket->setProxy( QNetworkProxy( proxyType, url.host(), url.port(), url.user(), url.pass() ) );
1505 		}
1506 	}
1507 
1508 	ClientStream *cs = new ClientStream( tcpSocket, 0 );
1509 
1510 	Kopete::SocketTimeoutWatcher* timeoutWatcher = Kopete::SocketTimeoutWatcher::watch(tcpSocket);
1511 	if ( timeoutWatcher )
1512 	{
1513 		QObject::connect( timeoutWatcher, SIGNAL(error(QAbstractSocket::SocketError)),
1514 		                  cs, SLOT(socketError(QAbstractSocket::SocketError)) );
1515 	}
1516 
1517 	*clientStream = cs;
1518 }
1519 
1520