Files
kmess/src/network/msnnotificationconnection.cpp
T
2009-07-17 15:44:01 +00:00

3176 lines
98 KiB
C++

/***************************************************************************
msnnotificationconnection.cpp - description
-------------------
begin : Thu Jan 23 2003
copyright : (C) 2003 by Mike K. Bennett
email : mkb137b@hotmail.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "msnnotificationconnection.h"
#include "../contact/contact.h"
#include "../model/contactlist.h"
#include "../utils/kmessshared.h"
#include "../currentaccount.h"
#include "../kmess.h"
#include "../kmessapplication.h"
#include "../kmessdebug.h"
#include "../kmessinterface.h"
#include "soap/addressbookservice.h"
#include "soap/passportloginservice.h"
#include "soap/offlineimservice.h"
#include "utils/kmessconfig.h"
#include "chatmessage.h"
#include "mimemessage.h"
#include "msnchallengehandler.h"
#include <QTextDocument> // for Qt::escape()
#include <QUrl>
#include <QtEndian>
#include <KDialog>
#include <KLocale>
#include <KMessageBox>
#ifdef KMESSDEBUG_CONTACTLISTMODELTEST
#include "../../tests/modeltest/modeltest.h"
#endif
#ifdef KMESSDEBUG_NOTIFICATION
#define KMESSDEBUG_NOTIFICATION_GENERAL
// #define KMESSDEBUG_NOTIFICATION_AUTH
// #define KMESSDEBUG_NOTIFICATION_MESSAGES
#endif
// Settings for debugging.
#define NOTIFICATION_HIDE_P2P_SUPPORT 0
/**
* Maximum allowed time between unique server responses
*
* KMess will wait at most this number of milliseconds after receiving a server command
* (any command, in fact). If nothing arrives and this timeout expires, the login process
* or the connection will be aborted.
*/
#define NOTIFICATION_COMMAND_INTERVAL_TIMEOUT 30000
/**
* @brief The constructor
*
* Initializes the default groups of the ContactList class.
*/
MsnNotificationConnection::MsnNotificationConnection()
: MsnConnection( MsnSocketBase::SERVER_NOTIFICATION ),
addressBookService_(0),
connected_(false),
currentAccount_(0),
enableNotifications_(false),
initialized_(false),
isInitiatingConnection_(false),
lastStatus_(-1),
offlineImService_(0),
passportLoginService_(0)
{
setObjectName( "MsnNotificationConnection" );
// We manage the contact list
contactList_ = new ContactList();
// TODO post 2.0: replace with proper impl
connect( contactList_, SIGNAL( dndMoveContactToGroup(QString,QString,QString) ),
this, SLOT ( moveContact(QString,QString,QString) ) );
#ifdef KMESSDEBUG_CONTACTLISTMODELTEST
// Start the Contact List model tests
new ModelTest( contactList_ );
#endif
// Periodically close expired switchboard requests
connect( this, SIGNAL( pingSent() ),
this, SLOT ( checkSwitchboardsTimeout() ) );
// Attach a timer to watch over the login process
loginTimer_.stop();
loginTimer_.setSingleShot( true );
connect( &loginTimer_, SIGNAL( timeout() ),
this, SLOT ( slotAuthenticationFailed() ) );
}
/**
* @brief The destructor
*
* Deletes the contact list.
* The base class closes the connection.
*/
MsnNotificationConnection::~MsnNotificationConnection()
{
delete contactList_;
// Remove all open requests.
qDeleteAll( openRequests_ );
openRequests_.clear();
// Remove all offline im messages
qDeleteAll( offlineImMessages_ );
offlineImMessages_.clear();
// closeConnection() also cleans up a lot too.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "DESTROYED.";
#endif
}
/**
* @brief Add a contact to another group.
*
* It's possible to add a contact to multiple groups,
* typically referred to as "copy contact to group..".
*
* @param handle The contact handle.
* @param groupId The group ID.
*/
void MsnNotificationConnection::addContactToGroup(QString handle, QString groupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "adding '" << handle << " to '" << groupId << "'.";
#endif
AddressBookService *addressBook = createAddressBookService();
Contact *contact = contactList_->getContactByHandle( handle );
if( contact == 0 )
{
return;
}
addressBook->addContactToGroup( contact->getGuid(), groupId );
}
/**
* @brief Re-add a known contact to the friends list
*
* The contact will no longer appear in a "allowed contacts"
* or "removed contacts" group, but appear at the friends list again.
* The contact should exist already at some list
* before (e.g. the block list or friends list).
*
*
* @param handle Email address of the contact.
* @param groupsId Groups where to add the contact.
*/
void MsnNotificationConnection::addExistingContact( QString handle, const QStringList& groupsId )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Re-adding contact " << handle;
#endif
// Request the re-adding of contact to AB
AddressBookService *addressBook = createAddressBookService();
addressBook->addContact( handle, groupsId , true );
}
/**
* @brief Add a new contact to the list.
*
* This is the standard method to add a contact. Optionally, it can also add it to a specific group.
*
* @param handle Email address of the contact.
* @param groupsId groupId's of the groups where to place the new contact.
*/
void MsnNotificationConnection::addNewContact( QString handle, const QStringList& groupsId )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Adding contact " << handle;
#endif
// Request the adding of contact to AB
AddressBookService *addressBook = createAddressBookService();
addressBook->addContact( handle, groupsId );
}
/**
* @brief Adds the given contact to the allow list only.
*
* It allows the contact to see the online state of the user.
* The contact will not appear in the main window
* until it's also added to the friends list using addExistingContact().
*
* This method can only be called when the contact exists
* in the reverse list only. This happens when the contact is new to the user,
* or it was completely removed. Call unblockContact() instead when the
* contact needs to be unblocked.
*
* This method is used when a new contact appears:
* - to add the contact to the friends list too, call addNewContact()
* - to allow the contact only, call this method.
* - to block the contact, call blockContact()
*
* Internally, this method runs the required putAdc() calls to change the contact lists:
* - the contact is added to the allow list (<code>AL</code>).
*
* @param handle Email address of the contact.
*/
void MsnNotificationConnection::allowContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Allowing contact " << handle;
#endif
putAdl( handle, Contact::MSN_LIST_ALLOWED );
AddressBookService *addressBook = createAddressBookService();
addressBook->unblockContact( handle );
}
/**
* @brief Add a new group
*
* This sends an <code>ADG</code> command to the server.
* When gotAdg() receives the server response, the ContactList object is updated.
*
* @param name Name of the group.
*/
void MsnNotificationConnection::addGroup(QString name)
{
AddressBookService *addressBook = createAddressBookService();
addressBook->addGroup( name );
}
/**
* @brief Block the given contact
*
* When the contact also exists in the friends list,
* it will remain there with a blocked status icon.
* When the contact only exists in the "allowed contacts" group,
* it moves to the "removed contacts" group with a blocked status icon.
*
* This method can also be used when a new contact appears:
* - to add the contact to the friends list too, call addNewContact()
* - to allow the contact only, call allowContact()
* - to block the contact, call this method.
*
* Internally, this method runs the required putAdc() calls to change the contact lists:
* - the contact is added to the block list (<code>BL</code>).
*
* @param handle Email address of the contact.
*/
void MsnNotificationConnection::blockContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Blocking contact " << handle;
#endif
putRml( handle, Contact::MSN_LIST_ALLOWED );
putAdl( handle, Contact::MSN_LIST_BLOCKED );
AddressBookService *addressBook = createAddressBookService();
addressBook->blockContact( handle );
}
/**
* @brief Change the current media of the user.
*
* This is used to exchange the "now playing" information.
* It sends an <code>UUX</code> command to the server.
* More information about this command can be found
* at: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#UUX
*
* @param application Name of the media application, typically empty. iTunes and Winamp are known to be accepted.
* @param mediaType Type of the media, either "Music", "Games" or "Office".
* @param enabled Whether the media setting is enabled or not.
* @param formatString A .Net style formatting string, e.g. "{0} - {1}".
* @param arguments Replacements for the placeholders in the formatStrirng.
*/
void MsnNotificationConnection::changeCurrentMedia( const QString &application, const QString &mediaType,
bool enabled, const QString &formatString,
const QStringList arguments )
{
// Update current media string
QString media( application + "\\0" + mediaType + "\\0" + ( enabled ? "1" : "0" ) + "\\0" +
formatString + "\\0" + arguments.join( "\\0" ) );
lastCurrentMedia_ = Qt::escape( media );
// Send personal status and current media
putUux();
}
/**
* @brief Called when the MSN Object of the current user changed.
*
* When this method is called, contacts will be informed the user has a new display picture.
* Some clients automatically initiate a background chat to retrieve the new display picture.
* Other clients wait until a real chat is initiated, to request the display picture at that moment.
*
* Internally, this method calls changeStatus( Status ) because
* that same command is used to notify about MsnObject changes.
* The MsnObject is extracted from the CurrentAccount class.
*/
void MsnNotificationConnection::changedMsnObject()
{
if(isConnected())
{
// Send the same status message, with a new msn object
changeStatus(currentAccount_->getStatus());
}
}
/**
* @brief Change the friendly name of the user.
*
* Internally, this method uses changeProperty() to set the <code>MFN</code> property.
* In MSNP15 the command PRP must be send with SOAP request to avoid nickname will disappear
* the next time we sign in.
*
* @param newName The new friendly name.
*/
void MsnNotificationConnection::changeFriendlyName( QString newName )
{
if ( newName.trimmed().isEmpty() )
{
kWarning() << "Tried to set an empty friendly name; ignoring.";
return;
}
if( ! currentAccount_->isVerified() )
{
return;
}
currentAccount_->setFriendlyName( newName );
changeProperty("MFN", newName);
AddressBookService *addressBook = createAddressBookService();
addressBook->contactUpdate( AddressBookService::PROPERTY_FRIENDLYNAME, newName );
}
// Change the personal properties
void MsnNotificationConnection::changePersonalProperties( const QString& friendlyname, const QString& cid, int blp )
{
// Send BLP command for privacy settings, please refer to:
// http://msnpiki.msnfanatic.com/index.php/Command:BLP
const QString& blpToSend( ( blp == 0 ) ? "BL" : "AL" );
sendCommand( "BLP", blpToSend );
changeFriendlyName( friendlyname );
Q_UNUSED( cid );
}
/**
* @brief Change the personal message of the user.
*
* The personal mesage typically contains a short status message
* displayed next to the contact.
*
* This method sends the <code>UUX</code> payload command to the server.
*
* @param newMessage The new status message.
*/
void MsnNotificationConnection::changePersonalMessage( QString newMessage )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Changing personal message to " << newMessage;
#endif
lastPsm_ = Qt::escape(newMessage);
putUux();
}
/**
* @brief Change a user property
*
* It sends the <code>PRP</code> (likely short for property) command to the server.
*
* Valid property types are:
* - <code>MFN</code>: MSN Friendly name
* - <code>PHH</code>: Phone home
* - <code>PHW</code>: Phone work
* - <code>PHM</code>: Phone mobile
* - <code>MOB</code>: MSN Mobile authorised for others: Y or N
* - <code>MBE</code>: MSN Mobile enabled
* - <code>WWE</code>: MSN direct?
* - <code>HSB</code>: Has blog: <code>1</code> or <code>0</code>
*
* This function is also used internally by other methods like changeFriendlyName()
*
* @param type The property type.
* @param value The new property value, can also be empty.
*/
void MsnNotificationConnection::changeProperty( QString type, QString value )
{
if( value.isEmpty() )
{
sendCommand( "PRP", type );
}
else
{
value = QUrl::toPercentEncoding( value );
// To avoid problem with a very long nick
value.truncate( 387 );
sendCommand( "PRP", type + " " + value );
}
}
/**
* @brief Change a contact property
*
* It sends the <code>SBP</code> (likely short for set-buddy-property) command to the server.
*
* @param handle Email address of the contact.
* @param type The property type, e.g. <code>MFN</code>, see the other changeProperty() method.
* @param value The new property value, can also be empty.
*/
void MsnNotificationConnection::changeProperty( QString handle, QString type, QString value )
{
// Get contact
const Contact *contact = contactList_->getContactByHandle(handle);
if(KMESS_NULL(contact)) return;
// Avoid disconnects by the server
if( contact->getGuid().isEmpty() )
{
kWarning() << "Can't change property, contact GUID is empty"
<< " (contact=" << handle << " property=" << type << ")." << endl;
return;
}
// First guess is that "SBP" means set-buddy-property
if( value.isEmpty() )
{
sendCommand( "SBP", contact->getGuid() + " " + type );
}
else
{
value = QUrl::toPercentEncoding( value );
// To avoid problem with a very long nick
value.truncate( 387 );
sendCommand( "SBP", contact->getGuid() + " " + type + " " + value );
}
}
/**
* @brief Change the status of the user.
*
* This sends the <code>CHG</code> command to the user.
* This command also informs contacts the user's MsnObject and client capabilities.
* The MsnObject contains the meta-data about the user's display picture.
* The display picture itself is transferred in a chat session.
*
* Note this method advertises the supported client features, like 'winks' and 'webcam'.
*
* @param newStatus The new user status.
* @todo Possibly rename this to putChg() instead, especially when webcam support makes the capabilities dynamic.
*/
void MsnNotificationConnection::changeStatus( const Status newStatus )
{
#if NOTIFICATION_HIDE_P2P_SUPPORT
const uint capabilities = 0;
#else
// NOTE: When changing this property, all MSNP2P code need to be retested again!!
// The capabilities are like a contract. The other client assumes everything works that's promised here.
const uint capabilities = ( Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET | Contact::MSN_CAP_INK_GIF );
#endif
const QString& statusCode( MsnStatus::getCode( newStatus ) );
const QString objStr( QUrl::toPercentEncoding( currentAccount_->getMsnObjectString() ) );
/*
// To test the old file transfer:
sendCommand( "CHG", newStatus + " 0");
return;
*/
if( objStr.isEmpty() )
{
// Just don't send it if we don't have it.
sendCommand( "CHG", statusCode + " " + QString::number(capabilities) );
}
else
{
sendCommand( "CHG", statusCode + " " + QString::number(capabilities) + " " + objStr );
}
}
// Removes any expired switchboard requests, so new ones can be made
void MsnNotificationConnection::checkSwitchboardsTimeout()
{
// There are no pending switchboard requests, bail out
if( openRequests_.count() < 1 )
{
return;
}
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Removing expired switchboard requests.";
#endif
// Delete any pending switchboard
uint now = QDateTime::currentDateTime().toTime_t();
foreach( ChatInformation *switchboardInfo, openRequests_ )
{
if( ( now - switchboardInfo->getTime() ) > 60 )
{
openRequests_.removeAll( switchboardInfo );
delete switchboardInfo;
}
}
}
/**
* @brief Close the connection with the server
*
* This disconnects from the server. It also cleans up the
* state variables and objects associated with the session,
* e.g. contactlist data, open chat invitations, pending offline-im messages and SOAP clients.
*
* The ContactList object is saved and reset as well.
*/
void MsnNotificationConnection::closeConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Close the connection.";
#endif
// Disconnect from the server first.
// emits the signals so other classes can clean up / save properties.
disconnectFromServer();
// Stop the login timer
loginTimer_.stop();
// Delete all SOAP clients, and unset local references here.
deleteSoapClients();
offlineImService_ = 0;
passportLoginService_ = 0;
isInitiatingConnection_ = false;
lastStatus_ = -1;
// Remove contacts and non-special groups from the contact list
if ( contactList_ != 0 )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Resetting contactlist.";
#endif
// Save the contact list settings
saveProperties();
// The contact list is not deleted, like this class it's being
// shared through the entire application, and attached to slots.
contactList_->reset( false );
}
// Clear all pending offline messages
pendingOfflineImMessages_.clear();
// Delete all offline im messages
qDeleteAll( offlineImMessages_ );
offlineImMessages_.clear();
// Delete all open requets
qDeleteAll( openRequests_ );
openRequests_.clear();
// Reset current account
// TODO: use different connection for this?
if( currentAccount_ != 0 )
{
currentAccount_->setNoEmails( 0 );
}
}
/**
* @brief Create and return one pointer to address book service.
*
*/
AddressBookService* MsnNotificationConnection::createAddressBookService()
{
if( addressBookService_ == 0 )
{
addressBookService_ = new AddressBookService( this );
addSoapClient( addressBookService_ );
// Contact actions signals/slots
connect( addressBookService_, SIGNAL( contactAdded( const QString&, const QString&, const QStringList& ) ),
this, SLOT( slotContactAdded( const QString&, const QString&, const QStringList& ) ) );
connect( addressBookService_, SIGNAL( contactDeleted( const QString& ) ),
this, SLOT( slotContactDeleted( const QString& ) ) );
connect( addressBookService_, SIGNAL( contactBlocked( const QString& ) ),
this, SLOT( slotContactBlocked( const QString& ) ) );
connect( addressBookService_, SIGNAL( contactUnblocked( const QString& ) ),
this, SLOT( slotContactUnblocked( const QString& ) ) );
// Contact added/remove to/from group signals/slots
connect( addressBookService_, SIGNAL( contactAddedToGroup( const QString&, const QString& ) ),
this, SLOT( slotContactAddedToGroup( const QString&, const QString& ) ) );
connect( addressBookService_, SIGNAL( contactDeletedFromGroup( const QString&, const QString& ) ),
this, SLOT( slotContactDeletedFromGroup( const QString&, const QString& ) ) );
// Group actions signals/slots
connect( addressBookService_, SIGNAL( groupAdded( const QString&, const QString& ) ),
contactList_, SLOT( addGroup( const QString&, const QString& ) ) );
connect( addressBookService_, SIGNAL( groupDeleted( const QString& ) ),
contactList_, SLOT( removeGroup( const QString& ) ) );
connect( addressBookService_, SIGNAL( groupRenamed( const QString&, const QString& ) ),
contactList_, SLOT( renameGroup( const QString&, const QString& ) ) );
// Membership and AddressBook signals/slots
connect( addressBookService_, SIGNAL( gotMembershipLists(const QString&,const QHash<QString,int>&) ),
this, SLOT( slotGotMembershipLists(const QString&,const QHash<QString,int>&) ) );
connect( addressBookService_, SIGNAL( gotAddressBookList( const QList< QHash<QString,QVariant> >& ) ),
this, SLOT( slotGotAddressBookList( const QList< QHash<QString,QVariant> >& )));
connect( addressBookService_, SIGNAL( gotGroup( const QString&, const QString& ) ),
this, SLOT( slotGotGroup( const QString&, const QString& ) ) );
connect( addressBookService_, SIGNAL( gotPersonalInformation(const QString&,const QString&,int) ),
this, SLOT( changePersonalProperties(const QString&,const QString&,int) ) );
connect( addressBookService_, SIGNAL( soapWarning(const QString&,bool) ),
this, SLOT ( slotWarning(const QString&,bool) ) );
}
return addressBookService_;
}
/**
* @brief Internal function to create the Offline-IM service
*
* This service is used to retrieve the Offline-IM messages.
* The signals are attached to receivedOfflineIm() and receivedMailData()
*
* @returns The Offline-IM webservice handler
*/
OfflineImService* MsnNotificationConnection::createOfflineImService()
{
// Extract the 't' and 'p' values from the current authentication ticket string
// This is done beacuse sendMessage() could has requested one new ticket
const QStringList& fields( currentAccount_->getToken( "Messenger" ).split("&") );
QString authT, authP;
foreach( const QString &field, fields )
{
if( field.startsWith("t=") )
{
authT = field.mid(2);
}
else if( field.startsWith("p=") )
{
authP = field.mid(2);
}
#ifdef KMESSDEBUG_NOTIFICATION
else
{
kWarning() << "Could not parse authentication field:" << field;
}
#endif
}
// Request the first offline-im message
// TODO reuse the pointer, and set the token like addressbook
OfflineImService *oimService = new OfflineImService( authT, authP, this );
// Add the soap client.
addSoapClient( oimService );
// Register signals
connect( oimService, SIGNAL( messageReceived( const QString&, const QString&, const QString&,
const QDateTime&, const QString&, const QString&, int ) ),
this, SLOT ( receivedOfflineIm( const QString&, const QString&, const QString&,
const QDateTime&, const QString&, const QString&, int ) ));
connect( oimService, SIGNAL( metaDataReceived( QDomElement ) ),
this, SLOT ( receivedMailData( QDomElement ) ));
return oimService;
}
/**
* @brief Internal function to create the passport login handler.
*
* This service is used by gotUsr() to retrieve the passport authentication token.
*
* @returns The Passport login webservice handler
*/
PassportLoginService* MsnNotificationConnection::createPassportLoginService()
{
// TODO reuse the pointer
// Create the login handler.
PassportLoginService *loginService = new PassportLoginService();
// Add the soap client.
addSoapClient( loginService );
// Connect all login notifications
connect( loginService, SIGNAL( loginIncorrect() ),
this, SLOT ( loginIncorrect() ) );
connect( loginService, SIGNAL( loginSucceeded() ),
this, SLOT ( loginSucceeded() ) );
return loginService;
}
/**
* @brief Compute a triple des encryption.
*
* Used to compute the value of string to send to server for SSO.
*
* @param key The key for compute the encryption
* @param secret The secret data to encryption
* @param iv The initial vector for cbc mode
*/
QByteArray MsnNotificationConnection::createTripleDes ( const QByteArray key, const QByteArray secret,
const QCA::InitializationVector& iv )
{
// check for Triple Des with CBC support.
if ( ! QCA::isSupported( "tripledes-cbc" ) )
{
kWarning() << "Triple DES encryption not supported, please install qca2 packages";
#warning "Translate this after string freeze!!"
slotError(I18N_NOOP("Triple DES encryption is not supported. This means you probably do not have "
"installed qca2 or qca2-openssl. Please install it and retry"), MsnSocketBase::ERROR_AUTH_COMPUTATION);
return "";
}
// Create the key using the key parameter and secret data
QCA::SymmetricKey desKey( key );
QCA::SecureArray data( secret );
// Create the TripleDes object.
QCA::Cipher des3( QString ( "tripledes" ), QCA::Cipher::CBC, QCA::Cipher::NoPadding, QCA::Encode, desKey, iv );
// Chiper the data and check for the errors
QCA::SecureArray temp = des3.update ( data );
if ( ! des3.ok() )
{
kWarning() << "Triple DES computation failed on update step";
return "";
}
// Finish the compute and check for errors
des3.final();
if ( ! des3.ok() )
{
kWarning() << "Triple DES computation failed on final step";
return "";
}
// Return the encrypted data
return temp.toByteArray();
}
/**
* @brief Return the contact list model
*
* @returns The model from the contact list managed by this class.
*/
QAbstractItemModel* MsnNotificationConnection::getContactListModel()
{
return contactList_;
}
/**
* @brief Called when the user is ready to go online.
*
* This method is called by gotSyn() or gotLst() when
* the entire contact list data is received.
* It changes some final things before the user goes online:
* - set the initial user status, e.g. "online" or "invisible".
* - ses the initial personal message.
* - requests other server data, like Hotmail email URL's.
* Finally, the connected() signal is fired.
*/
void MsnNotificationConnection::goOnline()
{
int ack;
if(KMESS_NULL(currentAccount_)) return;
// Stop the authentication timer, we're done with that part
loginTimer_.stop();
// Start the ping timer: from now on, it's its duty to verify that the connection is still active
setSendPings( true );
// Load the contact list settings
readProperties();
// Get the initial status to use: if we were previously disconnected by an error or network
// issue, use the last status instead
int initialStatus = currentAccount_->getInitialStatus();
if( lastStatus_ != -1 && lastStatus_ != STATUS_OFFLINE )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Last status was set:" << MsnStatus::getCode( (Status)lastStatus_ );
#endif
initialStatus = lastStatus_;
lastStatus_ = -1;
}
// Change to the initial status
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Changing initial status to" << MsnStatus::getCode( (Status)initialStatus );
#endif
changeStatus( (Status)initialStatus );
// Reset status fields.
lastCurrentMedia_ = QString::null;
lastPsm_ = QString::null;
// Start with default personal message (may be empty)
changePersonalMessage( currentAccount_->getPersonalMessage( STRING_ORIGINAL ) );
// When the account supports email (e.g. Hotmail), request the URL's.
// This didn't give problems with non-email accounts,
// but restricted accounts return a "710" error for this command.
if( currentAccount_->getEmailSupported() )
{
// The ACK is temporary stored as inbox-command,
// so the response can be mapped back to the requested folder.
QHash<QString,QString>& hash = currentAccount_->getUrlInformation();
// Ask for inbox URL
ack = sendCommand("URL", "INBOX");
hash.insert( QString::number(ack), "INBOX" );
// Ask for compose URL
ack = sendCommand("URL", "COMPOSE");
hash.insert( QString::number(ack), "COMPOSE" );
// Ask the personal profile URL
ack = sendCommand("URL", "PROFILE");
hash.insert( QString::number(ack), "PROFILE" );
// Ask the URL for change account info
ack = sendCommand("URL", "PERSON");
hash.insert( QString::number(ack), "PERSON" );
}
// Notify observers that the server is connected
emit connected();
// Start a timer: for the first three seconds after connecting, do not display any notifications.
// See unlockNotifications() for more details
QTimer::singleShot( 3000, this, SLOT( unlockNotifications() ) );
}
// Received response to adl command
void MsnNotificationConnection::gotAdl( const QStringList& command, const QString& payload )
{
// The server responds to a successful ADL command
if( command[2] == "OK" )
{
// Check if already connect
if( ! connected_ )
{
// Go online
goOnline();
emit connected();
connected_ = true;
}
return;
}
if( ! payload.isEmpty() )
{
QRegExp regexp( ".*n=\"([^\"]+)\".*n=\"([^\"]+)\".*l=\"([^\"]+)\"" );
int pos = regexp.indexIn( payload );
int lists = -1;
QString handle;
if( pos != -1 )
{
handle = regexp.cap(2) + "@" + regexp.cap(1);
lists = QVariant( regexp.cap(3) ).toInt();
}
if( lists == -1 || handle.isEmpty() )
{
return;
}
Contact *contact = contactList_->getContactByHandle( handle );
if( contact != 0 )
{
if( lists & Contact::MSN_LIST_FRIEND ) contact->setFriend ( true );
if( lists & Contact::MSN_LIST_ALLOWED ) contact->setAllowed( true );
if( lists & Contact::MSN_LIST_BLOCKED ) contact->setBlocked( true );
if( lists & Contact::MSN_LIST_REVERSE ) contact->setReverse( true );
if( lists & Contact::MSN_LIST_PENDING ) contact->setPending( true );
}
else
{
// Else if the contact isn't present, ask to user what he want to do.
kDebug() << "The contact: " << handle << " not present in contact list added user";
emit contactAddedUser( handle );
}
}
}
// Received confirmation of the user's status change
void MsnNotificationConnection::gotChg( const QStringList& command )
{
if(KMESS_NULL(currentAccount_)) return;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "User's status changed to " << command[2] << ".";
#endif
// Change the user's status
currentAccount_->setStatus( MsnStatus::codeToStatus( command[2] ) );
}
// Received a challenge from the server
void MsnNotificationConnection::gotChl( const QStringList& command )
{
// Handle challenge query
MSNChallengeHandler handler;
const QString& response( handler.computeHash( command[2] ) );
// Send response
sendPayloadMessage( "QRY", handler.getProductId(), response );
}
// Received a version update from the server
void MsnNotificationConnection::gotCvr( const QStringList& command )
{
Q_UNUSED( command );
if(KMESS_NULL(currentAccount_)) return;
// Set the connected status to false
connected_ = false;
// Send the USR command
sendCommand( "USR", "SSO I " + currentAccount_->getHandle() );
}
// Received notice that a contact went offline
void MsnNotificationConnection::gotFln( const QStringList& command )
{
if(KMESS_NULL(contactList_)) return;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << command[1] << " went offline.";
#endif
contactList_->changeContactStatus( command[1].toLower(), STATUS_OFFLINE );
}
// Received notice that a contact is already online
void MsnNotificationConnection::gotIln( const QStringList& command )
{
if(KMESS_NULL(contactList_)) return;
// Get the contact info from the message
const QString& handle ( command[3].toLower() );
const QString& friendlyName ( QUrl::fromPercentEncoding( command[5].toUtf8() ) );
QString msnObject ( QUrl::fromPercentEncoding( command[7].toUtf8() ) );
uint capabilities = command[6].toUInt();
Status status = MsnStatus::codeToStatus( command[2] );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << handle << " is initially " << command[2] << ".";
#endif
// Check if the received MSN object is empty, a "0" (Kopete), or shorter than the string "<msnobject/>"
if( msnObject.isEmpty() || msnObject.compare( QString( "0" ) ) <= 0 || msnObject.length() < 12 )
{
msnObject = QString::null;
}
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
if( ! msnObject.isEmpty() )
{
kDebug() << "This person has an msn6 picture: " << msnObject;
}
#endif
contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, false );
}
/**
* @brief Handle the <code>NLN</code> command; a contact changed it's status.
*
* When a user updates it's status with the <code>CHG</code> command,
* all it's contact receive a <code>NLN</code> command.
* KMess sends the <code>CHG</code> command with the changeStatus() method.
* The <code>CHG</code> allows a contact to change it's name, client capabilities or MsnObject.
*
* The capabilities are a bitwise flag. The list of known values
* are described in the ContactBase::MsnClientCapabilities enum.
*
* The MsnObject is an identifier for the display picture.
* To download the actual picture, a client needs to initiate a chat,
* and send an invitation there.
*
* @code
<<< NLN NLN user@kmessdemo.org KMess%20Demo 1342210080 %3Cmsnobj%20Creator%3D%22user%40kmessdemo.org%22%20Size%3D%2211581%22%20Type%3D%223%22%20Location%3D%22KMess.tmp%22%20Friendly%3D%22AA%3D%3D%22%20SHA1D%3D%223VfOMCTTkuYHDjJhPx4sSlPiM%2Bs%3D%22%20SHA1C%3D%22ULeT2WGsdabLGNP3RgbWYw7ve80%3D%22/%3E
@endcode
*
* @param command The command arguments.
* - <code>command[1]</code> is the status code, see changeStatus()
* - <code>command[2]</code> is the contact handle.
* - <code>command[3]</code> networkid.
* - <code>command[4]</code> is the contact name, url encoded.
* - <code>command[5]</code> is the client-capabilities flag.
* - <code>command[6]</code> is the url-encoded MsnObject XML code.
*/
void MsnNotificationConnection::gotNln( const QStringList& command )
{
if(KMESS_NULL(contactList_)) return;
// Get the contact info from the message
const QString& handle ( command[2].toLower() );
const QString& friendlyName ( QUrl::fromPercentEncoding( command[4].toUtf8() ) );
QString msnObject ( QUrl::fromPercentEncoding( command[6].toUtf8() ) );
Status status = MsnStatus::codeToStatus( command[1] );
uint capabilities = command[5].toUInt();
// Check if the contact removes his picture
if( msnObject == "0" )
{
msnObject = "";
}
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << handle << " is now " << MsnStatus::getCode( status ) << ".";
#endif
// Update contact status.
contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, enableNotifications_ );
}
// Received notice of disconnection from the server
void MsnNotificationConnection::gotOut( const QStringList& command )
{
// If the first command parameter is "OTH", it means that this account has connected from elsewhere.
if( command[1] == "OTH" )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Got notice that this account has connected from elsewhere.";
#endif
// No need to reconnect with the last status if we were kicked out
lastStatus_ = -1;
slotError( "Connected from another location",
MsnSocketBase::ERROR_CONNECTION_OTHER );
}
else
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Received notice of disconnection from the server.";
#endif
// Save the status, to use it again when reconnecting
lastStatus_ = currentAccount_->getStatus();
closeConnection();
// Try connecting again
emit reconnect( currentAccount_->getHandle() );
}
}
// Received a property change (friendly name, phone number, etc..)
void MsnNotificationConnection::gotPrp( const QStringList& command )
{
const QString& propertyName ( command[1] );
const QString& propertyValue( QUrl::fromPercentEncoding( command[2].toUtf8() ) );
if( propertyName == "MFN" )
{
currentAccount_->setFriendlyName( propertyValue );
}
else if( propertyName == "MBE" )
{
// Unimplemented.
// indicates whether MSN Mobile is enabled.
}
else if( propertyName == "WWE" )
{
// Unimplemented
// indicates whether MSN Direct is enabled.
}
else
{
// kWarning() << "Received unsupported property name/value: " << propertyName << " " << propertyValue << "!";
}
}
void MsnNotificationConnection::gotRml( const QStringList &command )
{
// The server responds to a successful RML command
if( command[2] == "OK" )
{
}
}
/**
* @brief Received a connection request from a contact
*
* Format at MSNP15:
* RNG 1295917726 65.54.228.15:1863 CKI 104194101.9520122 email@domain.com Friendly%20Name U messenger.msn.com 1
*/
void MsnNotificationConnection::gotRng( const QStringList& command )
{
// Pull the data from the message
const QString& chatId ( command[1] );
const QString& serverAndPort( command[2] );
const QString& auth ( command[4] );
const QString& handle ( command[5].toLower() );
const QString& server ( serverAndPort.section(':', 0, 0) );
const QString& portString( serverAndPort.section(':', 1, 1) );
bool goodPort;
quint16 port = (quint16) portString.toUInt( &goodPort );
if( ! goodPort )
{
kWarning() << "Couldn't get port from string " << portString;
return;
}
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << handle << " is requesting a chat at: "
<< server << ":" << port << " id=" << chatId << " auth=" << auth;
#endif
// Send connection information object
emit startSwitchboard( ChatInformation( this, handle, server, port, auth, chatId, ChatInformation::CONNECTION_BACKGROUND ) );
}
// Received information about a contact's personal message
void MsnNotificationConnection::gotUbx( const QStringList &command, const QByteArray &payload )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Got UBX information.";
#endif
Contact *contact = contactList_->getContactByHandle( command[1].toLower() );
if(KMESS_NULL(contact)) return;
if( command[3].toInt() == 0 )
{
// Size is 0, no personal message
contact->setPersonalStatus( QString::null );
}
else
{
// Load XML parser
QDomDocument xml;
if( ! xml.setContent(payload) )
{
kWarning() << "Could not parse personal status message (invalid XML, contact=" << command[1] << ")!";
kWarning() << "The invalid XML was:" << payload;
}
// Parse payload
const QDomElement& root( xml.namedItem("Data").toElement() );
const QString& psm( root.namedItem("PSM").toElement().text() );
QString currentMedia( root.namedItem("CurrentMedia").toElement().text() );
QString mediaType;
// Append current media if set.
if( currentMedia.length() > 0 )
{
// From http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes:
//
// Media string can be something like:
// <CurrentMedia>\0Music\01\0{0} - {1}\0Title\0Artist\0Album\0\0</CurrentMedia>
// <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia>
// <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia>
//
// The literal "\0" is the separator char.
// Fields are: application, mediatype, enabled, formatstring, arg1, arg2, arg3
// Split media string
const QStringList media( currentMedia.split( "\\0" ) );
mediaType = media[1];
// Check if media is enabled, to find out what this means.
if( media[2] != "1" )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "CurrentMedia is set, but not enabled.";
#endif
currentMedia = QString::null;
}
else
{
// Replace fields in .Net style format string
currentMedia = media[3]; // format
for( int i = 4; i < media.count(); i++ )
{
currentMedia = currentMedia.replace( "{" + QString::number(i - 4) + "}", media[i] );
}
}
}
// Store personal message
contact->setPersonalStatus( psm, mediaType, currentMedia );
}
}
// Received the folder and command info for Hotmail's inbox or compose
// The response from ns is like to:
// URL 68 /cgi-bin/compose https://login.live.com/ppsecure/md5auth.srf?lc=1040 2
// 68 is an ack number, /cgi-bin/compose is the ru ( url command ) for the specify service
// and the last url is the site to send the query. Note: KMess use sha1auth instead md5
// so the store of this last url is useless!
void MsnNotificationConnection::gotUrl( const QStringList& command )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Got URL information.";
#endif
if(KMESS_NULL(currentAccount_)) return;
// Grep the hash where store the informations
QHash<QString,QString>& hash( currentAccount_->getUrlInformation() );
// Check if in the hash there is already the ack request
// Remove the item with the key "ack" and value "SERVICE" and switch the element
// so the key will be the "SERVICE" and the value will be the URL.
const QString& service( hash.take( command[1] ) );
if( ! service.isEmpty() )
{
hash.insert( service, command[2] );
}
}
/**
* @brief Received a USR command from the server.
*
* USR command for the login. At first we must send the handle with wich we want to log on.
* When the server response to first USR, we take the nonce value used for compute the return value.
* The key used for encrypt the data ( 3DES ) must be taken from Passport SOAP request.
* The computed value ( encryption of nonce with secret key taken by SOAP ) must be sended to server.
*
*/
void MsnNotificationConnection::gotUsr( const QStringList& command )
{
if(KMESS_NULL(currentAccount_)) return;
const QString& step( command[2] );
// This is either asking for authentication or confirming it
if( step == "SSO" )
{
// The initial connection process (network connection establish and version exchange) is over.
// The MSN servers sometimes fail to warn the clients that something's wrong by their side and
// they close the connection before the USR. This is a nice workaround: if the connection gets
// closed before this point, we just try again with HTTP, which usually works.
isInitiatingConnection_ = false;
const QString& policy ( command[4] ); // Policy
nonceBase64_ = command[5]; // Base64 nonce
// Change statusbar
emit statusMessage( i18n("Authenticating..."), false );
// Start the passport based login
passportLoginService_ = createPassportLoginService();
passportLoginService_->login( policy );
}
else if( step == "OK" )
{
// This is a confirmation message. Get the user's friendly name from the message.
bool isVerified = ( command[4].toInt() == 1 );
currentAccount_->setVerified( isVerified );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
if( ! isVerified )
{
kWarning() << "Passport account of the user is not verified yet.";
}
#endif
// When users choose an existing account in the InitialView dialog,
// they may alter the password. This value is stored as temporary password.
// The password may be overwritten now, because the login was succesful.
currentAccount_->saveTemporaryPassword();
emit statusMessage( i18n( "Authenticated" ), false );
// Retrieve from with one SOAP request the membership list ( where there are the lists for
// FL AL BL contacts )
AddressBookService *addressBook = createAddressBookService();
addressBook->retrieveMembershipLists();
}
else
{
kWarning() << "Received unexpected USR step:" << step;
}
}
// Received version information
void MsnNotificationConnection::gotVer( const QStringList& command )
{
Q_UNUSED( command ); // Avoid compiler warning
if(KMESS_NULL(currentAccount_)) return;
// Send some fake info about the current version
// First parameter is the locale-id (0x0409 is U.S. English).
sendCommand( "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0324 msmsgs " + currentAccount_->getHandle() );
}
/**
* @brief Received server transfer information
*
* Format at MSNP15:
* XFR 32 SB 65.54.171.31:1863 CKI 1743299383.52212212.219110167 U messenger.msn.com 1
*/
void MsnNotificationConnection::gotXfr(const QStringList& command)
{
// Get id of transaction
int transactionId = command[1].toInt();
// Get informations about the server from that
const QString& serverType ( command[2] );
const QString& serverAndPort ( command[3] );
const QString& server ( serverAndPort.section( ':', 0, 0 ) );
const QString& portString( serverAndPort.section( ':', 1, 1 ) );
// Convert the port to an integer
bool goodPort;
quint16 port = (quint16) portString.toUInt( &goodPort );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Got " << serverType << " transfer to " << server << ":" << port << ".";
#endif
if( ! goodPort )
{
kDebug() << "Got bad port number : " << portString << ".";
return;
}
if( serverType == "NS" )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Disconnecting from old server.";
#endif
// Disconnect from the existing server.
disconnectFromServer( true );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Connecting to new server.";
#endif
#ifdef KMESS_NETWORK_WINDOW
KMESS_NET_INIT( this, "NS " + server );
#endif
// Stop the login timer: it'll be restarted while connecting to the new server
loginTimer_.stop();
// Set the connection as initiating, connectToServer() is from the parent class
// and can't do it itself
isInitiatingConnection_ = true;
// Connect to the new server.
connectToServer( server, port );
}
else if( serverType == "SB" )
{
#ifdef KMESSTEST
KMESS_ASSERT( openRequests_.count() > 0 );
int noChatsBefore = openRequests_.count();
#endif
// This is a switchboard server. This message should be in response to
// a user-requested chat. Look for the pending chat information.
ChatInformation *switchboardInfo = 0;
foreach( ChatInformation *chatInformation, openRequests_ )
{
if( chatInformation->getTransactionId() == transactionId )
{
switchboardInfo = chatInformation;
break;
}
}
if( switchboardInfo == 0 )
{
kWarning() << "Got an XFR response, but there are no corresponding open requests.";
return;
}
// Update the server information
switchboardInfo->setServerInformation( server, port, command[5] );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "emitting startSwitchboard() signal.";
#endif
// Signal the new connection
emit startSwitchboard( *switchboardInfo );
// Remove the first item from the active requests list
openRequests_.removeAll( switchboardInfo );
delete switchboardInfo;
#ifdef KMESSTEST
KMESS_ASSERT( openRequests_.count() == ( noChatsBefore - 1 ) );
#endif
}
}
/**
* @brief Send the ADL command.
*
* ADL: 'Add List' command, uses XML to identify each contact, and works as a payload message.
* Each ADL command may contain up to 150 contacts (a payload of roughly 7500 bytes).
*
*/
void MsnNotificationConnection::putAdl( const QString &handle, int list )
{
// If handle is present then the adl isn't initial ADL command, so use command for add someone into one list
if( ! handle.isEmpty() )
{
const QStringList& splittedHandle( handle.split( "@" ) );
const QString payload( "<ml>"
"<d n=\"" + splittedHandle[1] + "\">"
"<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />"
"</d>"
"</ml>" );
sendPayloadMessage( "ADL", QString::null, payload.arg( QString::number( list ) ) );
return;
}
// Use the current contact list to make the string to send with initial ADL command
const QHash< QString, Contact* >& contactList( contactList_->getContactList() );
// If the list is empty, send an empty ADL
if( contactList.isEmpty() )
{
sendPayloadMessage( "ADL", QString(), QByteArray( "<ml l=\"1\"></ml>" ) );
return;
}
QHashIterator< QString,Contact* > i( contactList );
const Contact *currentContact = 0;
QString adlList;
QString currentAdl;
bool send = false;
int userType = 1;
while( i.hasNext() )
{
i.next();
currentContact = i.value();
if( currentContact == 0 )
{
continue;
}
list = 0;
if( currentContact->isFriend() )
{
list |= Contact::MSN_LIST_FRIEND;
}
// The contact may be or blocked or allowed, if it is both the ADL command fails
if( currentContact->isBlocked() )
{
list |= Contact::MSN_LIST_BLOCKED;
}
else if( currentContact->isAllowed() )
{
list |= Contact::MSN_LIST_ALLOWED;
}
// Add to ADL command only user in at least one list
if( list != 0 )
{
// Split the domain from handle
const QStringList& splittedHandle( currentContact->getHandle().split( "@" ) );
// Set the user type
userType = 1;
if( currentContact->getInformation( "isMessenger3" ).toBool() )
{
userType = 2;
}
currentAdl = "<d n=\"" + splittedHandle[1] + "\"><c n=\"" + splittedHandle[0] + "\" l=\"" +
QString::number( list ) + "\" t=\"" + QString::number( userType ) + "\" /></d>";
// Check if the buffer is too large ( 7500 bytes maximum )
if( currentAdl.toLatin1().size() + adlList.toLatin1().size() >= 7450 )
{
// Switch to true the send variable and go back to previous element
send = true;
i.previous();
}
else
{
adlList += currentAdl;
}
}
else
{
// The user should decide in which list add it
emit contactAddedUser( currentContact->getHandle() );
}
// Send the buffer if the send variable is true or the current contact is the last of the list
if( send || ! i.hasNext() )
{
sendPayloadMessage( "ADL", QString::null, "<ml l=\"1\">" + adlList + "</ml>" );
adlList = "";
send = false;
}
}
}
// Send the RML command
void MsnNotificationConnection::putRml( const QString &handle, int list )
{
const QStringList& splittedHandle( handle.split( "@" ) );
const QString payload( "<ml>"
"<d n=\"" + splittedHandle[1] + "\">"
"<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />"
"</d>"
"</ml>" );
sendPayloadMessage( "RML", QString::null, payload.arg( QString::number( list ) ) );
}
// Initialize the object
bool MsnNotificationConnection::initialize()
{
if ( initialized_ )
{
kDebug() << "already initialized!";
return false;
}
if ( ! MsnConnection::initialize() )
{
kDebug() << "Couldn't initialize base class.";
return false;
}
// Determine which payload commands can be received by the Notification Connection
QStringList payloadCommands;
payloadCommands << "MSG" // Server message
<< "UBX" // contact personal status message
<< "UBN"
<< "GCF" // server config file
<< "NOT" // msn alerts, msn calendar
<< "IPG" // contact page
<< "ADL"
;
setAcceptedPayloadCommands( payloadCommands );
// Pass the current contactlist reference to the currentaccount
// so other classes can access contacts from a central point.
currentAccount_ = CurrentAccount::instance();
currentAccount_->setContactList( contactList_ );
// Connect the activation signal to send notification events
connect( NotificationManager::instance(), SIGNAL( eventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ),
this, SLOT ( slotErrorEventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ) );
initialized_ = true;
return initialized_;
}
/**
* @brief The passport login succeeded.
*
* When this method gets called, CurrentAccount will contain all
* the needed passport security tokens.
*/
void MsnNotificationConnection::loginSucceeded()
{
// Delete the login handler
// Delay deletion so it can complete the method.
deleteSoapClient( passportLoginService_ );
passportLoginService_ = 0;
// Initialize the QCA and create the IV key for triple des encryption
QCA::Initializer init;
QCA::InitializationVector iv(8);
// Create the keys and hashes needed
QByteArray key1, key2, key3;
QByteArray hash, des3hash;
// Use the nonce received from last USR commands and the proof key for authentication
QByteArray nonce = nonceBase64_.toLatin1();
// Compute the value to send with USR SSO command
QString magic1( "WS-SecureConversationSESSION KEY HASH" );
QString magic2( "WS-SecureConversationSESSION KEY ENCRYPTION" );
key1 = QByteArray::fromBase64( currentAccount_->getToken( "MessengerClearProof" ).toLatin1() );
key2 = KMessShared::deriveKey( key1, magic1.toLatin1() );
key3 = KMessShared::deriveKey( key1, magic2.toLatin1() );
hash = KMessShared::createHMACSha1( key2, nonce );
// The windows api always pads using \x08 so we pad 8 bytes.
nonce += QByteArray ( 8, '\x08' );
// Compute the 3des encryption
des3hash = createTripleDes ( key3, nonce, iv );
if( des3hash.isEmpty() )
{
return;
}
// Create the required 128 byte padded struct
MSNPMSNG blob;
blob.headerSize = qToLittleEndian( 28 );
blob.cryptMode = qToLittleEndian( 1 ); // CRYPT_MODE_CBC
blob.cipherType = qToLittleEndian( 0x6603 ); // Triple DES
blob.hashType = qToLittleEndian( 0x8004 ); // SHA-1
blob.ivLength = qToLittleEndian( 8 );
blob.hashLength = qToLittleEndian( 20 ); // Length of the HMAC hash
blob.cipherLength = qToLittleEndian( 72 );
// Copy the IV Bytes, the hash and chiper value in blob struct
memcpy( (char*) blob.ivBytes, iv.constData() , des3hash.size() );
memcpy( (char*) blob.hashBytes, hash.constData(), hash.size() );
memcpy( (char*) blob.cipherBytes, des3hash.constData(), des3hash.size() );
// Create the byte arrays and send for login
QByteArray blobs( (char*) &blob, 128 );
sendCommand( "USR", "SSO S " + currentAccount_->getToken( "MessengerClear" ) + " " + blobs.toBase64() );
}
// The passport login failed, username/password was incorrect
void MsnNotificationConnection::loginIncorrect()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Passport login was incorrect.";
#endif
// NOTE: a similar thing also happens in soapRequestFailed(),
// but for fatal SOAP errors.
slotError( "Authentication failed. Wrong login credentials.", MsnSocketBase::ERROR_AUTH_LOGIN );
}
// Move the contact to another group.
void MsnNotificationConnection::moveContact(QString handle, QString fromGroupId, QString toGroupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Moving '" << handle << "' from '" << fromGroupId << "' to '" << toGroupId << "'.";
#endif
AddressBookService *addressBook = createAddressBookService();
Contact *contact = contactList_->getContactByHandle( handle );
if( contact == 0 )
{
return;
}
addressBook->addContactToGroup( contact->getGuid(), toGroupId );
// Check if the "from group" is empty or not exists.
if( fromGroupId == "0" || fromGroupId.isEmpty() )
{
return;
}
addressBook->deleteContactFromGroup( contact->getGuid(), fromGroupId );
}
// Open a connection to the server
bool MsnNotificationConnection::openConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Opening connection.";
#endif
if ( !initialized_ )
{
kWarning() << "Notification should be initialized before attempting to connect.";
return false;
}
// If the last connection was using HTTP, try again using TCP first.
switchToTcpSocket();
// Initialize the contact list
contactList_->reset( true );
#ifdef KMESS_NETWORK_WINDOW
KMESS_NET_INIT(this, "NS messenger.hotmail.com");
#endif
emit statusMessage( i18n( "Connecting..." ), false );
// Set the flag to avoid an MSN server problem. See gotUsr()
isInitiatingConnection_ = true;
// Attempt to connect to the default MSN server
connectToServer();
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Socket started connecting to the server. Started timer.";
#endif
return true;
}
// Parse a regular command
void MsnNotificationConnection::parseCommand( const QStringList& command )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Parsing command '" << command[0] << "'.";
#endif
// Allow some time until the next message is received
if( loginTimer_.isActive() )
{
loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
}
// Find out what kind of command we've received. Numbers are errors, the others server commands.
bool isNumber = false;
command[0].toInt( &isNumber );
// If it's not a command, something has went wrong.
if( isNumber )
{
kWarning() << "Invalid command received:" << command;
return;
}
// Identify and handle the command
if( command[0] == "ADL" )
{
gotAdl( command );
}
else if ( command[0] == "BLP" )
{
// Unimplemented. This is a privacy setting. (buddy-list-policy?)
}
else if ( command[0] == "BPR" )
{
// Unimplemented. This contains buddy properties.
}
else if ( command[0] == "CHG" )
{
gotChg( command ); // Status change
}
else if ( command[0] == "CHL" )
{
gotChl( command ); // Server-side ping, aka 'challenge'
}
else if ( command[0] == "CVR" )
{
gotCvr( command ); // Client version info
}
else if ( command[0] == "FLN" )
{
gotFln( command ); // Contact offline
}
else if ( command[0] == "ILN" )
{
gotIln( command ); // Contact initially online
}
else if ( command[0] == "NLN" )
{
gotNln( command ); // Contact now online
}
else if ( command[0] == "OUT" )
{
gotOut( command ); // Disconnection from server
}
else if ( command[0] == "PRP" )
{
gotPrp( command ); // Contact property change
}
else if ( command[0] == "QRY" )
{
// Ignored. Indicates the challenge was succesful, contains no parameters.
}
else if ( command[0] == "RNG" )
{
gotRng( command ); // Chat or connection requests
}
else if( command[0] == "RML" )
{
gotRml( command );
}
else if( command[0] == "RFS" )
{
// Receveid the request for refresh list
// so the client reply with adl command
putAdl();
}
else if( command[0] == "UBN" )
{
// For the moment is useless
// Require more investigate
}
else if ( command[0] == "SBP" )
{
// Unimplemented.
}
else if ( command[0] == "SBS" )
{
// Ignored. The meaning of this command is unknown, but it's related to the
// user's mobile credits (MSN Mobile).
// See: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS for details
}
else if ( command[0] == "URL" )
{
gotUrl( command ); // Hotmail folder info
}
else if ( command[0] == "USR" )
{
gotUsr( command ); // User authentication information
}
else if ( command[0] == "UUX" )
{
// Ignored. It's an echo back from our UUX command. Unlike other commands,
// the UI is updated for this command already before the server confirms.
}
else if ( command[0] == "VER" )
{
gotVer( command ); // Version information
}
else if ( command[0] == "XFR" )
{
gotXfr( command ); // Server transfer data
}
else
{
// No need to bugger the common user with strange error messages
kWarning() << "Unknown command" << command[0] << "received from the server! Full string:" << command.join( " " );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
slotError( i18n( "Unknown command received from the server: %1", command[0] ), MsnSocketBase::ERROR_INTERNAL );
#endif
}
}
// Parse an error command
void MsnNotificationConnection::parseError( const QStringList& command, const QByteArray &payloadData )
{
// See if the error was from an open switchboard request.
if( ! openRequests_.isEmpty() )
{
int transactionId = command[1].toInt();
foreach( ChatInformation *chatInvite, openRequests_ )
{
if( chatInvite->getTransactionId() == transactionId )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Removing chat invitation from list.";
#endif
openRequests_.removeAll( chatInvite );
delete chatInvite;
break;
}
}
}
// Relay the error detection to the base class
MsnConnection::parseError( command, payloadData );
}
// Parse a message command
void MsnNotificationConnection::parseMimeMessage( const QStringList& command, const MimeMessage &mainMessage )
{
Q_UNUSED( command );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Got " << mainMessage.getSubValue("Content-Type") << ".";
#endif
const QString& contentType( mainMessage.getSubValue("Content-Type") );
const MimeMessage& message( mainMessage.getBody() );
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
mainMessage.print();
kDebug() << "Message body as mime:";
message.print();
#endif
// Check the message type
if ( contentType == "text/x-msmsgsprofile" )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Load profile information.";
#endif
uint externalPort = mainMessage.getValue("ClientPort").toUInt();
externalPort = ((externalPort & 0x00ff) << 8) | ((externalPort & 0xff00) >> 8); // Byte swapped
currentAccount_->setAccountInformation( mainMessage.getValue("MSPAuth"),
mainMessage.getValue("preferredEmail"),
mainMessage.getValue("sid"),
mainMessage.getValue("lang_preference"),
mainMessage.getValue("EmailEnabled") == "1",
mainMessage.getValue("ClientIP"),
externalPort,
getLocalIp() );
}
else if ( contentType == "text/x-msmsgsinitialmdatanotification"
|| contentType == "text/x-msmsgsinitialemailnotification"
|| contentType == "text/x-msmsgsoimnotification" )
{
// Parse both messages the same way,
// the x-msmsgsinitialmdatanotification message is an extended version of x-msmsgsoimnotification
// description of the fields: http://msnpiki.msnfanatic.com/index.php/MSNP13:Offline_IM
// Parse the URL fields (msmsgsinitialmdatanotification messages).
if( message.hasField("Post-URL") )
{
currentAccount_->setEmailUrl( message.getValue("Post-URL") );
}
// Parse the Mail-Data field.
const QString& mailDataSrc( message.getValue("Mail-Data") );
if( mailDataSrc == "too-large" )
{
// After a certain ammount of messages, the XML is replaced with "too-large".
// Use SOAP to request the actual mail data.
if( offlineImService_ == 0 )
{
offlineImService_ = createOfflineImService();
}
offlineImService_->getMetaData();
}
else
{
// Mail-Data was received directly in the message.
QDomDocument mailData;
if( ! mailData.setContent(message.getValue("Mail-Data")) )
{
kWarning() << "Could not parse XML Mail-Data field!";
return;
}
receivedMailData( mailData.namedItem("MD").toElement() );
}
}
else if ( contentType == "text/x-msmsgsemailnotification" )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "An email was received"
<< " from \"" << message.getValue("From") << "\""
<< " with the subject \"" << message.getValue("Subject") << "\""
<< " in the folder \"" << message.getValue("Dest-Folder") << "\"." << endl;
#endif
// If a message delta is to the active folder, emails were moved into it, so use a negative deletion
if ( message.getValue("Dest-Folder") == "ACTIVE" )
{
// Received in the inbox
currentAccount_->changeNoEmails( 1 );
}
emit newEmail( message.decodeRFC2047String( message.getValue("From").toUtf8() ),
message.decodeRFC2047String( message.getValue("Subject").toUtf8() ),
(message.getValue("Dest-Folder") == "ACTIVE"),
message.getValue("id"),
message.getValue("Message-URL"),
message.getValue("Post-URL") );
}
else if ( contentType == "text/x-msmsgsactivemailnotification" )
{
int messageDelta = message.getValue("Message-Delta").toInt();
// If a message delta is from the active folder, emails were deleted
if ( message.getValue("Src-Folder") == "ACTIVE" )
{
currentAccount_->changeNoEmails( -1 * messageDelta );
}
// If a message delta is to the active folder, emails were moved into it
else if ( message.getValue("Dest-Folder") == "ACTIVE" )
{
currentAccount_->changeNoEmails( messageDelta );
}
// When deleting Offline-IM messages, this message contains:
// Src-Folder: .!!OIM
// Dest-Folder: .!!trAsH
// Message-Delta: <number of messages deleted>
}
else if( contentType == "application/x-msmsgssystemmessage" )
{
// This is a maintenance info message from the server.
if( message.getValue("Type") == "1" )
{
int timeLeft = message.getValue("Arg1").toInt();
const QString& textTimeLeft( i18ncp( "Time left before server maintenance",
"%1 minute", "%1 minutes", timeLeft ) );
if( timeLeft > 2 )
{
emit statusMessage( i18n("Server closes for maintenance in %1!", textTimeLeft ), false );
}
else
{
slotError( i18nc( "Server maintenance dialog box text",
"The Live Messenger server will be going down in %1 for maintenance.", textTimeLeft ), MsnSocketBase::ERROR_SERVER );
}
}
}
else
{
kWarning() << "Content-Type '" << contentType << "' not reconized, message ignored.";
}
}
// Parse a payload command
void MsnNotificationConnection::parsePayloadMessage( const QStringList &command, const QByteArray &payload )
{
if( command[0] == "UBX" )
{
gotUbx( command, payload );
}
else if( command[0] == "GCF" )
{
// Ignore this
// Contains policy information, requesting clients
// to disable certain features or possibly block
// certain filenames/contents.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
kDebug() << "Ignoring security configuration file, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() );
#endif
}
else if( command[0] == "NOT" )
{
// Ignore "notification" messages coming from MSN Calendar, MSN Alerts, and MSN Mobile.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
kDebug() << "Ignoring text event notification, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() );
#endif
}
else if( command[0] == "ADL" )
{
gotAdl( command, payload );
}
else
{
kWarning() << "Unhandled payload command: " << command[0] << "!"
<< " (message dump follows)\n" << QString::fromUtf8(payload.data(), payload.size()) << endl;
}
}
/**
* @brief Send the personal status and current media fields.
*
* MSNP13 uses an extra field on the UUX command
*
*/
void MsnNotificationConnection::putUux()
{
// TODO Calculate a GUID for the specific machine, or (if possible) find out how
// WLM obtains the GUID and replicate the algorithm (to have GUIDs compatible with
// WLM). If that's not needed, add the machine name (WLM knows it).
const QString xml( "<Data>"
"<PSM>" + lastPsm_ + "</PSM>"
"<CurrentMedia>" + lastCurrentMedia_ + "</CurrentMedia>"
"<MachineGuid>{F26D1F07-95E2-403C-BC18-D4BFED493428}</MachineGuid>"
"</Data>" );
sendPayloadMessage( "UUX", QString::null, xml );
}
/**
* @brief Send the version command.
*
* Send versions of protocol supported by the client
*
*/
void MsnNotificationConnection::putVer()
{
// Indicate which protocol version we support.
// Most of the time it seems that:
// - even versions are for minor changes.
// - odd version is are for new revisions.
sendCommand( "VER", "MSNP15 MSNP14 MSNP13 CVR0" );
// Reset the addressBookService_ because when the connection is closed the
// function deleteSoap in MsnConnection delete the soap class.
addressBookService_ = 0;
}
// Received the Mail-Data field over SOAP or at the notification connection.
void MsnNotificationConnection::receivedMailData( QDomElement mailData )
{
#ifdef KMESSTEST
KMESS_ASSERT( mailData.tagName() == "MD" );
#endif
QDomNodeList childNodes = mailData.childNodes();
for( int i = 0; i < childNodes.count(); i++ )
{
QDomElement childNode = childNodes.item(i).toElement();
QString childName( childNode.nodeName() );
if( childName == "E" )
{
// Found an initial email status notification.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Load initial email information.";
#endif
QDomElement inboxUnread = childNode.namedItem("IU").toElement();
QDomElement othersUnread = childNode.namedItem("OU").toElement();
if( inboxUnread.isNull() || othersUnread.isNull() )
{
kWarning() << "Reveived email notification, but 'IU' and 'OU' elements are missing!";
}
#ifdef KMESSTEST
KMESS_ASSERT( ! inboxUnread.isNull() );
KMESS_ASSERT( ! othersUnread.isNull() );
#endif
currentAccount_->setInitialEmailInformation( inboxUnread.text().toInt(),
othersUnread.text().toInt() );
}
else if( childName == "M" )
{
// Using this for-loop, multiple 'M' elements are supported.
// Extract the message ID.
QString messageId( childNode.namedItem("I").toElement().text() );
if( messageId.isEmpty() )
{
kWarning() << "Unable to parse Mail-Data payload, offline-im message-id not found.";
return;
}
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Received offline-IM notification '" << messageId << "'";
#endif
// Create a SOAP client to request the Offline-IM
pendingOfflineImMessages_.append(messageId);
}
else if( childName == "Q" )
{
// Not implemented
}
else
{
// Warn for unsupported tags.
kWarning() << "Unsupported tag '" << childName << "' encountered in Mail-Data payload!";
}
}
// Start a new client if there are pending messages, and no OfflineImService is active
if( ! pendingOfflineImMessages_.isEmpty() )
{
// Initialize on demand
if( offlineImService_ == 0 )
{
offlineImService_ = createOfflineImService();
}
// Request the first offline-im message if the current class was
// not processing already. (otherwise receivedOfflineIm() picks the next one).
if( offlineImService_->isIdle() )
{
offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
// Processing continues at receivedOfflineIm()
}
}
}
// An Offline-IM message was downloaded
void MsnNotificationConnection::receivedOfflineIm( const QString &messageId, const QString &from, const QString &to,
const QDateTime &date, const QString &body,
const QString &runId, int sequenceNum )
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "Received an offline IM message "
<< "(from=" << from
<< " runid=" << runId
<< " sequenceNum=" << sequenceNum << ")" << endl;
#endif
#ifdef KMESSTEST
KMESS_ASSERT( to == currentAccount_->getHandle() );
#else
Q_UNUSED( to ); // Avoid compiler warning
#endif
// Find the SOAP client to send the next request
if( KMESS_NULL(offlineImService_) )
{
slotError( i18n( "KMess could not process Offline-IM messages.<br/>Details: %1",
i18n("SOAP client is no longer valid.") ),
MsnSocketBase::ERROR_INTERNAL );
qDeleteAll( offlineImMessages_ );
offlineImMessages_.clear();
pendingOfflineImMessages_.clear();
return;
}
// Store message until all messages are received.
OfflineImMessage *offlineIm = new OfflineImMessage;
offlineIm->messageId = messageId;
offlineIm->body = body;
offlineIm->date = date;
offlineIm->from = from;
offlineIm->runId = runId;
offlineIm->sequenceNum = sequenceNum;
// Order by date
uint insertPos = 0;
foreach( OfflineImMessage *nextOfflineIm, offlineImMessages_ )
{
if( nextOfflineIm->sequenceNum > sequenceNum )
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "inserted message '" << date << "', #" << sequenceNum << " "
<< "before '" << nextOfflineIm->date << "', #" << nextOfflineIm->sequenceNum << "." << endl;
#endif
break;
}
insertPos++;
}
offlineImMessages_.insert( insertPos, offlineIm );
// Remove from pending list
if( pendingOfflineImMessages_.removeAll( messageId ) == 0 )
{
kWarning() << "Could not remove "
"message '" << messageId << "' from the list!" << endl;
}
// If there are more messages, request the next one.
if( ! pendingOfflineImMessages_.isEmpty() )
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "Requesting the next offline-im message.";
#endif
// Not all messages are received yet, request next
offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
return;
}
// All messages received, and sorted,
// display the messages in the chat windows.
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "All messages received, displaying messages in chat window.";
#endif
const ContactBase *contact = 0;
QString name;
QString picture;
QStringList messageIds;
foreach( OfflineImMessage *offlineIm, offlineImMessages_ )
{
messageIds.append( offlineIm->messageId );
// Get contact details
// Re-use previous results if message is from the same contact
if( contact == 0 || contact->getHandle() != offlineIm->from )
{
contact = CurrentAccount::instance()->getContactByHandle( offlineIm->from );
if( contact != 0 )
{
// Display the name in a simple way, to make it clearer to
// see the CSS style for Offline IM
name = contact->getFriendlyName( STRING_CLEANED );
picture = contact->getContactPicturePath();
}
else
{
name = from;
picture = QString::null;
}
}
// Send out the chat message
emit offlineMessage( ChatMessage( ChatMessage::TYPE_OFFLINE_INCOMING,
ChatMessage::CONTENT_MESSAGE,
true,
offlineIm->body,
offlineIm->from,
name,
picture,
QFont(),
QString::null,
offlineIm->date ) );
}
// Delete all local messages
qDeleteAll( offlineImMessages_ );
offlineImMessages_.clear();
// Request to remove the Offline-IM message from the online storage.
offlineImService_->deleteMessages( messageIds );
// Keep reference to SOAP service so the connection is re-used for other messages.
}
// Remove a contact from the contact list completely
void MsnNotificationConnection::removeContact(QString handle, bool block)
{
Contact *contact = contactList_->getContactByHandle( handle );
if(KMESS_NULL(contact)) return;
QString contactId( contact->getGuid() );
if( contactId.isEmpty() )
{
return;
}
// If the user checks the block option then put the contact in Block Membership list ( if not already present )
if( block && ! contact->isBlocked() )
{
// Block the contact
blockContact( handle );
}
// Remove the contact from AB
AddressBookService *addressBook = createAddressBookService();;
addressBook->deleteContact( contactId );
}
//remove a contact from a single group
void MsnNotificationConnection::removeContactFromGroup(QString handle, QString groupId)
{
AddressBookService *addressBook = createAddressBookService();
Contact *contact = contactList_->getContactByHandle( handle );
if( contact == 0 )
{
return;
}
addressBook->deleteContactFromGroup( contact->getGuid(), groupId );
}
// Remove the current media advertising in the personal status.
void MsnNotificationConnection::removeCurrentMedia()
{
lastCurrentMedia_ = QString::null;
putUux();
}
// Remove a group
void MsnNotificationConnection::removeGroup(QString id)
{
AddressBookService *addressBook = createAddressBookService();;
addressBook->deleteGroup( id );
}
// Rename a group
void MsnNotificationConnection::renameGroup(QString id, QString newName)
{
AddressBookService *addressBook = createAddressBookService();;
addressBook->renameGroup( id, newName );
}
// Request a switchboard for a chat with a contact
void MsnNotificationConnection::requestSwitchboard( QString handle, ChatInformation::ConnectionType type )
{
// Protect again invalid uses.
if( handle.isEmpty() )
{
kWarning() << "no contact handle set!";
return;
}
// See if there is a open chat request.
foreach( ChatInformation *switchboardInfo, openRequests_ )
{
if( switchboardInfo->getContactHandle() == handle )
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "A pending switchboard for contact "
<< handle << " is already present." << endl;
#endif
return;
}
}
// Open directly fake switchboards for offline contacts
if( type == ChatInformation::CONNECTION_OFFLINE )
{
ChatInformation *offlineChatInfo = new ChatInformation( this, handle, -1, type );
emit startSwitchboard( *offlineChatInfo );
return;
}
// Send the command
int transactionId = sendCommand( "XFR", "SB" );
// Store the pending request information so the return value can be handled.
openRequests_.append( new ChatInformation( this, handle, transactionId, type ) );
}
// Load the contact list properties
void MsnNotificationConnection::readProperties()
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "Loading properties for the notification connection";
#endif
// Load the contact list's settings
contactList_->readProperties();
}
// Save the contact list properties
void MsnNotificationConnection::saveProperties()
{
#ifdef KMESSDEBUG_NOTIFICATION
kDebug() << "Saving properties for the notification connection";
#endif
// Do not save account properties when it's a temporary account
if( currentAccount_->isGuestAccount() )
{
kWarning() << "Requested to save properties, but this is a guest account!";
return;
}
// Save the contact list's settings for this account
contactList_->saveProperties();
}
/**
* @brief The authentication process has timed out
*
* Called by the login timer, when the login timer has expired
*/
void MsnNotificationConnection::slotAuthenticationFailed()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Connection not successful within time limit.";
#endif
// Bounce back to the unified error handler to give the user a message
slotError( i18n( "Authentication time limit exceeded" ), MsnSocketBase::ERROR_AUTH_TIME_LIMIT );
}
// Contact added, notify it to contactlist
void MsnNotificationConnection::slotContactAdded( const QString &handle, const QString &contactId, const QStringList &groupsId )
{
int lists = Contact::MSN_LIST_ALLOWED | Contact::MSN_LIST_FRIEND;
// If the contact already exists, the user requested re-adding to FL list.
Contact *contact = contactList_->getContactByHandle( handle );
if( contact != 0 )
{
contact->setGuid( contactId );
contact->setFriend( true );
// Ensure to block the contact if "already blocked"
if( contact->isBlocked() )
{
lists ^= Contact::MSN_LIST_ALLOWED;
}
}
else
{
// The contact is added to the reverse list: when the user adds a contact who has added the user,
// the user must appear to be present in the contact's list (this avoids seeing the newly added
// contact as not having the user in his/her list).
contactList_->addContact( handle, handle, lists | Contact::MSN_LIST_REVERSE, QStringList(), contactId );
}
// Check if the user has requested that contact is putted into groups
if( ! groupsId.isEmpty() )
{
foreach( const QString &groupId, groupsId )
{
if( ! contactList_->getGroupById( groupId ) )
{
continue;
}
addContactToGroup( handle, groupId );
}
}
// Ask the server to add the contact to FL and Allow/Block list.
putAdl( handle, lists );
}
// Contact added to group
void MsnNotificationConnection::slotContactAddedToGroup( const QString &contactId, const QString &groupId )
{
Contact *contact = contactList_->getContactByGuid( contactId );
if( contact == 0 )
{
return;
}
contact->addGroupId( groupId );
}
// Contact blocked
void MsnNotificationConnection::slotContactBlocked( const QString &handle )
{
Contact *contact = contactList_->getContactByHandle( handle );
if( contact != 0 )
{
contact->setBlocked( true );
contact->setAllowed( false );
}
}
// Contact deleted
void MsnNotificationConnection::slotContactDeleted( const QString& contactId )
{
Contact *contact = contactList_->getContactByGuid( contactId );
if( contact == 0 )
{
return;
}
const QString& handle( contact->getHandle() );
contact->setFriend( false );
// Remove it from our FL.
putRml( handle, Contact::MSN_LIST_FRIEND );
}
// Contact removed from group
void MsnNotificationConnection::slotContactDeletedFromGroup( const QString &contactId, const QString &groupId )
{
Contact *contact = contactList_->getContactByGuid( contactId );
if( contact == 0 )
{
return;
}
contact->removeGroupId( groupId );
}
void MsnNotificationConnection::slotGotAddressBookList( const QList< QHash<QString,QVariant> > &contacts )
{
// Stop the login timer to allow parsing the address book.
loginTimer_.stop();
QListIterator<QHash<QString,QVariant> > contact( contacts );
int lists;
// Cycle for each contact
while( contact.hasNext() )
{
// Grep the main informations
QHash<QString,QVariant> contactInfo( contact.next() );
const QString& handle( contactInfo.take( "handle" ).toString() );
const QString& friendlyName( contactInfo.take( "friendlyName" ).toString() );
const QString& contactId( contactInfo.take( "contactId" ).toString() );
const QStringList& guidList( contactInfo.take( "guidList" ).toStringList() );
// Add each contact in our AB to contactlist ( FL )
lists = contactsRole_.take( handle ) | Contact::MSN_LIST_FRIEND;
Contact *contact = contactList_->addContact( handle, friendlyName, lists , guidList , contactId );
// Set the remaining informations
if( contact != 0 )
{
contact->setInformations( contactInfo );
}
}
// If there are contacts not present in AB, but present in Membership List
// add them to contactlist
if( contactsRole_.count() > 0 )
{
QHashIterator< QString, int > i( contactsRole_ );
while( i.hasNext() )
{
i.next();
const QString& handle( i.key() );
contactList_->addContact( handle , handle , i.value(), QStringList(), "" );
}
contactsRole_.clear();
}
// Allow some time until the next message is received
if( loginTimer_.isActive() )
{
loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
}
putAdl();
}
/**
* @brief Called when address book services retrieved membership lists.
*
* In this list there are informations about FL, AL, BL lists.
*
*/
void MsnNotificationConnection::slotGotMembershipLists( const QString &serviceType, const QHash<QString,int> &contactsRole )
{
if( serviceType != "Messenger" )
{
kWarning() << "Unable to parse membership list:" << serviceType;
return;
}
contactsRole_ = contactsRole;
emit statusMessage( i18n("Waiting for contact list..."), false );
// Allow some time until the next message is received
if( loginTimer_.isActive() )
{
loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
}
AddressBookService *addressBook = createAddressBookService();;
addressBook->retrieveAddressBook();
}
/**
* @brief Called when address book services parsed a group.
*
* @param id the id of the group
* @param name the name of the group
*/
void MsnNotificationConnection::slotGotGroup( const QString &id, const QString &name )
{
contactList_->addGroup( id, name );
}
void MsnNotificationConnection::slotContactUnblocked( const QString &handle )
{
Contact *contact = contactList_->getContactByHandle( handle );
if( contact != 0 )
{
contact->setBlocked( false );
contact->setAllowed( true );
}
}
// Show warning notifications
void MsnNotificationConnection::slotWarning( const QString &warning, bool isImportant )
{
if( isImportant )
{
KMessageBox::sorry( 0,
i18nc( "Connection warning: dialog box with message",
"<p><i>Warning</i>: %1</p>", warning ),
i18nc( "Error dialog box title", "MSN Warning" ) );
}
else
{
// Save the error details in the event settings
NotificationManager::EventSettings settings;
settings.sender = this;
settings.contact = 0;
settings.widget = 0;
settings.buttons = NotificationManager::BUTTON_HIDE;
// Notify the user about the network error
NotificationManager::instance()->notify( "status", warning, settings );
}
}
// Unblock the given contact
void MsnNotificationConnection::unblockContact( QString handle )
{
putRml( handle, Contact::MSN_LIST_BLOCKED );
putAdl( handle, Contact::MSN_LIST_ALLOWED );
AddressBookService *addressBook = createAddressBookService();
addressBook->unblockContact( handle );
}
/**
* @brief Called when the connection has been established.
*
* This method calls putVer() to start the version exchange.
*/
void MsnNotificationConnection::slotConnected()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Connected to server, sending VER.";
#endif
// Start the login timer, allowing the defined maximum time between each
// received command
loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
putVer();
}
/**
* @brief Shows error dialog boxes
*
* Closes the connection, and displays
* a suitable type of message box.
*
* @param error The error reason or explanation
* @param type The type of error
*/
void MsnNotificationConnection::slotError( QString error, MsnSocketBase::ErrorType type )
{
kWarning() << "MSN Notification Connection error type" << type << ":" << error;
QString message;
QString additionalInfo;
QString detailedMessage;
bool tryReconnecting = true;
bool useNotification = true;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
// Add more details about the error in developer builds.
additionalInfo = i18n( "<br />Internal error reason: %1", error );
#endif
// If the server can't be contacted, try again with HTTP before giving up
if( ( isInitiatingConnection_ && type == MsnSocketBase::ERROR_DROP )
|| type == MsnSocketBase::ERROR_CONNECTING
|| type == MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT )
{
if( switchToHttpSocket() )
{
// Note how the first error is silently discarded. If it's something important, like
// the "server is too busy" error, it'll be received when connecting via HTTP anyways.
return;
}
}
// Clean up the mess left by the SOAP classes
if( qobject_cast<OfflineImService*>( sender() ) != 0 )
{
// No need to request more, it just failed.
qDeleteAll( offlineImMessages_ );
offlineImMessages_.clear();
}
// We have to save the lastStatus before closing the connection, because closeConnection() resets it,
// but it also resets CurrentAccount so we need to retrieve it now, and set it later.
// TODO XXX HACK FIXME: move this logic to KMessSession (::getInitialStatus?)
// see the IRC logs in #kmess2 at 14 july 2009, 01:41:44...
int lastStatus = -1;
if( currentAccount_ != 0 )
{
lastStatus = currentAccount_->getStatus();
}
// First disconnect
closeConnection();
// Save the status, to use it again when reconnecting
lastStatus_ = lastStatus;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Changed the lastStatus to " << MsnStatus::getCode( (Status)lastStatus_ );
#endif
// Then decide what kind of message to show
switch( type )
{
// Notify that connecting has failed due to a wrong handle or password
case MsnSocketBase::ERROR_AUTH_LOGIN:
// This is an immediate error, a dialog box is needed
KMessageBox::error( 0,
i18nc( "Connection error: dialog box",
"<p>Authentication has failed, please verify "
"your account email and password.</p>" ),
i18nc( "Error dialog box title", "MSN Error" ) );
// Do not attempt to reconnect
tryReconnecting = false;
// Don't send a passive notification
useNotification = false;
break;
// The user has connected this account from another location
case MsnSocketBase::ERROR_CONNECTION_OTHER:
message = i18nc( "Connection error: passive notification message",
"<p>The account <i>%1</i> has been connected from another location.</p>",
currentAccount_->getHandle() );
detailedMessage = i18nc( "Connection error: detailed message",
"<p>You have been disconnected: you have connected "
"with the account <i>%1</i> from another Messenger client, "
"or from another location.</p>",
currentAccount_->getHandle() );
// Do not attempt to reconnect
tryReconnecting = false;
break;
// The HTTP connection failed: there's a redirection page
case MsnSocketBase::ERROR_CONNECTING_GATEWAY:
message = i18nc( "Connection error: passive notification message",
"<p>Unable to connect to the Live Messenger service.<br/>"
"Maybe you need to authenticate before you can access the network?</p>" );
detailedMessage = i18nc( "Connection error: detailed message",
"<p>KMess could not connect to the Live Messenger servers.<br />"
"There may be a problem with your Internet connection, "
"or the Live Messenger servers may be temporarily unavailable.<br/>"
"It is also possible that an authentication to a web page or proxy "
"may be required to access the network.</p>"
"<p><a href='%1'>Click here</a> to visit the Messenger service "
"status page.</p>",
"http://status.messenger.msn.com/Status.aspx" );
// Do not attempt to reconnect
tryReconnecting = false;
break;
case MsnSocketBase::ERROR_AUTH_COMPUTATION:
#warning "Translate this after string freeze!"
message = I18N_NOOP("<p>Unable to resolve the authentication on the client<br/>"
"Maybe you do not have installed qca2 and/or qca2-plugin-ossl?</p>" );
detailedMessage = I18N_NOOP("<p>KMess could not connect to the Live Messenger servers.<br />"
"You probably need to install qca2 and qca2-plugin-ossl to make KMess work.<br/>"
"It is also possible that the MSN network is unavailable at the moment.</p>"
);
// Do not attempt to reconnect
tryReconnecting = false;
break;
// Continue as for authentication errors if the switch to HTTP had failed
case MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT:
case MsnSocketBase::ERROR_CONNECTING:
case MsnSocketBase::ERROR_AUTH_TIME_LIMIT:
case MsnSocketBase::ERROR_SOAP_TIME_LIMIT:
message = i18nc( "Connection error: passive notification message",
"<p>Unable to connect to the Live Messenger service.</p>" );
detailedMessage = i18nc( "Connection error: detailed message",
"<p>KMess could not connect to the Live Messenger servers.<br />"
"There may be a problem with your Internet connection, "
"or the Live Messenger servers may be temporarily unavailable.</p>"
"<p><a href='%1'>Click here</a> to visit the Messenger service "
"status page.</p>",
"http://status.messenger.msn.com/Status.aspx" );
break;
// Report errors caused by the user
case MsnSocketBase::ERROR_USER:
message = i18nc( "Connection error (Server-reported user error): passive notification message",
"<p><i>Error</i>: %1</p>", error );
detailedMessage = i18nc( "Connection error (Server-reported user error): detailed message",
"<p>The Live Messenger server has reported an error:</p>"
"<p>%1</p>",
error );
break;
// Report internal server errors
case MsnSocketBase::ERROR_SERVER:
case MsnSocketBase::ERROR_SOAP_TOOMANYREDIRECTS:
case MsnSocketBase::ERROR_SOAP_RESPONSE:
case MsnSocketBase::ERROR_SOAP_AUTHENTICATION:
message = i18nc( "Connection error (Server-reported server error): passive notification message",
"<p><i>Messenger Service Error</i>: %1</p>", error );
detailedMessage = i18nc( "Connection error (Server-reported server error): detailed message",
"<p>The Live Messenger server has reported an error:</p>"
"<p>%1</p>",
error );
break;
// Report internal KMess errors, usually after receiving an error by the MSN server
case MsnSocketBase::ERROR_INTERNAL:
message = i18nc( "Connection error (Server-reported client error): passive notification message",
"<p><i>KMess Error</i>: %1</p>", error );
detailedMessage = i18nc( "Connection error (Server-reported client error): detailed message",
"<p>KMess has encountered an internal error:</p>"
"<p>%1</p>",
error );
break;
case MsnSocketBase::ERROR_DROP:
case MsnSocketBase::ERROR_DATA:
case MsnSocketBase::ERROR_UNKNOWN:
case MsnSocketBase::ERROR_SOAP_UNKNOWN:
default:
message = i18nc( "Connection error: passive notification message",
"<p>Network connection lost.</p>" );
detailedMessage = i18nc( "Connection error: detailed message",
"<p>Connection to the Live Messenger server has been lost.</p>" );
break;
}
if( useNotification )
{
// Save the error details in the event settings
QMap<QString,QVariant> details;
details[ "errorType" ] = (int) type;
details[ "errorString" ] = error;
details[ "detailedMessage" ] = detailedMessage;
NotificationManager::EventSettings settings;
settings.sender = this;
settings.contact = 0;
settings.widget = 0;
settings.buttons = NotificationManager::BUTTON_HIDE | NotificationManager::BUTTON_MORE_DETAILS;
settings.data = details;
// Notify the user about the network error
NotificationManager::instance()->notify( "status", message, settings );
}
// Try connecting again
if( tryReconnecting )
{
emit reconnect( currentAccount_->getHandle() );
}
}
// Displays additional info about network errors
void MsnNotificationConnection::slotErrorEventActivated( NotificationManager::EventSettings settings, NotificationManager::Buttons button )
{
// The signal wasn't meant for us
if( settings.sender != this )
{
return;
}
KDialog *dialog;
QString developerInfo;
const QMap<QString,QVariant> &info = settings.data.toMap();
switch( button )
{
case NotificationManager::BUTTON_MORE_DETAILS:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Showing error details";
#endif
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
// Add more details about the error in developer builds.
developerInfo = i18nc( "Developer details placed on the network error dialog box",
"<p><b>Developer info:</b><br/>"
"<i>Error number:</i> %1<br/>"
"<i>Error string:</i> %2"
"</p>",
info["errorType" ].toString(),
info["errorString"].toString() );
#endif
// We need to create a non-modal informative message box: a modal one would block
// execution here, and if the user dismisses the box after the notification has
// already been deleted by KDE, kmess would crash.
dialog = new KDialog( 0 );
dialog->setAttribute( Qt::WA_DeleteOnClose );
dialog->setButtons( KDialog::Ok );
KMessageBox::createKMessageBox( dialog,
QMessageBox::Information,
info["detailedMessage"].toString() + developerInfo,
QStringList(),
QString(),
0,
KMessageBox::Notify | KMessageBox::AllowLink | KMessageBox::NoExec,
QString() );
dialog->show();
break;
case NotificationManager::BUTTON_HIDE:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Hiding status notification";
#endif
// Do nothing
break;
default:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
kDebug() << "Invalid button" << button;
#endif
return;
}
}
/**
* @brief Unlock notifications after the first seconds of connections
*
* The first moments KMess is connected notifications are blocked, to avoid receiving popups before
* the user could even look at the contact list, possibly distracting him/her or covering screen space.
*/
void MsnNotificationConnection::unlockNotifications()
{
enableNotifications_ = true;
}
#include "msnnotificationconnection.moc"