KTnef

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

KDE's Doxygen guidelines are available online.