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 | |
47 | using namespace KTnef; |
48 | |
49 | //@cond PRIVATE |
50 | typedef 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 |
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 ); |
73 | //@endcond |
74 | |
75 | //------------------------------------------------------------------------------ |
76 | |
77 | /** |
78 | * Private class that helps to provide binary compatibility between releases. |
79 | * @internal |
80 | */ |
81 | //@cond PRIVATE |
82 | class 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 | |
116 | KTNEFParser::KTNEFParser() |
117 | : d( new ParserPrivate ) |
118 | { |
119 | } |
120 | |
121 | KTNEFParser::~KTNEFParser() |
122 | { |
123 | d->deleteDevice(); |
124 | delete d; |
125 | } |
126 | |
127 | KTNEFMessage *KTNEFParser::message() const |
128 | { |
129 | return d->message_; |
130 | } |
131 | |
132 | void KTNEFParser::ParserPrivate::deleteDevice() |
133 | { |
134 | if ( deleteDevice_ ) { |
135 | delete device_; |
136 | } |
137 | device_ = 0; |
138 | deleteDevice_ = false; |
139 | } |
140 | |
141 | bool 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 | |
301 | bool 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 | |
375 | void KTNEFParser::( const QString &dirname ) |
376 | { |
377 | d->defaultdir_ = dirname; |
378 | } |
379 | |
380 | bool 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 | |
437 | bool KTNEFParser::( 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 | |
446 | bool KTNEFParser::ParserPrivate::( 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 | |
490 | bool KTNEFParser::() |
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 | |
502 | bool KTNEFParser::( 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 | |
514 | bool 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 | |
524 | bool KTNEFParser::openDevice( QIODevice *device ) |
525 | { |
526 | d->deleteDevice(); |
527 | d->device_ = device; |
528 | return d->parseDevice(); |
529 | } |
530 | |
531 | void 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 | |
581 | void clearMAPIName( MAPI_value &mapi ) |
582 | { |
583 | mapi.name.value.clear(); |
584 | } |
585 | |
586 | void clearMAPIValue( MAPI_value &mapi, bool clearName ) |
587 | { |
588 | mapi.value.clear(); |
589 | if ( clearName ) { |
590 | clearMAPIName( mapi ); |
591 | } |
592 | } |
593 | |
594 | QDateTime 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 | |
614 | QString 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 | |
653 | QDateTime 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 | |
661 | QString 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 | |
677 | QByteArray 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 | |
686 | QVariant 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 | |
699 | QString 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 | |
729 | quint16 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 | |
845 | bool 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 | |