|
|
|
/*
|
|
|
|
ktnefparser.cpp
|
|
|
|
|
|
|
|
Copyright (C) 2002 Michael Goffioul <kdeprint@swing.be>
|
|
|
|
|
|
|
|
This file is part of KTNEF, the KDE TNEF support library/program.
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
|
|
|
|
#include "ktnef/ktnefparser.h"
|
|
|
|
#include "ktnef/ktnefattach.h"
|
|
|
|
#include "ktnef/ktnefproperty.h"
|
|
|
|
#include "ktnef/ktnefmessage.h"
|
|
|
|
|
|
|
|
#include <tqdatetime.h>
|
|
|
|
#include <tqdatastream.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqvariant.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kmimetype.h>
|
|
|
|
#include <ksavefile.h>
|
|
|
|
|
|
|
|
#ifdef HAVE_INTTYPES_H
|
|
|
|
#include <inttypes.h>
|
|
|
|
#endif /* HAVE_INTTYPES_H */
|
|
|
|
|
|
|
|
#include "ktnef/ktnefdefs.h"
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
Q_UINT16 type;
|
|
|
|
Q_UINT16 tag;
|
|
|
|
TQVariant value;
|
|
|
|
struct {
|
|
|
|
Q_UINT32 type;
|
|
|
|
TQVariant value;
|
|
|
|
} name;
|
|
|
|
} MAPI_value;
|
|
|
|
|
|
|
|
void clearMAPIName( MAPI_value& mapi );
|
|
|
|
void clearMAPIValue(MAPI_value& mapi, bool clearName = true);
|
|
|
|
TQString readMAPIString( TQDataStream& stream, bool isUnicode = false, bool align = true, int len = -1 );
|
|
|
|
Q_UINT16 readMAPIValue(TQDataStream& stream, MAPI_value& mapi);
|
|
|
|
TQDateTime readTNEFDate( TQDataStream& stream );
|
|
|
|
TQString readTNEFAddress( TQDataStream& stream );
|
|
|
|
TQByteArray readTNEFData( TQDataStream& stream, Q_UINT32 len );
|
|
|
|
TQVariant readTNEFAttribute( TQDataStream& stream, Q_UINT16 type, Q_UINT32 len );
|
|
|
|
TQDateTime formatTime( Q_UINT32 lowB, Q_UINT32 highB );
|
|
|
|
TQString formatRecipient( const TQMap<int,KTNEFProperty*>& props );
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
class KTNEFParser::ParserPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ParserPrivate()
|
|
|
|
{
|
|
|
|
defaultdir_ = "/tmp/";
|
|
|
|
current_ = 0;
|
|
|
|
deleteDevice_ = false;
|
|
|
|
device_ = 0;
|
|
|
|
message_ = new KTNEFMessage;
|
|
|
|
}
|
|
|
|
~ParserPrivate()
|
|
|
|
{
|
|
|
|
delete message_;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDataStream stream_;
|
|
|
|
TQIODevice *device_;
|
|
|
|
bool deleteDevice_;
|
|
|
|
TQString defaultdir_;
|
|
|
|
KTNEFAttach *current_;
|
|
|
|
KTNEFMessage *message_;
|
|
|
|
};
|
|
|
|
|
|
|
|
KTNEFParser::KTNEFParser()
|
|
|
|
{
|
|
|
|
d = new ParserPrivate;
|
|
|
|
}
|
|
|
|
|
|
|
|
KTNEFParser::~KTNEFParser()
|
|
|
|
{
|
|
|
|
deleteDevice();
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
KTNEFMessage* KTNEFParser::message() const
|
|
|
|
{
|
|
|
|
return d->message_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTNEFParser::deleteDevice()
|
|
|
|
{
|
|
|
|
if ( d->deleteDevice_ )
|
|
|
|
delete d->device_;
|
|
|
|
d->device_ = 0;
|
|
|
|
d->deleteDevice_ = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::decodeMessage()
|
|
|
|
{
|
|
|
|
Q_UINT32 i1, i2, off;
|
|
|
|
Q_UINT16 u, tag, type;
|
|
|
|
TQVariant value;
|
|
|
|
|
|
|
|
// read (type+name)
|
|
|
|
d->stream_ >> i1;
|
|
|
|
u = 0;
|
|
|
|
tag = ( i1 & 0x0000FFFF );
|
|
|
|
type = ( ( i1 & 0xFFFF0000 ) >> 16 );
|
|
|
|
// read data length
|
|
|
|
d->stream_ >> i2;
|
|
|
|
// offset after reading the value
|
|
|
|
off = d->device_->at() + i2;
|
|
|
|
switch ( tag )
|
|
|
|
{
|
|
|
|
case attAIDOWNER:
|
|
|
|
d->stream_ >> value.asUInt();
|
|
|
|
d->message_->addProperty( 0x0062, MAPI_TYPE_ULONG, value );
|
|
|
|
kdDebug() << "Message Owner Appointment ID" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attREQUESTRES:
|
|
|
|
d->stream_ >> u;
|
|
|
|
d->message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u );
|
|
|
|
value = ( bool )u;
|
|
|
|
kdDebug() << "Message Request Response" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attDATERECD:
|
|
|
|
value = readTNEFDate( d->stream_ );
|
|
|
|
d->message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value );
|
|
|
|
kdDebug() << "Message Receive Date" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attMSGCLASS:
|
|
|
|
value = readMAPIString( d->stream_, false, false, i2 );
|
|
|
|
d->message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value );
|
|
|
|
kdDebug() << "Message Class" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attMSGPRIORITY:
|
|
|
|
d->stream_ >> u;
|
|
|
|
d->message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u );
|
|
|
|
value = u;
|
|
|
|
kdDebug() << "Message Priority" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attMAPIPROPS:
|
|
|
|
kdDebug() << "Message MAPI Properties" << " (length=" << i2 << ")" << endl;
|
|
|
|
{
|
|
|
|
int nProps = d->message_->properties().count();
|
|
|
|
i2 += d->device_->at();
|
|
|
|
readMAPIProperties( d->message_->properties(), 0 );
|
|
|
|
d->device_->at( i2 );
|
|
|
|
kdDebug() << "Properties: " << d->message_->properties().count() << endl;
|
|
|
|
value = TQString( "< %1 properties >" ).arg( d->message_->properties().count() - nProps );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case attTNEFVERSION:
|
|
|
|
d->stream_ >> value.asUInt();
|
|
|
|
kdDebug() << "Message TNEF Version" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attFROM:
|
|
|
|
d->message_->addProperty( 0x0024, MAPI_TYPE_STRING8, readTNEFAddress( d->stream_ ) );
|
|
|
|
d->device_->at( d->device_->at() - i2 );
|
|
|
|
value = readTNEFData( d->stream_, i2 );
|
|
|
|
kdDebug() << "Message From" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attSUBJECT:
|
|
|
|
value = readMAPIString( d->stream_, false, false, i2 );
|
|
|
|
d->message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value );
|
|
|
|
kdDebug() << "Message Subject" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attDATESENT:
|
|
|
|
value = readTNEFDate( d->stream_ );
|
|
|
|
d->message_->addProperty( 0x0039, MAPI_TYPE_TIME, value );
|
|
|
|
kdDebug() << "Message Date Sent" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attMSGSTATUS:
|
|
|
|
{
|
|
|
|
Q_UINT8 c;
|
|
|
|
Q_UINT32 flag = 0;
|
|
|
|
d->stream_ >> c;
|
|
|
|
if ( c & fmsRead ) flag |= MSGFLAG_READ;
|
|
|
|
if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED;
|
|
|
|
if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT;
|
|
|
|
if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH;
|
|
|
|
if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT;
|
|
|
|
d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
|
|
|
|
value = c;
|
|
|
|
}
|
|
|
|
kdDebug() << "Message Status" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attRECIPTABLE:
|
|
|
|
{
|
|
|
|
Q_UINT32 rows;
|
|
|
|
TQValueList<TQVariant> recipTable;
|
|
|
|
d->stream_ >> rows;
|
|
|
|
for ( uint i=0; i<rows; i++ )
|
|
|
|
{
|
|
|
|
TQMap<int,KTNEFProperty*> props;
|
|
|
|
readMAPIProperties( props, 0 );
|
|
|
|
recipTable << formatRecipient( props );
|
|
|
|
}
|
|
|
|
d->message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable );
|
|
|
|
d->device_->at( d->device_->at() - i2 );
|
|
|
|
value = readTNEFData( d->stream_, i2 );
|
|
|
|
}
|
|
|
|
kdDebug() << "Message Recipient Table" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attBODY:
|
|
|
|
value = readMAPIString( d->stream_, false, false, i2 );
|
|
|
|
d->message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value );
|
|
|
|
kdDebug() << "Message Body" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attDATEMODIFIED:
|
|
|
|
value = readTNEFDate( d->stream_ );
|
|
|
|
d->message_->addProperty( 0x3008, MAPI_TYPE_TIME, value );
|
|
|
|
kdDebug() << "Message Date Modified" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attMSGID:
|
|
|
|
value = readMAPIString( d->stream_, false, false, i2 );
|
|
|
|
d->message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value );
|
|
|
|
kdDebug() << "Message ID" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
case attOEMCODEPAGE:
|
|
|
|
value = readTNEFData( d->stream_, i2 );
|
|
|
|
kdDebug() << "Message OEM Code Page" << " (length=" << i2 << ")" << endl;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
value = readTNEFAttribute( d->stream_, type, i2 );
|
|
|
|
kdDebug().form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// skip data
|
|
|
|
if ( d->device_->at() != off && !d->device_->at( off ) )
|
|
|
|
return false;
|
|
|
|
// get checksum
|
|
|
|
d->stream_ >> u;
|
|
|
|
// add TNEF attribute
|
|
|
|
d->message_->addAttribute( tag, type, value, true );
|
|
|
|
//kdDebug() << "stream: " << d->device_->at() << endl;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::decodeAttachment()
|
|
|
|
{
|
|
|
|
Q_UINT32 i;
|
|
|
|
Q_UINT16 tag, type, u;
|
|
|
|
TQVariant value;
|
|
|
|
TQString str;
|
|
|
|
|
|
|
|
d->stream_ >> i; // i <- attribute type & name
|
|
|
|
tag = ( i & 0x0000FFFF );
|
|
|
|
type = ( ( i & 0xFFFF0000 ) >> 16 );
|
|
|
|
d->stream_ >> i; // i <- data length
|
|
|
|
checkCurrent( tag );
|
|
|
|
switch (tag)
|
|
|
|
{
|
|
|
|
case attATTACHTITLE:
|
|
|
|
value = readMAPIString( d->stream_, false, false, i );
|
|
|
|
d->current_->setName( value.toString() );
|
|
|
|
kdDebug() << "Attachment Title: " << d->current_->name() << endl;
|
|
|
|
break;
|
|
|
|
case attATTACHDATA:
|
|
|
|
d->current_->setSize( i );
|
|
|
|
d->current_->setOffset( d->device_->at() );
|
|
|
|
d->device_->at( d->device_->at() + i );
|
|
|
|
value = TQString( "< size=%1 >" ).arg( i );
|
|
|
|
kdDebug() << "Attachment Data: size=" << i << endl;
|
|
|
|
break;
|
|
|
|
case attATTACHMENT: // try to get attachment info
|
|
|
|
i += d->device_->at();
|
|
|
|
readMAPIProperties( d->current_->properties(), d->current_ );
|
|
|
|
d->device_->at( i );
|
|
|
|
d->current_->setIndex( d->current_->property( MAPI_TAG_INDEX ).toUInt() );
|
|
|
|
d->current_->setDisplaySize( d->current_->property( MAPI_TAG_SIZE ).toUInt() );
|
|
|
|
str = d->current_->property( MAPI_TAG_DISPLAYNAME ).toString();
|
|
|
|
if ( !str.isEmpty() )
|
|
|
|
d->current_->setDisplayName( str );
|
|
|
|
d->current_->setFileName( d->current_->property( MAPI_TAG_FILENAME ).toString() );
|
|
|
|
str = d->current_->property( MAPI_TAG_MIMETAG ).toString();
|
|
|
|
if ( !str.isEmpty() )
|
|
|
|
d->current_->setMimeTag( str );
|
|
|
|
d->current_->setExtension( d->current_->property( MAPI_TAG_EXTENSION ).toString() );
|
|
|
|
value = TQString( "< %1 properties >" ).arg( d->current_->properties().count() );
|
|
|
|
break;
|
|
|
|
case attATTACHMODDATE:
|
|
|
|
value = readTNEFDate( d->stream_ );
|
|
|
|
kdDebug() << "Attachment Modification Date: " << value.toString() << endl;
|
|
|
|
break;
|
|
|
|
case attATTACHCREATEDATE:
|
|
|
|
value = readTNEFDate( d->stream_ );
|
|
|
|
kdDebug() << "Attachment Creation Date: " << value.toString() << endl;
|
|
|
|
break;
|
|
|
|
case attATTACHMETAFILE:
|
|
|
|
kdDebug() << "Attachment Metafile: size=" << i << endl;
|
|
|
|
//value = TQString( "< size=%1 >" ).arg( i );
|
|
|
|
//d->device_->at( d->device_->at()+i );
|
|
|
|
value = readTNEFData( d->stream_, i );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
value = readTNEFAttribute( d->stream_, type, i );
|
|
|
|
kdDebug().form( "Attachment unknown field: tag=%x, length=%d\n", tag, i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
d->stream_ >> u; // u <- checksum
|
|
|
|
// add TNEF attribute
|
|
|
|
d->current_->addAttribute( tag, type, value, true );
|
|
|
|
//kdDebug() << "stream: " << d->device_->at() << endl;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTNEFParser::setDefaultExtractDir(const TQString& dirname)
|
|
|
|
{
|
|
|
|
d->defaultdir_ = dirname;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::parseDevice()
|
|
|
|
{
|
|
|
|
Q_UINT16 u;
|
|
|
|
Q_UINT32 i;
|
|
|
|
Q_UINT8 c;
|
|
|
|
|
|
|
|
d->message_->clearAttachments();
|
|
|
|
if (d->current_)
|
|
|
|
{
|
|
|
|
delete d->current_;
|
|
|
|
d->current_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !d->device_->open( IO_ReadOnly ) ) {
|
|
|
|
kdDebug() << "Couldn't open device" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->stream_.setDevice( d->device_ );
|
|
|
|
d->stream_.setByteOrder( TQDataStream::LittleEndian );
|
|
|
|
d->stream_ >> i;
|
|
|
|
if (i == TNEF_SIGNATURE)
|
|
|
|
{
|
|
|
|
d->stream_ >> u;
|
|
|
|
kdDebug().form( "Attachment cross reference key: 0x%04x\n",u );
|
|
|
|
//kdDebug() << "stream: " << d->device_->at() << endl;
|
|
|
|
while (!d->stream_.eof())
|
|
|
|
{
|
|
|
|
d->stream_ >> c;
|
|
|
|
switch (c)
|
|
|
|
{
|
|
|
|
case LVL_MESSAGE:
|
|
|
|
if (!decodeMessage()) goto end;
|
|
|
|
break;
|
|
|
|
case LVL_ATTACHMENT:
|
|
|
|
if (!decodeAttachment()) goto end;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
kdDebug() << "Unknown Level: " << c << ", at offset " << d->device_->at() << endl;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (d->current_)
|
|
|
|
{
|
|
|
|
checkCurrent(attATTACHDATA); // this line has the effect to append the
|
|
|
|
// attachment, if it has data. If not it does
|
|
|
|
// nothing, and the attachment will be discarded
|
|
|
|
delete d->current_;
|
|
|
|
d->current_ = 0;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdDebug() << "This is not a TNEF file" << endl;
|
|
|
|
end: d->device_->close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::extractFile(const TQString& filename)
|
|
|
|
{
|
|
|
|
KTNEFAttach *att = d->message_->attachment(filename);
|
|
|
|
if (!att) return false;
|
|
|
|
return extractAttachmentTo(att, d->defaultdir_);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::extractAttachmentTo(KTNEFAttach *att, const TQString& dirname)
|
|
|
|
{
|
|
|
|
QString filename = dirname + "/" + att->name();
|
|
|
|
if (!d->device_->isOpen())
|
|
|
|
return false;
|
|
|
|
if (!d->device_->at(att->offset()))
|
|
|
|
return false;
|
|
|
|
KSaveFile saveFile( filename );
|
|
|
|
TQFile *outfile = saveFile.file();
|
|
|
|
if ( !outfile )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
Q_UINT32 len = att->size(), sz(16384);
|
|
|
|
int n(0);
|
|
|
|
char *buf = new char[sz];
|
|
|
|
bool ok(true);
|
|
|
|
while (ok && len > 0)
|
|
|
|
{
|
|
|
|
n = d->device_->readBlock(buf,QMIN(sz,len));
|
|
|
|
if (n < 0)
|
|
|
|
ok = false;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
len -= n;
|
|
|
|
if (outfile->writeBlock(buf,n) != n)
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::extractAll()
|
|
|
|
{
|
|
|
|
TQPtrListIterator<KTNEFAttach> it(d->message_->attachmentList());
|
|
|
|
for (;it.current();++it)
|
|
|
|
if (!extractAttachmentTo(it.current(),d->defaultdir_)) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::extractFileTo(const TQString& filename, const TQString& dirname)
|
|
|
|
{
|
|
|
|
kdDebug() << "Extracting attachment: filename=" << filename << ", dir=" << dirname << endl;
|
|
|
|
KTNEFAttach *att = d->message_->attachment(filename);
|
|
|
|
if (!att) return false;
|
|
|
|
return extractAttachmentTo(att, dirname);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::openFile(const TQString& filename)
|
|
|
|
{
|
|
|
|
deleteDevice();
|
|
|
|
delete d->message_;
|
|
|
|
d->message_ = new KTNEFMessage();
|
|
|
|
d->device_ = new TQFile( filename );
|
|
|
|
d->deleteDevice_ = true;
|
|
|
|
return parseDevice();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::openDevice( TQIODevice *device )
|
|
|
|
{
|
|
|
|
deleteDevice();
|
|
|
|
d->device_ = device;
|
|
|
|
return parseDevice();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTNEFParser::checkCurrent( int key )
|
|
|
|
{
|
|
|
|
if ( !d->current_ )
|
|
|
|
d->current_ = new KTNEFAttach();
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( d->current_->attributes().contains( key ) )
|
|
|
|
{
|
|
|
|
if (d->current_->offset() >= 0 )
|
|
|
|
{
|
|
|
|
if (d->current_->name().isEmpty())
|
|
|
|
d->current_->setName("Unnamed");
|
|
|
|
if ( d->current_->mimeTag().isEmpty() )
|
|
|
|
{
|
|
|
|
// No mime type defined in the TNEF structure,
|
|
|
|
// try to find it from the attachment filename
|
|
|
|
// and/or content (using at most 32 bytes)
|
|
|
|
KMimeType::Ptr mimetype;
|
|
|
|
if ( !d->current_->fileName().isEmpty() )
|
|
|
|
mimetype = KMimeType::findByPath( d->current_->fileName(), 0, true );
|
|
|
|
if (!mimetype) return; // FIXME
|
|
|
|
if ( mimetype->name() == "application/octet-stream" && d->current_->size() > 0 )
|
|
|
|
{
|
|
|
|
int oldOffset = d->device_->at();
|
|
|
|
TQByteArray buffer( QMIN( 32, d->current_->size() ) );
|
|
|
|
d->device_->at( d->current_->offset() );
|
|
|
|
d->device_->readBlock( buffer.data(), buffer.size() );
|
|
|
|
mimetype = KMimeType::findByContent( buffer );
|
|
|
|
d->device_->at( oldOffset );
|
|
|
|
}
|
|
|
|
d->current_->setMimeTag( mimetype->name() );
|
|
|
|
}
|
|
|
|
d->message_->addAttachment( d->current_ );
|
|
|
|
d->current_ = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // invalid attachment, skip it
|
|
|
|
delete d->current_;
|
|
|
|
d->current_ = 0;
|
|
|
|
}
|
|
|
|
d->current_ = new KTNEFAttach();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); }
|
|
|
|
#define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR )
|
|
|
|
|
|
|
|
void clearMAPIName( MAPI_value& mapi )
|
|
|
|
{
|
|
|
|
mapi.name.value.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearMAPIValue(MAPI_value& mapi, bool clearName)
|
|
|
|
{
|
|
|
|
mapi.value.clear();
|
|
|
|
if ( clearName )
|
|
|
|
clearMAPIName( mapi );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDateTime formatTime( Q_UINT32 lowB, Q_UINT32 highB )
|
|
|
|
{
|
|
|
|
TQDateTime dt;
|
|
|
|
#if ( SIZEOF_UINT64_T == 8 )
|
|
|
|
uint64_t u64;
|
|
|
|
#elif ( SIZEOF_UNSIGNED_LONG_LONG == 8 )
|
|
|
|
unsigned long long u64;
|
|
|
|
#elif ( SIZEOF_UNSIGNED_LONG == 8 )
|
|
|
|
unsigned long u64;
|
|
|
|
#else
|
|
|
|
kdWarning() << "Unable to perform date conversion on this system, no 64-bits integer found" << endl;
|
|
|
|
dt.setTime_t( 0xffffffffU );
|
|
|
|
return dt;
|
|
|
|
#endif
|
|
|
|
u64 = highB;
|
|
|
|
u64 <<= 32;
|
|
|
|
u64 |= lowB;
|
|
|
|
u64 -= 116444736000000000LL;
|
|
|
|
u64 /= 10000000;
|
|
|
|
if ( u64 <= 0xffffffffU )
|
|
|
|
dt.setTime_t( ( unsigned int )u64 );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdWarning().form( "Invalid date: low byte=0x%08X, high byte=0x%08X\n", lowB, highB );
|
|
|
|
dt.setTime_t( 0xffffffffU );
|
|
|
|
}
|
|
|
|
return dt;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString formatRecipient( const TQMap<int,KTNEFProperty*>& props )
|
|
|
|
{
|
|
|
|
TQString s, dn, addr, t;
|
|
|
|
TQMap<int,KTNEFProperty*>::ConstIterator it;
|
|
|
|
if ( ( it = props.find( 0x3001 ) ) != props.end() )
|
|
|
|
dn = ( *it )->valueString();
|
|
|
|
if ( ( it = props.find( 0x3003 ) ) != props.end() )
|
|
|
|
addr = ( *it )->valueString();
|
|
|
|
if ( ( it = props.find( 0x0C15 ) ) != props.end() )
|
|
|
|
switch ( ( *it )->value().toInt() )
|
|
|
|
{
|
|
|
|
case 0: t = "From:"; break;
|
|
|
|
case 1: t = "To:"; break;
|
|
|
|
case 2: t = "Cc:"; break;
|
|
|
|
case 3: t = "Bcc:"; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !t.isEmpty() )
|
|
|
|
s.append( t );
|
|
|
|
if ( !dn.isEmpty() )
|
|
|
|
s.append( " " + dn );
|
|
|
|
if ( !addr.isEmpty() && addr != dn )
|
|
|
|
s.append( " <" + addr + ">" );
|
|
|
|
|
|
|
|
return s.stripWhiteSpace();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDateTime readTNEFDate( TQDataStream& stream )
|
|
|
|
{
|
|
|
|
// 14-bytes long
|
|
|
|
Q_UINT16 y, m, d, hh, mm, ss, dm;
|
|
|
|
stream >> y >> m >> d >> hh >> mm >> ss >> dm;
|
|
|
|
return TQDateTime( TQDate( y, m, d ), TQTime( hh, mm, ss ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString readTNEFAddress( TQDataStream& stream )
|
|
|
|
{
|
|
|
|
Q_UINT16 totalLen, strLen, addrLen;
|
|
|
|
TQString s;
|
|
|
|
stream >> totalLen >> totalLen >> strLen >> addrLen;
|
|
|
|
s.append( readMAPIString( stream, false, false, strLen ) );
|
|
|
|
s.append( " <" );
|
|
|
|
s.append( readMAPIString( stream, false, false, addrLen ) );
|
|
|
|
s.append( ">" );
|
|
|
|
Q_UINT8 c;
|
|
|
|
for ( int i=8+strLen+addrLen; i<totalLen; i++ )
|
|
|
|
stream >> c;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQByteArray readTNEFData( TQDataStream& stream, Q_UINT32 len )
|
|
|
|
{
|
|
|
|
TQByteArray array( len );
|
|
|
|
if ( len > 0 )
|
|
|
|
stream.readRawBytes( array.data(), len );
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQVariant readTNEFAttribute( TQDataStream& stream, Q_UINT16 type, Q_UINT32 len )
|
|
|
|
{
|
|
|
|
switch ( type )
|
|
|
|
{
|
|
|
|
case atpTEXT:
|
|
|
|
case atpSTRING:
|
|
|
|
return readMAPIString( stream, false, false, len );
|
|
|
|
case atpDATE:
|
|
|
|
return readTNEFDate( stream );
|
|
|
|
default:
|
|
|
|
return readTNEFData( stream, len );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString readMAPIString( TQDataStream& stream, bool isUnicode, bool align, int len_ )
|
|
|
|
{
|
|
|
|
Q_UINT32 len;
|
|
|
|
char *buf = 0;
|
|
|
|
if ( len_ == -1 )
|
|
|
|
stream >> len;
|
|
|
|
else
|
|
|
|
len = len_;
|
|
|
|
Q_UINT32 fullLen = len;
|
|
|
|
if ( align )
|
|
|
|
ALIGN( fullLen, 4 );
|
|
|
|
buf = new char[ len ];
|
|
|
|
stream.readRawBytes( buf, len );
|
|
|
|
Q_UINT8 c;
|
|
|
|
for ( uint i=len; i<fullLen; i++ )
|
|
|
|
stream >> c;
|
|
|
|
TQString res;
|
|
|
|
if ( isUnicode )
|
|
|
|
res = TQString::fromUcs2( ( const unsigned short* )buf );
|
|
|
|
else
|
|
|
|
res = TQString::fromLocal8Bit( buf );
|
|
|
|
delete [] buf;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_UINT16 readMAPIValue(TQDataStream& stream, MAPI_value& mapi)
|
|
|
|
{
|
|
|
|
Q_UINT32 d;
|
|
|
|
|
|
|
|
clearMAPIValue(mapi);
|
|
|
|
stream >> d;
|
|
|
|
mapi.type = (d & 0x0000FFFF);
|
|
|
|
mapi.tag = ((d & 0xFFFF0000) >> 16);
|
|
|
|
if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE )
|
|
|
|
{
|
|
|
|
// skip GUID
|
|
|
|
stream >> d >> d >> d >> d;
|
|
|
|
// name type
|
|
|
|
stream >> mapi.name.type;
|
|
|
|
// name
|
|
|
|
if ( mapi.name.type == 0 )
|
|
|
|
stream >> mapi.name.value.asUInt();
|
|
|
|
else if ( mapi.name.type == 1 )
|
|
|
|
mapi.name.value.asString() = readMAPIString( stream, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
int n = 1;
|
|
|
|
TQVariant value;
|
|
|
|
if ( ISVECTOR( mapi ) )
|
|
|
|
{
|
|
|
|
stream >> n;
|
|
|
|
mapi.value = TQValueList<TQVariant>();
|
|
|
|
}
|
|
|
|
for ( int i=0; i<n; i++ )
|
|
|
|
{
|
|
|
|
value.clear();
|
|
|
|
switch(mapi.type & 0x0FFF)
|
|
|
|
{
|
|
|
|
case MAPI_TYPE_UINT16:
|
|
|
|
stream >> d;
|
|
|
|
value.asUInt() = ( d & 0x0000FFFF );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_BOOLEAN:
|
|
|
|
case MAPI_TYPE_ULONG:
|
|
|
|
stream >> value.asUInt();
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_FLOAT:
|
|
|
|
stream >> d;
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_DOUBLE:
|
|
|
|
stream >> value.asDouble();
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_TIME:
|
|
|
|
{
|
|
|
|
Q_UINT32 lowB, highB;
|
|
|
|
stream >> lowB >> highB;
|
|
|
|
value = formatTime( lowB, highB );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_STRING8:
|
|
|
|
// in case of a vector'ed value, the number of elements
|
|
|
|
// has already been read in the upper for-loop
|
|
|
|
if ( ISVECTOR( mapi ) )
|
|
|
|
d = 1;
|
|
|
|
else
|
|
|
|
stream >> d;
|
|
|
|
for (uint i=0;i<d;i++)
|
|
|
|
{
|
|
|
|
value.clear();
|
|
|
|
value.asString() = readMAPIString( stream );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_USTRING:
|
|
|
|
mapi.type = MAPI_TYPE_NONE;
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_OBJECT:
|
|
|
|
case MAPI_TYPE_BINARY:
|
|
|
|
if ( ISVECTOR( mapi ) )
|
|
|
|
d = 1;
|
|
|
|
else
|
|
|
|
stream >> d;
|
|
|
|
for (uint i=0;i<d;i++)
|
|
|
|
{
|
|
|
|
value.clear();
|
|
|
|
Q_UINT32 len;
|
|
|
|
stream >> len;
|
|
|
|
value = TQByteArray( len );
|
|
|
|
if (len > 0)
|
|
|
|
{
|
|
|
|
int fullLen = len;
|
|
|
|
ALIGN(fullLen, 4);
|
|
|
|
stream.readRawBytes(value.asByteArray().data(), len);
|
|
|
|
Q_UINT8 c;
|
|
|
|
for ( int i=len; i<fullLen; i++ )
|
|
|
|
stream >> c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
mapi.type = MAPI_TYPE_NONE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ( ISVECTOR( mapi ) )
|
|
|
|
mapi.value.asList().append( value );
|
|
|
|
else
|
|
|
|
mapi.value = value;
|
|
|
|
}
|
|
|
|
return mapi.tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTNEFParser::readMAPIProperties( TQMap<int,KTNEFProperty*>& props, KTNEFAttach *attach )
|
|
|
|
{
|
|
|
|
Q_UINT32 n;
|
|
|
|
MAPI_value mapi;
|
|
|
|
KTNEFProperty *p;
|
|
|
|
TQMap<int,KTNEFProperty*>::ConstIterator it;
|
|
|
|
bool foundAttachment = false;
|
|
|
|
|
|
|
|
// some initializations
|
|
|
|
mapi.type = MAPI_TYPE_NONE;
|
|
|
|
mapi.value.clear();
|
|
|
|
|
|
|
|
// get number of properties
|
|
|
|
d->stream_ >> n;
|
|
|
|
kdDebug() << "MAPI Properties: " << n << endl;
|
|
|
|
for (uint i=0;i<n;i++)
|
|
|
|
{
|
|
|
|
if (d->stream_.eof())
|
|
|
|
{
|
|
|
|
clearMAPIValue(mapi);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
readMAPIValue(d->stream_, mapi);
|
|
|
|
if (mapi.type == MAPI_TYPE_NONE)
|
|
|
|
{
|
|
|
|
kdDebug().form( "MAPI unsupported: tag=%x, type=%x\n", mapi.tag, mapi.type );
|
|
|
|
clearMAPIValue(mapi);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
int key = mapi.tag;
|
|
|
|
switch (mapi.tag)
|
|
|
|
{
|
|
|
|
case MAPI_TAG_DATA:
|
|
|
|
{
|
|
|
|
if ( mapi.type == MAPI_TYPE_OBJECT && attach )
|
|
|
|
{
|
|
|
|
TQByteArray data = mapi.value.toByteArray();
|
|
|
|
int len = data.size();
|
|
|
|
ALIGN( len, 4 );
|
|
|
|
d->device_->at( d->device_->at()-len );
|
|
|
|
Q_UINT32 interface_ID;
|
|
|
|
d->stream_ >> interface_ID;
|
|
|
|
if ( interface_ID == MAPI_IID_IMessage )
|
|
|
|
{
|
|
|
|
// embedded TNEF file
|
|
|
|
attach->unsetDataParser();
|
|
|
|
attach->setOffset( d->device_->at()+12 );
|
|
|
|
attach->setSize( data.size()-16 );
|
|
|
|
attach->setMimeTag( "application/ms-tnef" );
|
|
|
|
attach->setDisplayName( "Embedded Message" );
|
|
|
|
kdDebug() << "MAPI Embedded Message: size=" << data.size() << endl;
|
|
|
|
}
|
|
|
|
d->device_->at( d->device_->at() + ( len-4 ) );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if ( mapi.type == MAPI_TYPE_BINARY && attach && attach->offset() < 0 )
|
|
|
|
{
|
|
|
|
foundAttachment = true;
|
|
|
|
int len = mapi.value.toByteArray().size();
|
|
|
|
ALIGN( len, 4 )
|
|
|
|
attach->setSize( len );
|
|
|
|
attach->setOffset( d->device_->at() - len );
|
|
|
|
attach->addAttribute( attATTACHDATA, atpBYTE, TQString( "< size=%1 >" ).arg( len ), false );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
kdDebug().form( "MAPI data: size=%d\n", mapi.value.toByteArray().size() );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
TQString mapiname = "";
|
|
|
|
if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE )
|
|
|
|
{
|
|
|
|
if ( mapi.name.type == 0 )
|
|
|
|
mapiname = TQString().sprintf( " [name = 0x%04x]", mapi.name.value.toUInt() );
|
|
|
|
else
|
|
|
|
mapiname = TQString( " [name = %1]" ).arg( mapi.name.value.toString() );
|
|
|
|
}
|
|
|
|
switch ( mapi.type & 0x0FFF )
|
|
|
|
{
|
|
|
|
case MAPI_TYPE_UINT16:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI short%s: 0x%x\n", mapi.tag, mapiname.ascii(), mapi.value.toUInt() );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_ULONG:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI long%s: 0x%x\n", mapi.tag, mapiname.ascii(), mapi.value.toUInt() );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_BOOLEAN:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI boolean%s: %s\n", mapi.tag, mapiname.ascii(), ( mapi.value.toBool() ? "true" : "false" ) );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_TIME:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI time%s: %s\n", mapi.tag, mapiname.ascii(), mapi.value.toString().ascii() );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_USTRING:
|
|
|
|
case MAPI_TYPE_STRING8:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI string%s: size=%d \"%s\"\n", mapi.tag, mapiname.ascii(), mapi.value.toByteArray().size(), mapi.value.toString().ascii() );
|
|
|
|
break;
|
|
|
|
case MAPI_TYPE_BINARY:
|
|
|
|
kdDebug().form( "(tag=%04x) MAPI binary%s: size=%d\n", mapi.tag, mapiname.ascii(), mapi.value.toByteArray().size() );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// do not remove potential existing similar entry
|
|
|
|
if ( ( it = props.find( key ) ) == props.end() )
|
|
|
|
{
|
|
|
|
p = new KTNEFProperty( key, ( mapi.type & 0x0FFF ), mapi.value, mapi.name.value );
|
|
|
|
props[ p->key() ] = p;
|
|
|
|
}
|
|
|
|
//kdDebug() << "stream: " << d->device_->at() << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( foundAttachment && attach )
|
|
|
|
{
|
|
|
|
attach->setIndex( attach->property( MAPI_TAG_INDEX ).toUInt() );
|
|
|
|
attach->setDisplaySize( attach->property( MAPI_TAG_SIZE ).toUInt() );
|
|
|
|
TQString str = attach->property( MAPI_TAG_DISPLAYNAME ).toString();
|
|
|
|
if ( !str.isEmpty() )
|
|
|
|
attach->setDisplayName( str );
|
|
|
|
attach->setFileName( attach->property( MAPI_TAG_FILENAME ).toString() );
|
|
|
|
str = attach->property( MAPI_TAG_MIMETAG ).toString();
|
|
|
|
if ( !str.isEmpty() )
|
|
|
|
attach->setMimeTag( str );
|
|
|
|
attach->setExtension( attach->property( MAPI_TAG_EXTENSION ).toString() );
|
|
|
|
if ( attach->name().isEmpty() )
|
|
|
|
attach->setName( attach->fileName() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|