Files
kmess/src/utils/kmessshared.cpp
T
2009-05-13 18:20:42 +00:00

567 lines
17 KiB
C++

/***************************************************************************
kmessshared.cpp - description
-------------------
begin : Thu May 8 2008
copyright : (C) 2008 by Valerio Pilo
email : valerio@kmess.org
***************************************************************************/
/***************************************************************************
* *
* 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 "kmessshared.h"
#include "../kmess.h"
#include "../kmessapplication.h"
#include "../kmessdebug.h"
#include "../currentaccount.h"
#include "../network/soap/passportloginservice.h"
#include "kmessconfig.h"
#include <KRun>
#include <KUrl>
#include <KShell>
#include <KToolInvocation>
#include <QCryptographicHash>
#include <QFile>
#ifdef KMESSDEBUG_SHAREDMETHODS
#define KMESSDEBUG_SHARED_OPENEXTERNAL
#endif
// Create a HMACSha1 hash
QByteArray KMessShared::createHMACSha1 ( const QByteArray &keyForHash , const QByteArray &secret )
{
// The algorithm is definited into RFC 2104
int blocksize = 64;
QByteArray key( keyForHash );
QByteArray opad( blocksize, 0x5c );
QByteArray ipad( blocksize, 0x36 );
// If key size is too larg, compute the hash
if( key.size() > blocksize )
{
key = QCryptographicHash::hash( key, QCryptographicHash::Sha1 );
}
// If too small, pad with 0x00
if( key.size() < blocksize )
{
key += QByteArray( blocksize - key.size() , 0x00 );
}
// Compute the XOR operations
for(int i=0; i < key.size() - 1; i++ )
{
ipad[i] = (char) ( ipad[i] ^ key[i] );
opad[i] = (char) ( opad[i] ^ key[i] );
}
// Append the data to ipad
ipad += secret;
// Compute result: hash sha1 of ipad and append the data to opad
opad += QCryptographicHash::hash( ipad, QCryptographicHash::Sha1 );;
// Return array contains the result of HMACSha1
return QCryptographicHash::hash( opad, QCryptographicHash::Sha1 );
}
// Create the html file to request the given service
QString KMessShared::createRequestFile( const QString &mailto, const QString &folder )
{
CurrentAccount *currentAccount = CurrentAccount::instance();
// Use one file for hml page with post method
QString filename( KMessConfig::instance()->getAccountDirectory( currentAccount-> getHandle() ) +
"/hotmail.htm" );
QFile file( filename );
if ( ! file.open( QIODevice::WriteOnly | QIODevice::Text ) )
{
return QString();
}
QString command( folder );
// Check if the user wants to send an email
if( ! mailto.isEmpty() )
{
command += "?mailto=1&to=" + mailto;
}
// Use the method into passport to compute the token
QString token( PassportLoginService::createHotmailToken( currentAccount->getToken( "Passport" ),
currentAccount->getToken( "PassportProof" ),
command ) );
QTextStream outBuffer( &file );
// Write the file contents
outBuffer << "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html>\n"
" <head></head>\n"
" <body onload=\"document.pform.submit(); \">\n"
" <form name=\"pform\""
"action=\"https://login.live.com/ppsecure/sha1auth.srf?lc=" +
currentAccount->getLanguageCode() +
"\" method=\"POST\">\n"
" <input type=\"hidden\" name=\"token\" value=\"" +
token +
"\">\n"
" <noscript><p>Please enable JavaScript!</p>\n"
" <input type=\"submit\" value=\"Click here to open Live Mail\"/>\n"
" </noscript>\n"
" </form>\n"
" </body>\n"
"</html>\n";
// Then close the buffer
file.close();
return filename;
}
// Return a derived key with HMACSha1 algorithm
QByteArray KMessShared::deriveKey ( const QByteArray &keyToDerive, const QByteArray &magic )
{
// create the four hashes needed for the implementation.
QByteArray hash1, hash2, hash3, hash4, temp;
QByteArray key(keyToDerive);
// append the magic string.
temp.append ( magic );
// create the HMAC Sha1 hash and assign it.
hash1 = createHMACSha1 ( key, temp );
temp.clear();
// append the first hash with the magic string.
temp.append ( hash1 + magic );
// create the HMAC Sha1 hash and assign it.
hash2 = createHMACSha1 ( key, temp );
temp.clear();
// append the first hash.
temp.append ( hash1 );
// create the HMAC Sha1 hash and assign it.
hash3 = createHMACSha1 ( key, temp );
temp.clear();
// assign the third hash with the magic string.
temp.append ( hash3 + magic );
// create the HMAC Sha1 hash and assign it.
hash4 = createHMACSha1 ( key, temp );
temp.clear();
// return the second hash with the first four bytes of the fourth hash.
return hash2 + hash4.left ( 4 );
}
/**
* @brief Generate a random GUID.
*
* This is used in MSNP2P for the Call ID and Branch ID.
*
* @return A randomly generated GUID value.
*/
QString KMessShared::generateGUID()
{
// This code is based on Kopete, but much shorter.
QString guid( "{"
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ "-"
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ "-"
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ "-"
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ "-"
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ QString::number((rand() & 0xAAFF) + 0x1111, 16)
+ "}" );
return guid.toUpper();
}
/**
* @brief Generate an random number to use as ID.
*
* For use in MSNP2P, the value will not be below 4
*
* @return A random value between 4 and 4294967295.
*/
quint32 KMessShared::generateID()
{
return (rand() & 0x00FFFFFC);
}
// Return the hash of a file name using the SHA1 algorithm
QByteArray KMessShared::generateFileHash( const QString &fileName )
{
// Read the file's contents into a byte array
QFile file( fileName );
if( ! file.open( QIODevice::ReadOnly ) )
{
return QByteArray();
}
QByteArray fileData( file.readAll() );
file.close();
// Retrieve and return the file's hash
return QCryptographicHash::hash( fileData, QCryptographicHash::Sha1 );
}
/**
* Converts a string with HTML to one with escaped entities
*
* Only the main HTML control characters are escaped; the string is made suitable for
* insertion within an HTML tag attribute.
* Neither KDE nor Qt have escape/unescape methods this thorough, they only replace the <>&. Annoying.
*
* @param string The string to modify
* @return A reference to the modified string
*/
QString &KMessShared::htmlEscape( QString &string )
{
string.replace( ";", "&#59;" )
.replace( "&", "&amp;" )
.replace( "&amp;#59;", "&#59;" ) // oh god :(
.replace( "<", "&lt;" )
.replace( ">", "&gt;" )
.replace( "'", "&#39;" )
.replace( '"', "&#34;" ); // NOTE: by not using &quot; this result is also usable for XML.
return string;
}
/**
* Converts a string constant with HTML to one with escaped entities
*
* This version is suitable for use with string constants, as it creates a copy of the original string.
*
* @param string The string to modify
* @return Another string with the escaped contents
*/
QString KMessShared::htmlEscape( const QString &string )
{
QString copy( string );
return htmlEscape( copy );
}
/**
* Converts a string with escaped entities to one with HTML
*
* Only the main HTML control characters are unescaped; the string is reverted back to its
* original state.
* Neither KDE nor Qt have HTML to text decoding methods. Annoying.
*
* @param string The string to modify
* @return A reference to the modified string
*/
QString &KMessShared::htmlUnescape( QString &string )
{
string.replace( "&#34;", "\"" )
.replace( "&#39;", "'" )
.replace( "&gt;", ">" )
.replace( "&lt;", "<" )
.replace( "&#59;", "&amp;#59;" )
.replace( "&amp;", "&" )
.replace( "&#59;", ";" );
return string;
}
/**
* Converts a string with escaped entities to one with HTML
*
* This version is suitable for use with string constants, as it creates a copy of the original string.
*
* @param string The string to modify
* @return Another string with the escaped contents
*/
QString KMessShared::htmlUnescape( const QString &string )
{
QString copy( string );
return htmlUnescape( copy );
}
/**
* @brief Open the given URL respecting the user's preference
*
* The program which will open the URL is the one selected in the Global Settings
*
* @param url The URL to open
*/
void KMessShared::openBrowser( const KUrl &url )
{
// Open the options
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
QString browser;
QString fallbackBrowser( group.readEntry( "customBrowser", "konqueror" ) );
// Obtain the widget of the main KMess window, to correctly link it to the opened browser
KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
QWidget *mainWindow = kmessApp->getContactListWindow()->window();
// Read which browser has to be opened
QString browserChoice( group.readEntry( "useBrowser", "KDE" ) );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Opening URL" << url << "- The setting is" << browserChoice;
#endif
// If the preference had an invalid choice, use the KDE default browser.
if( browserChoice != "custom"
&& browserChoice != "listed"
&& browserChoice != "KDE" )
{
kWarning() << "Invalid browser choice:" << browserChoice << ", using KDE default.";
browserChoice = "KDE";
}
// Launch the appropriate browser
if( browserChoice == "custom" )
{
browser = group.readEntry( "customBrowser", fallbackBrowser );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Custom browser selected:" << browser;
#endif
}
else if( browserChoice == "listed" )
{
browser = group.readEntry( "listedBrowser", fallbackBrowser );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Listed browser selected:" << browser;
#endif
}
// If the option isn't valid use the KDE default
if( browser.isEmpty() )
{
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Invalid browser option, using KDE default.";
#endif
browserChoice = "KDE";
}
if( browserChoice == "KDE" )
{
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "KDE default browser selected.";
#endif
KToolInvocation::invokeBrowser( url.url() );
return;
}
// Replace the %u in the command line with the actual URL, and launch the browser.
browser.replace( "%u", KShell::quoteArg( url.url() ), Qt::CaseInsensitive );
KRun::runCommand( browser, mainWindow );
}
/**
* @brief Open the given URL mail respecting the user's preference
*
* The program which will open the URL is the one selected in the Global Settings
*
* @param mailto The address to compose a message to (can also be empty to open the folder instead).
* @param folder The folder to open.
* @param isHotmailAvailable Whether Hotmail can be used to access the email for this account.
*/
void KMessShared::openEmailClient( const QString &mailto, const QString &folder, bool isHotmailAvailable )
{
// Open the options
KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
// When using Live! Mail accounts, the webmail can be used
if( isHotmailAvailable && group.readEntry( "useLiveMail", true ) )
{
// Create the request file
QString filename( createRequestFile( mailto, folder ) );
if( ! filename.isEmpty() )
{
// Open the webmail in the browser
openBrowser( KUrl( "file://" + filename ) );
}
return;
}
QString mailClient;
QString fallbackMailClient( group.readEntry( "customMailClient", "kmail" ) );
// Obtain the widget of the main KMess window, to correctly link it to the opened app
KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
QWidget *mainWindow = kmessApp->getContactListWindow()->window();
// Read which mail client has to be opened
QString mailClientChoice( group.readEntry( "useMailClient", "KDE" ) );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Opening mail at" << folder << "- The setting is" << mailClientChoice;
#endif
// If the preference had an invalid choice, use the KDE default mail client.
if( mailClientChoice != "custom"
&& mailClientChoice != "listed"
&& mailClientChoice != "KDE" )
{
kWarning() << "Invalid mail client choice:" << mailClientChoice << ", using KDE default.";
mailClientChoice = "KDE";
}
// Launch the appropriate mail client
if( mailClientChoice == "custom" )
{
mailClient = group.readEntry( "customMailClient", fallbackMailClient );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Custom mail client selected:" << mailClient;
#endif
}
else if( mailClientChoice == "listed" )
{
mailClient = group.readEntry( "listedMailClient", fallbackMailClient );
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Listed mail client selected:" << mailClient;
#endif
}
// If the option isn't valid use the KDE default
if( mailClient.isEmpty() )
{
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "Invalid mail client option, using KDE default.";
#endif
mailClientChoice = "KDE";
}
// Retrieve the mail address to open
if( mailClientChoice == "KDE" )
{
#ifdef KMESSDEBUG_SHARED_OPENEXTERNAL
kDebug() << "KDE default mail client selected.";
#endif
KToolInvocation::invokeMailer( KUrl( "mailto:" + mailto ) );
return;
}
// Replace the %u in the command line with the contact address, and launch the application.
mailClient.replace( "%u", KShell::quoteArg( mailto ), Qt::CaseInsensitive );
KRun::runCommand( mailClient, mainWindow );
}
/**
* Select the next available file.
*
* This method searches for numbered files and returns the last existing file,
* and the first number available for file creation.
*
*
* For example, calling selectNextFile( "/", "name", suffix, "ext", startingNumber ) returns:
* - if "/name.ext" doesn't exists, returns false, startingNumber = 1
* - if "/name.ext" exists, returns true, startingNumber = 1
* - if "/name.1.ext" and "/name.2.ext" exist, returns true, suffix = ".2",
* startingNumber = 3
*
* @param directory Path where the files will be searched, followed by '/'.
* May be relative.
* @param baseName Common name for the files, without the extension, see above.
* @param suffix Append to the baseName to get the name of the last
* existing numbered file.
* @param extension Extension of the file to search for, see above.
* @param startingNumber File number to start from, set to zero to start with
* the common name alone. Gets updated with the number of
* the first available file.
* @return bool If the file in the updated fileName param exists or not.
*/
bool KMessShared::selectNextFile( const QString &directory, const QString &baseName, QString &suffix, const QString &extension, int &startingNumber )
{
QString path( directory + baseName + "." );
// "baseName.ext" doesn't exist, find the next available file
if( startingNumber == 0 && ! QFile::exists( path + extension ) )
{
#ifdef KMESSDEBUG_SHAREDMETHODS
kDebug() << "Initial file" << ( path + extension ) << "does not exist, finding next available.";
#endif
while( QFile::exists( path + QString::number( ++startingNumber ) + "." + extension ) ) ;
#ifdef KMESSDEBUG_SHAREDMETHODS
kDebug() << "Next available:" << path << startingNumber;
#endif
return false;
}
#ifdef KMESSDEBUG_SHAREDMETHODS
kDebug() << "Initial file exists, finding the last numbered.";
#endif
// "baseName.ext" exists, find the last existing numbered one
// Start the sequence number from 1
if( startingNumber == 0 )
{
startingNumber = 1;
}
while( QFile::exists( path + QString::number( startingNumber ) + "." + extension ) )
{
startingNumber++;
}
// Found the first non-existing file
if( startingNumber == 1 )
{
// Select the first numbered file
suffix = QString();
}
else
{
suffix = "." + QString::number( startingNumber - 1 ); // That's the number of the last existing file
}
#ifdef KMESSDEBUG_SHAREDMETHODS
kDebug() << "Last existent file name:" << ( baseName + suffix );
#endif
return true;
}