Logo Search packages:      
Sourcecode: kmess version File versions

kmessshared.cpp

/***************************************************************************
                          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.
 */
00185 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.
 */
00214 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
 */
00251 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
 */
00274 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
 */
00292 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
 */
00313 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
 */
00326 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.
 */
00405 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", "ext", 0 ) 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, fileName = "name.2",
 *   startingNumber = 3
 *
 * @param directory       Path where the files will be searched, followed by '/'.
 *                        May be relative.
 * @param baseName        Common name for the files. Will be updated with the name
 *                        of the last existing numbered file.
 * @param suffix          Append to the baseName to get the name of the last
 *                        existing numbered file.
 * @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.
 */
00514 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( directory + baseName + "." + extension ) )
  {
#ifdef KMESSDEBUG_SHAREDMETHODS
    kDebug() << "Initial file" << ( directory + baseName + "." + 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;
}



Generated by  Doxygen 1.6.0   Back to index