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

KDE's Doxygen guidelines are available online.