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() ) {