38 #include <kmimetype.h>
39 #include <ksavefile.h>
41 #include <QtCore/QDateTime>
42 #include <QtCore/QDataStream>
43 #include <QtCore/QFile>
44 #include <QtCore/QVariant>
45 #include <QtCore/QList>
47 using namespace KTnef;
62 void clearMAPIName( MAPI_value &mapi );
63 void clearMAPIValue( MAPI_value &mapi,
bool clearName =
true );
64 QString readMAPIString( QDataStream &stream,
bool isUnicode =
false,
65 bool align =
true,
int len = -1 );
66 quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi );
67 QDateTime readTNEFDate( QDataStream &stream );
68 QString readTNEFAddress( QDataStream &stream );
69 QByteArray readTNEFData( QDataStream &stream, quint32 len );
70 QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len );
71 QDateTime formatTime( quint32 lowB, quint32 highB );
72 QString formatRecipient(
const QMap<int,KTnef::KTNEFProperty*> &props );
82 class KTnef::KTNEFParser::ParserPrivate
87 defaultdir_ =
"/tmp/";
89 deleteDevice_ =
false;
98 bool decodeAttachment();
100 bool extractAttachmentTo(
KTNEFAttach *att,
const QString &dirname );
101 void checkCurrent(
int key );
102 bool readMAPIProperties( QMap<int,KTNEFProperty*>& props,
117 : d( new ParserPrivate )
132 void KTNEFParser::ParserPrivate::deleteDevice()
134 if ( deleteDevice_ ) {
138 deleteDevice_ =
false;
141 bool KTNEFParser::ParserPrivate::decodeMessage()
144 quint16 u, tag, type;
150 tag = ( i1 & 0x0000FFFF );
151 type = ( ( i1 & 0xFFFF0000 ) >> 16 );
155 off = device_->pos() + i2;
161 value.setValue( tmp );
162 message_->
addProperty( 0x0062, MAPI_TYPE_ULONG, value );
163 kDebug() <<
"Message Owner Appointment ID" <<
"(length=" << i2 <<
")";
168 message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u );
170 kDebug() <<
"Message Request Response" <<
"(length=" << i2 <<
")";
173 value = readTNEFDate( stream_ );
174 message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value );
175 kDebug() <<
"Message Receive Date" <<
"(length=" << i2 <<
")";
178 value = readMAPIString( stream_,
false,
false, i2 );
179 message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value );
180 kDebug() <<
"Message Class" <<
"(length=" << i2 <<
")";
184 message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u );
186 kDebug() <<
"Message Priority" <<
"(length=" << i2 <<
")";
189 kDebug() <<
"Message MAPI Properties" <<
"(length=" << i2 <<
")";
191 int nProps = message_->properties().count();
192 i2 += device_->pos();
193 readMAPIProperties( message_->properties(), 0 );
195 kDebug() <<
"Properties:" << message_->properties().count();
196 value = QString(
"< %1 properties >" ).
197 arg( message_->properties().count() - nProps );
204 value.setValue( tmp );
205 kDebug() <<
"Message TNEF Version" <<
"(length=" << i2 <<
")";
209 message_->addProperty( 0x0024, MAPI_TYPE_STRING8, readTNEFAddress( stream_ ) );
210 device_->seek( device_->pos() - i2 );
211 value = readTNEFData( stream_, i2 );
212 kDebug() <<
"Message From" <<
"(length=" << i2 <<
")";
215 value = readMAPIString( stream_,
false,
false, i2 );
216 message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value );
217 kDebug() <<
"Message Subject" <<
"(length=" << i2 <<
")";
220 value = readTNEFDate( stream_ );
221 message_->addProperty( 0x0039, MAPI_TYPE_TIME, value );
222 kDebug() <<
"Message Date Sent" <<
"(length=" << i2 <<
")";
230 flag |= MSGFLAG_READ;
232 if ( !( c & fmsModified ) ) {
233 flag |= MSGFLAG_UNMODIFIED;
235 if ( c & fmsSubmitted ) {
236 flag |= MSGFLAG_SUBMIT;
238 if ( c & fmsHasAttach ) {
239 flag |= MSGFLAG_HASATTACH;
241 if ( c & fmsLocal ) {
242 flag |= MSGFLAG_UNSENT;
244 message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
247 kDebug() <<
"Message Status" <<
"(length=" << i2 <<
")";
252 QList<QVariant> recipTable;
254 for ( uint i=0; i<rows; i++ ) {
255 QMap<int,KTNEFProperty*> props;
256 readMAPIProperties( props, 0 );
257 recipTable << formatRecipient( props );
259 message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable );
260 device_->seek( device_->pos() - i2 );
261 value = readTNEFData( stream_, i2 );
263 kDebug() <<
"Message Recipient Table" <<
"(length=" << i2 <<
")";
266 value = readMAPIString( stream_,
false,
false, i2 );
267 message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value );
268 kDebug() <<
"Message Body" <<
"(length=" << i2 <<
")";
270 case attDATEMODIFIED:
271 value = readTNEFDate( stream_ );
272 message_->addProperty( 0x3008, MAPI_TYPE_TIME, value );
273 kDebug() <<
"Message Date Modified" <<
"(length=" << i2 <<
")";
276 value = readMAPIString( stream_,
false,
false, i2 );
277 message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value );
278 kDebug() <<
"Message ID" <<
"(length=" << i2 <<
")";
281 value = readTNEFData( stream_, i2 );
282 kDebug() <<
"Message OEM Code Page" <<
"(length=" << i2 <<
")";
285 value = readTNEFAttribute( stream_, type, i2 );
290 if ( device_->pos() != off && !device_->seek( off ) ) {
296 message_->addAttribute( tag, type, value,
true );
301 bool KTNEFParser::ParserPrivate::decodeAttachment()
304 quint16 tag, type, u;
309 tag = ( i & 0x0000FFFF );
310 type = ( ( i & 0xFFFF0000 ) >> 16 );
315 value = readMAPIString( stream_,
false,
false, i );
316 current_->setName( value.toString() );
317 kDebug() <<
"Attachment Title:" << current_->name();
320 current_->setSize( i );
321 current_->setOffset( device_->pos() );
322 device_->seek( device_->pos() + i );
323 value = QString(
"< size=%1 >" ).arg( i );
324 kDebug() <<
"Attachment Data: size=" << i;
328 readMAPIProperties( current_->properties(), current_ );
330 current_->setIndex( current_->property( MAPI_TAG_INDEX ).toUInt() );
331 current_->setDisplaySize( current_->property( MAPI_TAG_SIZE ).toUInt() );
332 str = current_->property( MAPI_TAG_DISPLAYNAME ).toString();
333 if ( !str.isEmpty() ) {
334 current_->setDisplayName( str );
336 current_->setFileName( current_->property( MAPI_TAG_FILENAME ).
338 str = current_->property( MAPI_TAG_MIMETAG ).toString();
339 if ( !str.isEmpty() ) {
340 current_->setMimeTag( str );
342 current_->setExtension( current_->property( MAPI_TAG_EXTENSION ).
344 value = QString(
"< %1 properties >" ).
345 arg( current_->properties().count() );
347 case attATTACHMODDATE:
348 value = readTNEFDate( stream_ );
349 kDebug() <<
"Attachment Modification Date:" << value.toString();
351 case attATTACHCREATEDATE:
352 value = readTNEFDate( stream_ );
353 kDebug() <<
"Attachment Creation Date:" << value.toString();
355 case attATTACHMETAFILE:
356 kDebug() <<
"Attachment Metafile: size=" << i;
359 value = readTNEFData( stream_, i );
362 value = readTNEFAttribute( stream_, type, i );
363 kDebug() <<
"Attachment unknown field: tag="
364 << hex << tag <<
", length=" << dec << i;
369 current_->addAttribute( tag, type, value,
true );
377 d->defaultdir_ = dirname;
380 bool KTNEFParser::ParserPrivate::parseDevice()
386 message_->clearAttachments();
390 if ( !device_->open( QIODevice::ReadOnly ) ) {
391 kDebug() <<
"Couldn't open device";
395 stream_.setDevice( device_ );
396 stream_.setByteOrder( QDataStream::LittleEndian );
398 if ( i == TNEF_SIGNATURE ) {
400 kDebug().nospace() <<
"Attachment cross reference key: 0x"
401 << hex << qSetFieldWidth( 4 ) << qSetPadChar(
'0' ) << u;
403 while ( !stream_.atEnd() ) {
407 if ( !decodeMessage() ) {
412 if ( !decodeAttachment() ) {
417 kDebug() <<
"Unknown Level:" << c <<
", at offset" << device_->pos();
422 checkCurrent( attATTACHDATA );
430 kDebug() <<
"This is not a TNEF file";
439 KTNEFAttach *att = d->message_->attachment( filename );
443 return d->extractAttachmentTo( att, d->defaultdir_ );
446 bool KTNEFParser::ParserPrivate::extractAttachmentTo(
KTNEFAttach *att,
447 const QString &dirname )
449 QString filename = dirname +
'/' + att->
name();
450 if ( !device_->isOpen() ) {
453 if ( !device_->seek( att->
offset() ) ) {
456 KSaveFile outfile( filename );
457 if ( !outfile.open() ) {
461 quint32 len = att->
size(), sz( 16384 );
463 char *buf =
new char[sz];
465 while ( ok && len > 0 ) {
466 n = device_->read( buf, qMin( sz, len ) );
471 if ( outfile.write( buf, n ) != n ) {
483 QList<KTNEFAttach*> l = d->message_->attachmentList();
484 QList<KTNEFAttach*>::const_iterator it = l.constBegin();
485 for ( ; it != l.constEnd(); ++it ) {
486 if ( !d->extractAttachmentTo( *it, d->defaultdir_ ) ) {
494 const QString &dirname )
const
496 kDebug() <<
"Extracting attachment: filename="
497 << filename <<
", dir=" << dirname;
498 KTNEFAttach *att = d->message_->attachment( filename );
502 return d->extractAttachmentTo( att, dirname );
510 d->device_ =
new QFile( filename );
511 d->deleteDevice_ =
true;
512 return d->parseDevice();
519 return d->parseDevice();
522 void KTNEFParser::ParserPrivate::checkCurrent(
int key )
527 if ( current_->attributes().contains( key ) ) {
528 if ( current_->offset() >= 0 ) {
529 if ( current_->name().isEmpty() ) {
530 current_->setName(
"Unnamed" );
532 if ( current_->mimeTag().isEmpty() ) {
536 KMimeType::Ptr mimetype;
537 if ( !current_->fileName().isEmpty() ) {
538 mimetype = KMimeType::findByPath( current_->fileName(), 0, true );
543 if ( mimetype->name() ==
"application/octet-stream" &&
544 current_->size() > 0 ) {
545 int oldOffset = device_->pos();
546 QByteArray buffer( qMin( 32, current_->size() ),
'\0' );
547 device_->seek( current_->offset() );
548 device_->read( buffer.data(), buffer.size() );
549 mimetype = KMimeType::findByContent( buffer );
550 device_->seek( oldOffset );
552 current_->setMimeTag( mimetype->name() );
554 message_->addAttachment( current_ );
569 #define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); }
570 #define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR )
572 void clearMAPIName( MAPI_value &mapi )
574 mapi.name.value.clear();
577 void clearMAPIValue( MAPI_value &mapi,
bool clearName )
581 clearMAPIName( mapi );
585 QDateTime formatTime( quint32 lowB, quint32 highB )
592 u64 -= 116444736000000000LL;
594 if ( u64 <= 0xffffffffU ) {
595 dt.setTime_t( (
unsigned int )u64 );
597 kWarning().nospace() <<
"Invalid date: low byte="
598 << showbase << qSetFieldWidth( 8 ) << qSetPadChar(
'0' )
599 << lowB <<
", high byte=" << highB;
600 dt.setTime_t( 0xffffffffU );
605 QString formatRecipient(
const QMap<int,KTnef::KTNEFProperty*> &props )
607 QString s, dn, addr, t;
608 QMap<int,KTnef::KTNEFProperty*>::ConstIterator it;
609 if ( ( it = props.find( 0x3001 ) ) != props.end() ) {
610 dn = ( *it )->valueString();
612 if ( ( it = props.find( 0x3003 ) ) != props.end() ) {
613 addr = ( *it )->valueString();
615 if ( ( it = props.find( 0x0C15 ) ) != props.end() ) {
616 switch ( ( *it )->value().toInt() ) {
631 if ( !t.isEmpty() ) {
634 if ( !dn.isEmpty() ) {
635 s.append(
' ' + dn );
637 if ( !addr.isEmpty() && addr != dn ) {
638 s.append(
" <" + addr +
'>' );
644 QDateTime readTNEFDate( QDataStream &stream )
647 quint16 y, m, d, hh, mm, ss, dm;
648 stream >> y >> m >> d >> hh >> mm >> ss >> dm;
649 return QDateTime( QDate( y, m, d ), QTime( hh, mm, ss ) );
652 QString readTNEFAddress( QDataStream &stream )
654 quint16 totalLen, strLen, addrLen;
656 stream >> totalLen >> totalLen >> strLen >> addrLen;
657 s.append( readMAPIString( stream,
false,
false, strLen ) );
659 s.append( readMAPIString( stream,
false,
false, addrLen ) );
662 for (
int i=8+strLen+addrLen; i<totalLen; i++ ) {
668 QByteArray readTNEFData( QDataStream &stream, quint32 len )
670 QByteArray array( len,
'\0' );
672 stream.readRawData( array.data(), len );
677 QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len )
682 return readMAPIString( stream,
false,
false, len );
684 return readTNEFDate( stream );
686 return readTNEFData( stream, len );
690 QString readMAPIString( QDataStream &stream,
bool isUnicode,
bool align,
700 quint32 fullLen = len;
704 buf =
new char[ len ];
705 stream.readRawData( buf, len );
707 for ( uint i=len; i<fullLen; i++ ) {
712 res = QString::fromUtf16( (
const unsigned short *)buf );
714 res = QString::fromLocal8Bit( buf );
720 quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi )
724 clearMAPIValue( mapi );
726 mapi.type = ( d & 0x0000FFFF );
727 mapi.tag = ( ( d & 0xFFFF0000 ) >> 16 );
728 if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) {
730 stream >> d >> d >> d >> d;
732 stream >> mapi.name.type;
734 if ( mapi.name.type == 0 ) {
737 mapi.name.value.setValue( tmp );
738 }
else if ( mapi.name.type == 1 ) {
739 mapi.name.value.setValue( readMAPIString( stream,
true ) );
745 if ( ISVECTOR( mapi ) ) {
747 mapi.value = QList<QVariant>();
749 for (
int i=0; i<n; i++ ) {
751 switch( mapi.type & 0x0FFF ) {
752 case MAPI_TYPE_UINT16:
754 value.setValue( d & 0x0000FFFF );
756 case MAPI_TYPE_BOOLEAN:
757 case MAPI_TYPE_ULONG:
761 value.setValue( tmp );
764 case MAPI_TYPE_FLOAT:
768 case MAPI_TYPE_DOUBLE:
772 value.setValue( tmp );
778 stream >> lowB >> highB;
779 value = formatTime( lowB, highB );
782 case MAPI_TYPE_USTRING:
783 case MAPI_TYPE_STRING8:
786 if ( ISVECTOR( mapi ) ) {
791 for ( uint i=0; i<d; i++ ) {
793 value.setValue( readMAPIString( stream,( mapi.type & 0x0FFF ) == MAPI_TYPE_USTRING ) );
796 case MAPI_TYPE_OBJECT:
797 case MAPI_TYPE_BINARY:
798 if ( ISVECTOR( mapi ) ) {
803 for ( uint i=0; i<d; i++ ) {
807 value = QByteArray( len,
'\0' );
811 stream.readRawData( value.toByteArray().data(), len );
813 for (
int i=len; i<fullLen; i++ ) {
821 mapi.type = MAPI_TYPE_NONE;
824 if ( ISVECTOR( mapi ) ) {
825 QList <QVariant> lst = mapi.value.toList();
827 mapi.value.setValue( lst );
836 bool KTNEFParser::ParserPrivate::readMAPIProperties( QMap<int,KTNEFProperty*> & props,
842 QMap<int,KTNEFProperty*>::ConstIterator it;
843 bool foundAttachment =
false;
846 mapi.
type = MAPI_TYPE_NONE;
851 kDebug() <<
"MAPI Properties:" << n;
852 for ( uint i=0; i<n; i++ ) {
853 if ( stream_.atEnd() ) {
854 clearMAPIValue( mapi );
857 readMAPIValue( stream_, mapi );
858 if ( mapi.type == MAPI_TYPE_NONE ) {
859 kDebug().nospace() <<
"MAPI unsupported: tag="
860 << hex << mapi.tag <<
", type=" << mapi.type;
861 clearMAPIValue( mapi );
865 switch ( mapi.tag ) {
868 if ( mapi.type == MAPI_TYPE_OBJECT && attach ) {
869 QByteArray data = mapi.value.toByteArray();
870 int len = data.size();
872 device_->seek( device_->pos()-len );
873 quint32 interface_ID;
874 stream_ >> interface_ID;
875 if ( interface_ID == MAPI_IID_IMessage ) {
879 attach->
setSize( data.size()-16 );
880 attach->
setMimeTag(
"application/vnd.ms-tnef" );
882 kDebug() <<
"MAPI Embedded Message: size=" << data.size();
884 device_->seek( device_->pos() + ( len-4 ) );
886 }
else if ( mapi.type == MAPI_TYPE_BINARY && attach && attach->
offset() < 0 ) {
887 foundAttachment =
true;
888 int len = mapi.value.toByteArray().size();
891 attach->
setOffset( device_->pos() - len );
892 attach->
addAttribute( attATTACHDATA, atpBYTE, QString(
"< size=%1 >" ).arg( len ),
false );
895 kDebug() <<
"MAPI data: size=" << mapi.value.toByteArray().size();
899 QString mapiname =
"";
900 if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) {
901 if ( mapi.name.type == 0 ) {
902 mapiname = QString().sprintf(
" [name = 0x%04x]", mapi.name.value.toUInt() );
904 mapiname = QString(
" [name = %1]" ).arg( mapi.name.value.toString() );
907 switch ( mapi.type & 0x0FFF ) {
908 case MAPI_TYPE_UINT16:
909 kDebug().nospace() <<
"(tag="
911 <<
") MAPI short" << mapiname.toLatin1().data()
912 <<
":" << hex << mapi.value.toUInt();
914 case MAPI_TYPE_ULONG:
915 kDebug().nospace() <<
"(tag="
917 <<
") MAPI long" << mapiname.toLatin1().data()
918 <<
":" << hex << mapi.value.toUInt();
920 case MAPI_TYPE_BOOLEAN:
921 kDebug().nospace() <<
"(tag="
923 <<
") MAPI boolean" << mapiname.toLatin1().data()
924 <<
":" << mapi.value.toBool();
927 kDebug().nospace() <<
"(tag="
929 <<
") MAPI time" << mapiname.toLatin1().data()
930 <<
":" << mapi.value.toString().toLatin1().data();
932 case MAPI_TYPE_USTRING:
933 case MAPI_TYPE_STRING8:
934 kDebug().nospace() <<
"(tag="
936 <<
") MAPI string" << mapiname.toLatin1().data()
937 <<
":size=" << mapi.value.toByteArray().size()
938 << mapi.value.toString();
940 case MAPI_TYPE_BINARY:
941 kDebug().nospace() <<
"(tag="
943 <<
") MAPI binary" << mapiname.toLatin1().data()
944 <<
":size=" << mapi.value.toByteArray().size();
951 if ( ( it = props.constFind( key ) ) == props.constEnd() ) {
953 mapi.value, mapi.name.value );
954 props[ p->
key() ] = p;
959 if ( foundAttachment && attach ) {
962 QString str = attach->
property( MAPI_TAG_DISPLAYNAME ).toString();
963 if ( !str.isEmpty() ) {
967 str = attach->
property( MAPI_TAG_MIMETAG ).toString();
968 if ( !str.isEmpty() ) {
972 if ( attach->
name().isEmpty() ) {
void setOffset(int offset)
Sets the offset value of this attachment to offset.
QVariant property(int key) const
Returns the property associcated with the specified key.
void addProperty(int key, int type, const QVariant &value, const QVariant &name=QVariant(), bool overwrite=false)
Adds a MAPI property.
void setDisplaySize(int size)
Sets the display size of the attachment to size.
bool extractAll()
Extracts all TNEF attachments into the default directory.
int key() const
Returns the integer key of the property.
~KTNEFParser()
Destroys the TNEF parser object.
void setDefaultExtractDir(const QString &dirname)
Sets the default extraction directory to dirname.
void setExtension(const QString &str)
Sets the filename extension of this attachment to str.
This file is part of the API for handling TNEF data and defines the KTNEFMessage class.
bool extractFileTo(const QString &filename, const QString &dirname) const
Extracts a TNEF attachment having filename filename into the directory dirname.
int type() const
Returns the integer type of the property.
bool extractFile(const QString &filename) const
Extracts a TNEF attachment having filename filename into the default directory.
Represents a TNEF message.
void addAttribute(int key, int type, const QVariant &value, bool overwrite=false)
Adds a TNEF attribute.
This file is part of the API for handling TNEF data and provides some basic definitions for general u...
This file is part of the API for handling TNEF data and defines the KTNEFParser class.
int size() const
Returns the size of the attachment.
QString fileName() const
Returns the filename of the attachment.
void setMimeTag(const QString &str)
Sets the MIME tag of this attachment to str.
void setFileName(const QString &str)
Sets the filename of this attachment to str.
void unsetDataParser()
Unsets the DataParsed flag for this attachment.
Represents a TNEF attachment.
bool openDevice(QIODevice *device)
Opens the #QIODevice device for parsing.
bool openFile(const QString &filename) const
Opens the filename for parsing.
void setName(const QString &str)
Sets the name of this attachment to str.
This file is part of the API for handling TNEF data and defines the KTNEFProperty class...
void setSize(int size)
Sets the size of the attachment to size.
int offset() const
Returns the offset value of the attachment.
KTNEFParser()
Constructs a TNEF parser object.
This file is part of the API for handling TNEF data and defines the KTNEFAttach class.
void setIndex(int indx)
Sets the index of this attachment to indx.
KTNEFMessage * message() const
Returns the KTNEFMessage used in the parsing process.
QString name() const
Returns the name of the attachment.
Interface for setting MAPI properties.
void setDisplayName(const QString &str)
Sets the display name of this attachment to str.