File indexing completed on 2024-04-28 05:19:26

0001 /*
0002     ktnefparser.cpp
0003 
0004     SPDX-FileCopyrightText: 2002 Michael Goffioul <kdeprint@swing.be>
0005 
0006     This file is part of KTNEF, the KDE TNEF support library/program.
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009  */
0010 /**
0011  * @file
0012  * This file is part of the API for handling TNEF data and
0013  * defines the KTNEFParser class.
0014  *
0015  * @author Michael Goffioul
0016  */
0017 
0018 #include "ktnefparser.h"
0019 #include "ktnefattach.h"
0020 #include "ktnefdefs.h"
0021 #include "ktnefmessage.h"
0022 #include "ktnefproperty.h"
0023 
0024 #include "ktnef_debug.h"
0025 #include <QMimeDatabase>
0026 #include <QMimeType>
0027 #include <QSaveFile>
0028 
0029 #include <QDataStream>
0030 #include <QDateTime>
0031 #include <QDir>
0032 #include <QFile>
0033 #include <QFileInfo>
0034 #include <QList>
0035 #include <QStandardPaths>
0036 #include <QVariant>
0037 
0038 using namespace KTnef;
0039 
0040 //@cond PRIVATE
0041 typedef struct {
0042     quint16 type;
0043     quint16 tag;
0044     QVariant value;
0045     struct {
0046         quint32 type;
0047         QVariant value;
0048     } name;
0049 } MAPI_value;
0050 //@endcond
0051 
0052 //@cond IGNORE
0053 void clearMAPIName(MAPI_value &mapi);
0054 void clearMAPIValue(MAPI_value &mapi, bool clearName = true);
0055 QString readMAPIString(QDataStream &stream, bool isUnicode = false, bool align = true, int len = -1);
0056 quint16 readMAPIValue(QDataStream &stream, MAPI_value &mapi);
0057 QDateTime readTNEFDate(QDataStream &stream);
0058 QString readTNEFAddress(QDataStream &stream);
0059 QByteArray readTNEFData(QDataStream &stream, quint32 len);
0060 QVariant readTNEFAttribute(QDataStream &stream, quint16 type, quint32 len);
0061 QDateTime formatTime(quint32 lowB, quint32 highB);
0062 QString formatRecipient(const QMap<int, KTnef::KTNEFProperty *> &props);
0063 //@endcond
0064 
0065 //------------------------------------------------------------------------------
0066 
0067 /**
0068  * Private class that helps to provide binary compatibility between releases.
0069  * @internal
0070  */
0071 //@cond PRIVATE
0072 class KTnef::KTNEFParser::ParserPrivate
0073 {
0074 public:
0075     ParserPrivate()
0076         : defaultdir_(QStandardPaths::writableLocation(QStandardPaths::TempLocation))
0077         , message_(new KTNEFMessage)
0078     {
0079     }
0080     ~ParserPrivate()
0081     {
0082         delete message_;
0083     }
0084 
0085     bool decodeAttachment();
0086     bool decodeMessage();
0087     bool extractAttachmentTo(KTNEFAttach *att, const QString &dirname);
0088     void checkCurrent(int key);
0089     bool readMAPIProperties(QMap<int, KTNEFProperty *> &props, KTNEFAttach *attach = nullptr);
0090     bool parseDevice();
0091     void deleteDevice();
0092 
0093     QString defaultdir_;
0094     QDataStream stream_;
0095     QIODevice *device_ = nullptr;
0096     KTNEFAttach *current_ = nullptr;
0097     KTNEFMessage *message_ = nullptr;
0098     bool deleteDevice_ = false;
0099 };
0100 //@endcond
0101 
0102 KTNEFParser::KTNEFParser()
0103     : d(new ParserPrivate)
0104 {
0105 }
0106 
0107 KTNEFParser::~KTNEFParser()
0108 {
0109     d->deleteDevice();
0110 }
0111 
0112 KTNEFMessage *KTNEFParser::message() const
0113 {
0114     return d->message_;
0115 }
0116 
0117 void KTNEFParser::ParserPrivate::deleteDevice()
0118 {
0119     if (deleteDevice_) {
0120         delete device_;
0121     }
0122     device_ = nullptr;
0123     deleteDevice_ = false;
0124 }
0125 
0126 bool KTNEFParser::ParserPrivate::decodeMessage()
0127 {
0128     quint32 i1;
0129     quint32 i2;
0130     quint32 off;
0131     quint16 u;
0132     quint16 tag;
0133     quint16 type;
0134     QVariant value;
0135 
0136     // read (type+name)
0137     stream_ >> i1;
0138     u = 0;
0139     tag = (i1 & 0x0000FFFF);
0140     type = ((i1 & 0xFFFF0000) >> 16);
0141     // read data length
0142     stream_ >> i2;
0143     // offset after reading the value
0144     off = device_->pos() + i2;
0145     switch (tag) {
0146     case attAIDOWNER: {
0147         uint tmp;
0148         stream_ >> tmp;
0149         value.setValue(tmp);
0150         message_->addProperty(0x0062, MAPI_TYPE_ULONG, value);
0151         qCDebug(KTNEF_LOG) << "Message Owner Appointment ID"
0152                            << "(length=" << i2 << ")";
0153         break;
0154     }
0155     case attREQUESTRES:
0156         stream_ >> u;
0157         message_->addProperty(0x0063, MAPI_TYPE_UINT16, u);
0158         value = (bool)u;
0159         qCDebug(KTNEF_LOG) << "Message Request Response"
0160                            << "(length=" << i2 << ")";
0161         break;
0162     case attDATERECD:
0163         value = readTNEFDate(stream_);
0164         message_->addProperty(0x0E06, MAPI_TYPE_TIME, value);
0165         qCDebug(KTNEF_LOG) << "Message Receive Date"
0166                            << "(length=" << i2 << ")";
0167         break;
0168     case attMSGCLASS:
0169         value = readMAPIString(stream_, false, false, i2);
0170         message_->addProperty(0x001A, MAPI_TYPE_STRING8, value);
0171         qCDebug(KTNEF_LOG) << "Message Class"
0172                            << "(length=" << i2 << ")";
0173         break;
0174     case attMSGPRIORITY:
0175         stream_ >> u;
0176         message_->addProperty(0x0026, MAPI_TYPE_ULONG, 2 - u);
0177         value = u;
0178         qCDebug(KTNEF_LOG) << "Message Priority"
0179                            << "(length=" << i2 << ")";
0180         break;
0181     case attMAPIPROPS:
0182         qCDebug(KTNEF_LOG) << "Message MAPI Properties"
0183                            << "(length=" << i2 << ")";
0184         {
0185             int nProps = message_->properties().count();
0186             i2 += device_->pos();
0187             readMAPIProperties(message_->properties(), nullptr);
0188             device_->seek(i2);
0189             qCDebug(KTNEF_LOG) << "Properties:" << message_->properties().count();
0190             value = QStringLiteral("< %1 properties >").arg(message_->properties().count() - nProps);
0191         }
0192         break;
0193     case attTNEFVERSION: {
0194         uint tmp;
0195         stream_ >> tmp;
0196         value.setValue(tmp);
0197         qCDebug(KTNEF_LOG) << "Message TNEF Version"
0198                            << "(length=" << i2 << ")";
0199     } break;
0200     case attFROM:
0201         message_->addProperty(0x0024, MAPI_TYPE_STRING8, readTNEFAddress(stream_));
0202         device_->seek(device_->pos() - i2);
0203         value = readTNEFData(stream_, i2);
0204         qCDebug(KTNEF_LOG) << "Message From"
0205                            << "(length=" << i2 << ")";
0206         break;
0207     case attSUBJECT:
0208         value = readMAPIString(stream_, false, false, i2);
0209         message_->addProperty(0x0037, MAPI_TYPE_STRING8, value);
0210         qCDebug(KTNEF_LOG) << "Message Subject"
0211                            << "(length=" << i2 << ")";
0212         break;
0213     case attDATESENT:
0214         value = readTNEFDate(stream_);
0215         message_->addProperty(0x0039, MAPI_TYPE_TIME, value);
0216         qCDebug(KTNEF_LOG) << "Message Date Sent"
0217                            << "(length=" << i2 << ")";
0218         break;
0219     case attMSGSTATUS: {
0220         quint8 c;
0221         quint32 flag = 0;
0222         stream_ >> c;
0223         if (c & fmsRead) {
0224             flag |= MSGFLAG_READ;
0225         }
0226         if (!(c & fmsModified)) {
0227             flag |= MSGFLAG_UNMODIFIED;
0228         }
0229         if (c & fmsSubmitted) {
0230             flag |= MSGFLAG_SUBMIT;
0231         }
0232         if (c & fmsHasAttach) {
0233             flag |= MSGFLAG_HASATTACH;
0234         }
0235         if (c & fmsLocal) {
0236             flag |= MSGFLAG_UNSENT;
0237         }
0238         message_->addProperty(0x0E07, MAPI_TYPE_ULONG, flag);
0239         value = c;
0240     }
0241         qCDebug(KTNEF_LOG) << "Message Status"
0242                            << "(length=" << i2 << ")";
0243         break;
0244     case attRECIPTABLE: {
0245         quint32 rows;
0246         QList<QVariant> recipTable;
0247         stream_ >> rows;
0248         if (rows > (INT_MAX / sizeof(QVariant))) {
0249             return false;
0250         }
0251         recipTable.reserve(rows);
0252         for (uint i = 0; i < rows; i++) {
0253             QMap<int, KTNEFProperty *> props;
0254             readMAPIProperties(props, nullptr);
0255             recipTable << formatRecipient(props);
0256         }
0257         message_->addProperty(0x0E12, MAPI_TYPE_STRING8, recipTable);
0258         device_->seek(device_->pos() - i2);
0259         value = readTNEFData(stream_, i2);
0260     }
0261         qCDebug(KTNEF_LOG) << "Message Recipient Table"
0262                            << "(length=" << i2 << ")";
0263         break;
0264     case attBODY:
0265         value = readMAPIString(stream_, false, false, i2);
0266         message_->addProperty(0x1000, MAPI_TYPE_STRING8, value);
0267         qCDebug(KTNEF_LOG) << "Message Body"
0268                            << "(length=" << i2 << ")";
0269         break;
0270     case attDATEMODIFIED:
0271         value = readTNEFDate(stream_);
0272         message_->addProperty(0x3008, MAPI_TYPE_TIME, value);
0273         qCDebug(KTNEF_LOG) << "Message Date Modified"
0274                            << "(length=" << i2 << ")";
0275         break;
0276     case attMSGID:
0277         value = readMAPIString(stream_, false, false, i2);
0278         message_->addProperty(0x300B, MAPI_TYPE_STRING8, value);
0279         qCDebug(KTNEF_LOG) << "Message ID"
0280                            << "(length=" << i2 << ")";
0281         break;
0282     case attOEMCODEPAGE:
0283         value = readTNEFData(stream_, i2);
0284         qCDebug(KTNEF_LOG) << "Message OEM Code Page"
0285                            << "(length=" << i2 << ")";
0286         break;
0287     default:
0288         value = readTNEFAttribute(stream_, type, i2);
0289         // qCDebug(KTNEF_LOG).form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u );
0290         break;
0291     }
0292     // skip data
0293     if (device_->pos() != off && !device_->seek(off)) {
0294         return false;
0295     }
0296     // get checksum
0297     stream_ >> u;
0298     // add TNEF attribute
0299     message_->addAttribute(tag, type, value, true);
0300     // qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
0301     return true;
0302 }
0303 
0304 bool KTNEFParser::ParserPrivate::decodeAttachment()
0305 {
0306     quint32 i;
0307     quint16 tag;
0308     quint16 type;
0309     quint16 u;
0310     QVariant value;
0311     QString str;
0312 
0313     stream_ >> i; // i <- attribute type & name
0314     tag = (i & 0x0000FFFF);
0315     type = ((i & 0xFFFF0000) >> 16);
0316     stream_ >> i; // i <- data length
0317     checkCurrent(tag);
0318     switch (tag) {
0319     case attATTACHTITLE:
0320         value = readMAPIString(stream_, false, false, i);
0321         current_->setName(value.toString());
0322         qCDebug(KTNEF_LOG) << "Attachment Title:" << current_->name();
0323         break;
0324     case attATTACHDATA:
0325         current_->setSize(i);
0326         current_->setOffset(device_->pos());
0327         device_->seek(device_->pos() + i);
0328         value = QStringLiteral("< size=%1 >").arg(i);
0329         qCDebug(KTNEF_LOG) << "Attachment Data: size=" << i;
0330         break;
0331     case attATTACHMENT: // try to get attachment info
0332         i += device_->pos();
0333         readMAPIProperties(current_->properties(), current_);
0334         device_->seek(i);
0335         current_->setIndex(current_->property(MAPI_TAG_INDEX).toUInt());
0336         current_->setDisplaySize(current_->property(MAPI_TAG_SIZE).toUInt());
0337         str = current_->property(MAPI_TAG_DISPLAYNAME).toString();
0338         if (!str.isEmpty()) {
0339             current_->setDisplayName(str);
0340         }
0341         current_->setFileName(current_->property(MAPI_TAG_FILENAME).toString());
0342         str = current_->property(MAPI_TAG_MIMETAG).toString();
0343         if (!str.isEmpty()) {
0344             current_->setMimeTag(str);
0345         }
0346         current_->setExtension(current_->property(MAPI_TAG_EXTENSION).toString());
0347         value = QStringLiteral("< %1 properties >").arg(current_->properties().count());
0348         break;
0349     case attATTACHMODDATE:
0350         value = readTNEFDate(stream_);
0351         qCDebug(KTNEF_LOG) << "Attachment Modification Date:" << value.toString();
0352         break;
0353     case attATTACHCREATEDATE:
0354         value = readTNEFDate(stream_);
0355         qCDebug(KTNEF_LOG) << "Attachment Creation Date:" << value.toString();
0356         break;
0357     case attATTACHMETAFILE:
0358         qCDebug(KTNEF_LOG) << "Attachment Metafile: size=" << i;
0359         // value = QString( "< size=%1 >" ).arg( i );
0360         // device_->seek( device_->pos()+i );
0361         value = readTNEFData(stream_, i);
0362         break;
0363     default:
0364         value = readTNEFAttribute(stream_, type, i);
0365         qCDebug(KTNEF_LOG) << "Attachment unknown field:         tag=" << Qt::hex << tag << ", length=" << Qt::dec << i;
0366         break;
0367     }
0368     stream_ >> u; // u <- checksum
0369     // add TNEF attribute
0370     current_->addAttribute(tag, type, value, true);
0371     // qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
0372 
0373     return true;
0374 }
0375 
0376 void KTNEFParser::setDefaultExtractDir(const QString &dirname)
0377 {
0378     d->defaultdir_ = dirname;
0379 }
0380 
0381 bool KTNEFParser::ParserPrivate::parseDevice()
0382 {
0383     quint16 u;
0384     quint32 i;
0385     quint8 c;
0386 
0387     message_->clearAttachments();
0388     delete current_;
0389     current_ = nullptr;
0390 
0391     if (!device_->isOpen()) {
0392         if (!device_->open(QIODevice::ReadOnly)) {
0393             qCDebug(KTNEF_LOG) << "Couldn't open device";
0394             return false;
0395         }
0396     }
0397     if (!device_->isReadable()) {
0398         qCDebug(KTNEF_LOG) << "Device not readable";
0399         return false;
0400     }
0401 
0402     stream_.setDevice(device_);
0403     stream_.setByteOrder(QDataStream::LittleEndian);
0404     stream_ >> i;
0405     if (i == TNEF_SIGNATURE) {
0406         stream_ >> u;
0407         qCDebug(KTNEF_LOG).nospace() << "Attachment cross reference key: 0x" << Qt::hex << qSetFieldWidth(4) << qSetPadChar(QLatin1Char('0')) << u;
0408         // qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
0409         while (!stream_.atEnd()) {
0410             stream_ >> c;
0411             switch (c) {
0412             case LVL_MESSAGE:
0413                 if (!decodeMessage()) {
0414                     goto end;
0415                 }
0416                 break;
0417             case LVL_ATTACHMENT:
0418                 if (!decodeAttachment()) {
0419                     goto end;
0420                 }
0421                 break;
0422             default:
0423                 qCDebug(KTNEF_LOG) << "Unknown Level:" << c << ", at offset" << device_->pos();
0424                 goto end;
0425             }
0426         }
0427         if (current_) {
0428             checkCurrent(attATTACHDATA); // this line has the effect to append the
0429             // attachment, if it has data. If not it does
0430             // nothing, and the attachment will be discarded
0431             delete current_;
0432             current_ = nullptr;
0433         }
0434         return true;
0435     } else {
0436         qCDebug(KTNEF_LOG) << "This is not a TNEF file";
0437     end:
0438         device_->close();
0439         return false;
0440     }
0441 }
0442 
0443 bool KTNEFParser::extractFile(const QString &filename) const
0444 {
0445     KTNEFAttach *att = d->message_->attachment(filename);
0446     if (!att) {
0447         return false;
0448     }
0449     return d->extractAttachmentTo(att, d->defaultdir_);
0450 }
0451 
0452 bool KTNEFParser::ParserPrivate::extractAttachmentTo(KTNEFAttach *att, const QString &dirname)
0453 {
0454     const QString destDir(QDir(dirname).absolutePath()); // get directory path without any "." or ".."
0455 
0456     QString filename = destDir + QLatin1Char('/');
0457     if (!att->fileName().isEmpty()) {
0458         filename += att->fileName();
0459     } else {
0460         filename += att->name();
0461     }
0462     if (filename.endsWith(QLatin1Char('/'))) {
0463         return false;
0464     }
0465 
0466     if (!device_->isOpen()) {
0467         return false;
0468     }
0469     if (!device_->seek(att->offset())) {
0470         return false;
0471     }
0472 
0473     const QFileInfo fi(filename);
0474     if (!fi.absoluteFilePath().startsWith(destDir)) {
0475         qWarning() << "Attempted extract into" << fi.absoluteFilePath() << "which is outside of the extraction root folder" << destDir << "."
0476                    << "Changing export of contained files to extraction root folder.";
0477         filename = destDir + QLatin1Char('/') + fi.fileName();
0478     }
0479 
0480     QSaveFile outfile(filename);
0481     if (!outfile.open(QIODevice::WriteOnly)) {
0482         return false;
0483     }
0484 
0485     quint32 len = att->size();
0486     quint32 sz(16384);
0487     char *buf = new char[sz];
0488     bool ok(true);
0489     while (ok && len > 0) {
0490         const int n = device_->read(buf, qMin(sz, len));
0491         if (n < 0) {
0492             ok = false;
0493         } else {
0494             len -= n;
0495             if (outfile.write(buf, n) != n) {
0496                 ok = false;
0497             }
0498         }
0499     }
0500     outfile.commit();
0501     delete[] buf;
0502 
0503     return ok;
0504 }
0505 
0506 bool KTNEFParser::extractAll()
0507 {
0508     QList<KTNEFAttach *> l = d->message_->attachmentList();
0509     QList<KTNEFAttach *>::const_iterator it = l.constBegin();
0510     const QList<KTNEFAttach *>::const_iterator itEnd = l.constEnd();
0511     for (; it != itEnd; ++it) {
0512         if (!d->extractAttachmentTo(*it, d->defaultdir_)) {
0513             return false;
0514         }
0515     }
0516     return true;
0517 }
0518 
0519 bool KTNEFParser::extractFileTo(const QString &filename, const QString &dirname) const
0520 {
0521     qCDebug(KTNEF_LOG) << "Extracting attachment: filename=" << filename << ", dir=" << dirname;
0522     KTNEFAttach *att = d->message_->attachment(filename);
0523     if (!att) {
0524         return false;
0525     }
0526     return d->extractAttachmentTo(att, dirname);
0527 }
0528 
0529 bool KTNEFParser::openFile(const QString &filename) const
0530 {
0531     d->deleteDevice();
0532     delete d->message_;
0533     d->message_ = new KTNEFMessage();
0534     auto file = new QFile(filename);
0535     d->device_ = file;
0536     d->deleteDevice_ = true;
0537     if (!file->exists()) {
0538         return false;
0539     }
0540     return d->parseDevice();
0541 }
0542 
0543 bool KTNEFParser::openDevice(QIODevice *device)
0544 {
0545     d->deleteDevice();
0546     d->device_ = device;
0547     return d->parseDevice();
0548 }
0549 
0550 void KTNEFParser::ParserPrivate::checkCurrent(int key)
0551 {
0552     if (!current_) {
0553         current_ = new KTNEFAttach();
0554     } else {
0555         if (current_->attributes().contains(key)) {
0556             if (current_->offset() >= 0) {
0557                 if (current_->name().isEmpty()) {
0558                     current_->setName(QStringLiteral("Unnamed"));
0559                 }
0560                 if (current_->mimeTag().isEmpty()) {
0561                     // No mime type defined in the TNEF structure,
0562                     // try to find it from the attachment filename
0563                     // and/or content (using at most 32 bytes)
0564                     QMimeType mimetype;
0565                     QMimeDatabase db;
0566                     if (!current_->fileName().isEmpty()) {
0567                         mimetype = db.mimeTypeForFile(current_->fileName(), QMimeDatabase::MatchExtension);
0568                     }
0569                     if (!mimetype.isValid()) {
0570                         return; // FIXME
0571                     }
0572                     if (mimetype.name() == QLatin1StringView("application/octet-stream") && current_->size() > 0) {
0573                         qint64 oldOffset = device_->pos();
0574                         QByteArray buffer(qMin(32, current_->size()), '\0');
0575                         device_->seek(current_->offset());
0576                         device_->read(buffer.data(), buffer.size());
0577                         mimetype = db.mimeTypeForData(buffer);
0578                         device_->seek(oldOffset);
0579                     }
0580                     current_->setMimeTag(mimetype.name());
0581                 }
0582                 message_->addAttachment(current_);
0583                 current_ = nullptr;
0584             } else {
0585                 // invalid attachment, skip it
0586                 delete current_;
0587                 current_ = nullptr;
0588             }
0589             current_ = new KTNEFAttach();
0590         }
0591     }
0592 }
0593 
0594 //------------------------------------------------------------------------------
0595 
0596 //@cond IGNORE
0597 #define ALIGN(n, b)                                                                                                                                            \
0598     if (n & (b - 1)) {                                                                                                                                         \
0599         n = (n + b) & ~(b - 1);                                                                                                                                \
0600     }
0601 #define ISVECTOR(m) (((m).type & 0xF000) == MAPI_TYPE_VECTOR)
0602 
0603 void clearMAPIName(MAPI_value &mapi)
0604 {
0605     mapi.name.value.clear();
0606 }
0607 
0608 void clearMAPIValue(MAPI_value &mapi, bool clearName)
0609 {
0610     mapi.value.clear();
0611     if (clearName) {
0612         clearMAPIName(mapi);
0613     }
0614 }
0615 
0616 QDateTime formatTime(quint32 lowB, quint32 highB)
0617 {
0618     QDateTime dt;
0619     quint64 u64;
0620     u64 = highB;
0621     u64 <<= 32;
0622     u64 |= lowB;
0623     u64 -= 116444736000000000LL;
0624     u64 /= 10000000;
0625     if (u64 <= 0xffffffffU) {
0626         dt = QDateTime::fromSecsSinceEpoch((unsigned int)u64);
0627     } else {
0628         qCWarning(KTNEF_LOG).nospace() << "Invalid date: low byte=" << Qt::showbase << qSetFieldWidth(8) << qSetPadChar(QLatin1Char('0')) << lowB
0629                                        << ", high byte=" << highB;
0630     }
0631     return dt;
0632 }
0633 
0634 QString formatRecipient(const QMap<int, KTnef::KTNEFProperty *> &props)
0635 {
0636     QString s;
0637     QString dn;
0638     QString addr;
0639     QString t;
0640     QMap<int, KTnef::KTNEFProperty *>::ConstIterator it;
0641     if ((it = props.find(0x3001)) != props.end()) {
0642         dn = (*it)->valueString();
0643     }
0644     if ((it = props.find(0x3003)) != props.end()) {
0645         addr = (*it)->valueString();
0646     }
0647     if ((it = props.find(0x0C15)) != props.end()) {
0648         switch ((*it)->value().toInt()) {
0649         case 0:
0650             t = QStringLiteral("From:");
0651             break;
0652         case 1:
0653             t = QStringLiteral("To:");
0654             break;
0655         case 2:
0656             t = QStringLiteral("Cc:");
0657             break;
0658         case 3:
0659             t = QStringLiteral("Bcc:");
0660             break;
0661         }
0662     }
0663     if (!t.isEmpty()) {
0664         s.append(t);
0665     }
0666     if (!dn.isEmpty()) {
0667         s.append(QLatin1Char(' ') + dn);
0668     }
0669     if (!addr.isEmpty() && addr != dn) {
0670         s.append(QLatin1StringView(" <") + addr + QLatin1Char('>'));
0671     }
0672 
0673     return s.trimmed();
0674 }
0675 
0676 QDateTime readTNEFDate(QDataStream &stream)
0677 {
0678     // 14-bytes long
0679     quint16 y;
0680     quint16 m;
0681     quint16 d;
0682     quint16 hh;
0683     quint16 mm;
0684     quint16 ss;
0685     quint16 dm;
0686     stream >> y >> m >> d >> hh >> mm >> ss >> dm;
0687     return QDateTime(QDate(y, m, d), QTime(hh, mm, ss));
0688 }
0689 
0690 QString readTNEFAddress(QDataStream &stream)
0691 {
0692     quint16 totalLen;
0693     quint16 strLen;
0694     quint16 addrLen;
0695     QString s;
0696     stream >> totalLen >> totalLen >> strLen >> addrLen;
0697     s.append(readMAPIString(stream, false, false, strLen));
0698     s.append(QLatin1StringView(" <"));
0699     s.append(readMAPIString(stream, false, false, addrLen));
0700     s.append(QLatin1StringView(">"));
0701     quint8 c;
0702     for (int i = 8 + strLen + addrLen; i < totalLen; i++) {
0703         stream >> c;
0704     }
0705     return s;
0706 }
0707 
0708 QByteArray readTNEFData(QDataStream &stream, quint32 len)
0709 {
0710     QByteArray array(len, '\0');
0711     if (len > 0) {
0712         stream.readRawData(array.data(), len);
0713     }
0714     return array;
0715 }
0716 
0717 QVariant readTNEFAttribute(QDataStream &stream, quint16 type, quint32 len)
0718 {
0719     switch (type) {
0720     case atpTEXT:
0721     case atpSTRING:
0722         return readMAPIString(stream, false, false, len);
0723     case atpDATE:
0724         return readTNEFDate(stream);
0725     default:
0726         return readTNEFData(stream, len);
0727     }
0728 }
0729 
0730 QString readMAPIString(QDataStream &stream, bool isUnicode, bool align, int len_)
0731 {
0732     quint32 len;
0733     char *buf = nullptr;
0734     if (len_ == -1) {
0735         stream >> len;
0736     } else {
0737         len = len_;
0738     }
0739     if (len > INT_MAX) {
0740         return QString();
0741     }
0742 
0743     quint32 fullLen = len;
0744     if (align) {
0745         ALIGN(fullLen, 4)
0746     }
0747     buf = new char[len];
0748     stream.readRawData(buf, len);
0749     quint8 c;
0750     for (uint i = len; i < fullLen; i++) {
0751         stream >> c;
0752     }
0753     QString res;
0754     if (isUnicode) {
0755         res = QString::fromUtf16((const char16_t *)buf);
0756     } else {
0757         res = QString::fromLatin1(buf);
0758     }
0759     delete[] buf;
0760     return res;
0761 }
0762 
0763 quint16 readMAPIValue(QDataStream &stream, MAPI_value &mapi)
0764 {
0765     quint32 d;
0766 
0767     clearMAPIValue(mapi);
0768     stream >> d;
0769     mapi.type = (d & 0x0000FFFF);
0770     mapi.tag = ((d & 0xFFFF0000) >> 16);
0771     if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
0772         // skip GUID
0773         stream >> d >> d >> d >> d;
0774         // name type
0775         stream >> mapi.name.type;
0776         // name
0777         if (mapi.name.type == 0) {
0778             uint tmp;
0779             stream >> tmp;
0780             mapi.name.value.setValue(tmp);
0781         } else if (mapi.name.type == 1) {
0782             mapi.name.value.setValue(readMAPIString(stream, true));
0783         }
0784     }
0785 
0786     int n = 1;
0787     QVariant value;
0788     if (ISVECTOR(mapi)) {
0789         stream >> n;
0790         mapi.value = QList<QVariant>();
0791     }
0792     for (int i = 0; i < n; i++) {
0793         value.clear();
0794         switch (mapi.type & 0x0FFF) {
0795         case MAPI_TYPE_UINT16:
0796             stream >> d;
0797             value.setValue(d & 0x0000FFFF);
0798             break;
0799         case MAPI_TYPE_BOOLEAN:
0800         case MAPI_TYPE_ULONG: {
0801             uint tmp;
0802             stream >> tmp;
0803             value.setValue(tmp);
0804         } break;
0805         case MAPI_TYPE_FLOAT:
0806             // FIXME: Don't we have to set the value here
0807             stream >> d;
0808             break;
0809         case MAPI_TYPE_DOUBLE: {
0810             double tmp;
0811             stream >> tmp;
0812             value.setValue(tmp);
0813         } break;
0814         case MAPI_TYPE_TIME: {
0815             quint32 lowB;
0816             quint32 highB;
0817             stream >> lowB >> highB;
0818             value = formatTime(lowB, highB);
0819         } break;
0820         case MAPI_TYPE_USTRING:
0821         case MAPI_TYPE_STRING8:
0822             // in case of a vector'ed value, the number of elements
0823             // has already been read in the upper for-loop
0824             if (ISVECTOR(mapi)) {
0825                 d = 1;
0826             } else {
0827                 stream >> d;
0828             }
0829             for (uint j = 0; j < d; j++) {
0830                 value.clear();
0831                 value.setValue(readMAPIString(stream, (mapi.type & 0x0FFF) == MAPI_TYPE_USTRING));
0832             }
0833             break;
0834         case MAPI_TYPE_OBJECT:
0835         case MAPI_TYPE_BINARY:
0836             if (ISVECTOR(mapi)) {
0837                 d = 1;
0838             } else {
0839                 stream >> d;
0840             }
0841             for (uint i = 0; i < d && !stream.atEnd(); i++) {
0842                 value.clear();
0843                 quint32 len;
0844                 stream >> len;
0845                 value = QByteArray(len, '\0');
0846                 if (len > 0 && len <= INT_MAX) {
0847                     uint fullLen = len;
0848                     ALIGN(fullLen, 4)
0849                     stream.readRawData(value.toByteArray().data(), len);
0850                     quint8 c;
0851                     for (uint i = len; i < fullLen; i++) {
0852                         stream >> c;
0853                     }
0854                     // FIXME: Shouldn't we do something with the value???
0855                 }
0856             }
0857             break;
0858         default:
0859             mapi.type = MAPI_TYPE_NONE;
0860             break;
0861         }
0862         if (ISVECTOR(mapi)) {
0863             QList<QVariant> lst = mapi.value.toList();
0864             lst << value;
0865             mapi.value.setValue(lst);
0866         } else {
0867             mapi.value = value;
0868         }
0869     }
0870     return mapi.tag;
0871 }
0872 //@endcond
0873 
0874 bool KTNEFParser::ParserPrivate::readMAPIProperties(QMap<int, KTNEFProperty *> &props, KTNEFAttach *attach)
0875 {
0876     quint32 n;
0877     MAPI_value mapi;
0878     KTNEFProperty *p;
0879     QMap<int, KTNEFProperty *>::ConstIterator it;
0880     bool foundAttachment = false;
0881 
0882     // some initializations
0883     mapi.type = MAPI_TYPE_NONE;
0884     mapi.value.clear();
0885 
0886     // get number of properties
0887     stream_ >> n;
0888     qCDebug(KTNEF_LOG) << "MAPI Properties:" << n;
0889     for (uint i = 0; i < n; i++) {
0890         if (stream_.atEnd()) {
0891             clearMAPIValue(mapi);
0892             return false;
0893         }
0894         readMAPIValue(stream_, mapi);
0895         if (mapi.type == MAPI_TYPE_NONE) {
0896             qCDebug(KTNEF_LOG).nospace() << "MAPI unsupported:         tag=" << Qt::hex << mapi.tag << ", type=" << mapi.type;
0897             clearMAPIValue(mapi);
0898             return false;
0899         }
0900         int key = mapi.tag;
0901         switch (mapi.tag) {
0902         case MAPI_TAG_DATA: {
0903             if (mapi.type == MAPI_TYPE_OBJECT && attach) {
0904                 QByteArray data = mapi.value.toByteArray();
0905                 int len = data.size();
0906                 ALIGN(len, 4)
0907                 device_->seek(device_->pos() - len);
0908                 quint32 interface_ID;
0909                 stream_ >> interface_ID;
0910                 if (interface_ID == MAPI_IID_IMessage) {
0911                     // embedded TNEF file
0912                     attach->unsetDataParser();
0913                     attach->setOffset(device_->pos() + 12);
0914                     attach->setSize(data.size() - 16);
0915                     attach->setMimeTag(QStringLiteral("application/vnd.ms-tnef"));
0916                     attach->setDisplayName(QStringLiteral("Embedded Message"));
0917                     qCDebug(KTNEF_LOG) << "MAPI Embedded Message: size=" << data.size();
0918                 }
0919                 device_->seek(device_->pos() + (len - 4));
0920                 break;
0921             } else if (mapi.type == MAPI_TYPE_BINARY && attach && attach->offset() < 0) {
0922                 foundAttachment = true;
0923                 int len = mapi.value.toByteArray().size();
0924                 ALIGN(len, 4)
0925                 attach->setSize(len);
0926                 attach->setOffset(device_->pos() - len);
0927                 attach->addAttribute(attATTACHDATA, atpBYTE, QStringLiteral("< size=%1 >").arg(len), false);
0928             }
0929         }
0930             qCDebug(KTNEF_LOG) << "MAPI data: size=" << mapi.value.toByteArray().size();
0931             break;
0932         default: {
0933             QString mapiname = QLatin1StringView("");
0934             if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
0935                 if (mapi.name.type == 0) {
0936                     mapiname = QString::asprintf(" [name = 0x%04x]", mapi.name.value.toUInt());
0937                 } else {
0938                     mapiname = QStringLiteral(" [name = %1]").arg(mapi.name.value.toString());
0939                 }
0940             }
0941             switch (mapi.type & 0x0FFF) {
0942             case MAPI_TYPE_UINT16:
0943                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI short" << mapiname.toLatin1().data() << ":" << Qt::hex
0944                                              << mapi.value.toUInt();
0945                 break;
0946             case MAPI_TYPE_ULONG:
0947                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI long" << mapiname.toLatin1().data() << ":" << Qt::hex
0948                                              << mapi.value.toUInt();
0949                 break;
0950             case MAPI_TYPE_BOOLEAN:
0951                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI boolean" << mapiname.toLatin1().data() << ":" << mapi.value.toBool();
0952                 break;
0953             case MAPI_TYPE_TIME:
0954                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI time" << mapiname.toLatin1().data() << ":"
0955                                              << mapi.value.toString().toLatin1().data();
0956                 break;
0957             case MAPI_TYPE_USTRING:
0958             case MAPI_TYPE_STRING8:
0959                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI string" << mapiname.toLatin1().data()
0960                                              << ":size=" << mapi.value.toByteArray().size() << mapi.value.toString();
0961                 break;
0962             case MAPI_TYPE_BINARY:
0963                 qCDebug(KTNEF_LOG).nospace() << "(tag=" << Qt::hex << mapi.tag << ") MAPI binary" << mapiname.toLatin1().data()
0964                                              << ":size=" << mapi.value.toByteArray().size();
0965                 break;
0966             }
0967         } break;
0968         }
0969         // do not remove potential existing similar entry
0970         if ((it = props.constFind(key)) == props.constEnd()) {
0971             p = new KTNEFProperty(key, (mapi.type & 0x0FFF), mapi.value, mapi.name.value);
0972             props[p->key()] = p;
0973         }
0974         // qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
0975     }
0976 
0977     if (foundAttachment && attach) {
0978         attach->setIndex(attach->property(MAPI_TAG_INDEX).toUInt());
0979         attach->setDisplaySize(attach->property(MAPI_TAG_SIZE).toUInt());
0980         QString str = attach->property(MAPI_TAG_DISPLAYNAME).toString();
0981         if (!str.isEmpty()) {
0982             attach->setDisplayName(str);
0983         }
0984         attach->setFileName(attach->property(MAPI_TAG_FILENAME).toString());
0985         str = attach->property(MAPI_TAG_MIMETAG).toString();
0986         if (!str.isEmpty()) {
0987             attach->setMimeTag(str);
0988         }
0989         attach->setExtension(attach->property(MAPI_TAG_EXTENSION).toString());
0990         if (attach->name().isEmpty()) {
0991             attach->setName(attach->fileName());
0992         }
0993     }
0994 
0995     return true;
0996 }