/***************************************************************************
                          msnobject.cpp  -  description
                             -------------------
    begin                : Tue Jul 15 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mike@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 "msnobject.h"

#include "../kmessdebug.h"

#include <QCryptographicHash>
#include <QFile>
#include <QRegExp>
#include <QStringList>



// The constructor
MsnObject::MsnObject()
 : size_(0)
 , type_( INVALID )
{
}



MsnObject::MsnObject(const QString &object)
: original_(object)
{
  if( object.isEmpty() )
  {
    kWarning() << "passed object is empty.";
    return;
  }

  loadObject(object);
}



// Copy constructor
MsnObject::MsnObject(const MsnObject &other)
: creator_(other.creator_)
, friendly_(other.friendly_)
, location_(other.location_)
, original_(other.original_)
, size_(other.size_)
, type_(other.type_)
, sha1d_(other.sha1d_)
, sha1c_(other.sha1c_)
{

}



MsnObject::MsnObject( const QString &creator, const QString &location,
                      const QString &friendly, MsnObjectType type,
                      const QByteArray &fileData, const QByteArray &stamp )
: creator_(creator)
, friendly_(friendly)
, location_(location)
, type_(type)
, stamp_(stamp)
{
  size_ = fileData.size();

  // Generate hashes.
  sha1d_= generateDataHash( fileData );
  sha1c_= generateObjectHash();
}



// The destructor
MsnObject::~MsnObject()
{
}



// Parse an attribute from the object string
QString MsnObject::getAttribute( const QString& name, const QString& object )
{
  // Find attribute
  QRegExp findAttribute( name + "=\"([^ ]*)\"" );
  int index = findAttribute.indexIn( object );

  // Return if found
  if( index >= 0 && findAttribute.numCaptures() > 0 )
  {
    return findAttribute.cap( 1 );
  }

  return QString::null;
}



// Get the sha1c hash
const QString MsnObject::getContentHash() const
{
  return sha1c_;
}



// Get the object's creator
const QString& MsnObject::getCreator() const
{
  return creator_;
}



// Get the sha1d hash
const QString MsnObject::getDataHash() const
{
  return sha1d_;
}


// Get the object's location
const QString& MsnObject::getLocation() const
{
  return location_;
}



const QString& MsnObject::getFriendly() const
{
  // This is actually base-64 encoded. We must decode it before
  // returning the information.
  return friendly_;
}



// Get the object's size
int MsnObject::getSize() const
{
  return size_;
}



// Get the object's type
MsnObject::MsnObjectType MsnObject::getType() const
{
  return type_;
}



// Get if this is a valid MSNObject.
bool MsnObject::isValid() const
{
  return ( type_ != INVALID );
}



// Use an MSN object descriptor from the server to load data
void MsnObject::loadObject( const QString& object )
{
  creator_  = getAttribute( "Creator", object );
  location_ = getAttribute( "Location", object );
  size_     = getAttribute( "Size", object ).toInt();
  type_     = (MsnObjectType)getAttribute( "Type", object ).toInt();
  sha1d_    = getAttribute("SHA1D", object).toUtf8();
  sha1c_    = getAttribute("SHA1C", object).toUtf8();

  // Get the friendly name from the object
  const QString& friendlyEncoded( getAttribute( "Friendly", object ) );

  // The string is empty, store an empty friendly
  if( friendlyEncoded.isEmpty() )
  {
    friendly_ = QString();
    return;
  }

  // Don't check for invalid characters in the encoded string, QByteArray does it
  const QByteArray friendlyUcs2( QByteArray::fromBase64( friendlyEncoded.toUtf8() ) );
  friendly_ = QString::fromUtf16( reinterpret_cast<const ushort*>( friendlyUcs2.data() ), friendlyUcs2.size() ).toUtf8();
}



bool MsnObject::verifyObjectHash() const
{
  // And yes, QByteArray overloads operator==
  return generateObjectHash() == sha1c_;
}



bool MsnObject::verifyFile( const QString &fileName ) const
{
  QFile file( fileName );
  if( ! file.open( QIODevice::ReadOnly ) )
  {
    kWarning() << "unable to open file '" << fileName << "'.";
    return false;
  }

  const QByteArray& fileData( file.readAll() );
  file.close();

  return generateDataHash( fileData ) == sha1d_;
}



const QByteArray MsnObject::generateDataHash( const QByteArray &fileData ) const
{
  // Thanks Qt 4.3... :-)
  return QCryptographicHash::hash( fileData, QCryptographicHash::Sha1 ).toBase64();
}



const QByteArray MsnObject::generateObjectHash() const
{
  // Generate input
  // Note the stamp must be excluded from the compute of hash
  const QByteArray baseString(  "Creator"  + creator_.toUtf8() +
                                "Size"     + QByteArray::number( size_ ) +
                                "Type"     + QByteArray::number( type_ ) +
                                "Location" + location_.toUtf8() +
                                "Friendly" + friendly_.toUtf8() +
                                "SHA1D"    + sha1d_ );

  // Hash as sha1, and encode with base64
  return QCryptographicHash::hash( baseString, QCryptographicHash::Sha1 ).toBase64();
}



const QString MsnObject::objectString() const
{
  if( ! original_.isEmpty() )
  {
    return original_;
  }

  QByteArray friendlyBase64;

  if( friendly_.length() == 0 )
  {
    friendlyBase64 = "AA==";  // null-terminator and padding.
  }
  else
  {
    // Copy existing UTF16 string to an QByteArray
    // remains valid until string is modified.
    const char *ucs2Data = reinterpret_cast<const char*>( friendly_.utf16() );
    friendlyBase64 = QByteArray( ucs2Data ).toBase64();
  }

  // Create msn object string.
  QString msnObjectString( "<msnobj Creator=\"" + creator_ +
                           "\" Size=\""     + QString::number( size_ ) +
                           "\" Type=\""     + QString::number( type_ ) +
                           "\" Location=\"" + location_ +
                           "\" Friendly=\"" + friendlyBase64 +
                           "\" SHA1D=\""    + sha1d_ +
                           "\" SHA1C=\""    + sha1c_ );

  // If the stamp isn't empty use it: it's not empty when we are creating msn object
  // for a wink, so the stamp is the certificate associated.
  if( ! stamp_.isEmpty() )
  {
    msnObjectString += "\" stamp=\"" + stamp_;
  }

  return msnObjectString + "\"/>";
}


bool MsnObject::hasChanged( const QString &newObj ) const
{
  // If no hash is present we can't check it, assume it's changed.
  // Will probably just waste a bit of bandwidth.
  if( sha1c_.isEmpty() && sha1d_.isEmpty() )
  {
    return true;
  }

  const QString& objSha1d( getAttribute( "SHA1D", newObj ) );

  // The data hash is more important than the full object hash, if it's present, use it
  if( ! sha1d_.isEmpty() && ! objSha1d.isEmpty() )
  {
    // If false the data is the same, the object hasn't changed
    return ( sha1d_ != objSha1d.toUtf8() );
  }

  // The data hash is not available: use the full object hash
  return ( sha1c_ != getAttribute( "SHA1C", newObj ).toUtf8() );
}

