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