1/*
2 ktnefparser.cpp
3
4 Copyright (C) 2002 Michael Goffioul <kdeprint@swing.be>
5
6 This file is part of KTNEF, the KDE TNEF support library/program.
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22 */
23/**
24 * @file
25 * This file is part of the API for handling TNEF data and
26 * defines the KTNEFParser class.
27 *
28 * @author Michael Goffioul
29 */
30
31#include "ktnefparser.h"
32#include "ktnefattach.h"
33#include "ktnefproperty.h"
34#include "ktnefmessage.h"
35#include "ktnefdefs.h"
36
37#include <kdebug.h>
38#include <kmimetype.h>
39#include <ksavefile.h>
40
41#include <QtCore/QDateTime>
42#include <QtCore/QDataStream>
43#include <QtCore/QFile>
44#include <QtCore/QVariant>
45#include <QtCore/QList>
46
47using namespace KTnef;
48
49//@cond PRIVATE
50typedef struct {
51 quint16 type;
52 quint16 tag;
53 QVariant value;
54 struct {
55 quint32 type;
56 QVariant value;
57 } name;
58} MAPI_value;
59//@endcond
60
61//@cond IGNORE
62void clearMAPIName( MAPI_value &mapi );
63void clearMAPIValue( MAPI_value &mapi, bool clearName = true );
64QString readMAPIString( QDataStream &stream, bool isUnicode = false,
65 bool align = true, int len = -1 );
66quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi );
67QDateTime readTNEFDate( QDataStream &stream );
68QString readTNEFAddress( QDataStream &stream );
69QByteArray readTNEFData( QDataStream &stream, quint32 len );
70QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len );
71QDateTime formatTime( quint32 lowB, quint32 highB );
72QString formatRecipient( const QMap<int,KTnef::KTNEFProperty*> &props );
73//@endcond
74
75//------------------------------------------------------------------------------
76
77/**
78 * Private class that helps to provide binary compatibility between releases.
79 * @internal
80 */
81//@cond PRIVATE
82class KTnef::KTNEFParser::ParserPrivate
83{
84 public:
85 ParserPrivate()
86 {
87 defaultdir_ = "/tmp/";
88 current_ = 0;
89 deleteDevice_ = false;
90 device_ = 0;
91 message_ = new KTNEFMessage;
92 }
93 ~ParserPrivate()
94 {
95 delete message_;
96 }
97
98 bool decodeAttachment();
99 bool decodeMessage();
100 bool extractAttachmentTo( KTNEFAttach *att, const QString &dirname );
101 void checkCurrent( int key );
102 bool readMAPIProperties( QMap<int,KTNEFProperty*>& props,
103 KTNEFAttach *attach = 0 );
104 bool parseDevice();
105 void deleteDevice();
106
107 QDataStream stream_;
108 QIODevice *device_;
109 bool deleteDevice_;
110 QString defaultdir_;
111 KTNEFAttach *current_;
112 KTNEFMessage *message_;
113};
114//@endcond
115
116KTNEFParser::KTNEFParser()
117 : d( new ParserPrivate )
118{
119}
120
121KTNEFParser::~KTNEFParser()
122{
123 d->deleteDevice();
124 delete d;
125}
126
127KTNEFMessage *KTNEFParser::message() const
128{
129 return d->message_;
130}
131
132void KTNEFParser::ParserPrivate::deleteDevice()
133{
134 if ( deleteDevice_ ) {
135 delete device_;
136 }
137 device_ = 0;
138 deleteDevice_ = false;
139}
140
141bool KTNEFParser::ParserPrivate::decodeMessage()
142{
143 quint32 i1, i2, off;
144 quint16 u, tag, type;
145 QVariant value;
146
147 // read (type+name)
148 stream_ >> i1;
149 u = 0;
150 tag = ( i1 & 0x0000FFFF );
151 type = ( ( i1 & 0xFFFF0000 ) >> 16 );
152 // read data length
153 stream_ >> i2;
154 // offset after reading the value
155 off = device_->pos() + i2;
156 switch ( tag ) {
157 case attAIDOWNER:
158 {
159 uint tmp;
160 stream_ >> tmp;
161 value.setValue( tmp );
162 message_->addProperty( 0x0062, MAPI_TYPE_ULONG, value );
163 kDebug() << "Message Owner Appointment ID" << "(length=" << i2 << ")";
164 break;
165 }
166 case attREQUESTRES:
167 stream_ >> u;
168 message_->addProperty( 0x0063, MAPI_TYPE_UINT16, u );
169 value = ( bool )u;
170 kDebug() << "Message Request Response" << "(length=" << i2 << ")";
171 break;
172 case attDATERECD:
173 value = readTNEFDate( stream_ );
174 message_->addProperty( 0x0E06, MAPI_TYPE_TIME, value );
175 kDebug() << "Message Receive Date" << "(length=" << i2 << ")";
176 break;
177 case attMSGCLASS:
178 value = readMAPIString( stream_, false, false, i2 );
179 message_->addProperty( 0x001A, MAPI_TYPE_STRING8, value );
180 kDebug() << "Message Class" << "(length=" << i2 << ")";
181 break;
182 case attMSGPRIORITY:
183 stream_ >> u;
184 message_->addProperty( 0x0026, MAPI_TYPE_ULONG, 2-u );
185 value = u;
186 kDebug() << "Message Priority" << "(length=" << i2 << ")";
187 break;
188 case attMAPIPROPS:
189 kDebug() << "Message MAPI Properties" << "(length=" << i2 << ")";
190 {
191 int nProps = message_->properties().count();
192 i2 += device_->pos();
193 readMAPIProperties( message_->properties(), 0 );
194 device_->seek( i2 );
195 kDebug() << "Properties:" << message_->properties().count();
196 value = QString( "< %1 properties >" ).
197 arg( message_->properties().count() - nProps );
198 }
199 break;
200 case attTNEFVERSION:
201 {
202 uint tmp;
203 stream_ >> tmp;
204 value.setValue( tmp );
205 kDebug() << "Message TNEF Version" << "(length=" << i2 << ")";
206 }
207 break;
208 case attFROM:
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 << ")";
213 break;
214 case attSUBJECT:
215 value = readMAPIString( stream_, false, false, i2 );
216 message_->addProperty( 0x0037, MAPI_TYPE_STRING8, value );
217 kDebug() << "Message Subject" << "(length=" << i2 << ")";
218 break;
219 case attDATESENT:
220 value = readTNEFDate( stream_ );
221 message_->addProperty( 0x0039, MAPI_TYPE_TIME, value );
222 kDebug() << "Message Date Sent" << "(length=" << i2 << ")";
223 break;
224 case attMSGSTATUS:
225 {
226 quint8 c;
227 quint32 flag = 0;
228 stream_ >> c;
229 if ( c & fmsRead ) {
230 flag |= MSGFLAG_READ;
231 }
232 if ( !( c & fmsModified ) ) {
233 flag |= MSGFLAG_UNMODIFIED;
234 }
235 if ( c & fmsSubmitted ) {
236 flag |= MSGFLAG_SUBMIT;
237 }
238 if ( c & fmsHasAttach ) {
239 flag |= MSGFLAG_HASATTACH;
240 }
241 if ( c & fmsLocal ) {
242 flag |= MSGFLAG_UNSENT;
243 }
244 message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
245 value = c;
246 }
247 kDebug() << "Message Status" << "(length=" << i2 << ")";
248 break;
249 case attRECIPTABLE:
250 {
251 quint32 rows;
252 QList<QVariant> recipTable;
253 stream_ >> rows;
254 for ( uint i=0; i<rows; i++ ) {
255 QMap<int,KTNEFProperty*> props;
256 readMAPIProperties( props, 0 );
257 recipTable << formatRecipient( props );
258 }
259 message_->addProperty( 0x0E12, MAPI_TYPE_STRING8, recipTable );
260 device_->seek( device_->pos() - i2 );
261 value = readTNEFData( stream_, i2 );
262 }
263 kDebug() << "Message Recipient Table" << "(length=" << i2 << ")";
264 break;
265 case attBODY:
266 value = readMAPIString( stream_, false, false, i2 );
267 message_->addProperty( 0x1000, MAPI_TYPE_STRING8, value );
268 kDebug() << "Message Body" << "(length=" << i2 << ")";
269 break;
270 case attDATEMODIFIED:
271 value = readTNEFDate( stream_ );
272 message_->addProperty( 0x3008, MAPI_TYPE_TIME, value );
273 kDebug() << "Message Date Modified" << "(length=" << i2 << ")";
274 break;
275 case attMSGID:
276 value = readMAPIString( stream_, false, false, i2 );
277 message_->addProperty( 0x300B, MAPI_TYPE_STRING8, value );
278 kDebug() << "Message ID" << "(length=" << i2 << ")";
279 break;
280 case attOEMCODEPAGE:
281 value = readTNEFData( stream_, i2 );
282 kDebug() << "Message OEM Code Page" << "(length=" << i2 << ")";
283 break;
284 default:
285 value = readTNEFAttribute( stream_, type, i2 );
286 //kDebug().form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u );
287 break;
288 }
289 // skip data
290 if ( device_->pos() != off && !device_->seek( off ) ) {
291 return false;
292 }
293 // get checksum
294 stream_ >> u;
295 // add TNEF attribute
296 message_->addAttribute( tag, type, value, true );
297 //kDebug() << "stream:" << device_->pos();
298 return true;
299}
300
301bool KTNEFParser::ParserPrivate::decodeAttachment()
302{
303 quint32 i;
304 quint16 tag, type, u;
305 QVariant value;
306 QString str;
307
308 stream_ >> i; // i <- attribute type & name
309 tag = ( i & 0x0000FFFF );
310 type = ( ( i & 0xFFFF0000 ) >> 16 );
311 stream_ >> i; // i <- data length
312 checkCurrent( tag );
313 switch ( tag ) {
314 case attATTACHTITLE:
315 value = readMAPIString( stream_, false, false, i );
316 current_->setName( value.toString() );
317 kDebug() << "Attachment Title:" << current_->name();
318 break;
319 case attATTACHDATA:
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;
325 break;
326 case attATTACHMENT: // try to get attachment info
327 i += device_->pos();
328 readMAPIProperties( current_->properties(), current_ );
329 device_->seek( i );
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 );
335 }
336 current_->setFileName( current_->property( MAPI_TAG_FILENAME ).
337 toString() );
338 str = current_->property( MAPI_TAG_MIMETAG ).toString();
339 if ( !str.isEmpty() ) {
340 current_->setMimeTag( str );
341 }
342 current_->setExtension( current_->property( MAPI_TAG_EXTENSION ).
343 toString() );
344 value = QString( "< %1 properties >" ).
345 arg( current_->properties().count() );
346 break;
347 case attATTACHMODDATE:
348 value = readTNEFDate( stream_ );
349 kDebug() << "Attachment Modification Date:" << value.toString();
350 break;
351 case attATTACHCREATEDATE:
352 value = readTNEFDate( stream_ );
353 kDebug() << "Attachment Creation Date:" << value.toString();
354 break;
355 case attATTACHMETAFILE:
356 kDebug() << "Attachment Metafile: size=" << i;
357 //value = QString( "< size=%1 >" ).arg( i );
358 //device_->seek( device_->pos()+i );
359 value = readTNEFData( stream_, i );
360 break;
361 default:
362 value = readTNEFAttribute( stream_, type, i );
363 kDebug() << "Attachment unknown field: tag="
364 << hex << tag << ", length=" << dec << i;
365 break;
366 }
367 stream_ >> u; // u <- checksum
368 // add TNEF attribute
369 current_->addAttribute( tag, type, value, true );
370 //kDebug() << "stream:" << device_->pos();
371
372 return true;
373}
374
375void KTNEFParser::setDefaultExtractDir( const QString &dirname )
376{
377 d->defaultdir_ = dirname;
378}
379
380bool KTNEFParser::ParserPrivate::parseDevice()
381{
382 quint16 u;
383 quint32 i;
384 quint8 c;
385
386 message_->clearAttachments();
387 delete current_;
388 current_ = 0;
389
390 if ( !device_->open( QIODevice::ReadOnly ) ) {
391 kDebug() << "Couldn't open device";
392 return false;
393 }
394
395 stream_.setDevice( device_ );
396 stream_.setByteOrder( QDataStream::LittleEndian );
397 stream_ >> i;
398 if ( i == TNEF_SIGNATURE ) {
399 stream_ >> u;
400 kDebug().nospace() << "Attachment cross reference key: 0x"
401 << hex << qSetFieldWidth( 4 ) << qSetPadChar( '0' ) << u;
402 //kDebug() << "stream:" << device_->pos();
403 while ( !stream_.atEnd() ) {
404 stream_ >> c;
405 switch( c ) {
406 case LVL_MESSAGE:
407 if ( !decodeMessage() ) {
408 goto end;
409 }
410 break;
411 case LVL_ATTACHMENT:
412 if ( !decodeAttachment() ) {
413 goto end;
414 }
415 break;
416 default:
417 kDebug() << "Unknown Level:" << c << ", at offset" << device_->pos();
418 goto end;
419 }
420 }
421 if ( current_ ) {
422 checkCurrent( attATTACHDATA ); // this line has the effect to append the
423 // attachment, if it has data. If not it does
424 // nothing, and the attachment will be discarded
425 delete current_;
426 current_ = 0;
427 }
428 return true;
429 } else {
430 kDebug() << "This is not a TNEF file";
431 end:
432 device_->close();
433 return false;
434 }
435}
436
437bool KTNEFParser::extractFile( const QString &filename ) const
438{
439 KTNEFAttach *att = d->message_->attachment( filename );
440 if ( !att ) {
441 return false;
442 }
443 return d->extractAttachmentTo( att, d->defaultdir_ );
444}
445
446bool KTNEFParser::ParserPrivate::extractAttachmentTo( KTNEFAttach *att,
447 const QString &dirname )
448{
449 QString filename = dirname + '/';
450 if ( !att->fileName().isEmpty()) {
451 filename += att->fileName();
452 } else {
453 filename += att->name();
454 }
455 if ( filename.endsWith( '/') ) {
456 return false;
457 }
458
459 if ( !device_->isOpen() ) {
460 return false;
461 }
462 if ( !device_->seek( att->offset() ) ) {
463 return false;
464 }
465 KSaveFile outfile( filename );
466 if ( !outfile.open() ) {
467 return false;
468 }
469
470 quint32 len = att->size(), sz( 16384 );
471 int n( 0 );
472 char *buf = new char[sz];
473 bool ok( true );
474 while ( ok && len > 0 ) {
475 n = device_->read( buf, qMin( sz, len ) );
476 if ( n < 0 ) {
477 ok = false;
478 } else {
479 len -= n;
480 if ( outfile.write( buf, n ) != n ) {
481 ok = false;
482 }
483 }
484 }
485 delete [] buf;
486
487 return ok;
488}
489
490bool KTNEFParser::extractAll()
491{
492 QList<KTNEFAttach*> l = d->message_->attachmentList();
493 QList<KTNEFAttach*>::const_iterator it = l.constBegin();
494 for ( ; it != l.constEnd(); ++it ) {
495 if ( !d->extractAttachmentTo( *it, d->defaultdir_ ) ) {
496 return false;
497 }
498 }
499 return true;
500}
501
502bool KTNEFParser::extractFileTo( const QString &filename,
503 const QString &dirname ) const
504{
505 kDebug() << "Extracting attachment: filename="
506 << filename << ", dir=" << dirname;
507 KTNEFAttach *att = d->message_->attachment( filename );
508 if ( !att ) {
509 return false;
510 }
511 return d->extractAttachmentTo( att, dirname );
512}
513
514bool KTNEFParser::openFile( const QString &filename ) const
515{
516 d->deleteDevice();
517 delete d->message_;
518 d->message_ = new KTNEFMessage();
519 d->device_ = new QFile( filename );
520 d->deleteDevice_ = true;
521 return d->parseDevice();
522}
523
524bool KTNEFParser::openDevice( QIODevice *device )
525{
526 d->deleteDevice();
527 d->device_ = device;
528 return d->parseDevice();
529}
530
531void KTNEFParser::ParserPrivate::checkCurrent( int key )
532{
533 if ( !current_ ) {
534 current_ = new KTNEFAttach();
535 } else {
536 if ( current_->attributes().contains( key ) ) {
537 if ( current_->offset() >= 0 ) {
538 if ( current_->name().isEmpty() ) {
539 current_->setName( "Unnamed" );
540 }
541 if ( current_->mimeTag().isEmpty() ) {
542 // No mime type defined in the TNEF structure,
543 // try to find it from the attachment filename
544 // and/or content (using at most 32 bytes)
545 KMimeType::Ptr mimetype;
546 if ( !current_->fileName().isEmpty() ) {
547 mimetype = KMimeType::findByPath( current_->fileName(), 0, true );
548 }
549 if ( !mimetype ) {
550 return; // FIXME
551 }
552 if ( mimetype->name() == "application/octet-stream" &&
553 current_->size() > 0 ) {
554 int oldOffset = device_->pos();
555 QByteArray buffer( qMin( 32, current_->size() ), '\0' );
556 device_->seek( current_->offset() );
557 device_->read( buffer.data(), buffer.size() );
558 mimetype = KMimeType::findByContent( buffer );
559 device_->seek( oldOffset );
560 }
561 current_->setMimeTag( mimetype->name() );
562 }
563 message_->addAttachment( current_ );
564 current_ = 0;
565 } else {
566 // invalid attachment, skip it
567 delete current_;
568 current_ = 0;
569 }
570 current_ = new KTNEFAttach();
571 }
572 }
573}
574
575//------------------------------------------------------------------------------
576
577//@cond IGNORE
578#define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); }
579#define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR )
580
581void clearMAPIName( MAPI_value &mapi )
582{
583 mapi.name.value.clear();
584}
585
586void clearMAPIValue( MAPI_value &mapi, bool clearName )
587{
588 mapi.value.clear();
589 if ( clearName ) {
590 clearMAPIName( mapi );
591 }
592}
593
594QDateTime formatTime( quint32 lowB, quint32 highB )
595{
596 QDateTime dt;
597 quint64 u64;
598 u64 = highB;
599 u64 <<= 32;
600 u64 |= lowB;
601 u64 -= 116444736000000000LL;
602 u64 /= 10000000;
603 if ( u64 <= 0xffffffffU ) {
604 dt.setTime_t( ( unsigned int )u64 );
605 } else {
606 kWarning().nospace() << "Invalid date: low byte="
607 << showbase << qSetFieldWidth( 8 ) << qSetPadChar( '0' )
608 << lowB << ", high byte=" << highB;
609 dt.setTime_t( 0xffffffffU );
610 }
611 return dt;
612}
613
614QString formatRecipient( const QMap<int,KTnef::KTNEFProperty*> &props )
615{
616 QString s, dn, addr, t;
617 QMap<int,KTnef::KTNEFProperty*>::ConstIterator it;
618 if ( ( it = props.find( 0x3001 ) ) != props.end() ) {
619 dn = ( *it )->valueString();
620 }
621 if ( ( it = props.find( 0x3003 ) ) != props.end() ) {
622 addr = ( *it )->valueString();
623 }
624 if ( ( it = props.find( 0x0C15 ) ) != props.end() ) {
625 switch ( ( *it )->value().toInt() ) {
626 case 0:
627 t = "From:";
628 break;
629 case 1:
630 t = "To:";
631 break;
632 case 2:
633 t = "Cc:";
634 break;
635 case 3:
636 t = "Bcc:";
637 break;
638 }
639 }
640 if ( !t.isEmpty() ) {
641 s.append( t );
642 }
643 if ( !dn.isEmpty() ) {
644 s.append( ' ' + dn );
645 }
646 if ( !addr.isEmpty() && addr != dn ) {
647 s.append( " <" + addr + '>' );
648 }
649
650 return s.trimmed();
651}
652
653QDateTime readTNEFDate( QDataStream &stream )
654{
655 // 14-bytes long
656 quint16 y, m, d, hh, mm, ss, dm;
657 stream >> y >> m >> d >> hh >> mm >> ss >> dm;
658 return QDateTime( QDate( y, m, d ), QTime( hh, mm, ss ) );
659}
660
661QString readTNEFAddress( QDataStream &stream )
662{
663 quint16 totalLen, strLen, addrLen;
664 QString s;
665 stream >> totalLen >> totalLen >> strLen >> addrLen;
666 s.append( readMAPIString( stream, false, false, strLen ) );
667 s.append( " <" );
668 s.append( readMAPIString( stream, false, false, addrLen ) );
669 s.append( ">" );
670 quint8 c;
671 for ( int i=8+strLen+addrLen; i<totalLen; i++ ) {
672 stream >> c;
673 }
674 return s;
675}
676
677QByteArray readTNEFData( QDataStream &stream, quint32 len )
678{
679 QByteArray array( len, '\0' );
680 if ( len > 0 ) {
681 stream.readRawData( array.data(), len );
682 }
683 return array;
684}
685
686QVariant readTNEFAttribute( QDataStream &stream, quint16 type, quint32 len )
687{
688 switch ( type ) {
689 case atpTEXT:
690 case atpSTRING:
691 return readMAPIString( stream, false, false, len );
692 case atpDATE:
693 return readTNEFDate( stream );
694 default:
695 return readTNEFData( stream, len );
696 }
697}
698
699QString readMAPIString( QDataStream &stream, bool isUnicode, bool align,
700 int len_ )
701{
702 quint32 len;
703 char *buf = 0;
704 if ( len_ == -1 ) {
705 stream >> len;
706 } else {
707 len = len_;
708 }
709 quint32 fullLen = len;
710 if ( align ) {
711 ALIGN( fullLen, 4 );
712 }
713 buf = new char[ len ];
714 stream.readRawData( buf, len );
715 quint8 c;
716 for ( uint i=len; i<fullLen; i++ ) {
717 stream >> c;
718 }
719 QString res;
720 if ( isUnicode ) {
721 res = QString::fromUtf16( ( const unsigned short *)buf );
722 } else {
723 res = QString::fromLocal8Bit( buf );
724 }
725 delete [] buf;
726 return res;
727}
728
729quint16 readMAPIValue( QDataStream &stream, MAPI_value &mapi )
730{
731 quint32 d;
732
733 clearMAPIValue( mapi );
734 stream >> d;
735 mapi.type = ( d & 0x0000FFFF );
736 mapi.tag = ( ( d & 0xFFFF0000 ) >> 16 );
737 if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) {
738 // skip GUID
739 stream >> d >> d >> d >> d;
740 // name type
741 stream >> mapi.name.type;
742 // name
743 if ( mapi.name.type == 0 ) {
744 uint tmp;
745 stream >> tmp;
746 mapi.name.value.setValue( tmp );
747 } else if ( mapi.name.type == 1 ) {
748 mapi.name.value.setValue( readMAPIString( stream, true ) );
749 }
750 }
751
752 int n = 1;
753 QVariant value;
754 if ( ISVECTOR( mapi ) ) {
755 stream >> n;
756 mapi.value = QList<QVariant>();
757 }
758 for ( int i=0; i<n; i++ ) {
759 value.clear();
760 switch( mapi.type & 0x0FFF ) {
761 case MAPI_TYPE_UINT16:
762 stream >> d;
763 value.setValue( d & 0x0000FFFF );
764 break;
765 case MAPI_TYPE_BOOLEAN:
766 case MAPI_TYPE_ULONG:
767 {
768 uint tmp;
769 stream >> tmp;
770 value.setValue( tmp );
771 }
772 break;
773 case MAPI_TYPE_FLOAT:
774 // FIXME: Don't we have to set the value here
775 stream >> d;
776 break;
777 case MAPI_TYPE_DOUBLE:
778 {
779 double tmp;
780 stream >> tmp;
781 value.setValue( tmp );
782 }
783 break;
784 case MAPI_TYPE_TIME:
785 {
786 quint32 lowB, highB;
787 stream >> lowB >> highB;
788 value = formatTime( lowB, highB );
789 }
790 break;
791 case MAPI_TYPE_USTRING:
792 case MAPI_TYPE_STRING8:
793 // in case of a vector'ed value, the number of elements
794 // has already been read in the upper for-loop
795 if ( ISVECTOR( mapi ) ) {
796 d = 1;
797 } else {
798 stream >> d;
799 }
800 for ( uint i=0; i<d; i++ ) {
801 value.clear();
802 value.setValue( readMAPIString( stream,( mapi.type & 0x0FFF ) == MAPI_TYPE_USTRING ) );
803 }
804 break;
805 case MAPI_TYPE_OBJECT:
806 case MAPI_TYPE_BINARY:
807 if ( ISVECTOR( mapi ) ) {
808 d = 1;
809 } else {
810 stream >> d;
811 }
812 for ( uint i=0; i<d; i++ ) {
813 value.clear();
814 quint32 len;
815 stream >> len;
816 value = QByteArray( len, '\0' );
817 if ( len > 0 ) {
818 int fullLen = len;
819 ALIGN( fullLen, 4 );
820 stream.readRawData( value.toByteArray().data(), len );
821 quint8 c;
822 for ( int i=len; i<fullLen; i++ ) {
823 stream >> c;
824 }
825 // FIXME: Shouldn't we do something with the value???
826 }
827 }
828 break;
829 default:
830 mapi.type = MAPI_TYPE_NONE;
831 break;
832 }
833 if ( ISVECTOR( mapi ) ) {
834 QList <QVariant> lst = mapi.value.toList();
835 lst << value;
836 mapi.value.setValue( lst );
837 } else {
838 mapi.value = value;
839 }
840 }
841 return mapi.tag;
842}
843//@endcond
844
845bool KTNEFParser::ParserPrivate::readMAPIProperties( QMap<int,KTNEFProperty*> & props,
846 KTNEFAttach *attach )
847{
848 quint32 n;
849 MAPI_value mapi;
850 KTNEFProperty *p;
851 QMap<int,KTNEFProperty*>::ConstIterator it;
852 bool foundAttachment = false;
853
854 // some initializations
855 mapi.type = MAPI_TYPE_NONE;
856 mapi.value.clear();
857
858 // get number of properties
859 stream_ >> n;
860 kDebug() << "MAPI Properties:" << n;
861 for ( uint i=0; i<n; i++ ) {
862 if ( stream_.atEnd() ) {
863 clearMAPIValue( mapi );
864 return false;
865 }
866 readMAPIValue( stream_, mapi );
867 if ( mapi.type == MAPI_TYPE_NONE ) {
868 kDebug().nospace() << "MAPI unsupported: tag="
869 << hex << mapi.tag << ", type=" << mapi.type;
870 clearMAPIValue( mapi );
871 return false;
872 }
873 int key = mapi.tag;
874 switch ( mapi.tag ) {
875 case MAPI_TAG_DATA:
876 {
877 if ( mapi.type == MAPI_TYPE_OBJECT && attach ) {
878 QByteArray data = mapi.value.toByteArray();
879 int len = data.size();
880 ALIGN( len, 4 );
881 device_->seek( device_->pos()-len );
882 quint32 interface_ID;
883 stream_ >> interface_ID;
884 if ( interface_ID == MAPI_IID_IMessage ) {
885 // embedded TNEF file
886 attach->unsetDataParser();
887 attach->setOffset( device_->pos()+12 );
888 attach->setSize( data.size()-16 );
889 attach->setMimeTag( "application/vnd.ms-tnef" );
890 attach->setDisplayName( "Embedded Message" );
891 kDebug() << "MAPI Embedded Message: size=" << data.size();
892 }
893 device_->seek( device_->pos() + ( len-4 ) );
894 break;
895 } else if ( mapi.type == MAPI_TYPE_BINARY && attach && attach->offset() < 0 ) {
896 foundAttachment = true;
897 int len = mapi.value.toByteArray().size();
898 ALIGN( len, 4 );
899 attach->setSize( len );
900 attach->setOffset( device_->pos() - len );
901 attach->addAttribute( attATTACHDATA, atpBYTE, QString( "< size=%1 >" ).arg( len ), false );
902 }
903 }
904 kDebug() << "MAPI data: size=" << mapi.value.toByteArray().size();
905 break;
906 default:
907 {
908 QString mapiname = "";
909 if ( mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE ) {
910 if ( mapi.name.type == 0 ) {
911 mapiname = QString().sprintf( " [name = 0x%04x]", mapi.name.value.toUInt() );
912 } else {
913 mapiname = QString( " [name = %1]" ).arg( mapi.name.value.toString() );
914 }
915 }
916 switch ( mapi.type & 0x0FFF ) {
917 case MAPI_TYPE_UINT16:
918 kDebug().nospace() << "(tag="
919 << hex << mapi.tag
920 << ") MAPI short" << mapiname.toLatin1().data()
921 << ":" << hex << mapi.value.toUInt();
922 break;
923 case MAPI_TYPE_ULONG:
924 kDebug().nospace() << "(tag="
925 << hex << mapi.tag
926 << ") MAPI long" << mapiname.toLatin1().data()
927 << ":" << hex << mapi.value.toUInt();
928 break;
929 case MAPI_TYPE_BOOLEAN:
930 kDebug().nospace() << "(tag="
931 << hex << mapi.tag
932 << ") MAPI boolean" << mapiname.toLatin1().data()
933 << ":" << mapi.value.toBool();
934 break;
935 case MAPI_TYPE_TIME:
936 kDebug().nospace() << "(tag="
937 << hex << mapi.tag
938 << ") MAPI time" << mapiname.toLatin1().data()
939 << ":" << mapi.value.toString().toLatin1().data();
940 break;
941 case MAPI_TYPE_USTRING:
942 case MAPI_TYPE_STRING8:
943 kDebug().nospace() << "(tag="
944 << hex << mapi.tag
945 << ") MAPI string" << mapiname.toLatin1().data()
946 << ":size=" << mapi.value.toByteArray().size()
947 << mapi.value.toString();
948 break;
949 case MAPI_TYPE_BINARY:
950 kDebug().nospace() << "(tag="
951 << hex << mapi.tag
952 << ") MAPI binary" << mapiname.toLatin1().data()
953 << ":size=" << mapi.value.toByteArray().size();
954 break;
955 }
956 }
957 break;
958 }
959 // do not remove potential existing similar entry
960 if ( ( it = props.constFind( key ) ) == props.constEnd() ) {
961 p = new KTNEFProperty( key, ( mapi.type & 0x0FFF ),
962 mapi.value, mapi.name.value );
963 props[ p->key() ] = p;
964 }
965 //kDebug() << "stream:" << device_->pos();
966 }
967
968 if ( foundAttachment && attach ) {
969 attach->setIndex( attach->property( MAPI_TAG_INDEX ).toUInt() );
970 attach->setDisplaySize( attach->property( MAPI_TAG_SIZE ).toUInt() );
971 QString str = attach->property( MAPI_TAG_DISPLAYNAME ).toString();
972 if ( !str.isEmpty() ) {
973 attach->setDisplayName( str );
974 }
975 attach->setFileName( attach->property( MAPI_TAG_FILENAME ).toString() );
976 str = attach->property( MAPI_TAG_MIMETAG ).toString();
977 if ( !str.isEmpty() ) {
978 attach->setMimeTag( str );
979 }
980 attach->setExtension( attach->property( MAPI_TAG_EXTENSION ).toString() );
981 if ( attach->name().isEmpty() ) {
982 attach->setName( attach->fileName() );
983 }
984 }
985
986 return true;
987}
988