/*************************************************************************** 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 "crypt/sha1.h" #include <qfile.h> #include <qregexp.h> #include <kmdcodec.h> #include <kdebug.h> // The constructor MsnObject::MsnObject() : size_(0) { } 00035 MsnObject::MsnObject(const QString &object) : original_(object) { if( object.isEmpty() ) { kdWarning() << "MsnObject: passed object is empty." << endl; return; } loadObject(object); } // Copy constructor 00049 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_) { } 00063 MsnObject::MsnObject(const QString &creator, const QString &location, const QString &friendly, MsnObjectType type, const QByteArray &fileData) : creator_(creator) , friendly_(friendly) , location_(location) , type_(type) { size_ = fileData.size(); // Generate hashes. sha1d_= generateDataHash( fileData ); sha1c_= generateObjectHash(); } // The destructor MsnObject::~MsnObject() { } // Parse an attribute from the object string 00090 QString MsnObject::getAttribute( const QString& name, const QString& object ) { QString value; int index; QRegExp findAttribute( name + "=\"([^ ]*)\"" ); index = findAttribute.search( object ); if ( index >= 0 ) { if ( findAttribute.numCaptures() > 0 ) { value = findAttribute.capturedTexts()[1]; } } return value; } // 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_; } // Use an MSN object descriptor from the server to load data 00162 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).utf8(); sha1c_ = getAttribute("SHA1C", object).utf8(); // Get friendly name, make sure it's parsed correctly QString friendlyEncoded = getAttribute( "Friendly", object ); if( ! friendlyEncoded.contains("AA") ) { // This could cause crashes. kdWarning() << "MsnObject: friendly name is missing a null-terminator (contact=" << creator_ << ")!" << endl; } else { QByteArray friendlyUcs2; KCodecs::base64Decode( friendlyEncoded.utf8(), friendlyUcs2 ); friendly_ = QString::fromUcs2( reinterpret_cast<const unsigned short*>(friendlyUcs2.data()) ); } // Bah! Verification is for the weak. //if(!verifyObjectHash()) //{ // kdDebug() << "Hash does not match!"; // kdDebug() << "MSN6 data for contact: " << creator_ << " " << location_ << " " << size_ << " " << type_ << " " << getFriendly() << endl; // kdDebug() << "Real sha1c " << sha1c_ << " vs. calculated " << generateObjectHash() << endl; //} } 00194 bool MsnObject::verifyObjectHash() const { // And yes, QCString overloads operator== return generateObjectHash() == sha1c_; } 00201 bool MsnObject::verifyFile( const QString &fileName ) const { QFile file( fileName ); if( ! file.open( IO_ReadOnly ) ) { kdWarning() << "MsnObject::verifyFile() - unable to open file '" << fileName << "'." << endl; return false; } QByteArray fileData = file.readAll(); file.close(); return generateDataHash( fileData ) == sha1d_; } 00217 const QCString MsnObject::generateDataHash( const QByteArray &fileData ) const { Sha1 sha1; QByteArray hash; hash.duplicate( (const char *) sha1.calcSha1( (const byte *) fileData.data(), fileData.size() ), 20 ); return KCodecs::base64Encode(hash); } 00225 const QCString MsnObject::generateObjectHash() const { // First, we must build the string that needs to be hashed. // See amsn protocol.tcl (create_msnobj) for an example. // We concatenate field names with their values, in the following order: // Creator, Size, Type, Location, Friendly, SHA1D QCString baseString= "Creator" + creator_.utf8() + "Size" + QString::number(size_).utf8() + "Type" + QString::number((int)type_).utf8() + "Location" + location_.utf8() + "Friendly" + friendly_.utf8() + "SHA1D" + sha1d_; // Sha1 the thing QByteArray shaData(20); // 160 bits of output Sha1 sha1; shaData.duplicate( (const char *) sha1.calcSha1( (const byte*) baseString.data(), baseString.length() ), 20 ); // Return the base64-encoded version // No worry about linefeeds: should come out to 28 bytes return KCodecs::base64Encode(shaData); } 00249 const QString MsnObject::objectString() const { if( ! original_.isEmpty() ) { return original_; } QCString friendlyBase64; if( friendly_.length() == 0 ) { friendlyBase64 = "AA=="; // null-terminator and padding. } else { QByteArray friendlyUcs2; int size = friendly_.length() * 2; // Copy existing UTF16 string to an QByteArray friendlyUcs2.duplicate( reinterpret_cast<const char*>(friendly_.ucs2()), size ); // Add null-padding friendlyUcs2.resize( size + 4 ); friendlyUcs2[size + 0] = '\0'; friendlyUcs2[size + 1] = '\0'; friendlyUcs2[size + 2] = '\0'; friendlyUcs2[size + 3] = '\0'; // To BASE64 encoding. friendlyBase64 = KCodecs::base64Decode(friendlyUcs2); } // Create msn object string. return QString( "<msnobj Creator=\"%1\" Size=\"%2\" Type=\"%3\"" " Location=\"%4\" Friendly=\"%5\"" " SHA1D=\"%6\" SHA1C=\"%7\"/>" ) .arg(creator_).arg(size_).arg(type_).arg(location_).arg(friendlyBase64) .arg(sha1d_).arg(sha1c_); } 00291 bool MsnObject::hasChanged( const QString &newObj ) const { // TODO: The QString comparing with != should be overridden to match on QCStrings or char*'s // 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; } 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.utf8() ); } // The data hash is not available: use the full object hash return ( sha1c_ != getAttribute( "SHA1C", newObj ).utf8() ); }