3176 lines
98 KiB
C++
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"
|