mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
987 lines
27 KiB
C++
987 lines
27 KiB
C++
/*
|
|
ktnefparser.cpp
|
|
|
|
Copyright (C) 2002 Michael Goffioul <kdeprint@swing.be>
|
|
|
|
This file is part of KTNEF, the KDE TNEF support library/program.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
/**
|
|
* @file
|
|
* This file is part of the API for handling TNEF data and
|
|
* defines the KTNEFParser class.
|
|
*
|
|
* @author Michael Goffioul
|
|
*/
|
|
|
|
#include "ktnefparser.h"
|
|
#include "ktnefattach.h"
|
|
#include "ktnefproperty.h"
|
|
#include "ktnefmessage.h"
|
|
#include "ktnefdefs.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <kmimetype.h>
|
|
#include <ksavefile.h>
|
|
|
|
#include <QtCore/QDateTime>
|
|
#include <QtCore/QDataStream>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QVariant>
|
|
#include <QtCore/QList>
|
|
|
|
using namespace KTnef;
|
|
|
|
//@cond PRIVATE
|
|
typedef struct {
|
|
quint16 type;
|
|
quint16 tag;
|
|
QVariant value;
|
|
struct {
|
|
quint32 type;
|
|
QVariant value;
|
|
} name;
|
|
} MAPI_value;
|
|
//@endcond
|
|
|
|
//@cond IGNORE
|
|
void clearMAPIName( MAPI_value &mapi );
|
|
void clearMAPIValue( MAPI_value &mapi, bool clearName = true );
|
|
QString readMAPIString( QDataStream &stream, bool isUnicode = false,
|
|
bool align = true, int len = -1 );
|
|
quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi );
|
|
QDateTime readTNEFDate( QDataStream &stream );
|
|
QString readTNEFAddress( QDataStream &stream );
|
|
QByteArray readTNEFData( QDataStream &stream, quint32 len );
|
|
QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len );
|
|
QDateTime formatTime( quint32 lowB, quint32 highB );
|
|
QString formatRecipient( const QMap<int,KTnef::KTNEFProperty*> &props );
|
|
//@endcond
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Private class that helps to provide binary compatibility between releases.
|
|
* @internal
|
|
*/
|
|
//@cond PRIVATE
|
|
class KTnef::KTNEFParser::ParserPrivate
|
|
{
|
|
public:
|
|
ParserPrivate()
|
|
{
|
|
defaultdir_ = "/tmp/";
|
|
current_ = 0;
|
|
deleteDevice_ = false;
|
|
device_ = 0;
|
|
message_ = new KTNEFMessage;
|
|
}
|
|
~ParserPrivate()
|
|
{
|
|
delete message_;
|
|
}
|
|
|
|
bool decodeAttachment();
|
|
bool decodeMessage();
|
|
bool extractAttachmentTo( KTNEFAttach *att, const QString &dirname );
|
|
void checkCurrent( int key );
|
|
bool readMAPIProperties( QMap<int,KTNEFProperty*>& props,
|
|
KTNEFAttach *attach = 0 );
|
|
bool parseDevice();
|
|
void deleteDevice();
|
|
|
|
QDataStream stream_;
|
|
QIODevice *device_;
|
|
bool deleteDevice_;
|
|
QString defaultdir_;
|
|
KTNEFAttach *current_;
|
|
KTNEFMessage *message_;
|
|
};
|
|
//@endcond
|
|
|
|
KTNEFParser::KTNEFParser()
|
|
: d( new ParserPrivate )
|
|
{
|
|
}
|
|
|
|
KTNEFParser::~KTNEFParser()
|
|
{
|
|
d->deleteDevice();
|
|
delete d;
|
|
}
|
|
|
|
KTNEFMessage *KTNEFParser::message() const
|
|
{
|
|
return d->message_;
|
|
}
|
|
|
|
void KTNEFParser::ParserPrivate::deleteDevice()
|
|
{
|
|
if ( deleteDevice_ ) {
|
|
delete device_;
|
|
}
|
|
device_ = 0;
|
|
deleteDevice_ = false;
|
|
}
|
|
|
|
bool KTNEFParser::ParserPrivate::decodeMessage()
|
|
{
|
|
quint32 i1, i2, off;
|
|
quint16 u, tag, type;
|
|
QVariant value;
|
|
|
|
// read (type+name)
|
|
stream_ >> i1;
|
|
u = 0;
|
|
tag = ( i1 & 0x0000FFFF );
|
|
type = ( ( i1 & 0xFFFF0000 ) >> 16 );
|
|
// read data length
|
|
stream_ >> i2;
|
|
// offset after reading the value
|
|
off = device_->pos() + i2;
|
|
switch ( tag ) {
|
|
case attAIDOWNER:
|
|
{
|
|
uint tmp;
|
|
stream_ >> tmp;
|
|
value.setValue( tmp );
|
|
message_->addProperty( 0x0062, MAPI_TYPE_ULONG, value );
|
|
kDebug() << "Message Owner Appointment ID" << "(length=" << i2 << ")";
|
|
break;
|
|
}
|
|
case attREQUESTRES:
|
|
stream_ >> u;
|
|
message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u );
|
|
value = ( bool )u;
|
|
kDebug() << "Message Request Response" << "(length=" << i2 << ")";
|
|
break;
|
|
case attDATERECD:
|
|
value = readTNEFDate( stream_ );
|
|
message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value );
|
|
kDebug() << "Message Receive Date" << "(length=" << i2 << ")";
|
|
break;
|
|
case attMSGCLASS:
|
|
value = readMAPIString( stream_, false, false, i2 );
|
|
message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value );
|
|
kDebug() << "Message Class" << "(length=" << i2 << ")";
|
|
break;
|
|
case attMSGPRIORITY:
|
|
stream_ >> u;
|
|
message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u );
|
|
value = u;
|
|
kDebug() << "Message Priority" << "(length=" << i2 << ")";
|
|
break;
|
|
case attMAPIPROPS:
|
|
kDebug() << "Message MAPI Properties" << "(length=" << i2 << ")";
|
|
{
|
|
int nProps = message_->properties().count();
|
|
i2 += device_->pos();
|
|
readMAPIProperties( message_->properties(), 0 );
|
|
device_->seek( i2 );
|
|
kDebug() << "Properties:" << message_->properties().count();
|
|
value = QString( "< %1 properties >" ).
|
|
arg( message_->properties().count() - nProps );
|
|
}
|
|
break;
|
|
case attTNEFVERSION:
|
|
{
|
|
uint tmp;
|
|
stream_ >> tmp;
|
|
value.setValue( tmp );
|
|
kDebug() << "Message TNEF Version" << "(length=" << i2 << ")";
|
|
}
|
|
break;
|
|
case attFROM:
|
|
message_->addProperty( 0x0024, MAPI_TYPE_STRING8, readTNEFAddress( stream_ ) );
|
|
device_->seek( device_->pos() - i2 );
|
|
value = readTNEFData( stream_, i2 );
|
|
kDebug() << "Message From" << "(length=" << i2 << ")";
|
|
break;
|
|
case attSUBJECT:
|
|
value = readMAPIString( stream_, false, false, i2 );
|
|
message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value );
|
|
kDebug() << "Message Subject" << "(length=" << i2 << ")";
|
|
break;
|
|
case attDATESENT:
|
|
value = readTNEFDate( stream_ );
|
|
message_->addProperty( 0x0039, MAPI_TYPE_TIME, value );
|
|
kDebug() << "Message Date Sent" << "(length=" << i2 << ")";
|
|
break;
|
|
case attMSGSTATUS:
|
|
{
|
|
quint8 c;
|
|
quint32 flag = 0;
|
|
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;
|
|
}
|
|
message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
|
|
value = c;
|
|
}
|
|
kDebug() << "Message Status" << "(length=" << i2 << ")";
|
|
break;
|
|
case attRECIPTABLE:
|
|
{
|
|
quint32 rows;
|
|
QList<QVariant> recipTable;
|
|
stream_ >> rows;
|
|
for ( uint i=0; i<rows; i++ ) {
|
|
QMap<int,KTNEFProperty*> props;
|
|
readMAPIProperties( props, 0 );
|
|
recipTable << formatRecipient( props );
|
|
}
|
|
message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable );
|
|
device_->seek( device_->pos() - i2 );
|
|
value = readTNEFData( stream_, i2 );
|
|
}
|
|
kDebug() << "Message Recipient Table" << "(length=" << i2 << ")";
|
|
break;
|
|
case attBODY:
|
|
value = readMAPIString( stream_, false, false, i2 );
|
|
message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value );
|
|
kDebug() << "Message Body" << "(length=" << i2 << ")";
|
|
break;
|
|
case attDATEMODIFIED:
|
|
value = readTNEFDate( stream_ );
|
|
message_->addProperty( 0x3008, MAPI_TYPE_TIME, value );
|
|
kDebug() << "Message Date Modified" << "(length=" << i2 << ")";
|
|
break;
|
|
case attMSGID:
|
|
value = readMAPIString( stream_, false, false, i2 );
|
|
message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value );
|
|
kDebug() << "Message ID" << "(length=" << i2 << ")";
|
|
break;
|
|
case attOEMCODEPAGE:
|
|
value = readTNEFData( stream_, i2 );
|
|
kDebug() << "Message OEM Code Page" << "(length=" << i2 << ")";
|
|
break;
|
|
default:
|
|
value = readTNEFAttribute( stream_, type, i2 );
|
|
//kDebug().form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u );
|
|
break;
|
|
}
|
|
// skip data
|
|
if ( device_->pos() != off && !device_->seek( off ) ) {
|
|
return false;
|
|
}
|
|
// get checksum
|
|
stream_ >> u;
|
|
// add TNEF attribute
|
|
message_->addAttribute( tag, type, value, true );
|
|
//kDebug() << "stream:" << device_->pos();
|
|
return true;
|
|
}
|
|
|
|
bool KTNEFParser::ParserPrivate::decodeAttachment()
|
|
{
|
|
quint32 i;
|
|
quint16 tag, type, u;
|
|
QVariant value;
|
|
QString str;
|
|
|
|
stream_ >> i; // i <- attribute type & name
|
|
tag = ( i & 0x0000FFFF );
|
|
type = ( ( i & 0xFFFF0000 ) >> 16 );
|
|
stream_ >> i; // i <- data length
|
|
checkCurrent( tag );
|
|
switch ( tag ) {
|
|
case attATTACHTITLE:
|
|
value = readMAPIString( stream_, false, false, i );
|
|
current_->setName( value.toString() );
|
|
kDebug() << "Attachment Title:" << current_->name();
|
|
break;
|
|
case attATTACHDATA:
|
|
current_->setSize( i );
|
|
current_->setOffset( device_->pos() );
|
|
device_->seek( device_->pos() + i );
|
|
value = QString( "< size=%1 >" ).arg( i );
|
|
kDebug() << "Attachment Data: size=" << i;
|
|
break;
|
|
case attATTACHMENT: // try to get attachment info
|
|
i += device_->pos();
|
|
readMAPIProperties( current_->properties(), current_ );
|
|
device_->seek( i );
|
|
current_->setIndex( current_->property( MAPI_TAG_INDEX ).toUInt() );
|
|
current_->setDisplaySize( current_->property( MAPI_TAG_SIZE ).toUInt() );
|
|
str = current_->property( MAPI_TAG_DISPLAYNAME ).toString();
|
|
if ( !str.isEmpty() ) {
|
|
current_->setDisplayName( str );
|
|
}
|
|
current_->setFileName( current_->property( MAPI_TAG_FILENAME ).
|
|
toString() );
|
|
str = current_->property( MAPI_TAG_MIMETAG ).toString();
|
|
if ( !str.isEmpty() ) {
|
|
current_->setMimeTag( str );
|
|
}
|
|
current_->setExtension( current_->property( MAPI_TAG_EXTENSION ).
|
|
toString() );
|
|
value = QString( "< %1 properties >" ).
|
|
arg( current_->properties().count() );
|
|
break;
|
|
case attATTACHMODDATE:
|
|
value = readTNEFDate( stream_ );
|
|
kDebug() << "Attachment Modification Date:" << value.toString();
|
|
break;
|
|
case attATTACHCREATEDATE:
|
|
value = readTNEFDate( stream_ );
|
|
kDebug() << "Attachment Creation Date:" << value.toString();
|
|
break;
|
|
case attATTACHMETAFILE:
|
|
kDebug() << "Attachment Metafile: size=" << i;
|
|
//value = QString( "< size=%1 >" ).arg( i );
|
|
//device_->seek( device_->pos()+i );
|
|
value = readTNEFData( stream_, i );
|
|
break;
|
|
default:
|
|
value = readTNEFAttribute( stream_, type, i );
|
|
kDebug() << "Attachment unknown field: tag="
|
|
<< hex << tag << ", length=" << dec << i;
|
|
break;
|
|
}
|
|
stream_ >> u; // u <- checksum
|
|
// add TNEF attribute
|
|
current_->addAttribute( tag, type, value, true );
|
|
//kDebug() << "stream:" << device_->pos();
|
|
|
|
return true;
|
|
}
|
|
|
|
void KTNEFParser::setDefaultExtractDir( const QString &dirname )
|
|
{
|
|
d->defaultdir_ = dirname;
|
|
}
|
|
|
|
bool KTNEFParser::ParserPrivate::parseDevice()
|
|
{
|
|
quint16 u;
|
|
quint32 i;
|
|
quint8 c;
|
|
|
|
message_->clearAttachments();
|
|
delete current_;
|
|
current_ = 0;
|
|
|
|
if ( !device_->open( QIODevice::ReadOnly ) ) {
|
|
kDebug() << "Couldn't open device";
|
|
return false;
|
|
}
|
|
|
|
stream_.setDevice( device_ );
|
|
stream_.setByteOrder( QDataStream::LittleEndian );
|
|
stream_ >> i;
|
|
if ( i == TNEF_SIGNATURE ) {
|
|
stream_ >> u;
|
|
kDebug().nospace() << "Attachment cross reference key: 0x"
|
|
<< hex << qSetFieldWidth( 4 ) << qSetPadChar( '0' ) << u;
|
|
//kDebug() << "stream:" << device_->pos();
|
|
while ( !stream_.atEnd() ) {
|
|
stream_ >> c;
|
|
switch( c ) {
|
|
case LVL_MESSAGE:
|
|
if ( !decodeMessage() ) {
|
|
goto end;
|
|
}
|
|
break;
|
|
case LVL_ATTACHMENT:
|
|
if ( !decodeAttachment() ) {
|
|
goto end;
|
|
}
|
|
break;
|
|
default:
|
|
kDebug() << "Unknown Level:" << c << ", at offset" << device_->pos();
|
|
goto end;
|
|
}
|
|
}
|
|
if ( 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 current_;
|
|
current_ = 0;
|
|
}
|
|
return true;
|
|
} else {
|
|
kDebug() << "This is not a TNEF file";
|
|
end:
|
|
device_->close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool KTNEFParser::extractFile( const QString &filename ) const
|
|
{
|
|
KTNEFAttach *att = d->message_->attachment( filename );
|
|
if ( !att ) {
|
|
return false;
|
|
}
|
|
return d->extractAttachmentTo( att, d->defaultdir_ );
|
|
}
|
|
|
|
bool KTNEFParser::ParserPrivate::extractAttachmentTo( KTNEFAttach *att,
|
|
const QString &dirname )
|
|
{
|
|
QString filename = dirname + '/';
|
|
if ( !att->fileName().isEmpty()) {
|
|
filename += att->fileName();
|
|
} else {
|
|
filename += att->name();
|
|
}
|
|
if ( filename.endsWith( '/') ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !device_->isOpen() ) {
|
|
return false;
|
|
}
|
|
if ( !device_->seek( att->offset() ) ) {
|
|
return false;
|
|
}
|
|
KSaveFile outfile( filename );
|
|
if ( !outfile.open() ) {
|
|
return false;
|
|
}
|
|
|
|
quint32 len = att->size(), sz( 16384 );
|
|
int n( 0 );
|
|
char *buf = new char[sz];
|
|
bool ok( true );
|
|
while ( ok && len > 0 ) {
|
|
n = device_->read( buf, qMin( sz, len ) );
|
|
if ( n < 0 ) {
|
|
ok = false;
|
|
} else {
|
|
len -= n;
|
|
if ( outfile.write( buf, n ) != n ) {
|
|
ok = false;
|
|
}
|
|
}
|
|
}
|
|
delete [] buf;
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool KTNEFParser::extractAll()
|
|
{
|
|
QList<KTNEFAttach*> l = d->message_->attachmentList();
|
|
QList<KTNEFAttach*>::const_iterator it = l.constBegin();
|
|
for ( ; it != l.constEnd(); ++it ) {
|
|
if ( !d->extractAttachmentTo( *it, d->defaultdir_ ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KTNEFParser::extractFileTo( const QString &filename,
|
|
const QString &dirname ) const
|
|
{
|
|
kDebug() << "Extracting attachment: filename="
|
|
<< filename << ", dir=" << dirname;
|
|
KTNEFAttach *att = d->message_->attachment( filename );
|
|
if ( !att ) {
|
|
return false;
|
|
}
|
|
return d->extractAttachmentTo( att, dirname );
|
|
}
|
|
|
|
bool KTNEFParser::openFile( const QString &filename ) const
|
|
{
|
|
d->deleteDevice();
|
|
delete d->message_;
|
|
d->message_ = new KTNEFMessage();
|
|
d->device_ = new QFile( filename );
|
|
d->deleteDevice_ = true;
|
|
return d->parseDevice();
|
|
}
|
|
|
|
bool KTNEFParser::openDevice( QIODevice *device )
|
|
{
|
|
d->deleteDevice();
|
|
d->device_ = device;
|
|
return d->parseDevice();
|
|
}
|
|
|
|
void KTNEFParser::ParserPrivate::checkCurrent( int key )
|
|
{
|
|
if ( !current_ ) {
|
|
current_ = new KTNEFAttach();
|
|
} else {
|
|
if ( current_->attributes().contains( key ) ) {
|
|
if ( current_->offset() >= 0 ) {
|
|
if ( current_->name().isEmpty() ) {
|
|
current_->setName( "Unnamed" );
|
|
}
|
|
if ( 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 ( !current_->fileName().isEmpty() ) {
|
|
mimetype = KMimeType::findByPath( current_->fileName(), 0, true );
|
|
}
|
|
if ( !mimetype ) {
|
|
return; // FIXME
|
|
}
|
|
if ( mimetype->name() == "application/octet-stream" &&
|
|
current_->size() > 0 ) {
|
|
int oldOffset = device_->pos();
|
|
QByteArray buffer( qMin( 32, current_->size() ), '\0' );
|
|
device_->seek( current_->offset() );
|
|
device_->read( buffer.data(), buffer.size() );
|
|
mimetype = KMimeType::findByContent( buffer );
|
|
device_->seek( oldOffset );
|
|
}
|
|
current_->setMimeTag( mimetype->name() );
|
|
}
|
|
message_->addAttachment( current_ );
|
|
current_ = 0;
|
|
} else {
|
|
// invalid attachment, skip it
|
|
delete current_;
|
|
current_ = 0;
|
|
}
|
|
current_ = new KTNEFAttach();
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//@cond IGNORE
|
|
#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 );
|
|
}
|
|
}
|
|
|
|
QDateTime formatTime( quint32 lowB, quint32 highB )
|
|
{
|
|
QDateTime dt;
|
|
quint64 u64;
|
|
u64 = highB;
|
|
u64 <<= 32;
|
|
u64 |= lowB;
|
|
u64 -= 116444736000000000LL;
|
|
u64 /= 10000000;
|
|
if ( u64 <= 0xffffffffU ) {
|
|
dt.setTime_t( ( unsigned int )u64 );
|
|
} else {
|
|
kWarning().nospace() << "Invalid date: low byte="
|
|
<< showbase << qSetFieldWidth( 8 ) << qSetPadChar( '0' )
|
|
<< lowB << ", high byte=" << highB;
|
|
dt.setTime_t( 0xffffffffU );
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
QString formatRecipient( const QMap<int,KTnef::KTNEFProperty*> &props )
|
|
{
|
|
QString s, dn, addr, t;
|
|
QMap<int,KTnef::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.trimmed();
|
|
}
|
|
|
|
QDateTime readTNEFDate( QDataStream &stream )
|
|
{
|
|
// 14-bytes long
|
|
quint16 y, m, d, hh, mm, ss, dm;
|
|
stream >> y >> m >> d >> hh >> mm >> ss >> dm;
|
|
return QDateTime( QDate( y, m, d ), QTime( hh, mm, ss ) );
|
|
}
|
|
|
|
QString readTNEFAddress( QDataStream &stream )
|
|
{
|
|
quint16 totalLen, strLen, addrLen;
|
|
QString s;
|
|
stream >> totalLen >> totalLen >> strLen >> addrLen;
|
|
s.append( readMAPIString( stream, false, false, strLen ) );
|
|
s.append( " <" );
|
|
s.append( readMAPIString( stream, false, false, addrLen ) );
|
|
s.append( ">" );
|
|
quint8 c;
|
|
for ( int i=8+strLen+addrLen; i<totalLen; i++ ) {
|
|
stream >> c;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
QByteArray readTNEFData( QDataStream &stream, quint32 len )
|
|
{
|
|
QByteArray array( len, '\0' );
|
|
if ( len > 0 ) {
|
|
stream.readRawData( array.data(), len );
|
|
}
|
|
return array;
|
|
}
|
|
|
|
QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len )
|
|
{
|
|
switch ( type ) {
|
|
case atpTEXT:
|
|
case atpSTRING:
|
|
return readMAPIString( stream, false, false, len );
|
|
case atpDATE:
|
|
return readTNEFDate( stream );
|
|
default:
|
|
return readTNEFData( stream, len );
|
|
}
|
|
}
|
|
|
|
QString readMAPIString( QDataStream &stream, bool isUnicode, bool align,
|
|
int len_ )
|
|
{
|
|
quint32 len;
|
|
char *buf = 0;
|
|
if ( len_ == -1 ) {
|
|
stream >> len;
|
|
} else {
|
|
len = len_;
|
|
}
|
|
quint32 fullLen = len;
|
|
if ( align ) {
|
|
ALIGN( fullLen, 4 );
|
|
}
|
|
buf = new char[ len ];
|
|
stream.readRawData( buf, len );
|
|
quint8 c;
|
|
for ( uint i=len; i<fullLen; i++ ) {
|
|
stream >> c;
|
|
}
|
|
QString res;
|
|
if ( isUnicode ) {
|
|
res = QString::fromUtf16( ( const unsigned short *)buf );
|
|
} else {
|
|
res = QString::fromLocal8Bit( buf );
|
|
}
|
|
delete [] buf;
|
|
return res;
|
|
}
|
|
|
|
quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi )
|
|
{
|
|
quint32 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 ) {
|
|
uint tmp;
|
|
stream >> tmp;
|
|
mapi.name.value.setValue( tmp );
|
|
} else if ( mapi.name.type == 1 ) {
|
|
mapi.name.value.setValue( readMAPIString( stream, true ) );
|
|
}
|
|
}
|
|
|
|
int n = 1;
|
|
QVariant value;
|
|
if ( ISVECTOR( mapi ) ) {
|
|
stream >> n;
|
|
mapi.value = QList<QVariant>();
|
|
}
|
|
for ( int i=0; i<n; i++ ) {
|
|
value.clear();
|
|
switch( mapi.type & 0x0FFF ) {
|
|
case MAPI_TYPE_UINT16:
|
|
stream >> d;
|
|
value.setValue( d & 0x0000FFFF );
|
|
break;
|
|
case MAPI_TYPE_BOOLEAN:
|
|
case MAPI_TYPE_ULONG:
|
|
{
|
|
uint tmp;
|
|
stream >> tmp;
|
|
value.setValue( tmp );
|
|
}
|
|
break;
|
|
case MAPI_TYPE_FLOAT:
|
|
// FIXME: Don't we have to set the value here
|
|
stream >> d;
|
|
break;
|
|
case MAPI_TYPE_DOUBLE:
|
|
{
|
|
double tmp;
|
|
stream >> tmp;
|
|
value.setValue( tmp );
|
|
}
|
|
break;
|
|
case MAPI_TYPE_TIME:
|
|
{
|
|
quint32 lowB, highB;
|
|
stream >> lowB >> highB;
|
|
value = formatTime( lowB, highB );
|
|
}
|
|
break;
|
|
case MAPI_TYPE_USTRING:
|
|
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.setValue( readMAPIString( stream,( mapi.type & 0x0FFF ) == MAPI_TYPE_USTRING ) );
|
|
}
|
|
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();
|
|
quint32 len;
|
|
stream >> len;
|
|
value = QByteArray( len, '\0' );
|
|
if ( len > 0 ) {
|
|
int fullLen = len;
|
|
ALIGN( fullLen, 4 );
|
|
stream.readRawData( value.toByteArray().data(), len );
|
|
quint8 c;
|
|
for ( int i=len; i<fullLen; i++ ) {
|
|
stream >> c;
|
|
}
|
|
// FIXME: Shouldn't we do something with the value???
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
mapi.type = MAPI_TYPE_NONE;
|
|
break;
|
|
}
|
|
if ( ISVECTOR( mapi ) ) {
|
|
QList <QVariant> lst = mapi.value.toList();
|
|
lst << value;
|
|
mapi.value.setValue( lst );
|
|
} else {
|
|
mapi.value = value;
|
|
}
|
|
}
|
|
return mapi.tag;
|
|
}
|
|
//@endcond
|
|
|
|
bool KTNEFParser::ParserPrivate::readMAPIProperties( QMap<int,KTNEFProperty*> & props,
|
|
KTNEFAttach *attach )
|
|
{
|
|
quint32 n;
|
|
MAPI_value mapi;
|
|
KTNEFProperty *p;
|
|
QMap<int,KTNEFProperty*>::ConstIterator it;
|
|
bool foundAttachment = false;
|
|
|
|
// some initializations
|
|
mapi.type = MAPI_TYPE_NONE;
|
|
mapi.value.clear();
|
|
|
|
// get number of properties
|
|
stream_ >> n;
|
|
kDebug() << "MAPI Properties:" << n;
|
|
for ( uint i=0; i<n; i++ ) {
|
|
if ( stream_.atEnd() ) {
|
|
clearMAPIValue( mapi );
|
|
return false;
|
|
}
|
|
readMAPIValue( stream_, mapi );
|
|
if ( mapi.type == MAPI_TYPE_NONE ) {
|
|
kDebug().nospace() << "MAPI unsupported: tag="
|
|
<< hex << mapi.tag << ", type=" << mapi.type;
|
|
clearMAPIValue( mapi );
|
|
return false;
|
|
}
|
|
int key = mapi.tag;
|
|
switch ( mapi.tag ) {
|
|
case MAPI_TAG_DATA:
|
|
{
|
|
if ( mapi.type == MAPI_TYPE_OBJECT && attach ) {
|
|
QByteArray data = mapi.value.toByteArray();
|
|
int len = data.size();
|
|
ALIGN( len, 4 );
|
|
device_->seek( device_->pos()-len );
|
|
quint32 interface_ID;
|
|
stream_ >> interface_ID;
|
|
if ( interface_ID == MAPI_IID_IMessage ) {
|
|
// embedded TNEF file
|
|
attach->unsetDataParser();
|
|
attach->setOffset( device_->pos()+12 );
|
|
attach->setSize( data.size()-16 );
|
|
attach->setMimeTag( "application/vnd.ms-tnef" );
|
|
attach->setDisplayName( "Embedded Message" );
|
|
kDebug() << "MAPI Embedded Message: size=" << data.size();
|
|
}
|
|
device_->seek( device_->pos() + ( 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( device_->pos() - len );
|
|
attach->addAttribute( attATTACHDATA, atpBYTE, QString( "< size=%1 >" ).arg( len ), false );
|
|
}
|
|
}
|
|
kDebug() << "MAPI data: size=" << mapi.value.toByteArray().size();
|
|
break;
|
|
default:
|
|
{
|
|
QString mapiname = "";
|
|
if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) {
|
|
if ( mapi.name.type == 0 ) {
|
|
mapiname = QString().sprintf( " [name = 0x%04x]", mapi.name.value.toUInt() );
|
|
} else {
|
|
mapiname = QString( " [name = %1]" ).arg( mapi.name.value.toString() );
|
|
}
|
|
}
|
|
switch ( mapi.type & 0x0FFF ) {
|
|
case MAPI_TYPE_UINT16:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI short" << mapiname.toLatin1().data()
|
|
<< ":" << hex << mapi.value.toUInt();
|
|
break;
|
|
case MAPI_TYPE_ULONG:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI long" << mapiname.toLatin1().data()
|
|
<< ":" << hex << mapi.value.toUInt();
|
|
break;
|
|
case MAPI_TYPE_BOOLEAN:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI boolean" << mapiname.toLatin1().data()
|
|
<< ":" << mapi.value.toBool();
|
|
break;
|
|
case MAPI_TYPE_TIME:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI time" << mapiname.toLatin1().data()
|
|
<< ":" << mapi.value.toString().toLatin1().data();
|
|
break;
|
|
case MAPI_TYPE_USTRING:
|
|
case MAPI_TYPE_STRING8:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI string" << mapiname.toLatin1().data()
|
|
<< ":size=" << mapi.value.toByteArray().size()
|
|
<< mapi.value.toString();
|
|
break;
|
|
case MAPI_TYPE_BINARY:
|
|
kDebug().nospace() << "(tag="
|
|
<< hex << mapi.tag
|
|
<< ") MAPI binary" << mapiname.toLatin1().data()
|
|
<< ":size=" << mapi.value.toByteArray().size();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// do not remove potential existing similar entry
|
|
if ( ( it = props.constFind( key ) ) == props.constEnd() ) {
|
|
p = new KTNEFProperty( key, ( mapi.type & 0x0FFF ),
|
|
mapi.value, mapi.name.value );
|
|
props[ p->key() ] = p;
|
|
}
|
|
//kDebug() << "stream:" << device_->pos();
|
|
}
|
|
|
|
if ( foundAttachment && attach ) {
|
|
attach->setIndex( attach->property( MAPI_TAG_INDEX ).toUInt() );
|
|
attach->setDisplaySize( attach->property( MAPI_TAG_SIZE ).toUInt() );
|
|
QString 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;
|
|
}
|