File indexing completed on 2024-06-16 05:00:15

0001 /*
0002   SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
0003   SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
0004 
0005   SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "nodehelper.h"
0009 #include "interfaces/bodypart.h"
0010 #include "messagepart.h"
0011 #include "mimetreeparser_debug.h"
0012 #include "partmetadata.h"
0013 #include "temporaryfile/attachmenttemporaryfilesdirs.h"
0014 
0015 #include <KMime/Content>
0016 
0017 #include <KCharsets>
0018 #include <KLocalizedString>
0019 #include <QTemporaryFile>
0020 
0021 #include <QDir>
0022 #include <QRegularExpressionMatch>
0023 #include <QStringDecoder>
0024 #include <QUrl>
0025 
0026 #include <QFileDevice>
0027 #include <QMimeDatabase>
0028 #include <QMimeType>
0029 #include <algorithm>
0030 #include <sstream>
0031 #include <string>
0032 
0033 namespace MimeTreeParser
0034 {
0035 NodeHelper::NodeHelper()
0036     : mAttachmentFilesDir(new AttachmentTemporaryFilesDirs())
0037 {
0038     mListAttachmentTemporaryDirs.append(mAttachmentFilesDir);
0039     // TODO(Andras) add methods to modify these prefixes
0040 }
0041 
0042 NodeHelper::~NodeHelper()
0043 {
0044     for (auto att : mListAttachmentTemporaryDirs) {
0045         if (att) {
0046             att->forceCleanTempFiles();
0047             delete att;
0048         }
0049     }
0050     clear();
0051 }
0052 
0053 void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse)
0054 {
0055     if (!node) {
0056         return;
0057     }
0058     mProcessedNodes.append(node);
0059     qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
0060     //<< " decodedContent" << node->decodedContent();
0061     if (recurse) {
0062         const auto contents = node->contents();
0063         for (KMime::Content *c : contents) {
0064             setNodeProcessed(c, true);
0065         }
0066     }
0067 }
0068 
0069 void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse)
0070 {
0071     if (!node) {
0072         return;
0073     }
0074     mProcessedNodes.removeAll(node);
0075 
0076     // avoid double addition of extra nodes, eg. encrypted attachments
0077     const QMap<KMime::Content *, QList<KMime::Content *>>::iterator it = mExtraContents.find(node);
0078     if (it != mExtraContents.end()) {
0079         const auto contents = it.value();
0080         for (KMime::Content *c : contents) {
0081             KMime::Content *p = c->parent();
0082             if (p) {
0083                 p->takeContent(c);
0084             }
0085         }
0086         qDeleteAll(it.value());
0087         qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
0088         mExtraContents.erase(it);
0089     }
0090 
0091     qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node;
0092     if (recurse) {
0093         const auto contents = node->contents();
0094         for (KMime::Content *c : contents) {
0095             setNodeUnprocessed(c, true);
0096         }
0097     }
0098 }
0099 
0100 bool NodeHelper::nodeProcessed(KMime::Content *node) const
0101 {
0102     if (!node) {
0103         return true;
0104     }
0105     return mProcessedNodes.contains(node);
0106 }
0107 
0108 static void clearBodyPartMemento(QMap<QByteArray, Interface::BodyPartMemento *> &bodyPartMementoMap)
0109 {
0110     for (QMap<QByteArray, Interface::BodyPartMemento *>::iterator it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end(); it != end; ++it) {
0111         Interface::BodyPartMemento *memento = it.value();
0112         memento->detach();
0113         delete memento;
0114     }
0115     bodyPartMementoMap.clear();
0116 }
0117 
0118 void NodeHelper::clear()
0119 {
0120     mProcessedNodes.clear();
0121     mEncryptionState.clear();
0122     mSignatureState.clear();
0123     mOverrideCodecs.clear();
0124     std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(), &clearBodyPartMemento);
0125     mBodyPartMementoMap.clear();
0126     QMap<KMime::Content *, QList<KMime::Content *>>::ConstIterator end(mExtraContents.constEnd());
0127 
0128     for (QMap<KMime::Content *, QList<KMime::Content *>>::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) {
0129         const auto contents = it.value();
0130         for (KMime::Content *c : contents) {
0131             KMime::Content *p = c->parent();
0132             if (p) {
0133                 p->takeContent(c);
0134             }
0135         }
0136         qDeleteAll(it.value());
0137         qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
0138     }
0139     mExtraContents.clear();
0140     mDisplayEmbeddedNodes.clear();
0141     mDisplayHiddenNodes.clear();
0142 }
0143 
0144 void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state)
0145 {
0146     mEncryptionState[node] = state;
0147 }
0148 
0149 KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const
0150 {
0151     return mEncryptionState.value(node, KMMsgNotEncrypted);
0152 }
0153 
0154 void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state)
0155 {
0156     mSignatureState[node] = state;
0157 }
0158 
0159 KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const
0160 {
0161     return mSignatureState.value(node, KMMsgNotSigned);
0162 }
0163 
0164 PartMetaData NodeHelper::partMetaData(KMime::Content *node)
0165 {
0166     return mPartMetaDatas.value(node, PartMetaData());
0167 }
0168 
0169 void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData)
0170 {
0171     mPartMetaDatas.insert(node, metaData);
0172 }
0173 
0174 QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename)
0175 {
0176     QString fname = createTempDir(persistentIndex(node));
0177     if (fname.isEmpty()) {
0178         return {};
0179     }
0180     fname += QLatin1Char('/') + filename;
0181     QFile f(fname);
0182     if (!f.open(QIODevice::ReadWrite)) {
0183         qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
0184         mAttachmentFilesDir->addTempFile(fname);
0185         return {};
0186     }
0187     f.write(QByteArray());
0188     mAttachmentFilesDir->addTempFile(fname);
0189     // make file read-only so that nobody gets the impression that he might
0190     // edit attached files (cf. bug #52813)
0191     f.setPermissions(QFileDevice::ReadUser);
0192     f.close();
0193 
0194     return fname;
0195 }
0196 
0197 QString NodeHelper::writeNodeToTempFile(KMime::Content *node)
0198 {
0199     // If the message part is already written to a file, no point in doing it again.
0200     // This function is called twice actually, once from the rendering of the attachment
0201     // in the body and once for the header.
0202     const QUrl existingFileName = tempFileUrlFromNode(node);
0203     if (!existingFileName.isEmpty()) {
0204         return existingFileName.toLocalFile();
0205     }
0206 
0207     QString fname = createTempDir(persistentIndex(node));
0208     if (fname.isEmpty()) {
0209         return {};
0210     }
0211 
0212     QString fileName = NodeHelper::fileName(node);
0213     // strip off a leading path
0214     int slashPos = fileName.lastIndexOf(QLatin1Char('/'));
0215     if (-1 != slashPos) {
0216         fileName = fileName.mid(slashPos + 1);
0217     }
0218     if (fileName.isEmpty()) {
0219         fileName = QStringLiteral("unnamed");
0220     }
0221     fname += QLatin1Char('/') + fileName;
0222 
0223     qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname;
0224     QByteArray data = node->decodedContent();
0225     if (node->contentType()->isText() && !data.isEmpty()) {
0226         // convert CRLF to LF before writing text attachments to disk
0227         data = KMime::CRLFtoLF(data);
0228     }
0229     QFile f(fname);
0230     if (!f.open(QIODevice::ReadWrite)) {
0231         qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
0232         mAttachmentFilesDir->addTempFile(fname);
0233         return {};
0234     }
0235     f.write(data);
0236     mAttachmentFilesDir->addTempFile(fname);
0237     // make file read-only so that nobody gets the impression that he might
0238     // edit attached files (cf. bug #52813)
0239     f.setPermissions(QFileDevice::ReadUser);
0240     f.close();
0241 
0242     return fname;
0243 }
0244 
0245 QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node)
0246 {
0247     if (!node) {
0248         return {};
0249     }
0250 
0251     const QString index = persistentIndex(node);
0252 
0253     const QStringList temporaryFiles = mAttachmentFilesDir->temporaryFiles();
0254     for (const QString &path : temporaryFiles) {
0255         const int right = path.lastIndexOf(QLatin1Char('/'));
0256         int left = path.lastIndexOf(QLatin1StringView(".index."), right);
0257         if (left != -1) {
0258             left += 7;
0259         }
0260         const QStringView strView(path);
0261         const QStringView storedIndex = strView.sliced(left, right - left);
0262         if (left != -1 && storedIndex == index) {
0263             return QUrl::fromLocalFile(path);
0264         }
0265     }
0266     return {};
0267 }
0268 
0269 QString NodeHelper::createTempDir(const QString &param)
0270 {
0271     auto tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1StringView("/messageviewer_XXXXXX") + QLatin1StringView(".index.") + param);
0272     tempFile->open();
0273     const QString fname = tempFile->fileName();
0274     delete tempFile;
0275 
0276     QFile fFile(fname);
0277     if (!(fFile.permissions() & QFileDevice::WriteUser)) {
0278         // Not there or not writable
0279         if (!QDir().mkpath(fname) || !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) {
0280             mAttachmentFilesDir->addTempDir(fname);
0281             return {}; // failed create
0282         }
0283     }
0284 
0285     Q_ASSERT(!fname.isNull());
0286 
0287     mAttachmentFilesDir->addTempDir(fname);
0288     return fname;
0289 }
0290 
0291 void NodeHelper::forceCleanTempFiles()
0292 {
0293     mAttachmentFilesDir->forceCleanTempFiles();
0294     delete mAttachmentFilesDir;
0295     mAttachmentFilesDir = nullptr;
0296 }
0297 
0298 void NodeHelper::removeTempFiles()
0299 {
0300     // Don't delete as it will be deleted in class
0301     if (mAttachmentFilesDir) {
0302         mAttachmentFilesDir->removeTempFiles();
0303     }
0304     mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
0305     mListAttachmentTemporaryDirs.append(mAttachmentFilesDir);
0306 }
0307 
0308 void NodeHelper::addTempFile(const QString &file)
0309 {
0310     mAttachmentFilesDir->addTempFile(file);
0311 }
0312 
0313 bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
0314 {
0315     const KMime::Content *const topLevel = node->topLevel();
0316     const KMime::Content *cur = node;
0317     while (cur && cur != topLevel) {
0318         const bool parentIsMessage =
0319             cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType(false)->mimeType().toLower() == "message/rfc822";
0320         if (parentIsMessage && cur->parent() != topLevel) {
0321             return true;
0322         }
0323         cur = cur->parent();
0324     }
0325     return false;
0326 }
0327 
0328 QByteArray NodeHelper::charset(KMime::Content *node)
0329 {
0330     if (node->contentType(false)) {
0331         return node->contentType(false)->charset();
0332     } else {
0333         return node->defaultCharset();
0334     }
0335 }
0336 
0337 KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const
0338 {
0339     KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
0340     if (!node) {
0341         return myState;
0342     }
0343 
0344     KMime::Content *parent = node->parent();
0345     auto contents = parent ? parent->contents() : KMime::Content::List();
0346     if (contents.isEmpty()) {
0347         contents.append(node);
0348     }
0349     int i = contents.indexOf(const_cast<KMime::Content *>(node));
0350     if (i < 0) {
0351         return myState;
0352     }
0353     for (; i < contents.size(); ++i) {
0354         auto next = contents.at(i);
0355         KMMsgEncryptionState otherState = encryptionState(next);
0356 
0357         // NOTE: children are tested ONLY when parent is not encrypted
0358         if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) {
0359             otherState = overallEncryptionState(next->contents().at(0));
0360         }
0361 
0362         if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) {
0363             otherState = overallEncryptionState(extraContents(next).at(0));
0364         }
0365 
0366         if (next == node) {
0367             myState = otherState;
0368         }
0369 
0370         switch (otherState) {
0371         case KMMsgEncryptionStateUnknown:
0372             break;
0373         case KMMsgNotEncrypted:
0374             if (myState == KMMsgFullyEncrypted) {
0375                 myState = KMMsgPartiallyEncrypted;
0376             } else if (myState != KMMsgPartiallyEncrypted) {
0377                 myState = KMMsgNotEncrypted;
0378             }
0379             break;
0380         case KMMsgPartiallyEncrypted:
0381             myState = KMMsgPartiallyEncrypted;
0382             break;
0383         case KMMsgFullyEncrypted:
0384             if (myState != KMMsgFullyEncrypted) {
0385                 myState = KMMsgPartiallyEncrypted;
0386             }
0387             break;
0388         case KMMsgEncryptionProblematic:
0389             break;
0390         }
0391     }
0392 
0393     qCDebug(MIMETREEPARSER_LOG) << "\n\n  KMMsgEncryptionState:" << myState;
0394 
0395     return myState;
0396 }
0397 
0398 KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const
0399 {
0400     KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
0401     if (!node) {
0402         return myState;
0403     }
0404 
0405     KMime::Content *parent = node->parent();
0406     auto contents = parent ? parent->contents() : KMime::Content::List();
0407     if (contents.isEmpty()) {
0408         contents.append(node);
0409     }
0410     int i = contents.indexOf(const_cast<KMime::Content *>(node));
0411     if (i < 0) { // Be safe
0412         return myState;
0413     }
0414     for (; i < contents.size(); ++i) {
0415         auto next = contents.at(i);
0416         KMMsgSignatureState otherState = signatureState(next);
0417 
0418         // NOTE: children are tested ONLY when parent is not encrypted
0419         if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) {
0420             otherState = overallSignatureState(next->contents().at(0));
0421         }
0422 
0423         if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) {
0424             otherState = overallSignatureState(extraContents(next).at(0));
0425         }
0426 
0427         if (next == node) {
0428             myState = otherState;
0429         }
0430 
0431         switch (otherState) {
0432         case KMMsgSignatureStateUnknown:
0433             break;
0434         case KMMsgNotSigned:
0435             if (myState == KMMsgFullySigned) {
0436                 myState = KMMsgPartiallySigned;
0437             } else if (myState != KMMsgPartiallySigned) {
0438                 myState = KMMsgNotSigned;
0439             }
0440             break;
0441         case KMMsgPartiallySigned:
0442             myState = KMMsgPartiallySigned;
0443             break;
0444         case KMMsgFullySigned:
0445             if (myState != KMMsgFullySigned) {
0446                 myState = KMMsgPartiallySigned;
0447             }
0448             break;
0449         case KMMsgSignatureProblematic:
0450             break;
0451         }
0452     }
0453 
0454     qCDebug(MIMETREEPARSER_LOG) << "\n\n  KMMsgSignatureState:" << myState;
0455 
0456     return myState;
0457 }
0458 
0459 void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
0460 {
0461     const QByteArray body = aAutoDecode ? node->decodedContent() : node->body();
0462     QMimeDatabase db;
0463     const QMimeType mime = db.mimeTypeForData(body);
0464 
0465     const QString mimetype = mime.name();
0466     node->contentType()->setMimeType(mimetype.toLatin1());
0467 }
0468 
0469 bool NodeHelper::hasMailHeader(const char *header, const KMime::Content *message) const
0470 {
0471     if (mHeaderOverwrite.contains(message)) {
0472         const auto parts = mHeaderOverwrite.value(message);
0473         for (const auto &messagePart : parts) {
0474             if (messagePart->hasHeader(header)) {
0475                 return true;
0476             }
0477         }
0478     }
0479     return message->hasHeader(header);
0480 }
0481 
0482 QList<MessagePart::Ptr> NodeHelper::messagePartsOfMailHeader(const char *header, const KMime::Content *message) const
0483 {
0484     QList<MessagePart::Ptr> ret;
0485     if (mHeaderOverwrite.contains(message)) {
0486         const auto parts = mHeaderOverwrite.value(message);
0487         for (const auto &messagePart : parts) {
0488             if (messagePart->hasHeader(header)) {
0489                 ret << messagePart;
0490             }
0491         }
0492     }
0493     return ret;
0494 }
0495 
0496 QList<KMime::Headers::Base *> NodeHelper::headers(const char *header, const KMime::Content *message)
0497 {
0498     const auto mp = messagePartsOfMailHeader(header, message);
0499     if (mp.size() > 0) {
0500         return mp.value(0)->headers(header);
0501     }
0502 
0503     return message->headersByType(header);
0504 }
0505 
0506 KMime::Headers::Base const *NodeHelper::mailHeaderAsBase(const char *header, const KMime::Content *message) const
0507 {
0508     if (mHeaderOverwrite.contains(message)) {
0509         const auto parts = mHeaderOverwrite.value(message);
0510         for (const auto &messagePart : parts) {
0511             if (messagePart->hasHeader(header)) {
0512                 return messagePart->header(header); // Found.
0513             }
0514         }
0515     }
0516     return message->headerByType(header);
0517 }
0518 
0519 QSharedPointer<KMime::Headers::Generics::AddressList> NodeHelper::mailHeaderAsAddressList(const char *header, const KMime::Content *message) const
0520 {
0521     const auto hrd = mailHeaderAsBase(header, message);
0522     if (!hrd) {
0523         return nullptr;
0524     }
0525     QSharedPointer<KMime::Headers::Generics::AddressList> addressList(new KMime::Headers::Generics::AddressList());
0526     addressList->from7BitString(hrd->as7BitString(false));
0527     return addressList;
0528 }
0529 
0530 void NodeHelper::clearOverrideHeaders()
0531 {
0532     mHeaderOverwrite.clear();
0533 }
0534 
0535 void NodeHelper::registerOverrideHeader(KMime::Content *message, MessagePart::Ptr part)
0536 {
0537     if (!mHeaderOverwrite.contains(message)) {
0538         mHeaderOverwrite[message] = QList<MessagePart::Ptr>();
0539     }
0540     mHeaderOverwrite[message].append(part);
0541 }
0542 
0543 QDateTime NodeHelper::dateHeader(KMime::Content *message) const
0544 {
0545     const auto dateHeader = mailHeaderAsBase("date", message);
0546     if (dateHeader != nullptr) {
0547         return static_cast<const KMime::Headers::Date *>(dateHeader)->dateTime();
0548     }
0549     return {};
0550 }
0551 
0552 void NodeHelper::setOverrideCodec(KMime::Content *node, const QByteArray &codec)
0553 {
0554     if (!node) {
0555         return;
0556     }
0557 
0558     mOverrideCodecs[node] = codec;
0559 }
0560 
0561 QByteArray NodeHelper::codecName(KMime::Content *node) const
0562 {
0563     if (!node || !node->contentType()) {
0564         return "utf-8";
0565     }
0566 
0567     auto c = mOverrideCodecs.value(node);
0568     if (c.isEmpty()) {
0569         // no override-codec set for this message, try the CT charset parameter:
0570         c = node->contentType()->charset();
0571 
0572         // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
0573         // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
0574         if (c.toLower() == "us-ascii") {
0575             c = "utf-8";
0576         }
0577         if (QStringDecoder codec(c.constData()); !codec.isValid()) {
0578             c = "utf-8";
0579         }
0580     }
0581     if (c.isEmpty()) {
0582         // no charset means us-ascii (RFC 2045), so using local encoding should
0583         // be okay
0584         c = "UTF-8";
0585     }
0586     return c;
0587 }
0588 
0589 QString NodeHelper::fileName(const KMime::Content *node)
0590 {
0591     QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
0592     if (name.isEmpty()) {
0593         name = const_cast<KMime::Content *>(node)->contentType()->name();
0594     }
0595 
0596     name = name.trimmed();
0597     return name;
0598 }
0599 
0600 // FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
0601 Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const
0602 {
0603     const QMap<QString, QMap<QByteArray, Interface::BodyPartMemento *>>::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node));
0604     if (nit == mBodyPartMementoMap.end()) {
0605         return nullptr;
0606     }
0607     const QMap<QByteArray, Interface::BodyPartMemento *>::const_iterator it = nit->find(which.toLower());
0608     return it != nit->end() ? it.value() : nullptr;
0609 }
0610 
0611 // FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
0612 void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento)
0613 {
0614     QMap<QByteArray, Interface::BodyPartMemento *> &mementos = mBodyPartMementoMap[persistentIndex(node)];
0615 
0616     const QByteArray whichLower = which.toLower();
0617     const QMap<QByteArray, Interface::BodyPartMemento *>::iterator it = mementos.lowerBound(whichLower);
0618 
0619     if (it != mementos.end() && it.key() == whichLower) {
0620         delete it.value();
0621         if (memento) {
0622             it.value() = memento;
0623         } else {
0624             mementos.erase(it);
0625         }
0626     } else {
0627         mementos.insert(whichLower, memento);
0628     }
0629 }
0630 
0631 bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const
0632 {
0633     qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node);
0634     return mDisplayEmbeddedNodes.contains(node);
0635 }
0636 
0637 void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded)
0638 {
0639     qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded;
0640     if (displayedEmbedded) {
0641         mDisplayEmbeddedNodes.insert(node);
0642     } else {
0643         mDisplayEmbeddedNodes.remove(node);
0644     }
0645 }
0646 
0647 bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const
0648 {
0649     return mDisplayHiddenNodes.contains(node);
0650 }
0651 
0652 void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden)
0653 {
0654     if (displayedHidden) {
0655         mDisplayHiddenNodes.insert(node);
0656     } else {
0657         mDisplayEmbeddedNodes.remove(node);
0658     }
0659 }
0660 
0661 /*!
0662   Creates a persistent index string that bridges the gap between the
0663   permanent nodes and the temporary ones.
0664 
0665   Used internally for robust indexing.
0666 */
0667 QString NodeHelper::persistentIndex(const KMime::Content *node) const
0668 {
0669     if (!node) {
0670         return {};
0671     }
0672 
0673     QString indexStr = node->index().toString();
0674     if (indexStr.isEmpty()) {
0675         QMapIterator<KMime::Message::Content *, QList<KMime::Content *>> it(mExtraContents);
0676         while (it.hasNext()) {
0677             it.next();
0678             const auto &extraNodes = it.value();
0679             for (int i = 0; i < extraNodes.size(); ++i) {
0680                 if (extraNodes[i] == node) {
0681                     indexStr = QStringLiteral("e%1").arg(i);
0682                     const QString parentIndex = persistentIndex(it.key());
0683                     if (!parentIndex.isEmpty()) {
0684                         indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
0685                     }
0686                     return indexStr;
0687                 }
0688             }
0689         }
0690     } else {
0691         const KMime::Content *const topLevel = node->topLevel();
0692         // if the node is an extra node, prepend the index of the extra node to the url
0693         QMapIterator<KMime::Message::Content *, QList<KMime::Content *>> it(mExtraContents);
0694         while (it.hasNext()) {
0695             it.next();
0696             const QList<KMime::Content *> &extraNodes = extraContents(it.key());
0697             for (int i = 0; i < extraNodes.size(); ++i) {
0698                 KMime::Content *const extraNode = extraNodes[i];
0699                 if (topLevel == extraNode) {
0700                     indexStr.prepend(QStringLiteral("e%1:").arg(i));
0701                     const QString parentIndex = persistentIndex(it.key());
0702                     if (!parentIndex.isEmpty()) {
0703                         indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
0704                     }
0705                     return indexStr;
0706                 }
0707             }
0708         }
0709     }
0710 
0711     return indexStr;
0712 }
0713 
0714 KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
0715 {
0716     if (!node) {
0717         return nullptr;
0718     }
0719     KMime::Content *c = node->topLevel();
0720     if (c) {
0721         const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), Qt::SkipEmptyParts);
0722         const int pathPartsSize(pathParts.size());
0723         for (int i = 0; i < pathPartsSize; ++i) {
0724             const QString &path = pathParts[i];
0725             if (path.startsWith(QLatin1Char('e'))) {
0726                 const QList<KMime::Content *> &extraParts = mExtraContents.value(c);
0727                 const int idx = QStringView(path).mid(1).toInt();
0728                 c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
0729             } else {
0730                 c = c->content(KMime::ContentIndex(path));
0731             }
0732             if (!c) {
0733                 break;
0734             }
0735         }
0736     }
0737     return c;
0738 }
0739 
0740 QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
0741 {
0742     return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
0743 }
0744 
0745 QString NodeHelper::asHREF(const KMime::Content *node) const
0746 {
0747     return QStringLiteral("attachment:%1").arg(persistentIndex(node));
0748 }
0749 
0750 KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
0751 {
0752     if (url.isEmpty()) {
0753         return mMessage.data();
0754     }
0755 
0756     if (!url.isLocalFile()) {
0757         return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
0758     } else {
0759         const QString path = url.toLocalFile();
0760         const QString extractedPath = extractAttachmentIndex(path);
0761         if (!extractedPath.isEmpty()) {
0762             return contentFromIndex(mMessage.data(), extractedPath);
0763         }
0764         return mMessage.data();
0765     }
0766 }
0767 
0768 QString NodeHelper::extractAttachmentIndex(const QString &path) const
0769 {
0770     // extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
0771     // start of the index is something that is not a number followed by a dot: \D.
0772     // index is only made of numbers,"." and ":": ([0-9.:]+)
0773     // index is the last part of the folder name: /
0774     static const QRegularExpression re(QStringLiteral("\\D\\.([e0-9.:]+)/"));
0775     QRegularExpressionMatch rmatch;
0776     path.lastIndexOf(re, -1, &rmatch);
0777     if (rmatch.hasMatch()) {
0778         return rmatch.captured(1);
0779     }
0780     return {};
0781 }
0782 
0783 QString NodeHelper::fixEncoding(const QString &encoding)
0784 {
0785     QString returnEncoding = encoding;
0786     // According to https://www.iana.org/assignments/character-sets, uppercase is
0787     // preferred in MIME headers
0788     const QString returnEncodingToUpper = returnEncoding.toUpper();
0789     if (returnEncodingToUpper.contains(QLatin1StringView("ISO "))) {
0790         returnEncoding = returnEncodingToUpper;
0791         returnEncoding.replace(QLatin1StringView("ISO "), QStringLiteral("ISO-"));
0792     }
0793     return returnEncoding;
0794 }
0795 
0796 //-----------------------------------------------------------------------------
0797 QString NodeHelper::encodingForName(const QString &descriptiveName)
0798 {
0799     const QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
0800     return NodeHelper::fixEncoding(encoding);
0801 }
0802 
0803 QStringList NodeHelper::supportedEncodings(bool usAscii)
0804 {
0805     QStringList encodingNames = KCharsets::charsets()->availableEncodingNames();
0806     QStringList encodings;
0807     QMap<QString, bool> mimeNames;
0808     QStringList::ConstIterator constEnd(encodingNames.constEnd());
0809     for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) {
0810         QStringDecoder codec((*it).toLatin1().constData());
0811         const QString mimeName = (codec.isValid()) ? QString::fromLatin1(codec.name()).toLower() : (*it);
0812         if (!mimeNames.contains(mimeName)) {
0813             encodings.append(KCharsets::charsets()->descriptionForEncoding(*it));
0814             mimeNames.insert(mimeName, true);
0815         }
0816     }
0817     encodings.sort();
0818     if (usAscii) {
0819         encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
0820     }
0821     return encodings;
0822 }
0823 
0824 QString NodeHelper::fromAsString(KMime::Content *node) const
0825 {
0826     if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
0827         return topLevel->from()->asUnicodeString();
0828     } else {
0829         auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QList<KMime::Content *> &nodes) {
0830             return nodes.contains(node);
0831         });
0832         if (realNode != mExtraContents.cend()) {
0833             return fromAsString(realNode.key());
0834         }
0835     }
0836 
0837     return {};
0838 }
0839 
0840 void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
0841 {
0842     qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
0843     mExtraContents[topLevelNode].append(content);
0844 }
0845 
0846 void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode)
0847 {
0848     qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode;
0849     mExtraContents[topLevelNode].clear();
0850 }
0851 
0852 QList<KMime::Content *> NodeHelper::extraContents(KMime::Content *topLevelnode) const
0853 {
0854     return mExtraContents.value(topLevelnode);
0855 }
0856 
0857 void NodeHelper::mergeExtraNodes(KMime::Content *node)
0858 {
0859     if (!node) {
0860         return;
0861     }
0862 
0863     const QList<KMime::Content *> extraNodes = extraContents(node);
0864     for (KMime::Content *extra : extraNodes) {
0865         if (node->bodyIsMessage()) {
0866             qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
0867                                           << node->encodedContent() << "\n====== with =======\n"
0868                                           << extra << extra->encodedContent();
0869             continue;
0870         }
0871         auto c = new KMime::Content(node);
0872         c->setContent(extra->encodedContent());
0873         c->parse();
0874         node->appendContent(c);
0875     }
0876 
0877     const auto contents{node->contents()};
0878     for (KMime::Content *child : contents) {
0879         mergeExtraNodes(child);
0880     }
0881 }
0882 
0883 void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
0884 {
0885     if (!node) {
0886         return;
0887     }
0888     const QList<KMime::Content *> extraNodes = extraContents(node);
0889     for (KMime::Content *extra : extraNodes) {
0890         QByteArray s = extra->encodedContent();
0891         const auto children = node->contents();
0892         for (KMime::Content *c : children) {
0893             if (c->encodedContent() == s) {
0894                 node->takeContent(c);
0895             }
0896         }
0897     }
0898     const auto contents{node->contents()};
0899     for (KMime::Content *child : contents) {
0900         cleanFromExtraNodes(child);
0901     }
0902 }
0903 
0904 KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
0905 {
0906     /*The merge is done in several steps:
0907       1) merge the extra nodes into topLevelNode
0908       2) copy the modified (merged) node tree into a new node tree
0909       3) restore the original node tree in topLevelNode by removing the extra nodes from it
0910 
0911       The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
0912     */
0913     if (!topLevelNode) {
0914         return nullptr;
0915     }
0916 
0917     mergeExtraNodes(topLevelNode);
0918 
0919     auto m = new KMime::Message;
0920     m->setContent(topLevelNode->encodedContent());
0921     m->parse();
0922 
0923     cleanFromExtraNodes(topLevelNode);
0924     //   qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
0925     //   qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
0926 
0927     return m;
0928 }
0929 
0930 KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
0931 {
0932     const QList<KMime::Content *> xc = extraContents(content);
0933     if (!xc.empty()) {
0934         if (xc.size() == 1) {
0935             return xc.front();
0936         } else {
0937             qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
0938         }
0939     }
0940     return nullptr;
0941 }
0942 
0943 bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel)
0944 {
0945     bool returnValue = false;
0946     if (node) {
0947         KMime::Content *curNode = node;
0948         KMime::Content *decryptedNode = nullptr;
0949         const QByteArray type = node->contentType(false) ? QByteArray(node->contentType(false)->mediaType()).toLower() : "text";
0950         const QByteArray subType = node->contentType(false) ? node->contentType(false)->subType().toLower() : "plain";
0951         const bool isMultipart = node->contentType(false) && node->contentType(false)->isMultipart();
0952         bool isSignature = false;
0953 
0954         qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
0955 
0956         if (isMultipart) {
0957             if (subType == "signed") {
0958                 isSignature = true;
0959             } else if (subType == "encrypted") {
0960                 decryptedNode = decryptedNodeForContent(curNode);
0961             }
0962         } else if (type == "application") {
0963             if (subType == "octet-stream") {
0964                 decryptedNode = decryptedNodeForContent(curNode);
0965             } else if (subType == "pkcs7-signature") {
0966                 isSignature = true;
0967             } else if (subType == "pkcs7-mime") {
0968                 // note: subtype pkcs7-mime can also be signed
0969                 //       and we do NOT want to remove the signature!
0970                 if (encryptionState(curNode) != KMMsgNotEncrypted) {
0971                     decryptedNode = decryptedNodeForContent(curNode);
0972                 }
0973             }
0974         }
0975 
0976         if (decryptedNode) {
0977             qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
0978                                            "and then processing the children.";
0979 
0980             Q_ASSERT(addHeaders);
0981             KMime::Content headers;
0982             headers.setHead(curNode->head());
0983             headers.parse();
0984             if (auto ct = decryptedNode->contentType(false)) {
0985                 headers.contentType()->from7BitString(ct->as7BitString(false));
0986             } else {
0987                 headers.removeHeader<KMime::Headers::ContentType>();
0988             }
0989             if (auto ct = decryptedNode->contentTransferEncoding(false)) {
0990                 headers.contentTransferEncoding()->from7BitString(ct->as7BitString(false));
0991             } else {
0992                 headers.removeHeader<KMime::Headers::ContentTransferEncoding>();
0993             }
0994             if (auto cd = decryptedNode->contentDisposition(false)) {
0995                 headers.contentDisposition()->from7BitString(cd->as7BitString(false));
0996             } else {
0997                 headers.removeHeader<KMime::Headers::ContentDisposition>();
0998             }
0999             if (auto cd = decryptedNode->contentDescription(false)) {
1000                 headers.contentDescription()->from7BitString(cd->as7BitString(false));
1001             } else {
1002                 headers.removeHeader<KMime::Headers::ContentDescription>();
1003             }
1004             headers.assemble();
1005 
1006             resultingData += headers.head() + '\n';
1007             unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);
1008 
1009             returnValue = true;
1010         } else if (isSignature) {
1011             qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
1012             // We can't change the nodes under the signature, as that would invalidate it. Add the signature
1013             // and its child as-is
1014             if (addHeaders) {
1015                 resultingData += curNode->head() + '\n';
1016             }
1017             resultingData += curNode->encodedBody();
1018             returnValue = false;
1019         } else if (isMultipart) {
1020             qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
1021             // Normal multipart node, add the header and all of its children
1022             bool somethingChanged = false;
1023             if (addHeaders) {
1024                 resultingData += curNode->head() + '\n';
1025             }
1026             const QByteArray boundary = curNode->contentType()->boundary();
1027             const auto contents = curNode->contents();
1028             for (KMime::Content *child : contents) {
1029                 resultingData += "\n--" + boundary + '\n';
1030                 const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
1031                 if (changed) {
1032                     somethingChanged = true;
1033                 }
1034             }
1035             resultingData += "\n--" + boundary + "--\n\n";
1036             returnValue = somethingChanged;
1037         } else if (curNode->bodyIsMessage()) {
1038             qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
1039             if (addHeaders) {
1040                 resultingData += curNode->head() + '\n';
1041             }
1042 
1043             returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
1044         } else {
1045             qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
1046             if (addHeaders) {
1047                 resultingData += curNode->head() + '\n';
1048             }
1049             resultingData += curNode->body();
1050             returnValue = false;
1051         }
1052     }
1053 
1054     qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
1055     return returnValue;
1056 }
1057 
1058 KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
1059 {
1060     QByteArray resultingData;
1061     const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
1062     if (messageChanged) {
1063 #if 0
1064         qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
1065         QFile bla("stripped.mbox");
1066         bla.open(QIODevice::WriteOnly);
1067         bla.write(resultingData);
1068         bla.close();
1069 #endif
1070         KMime::Message::Ptr newMessage(new KMime::Message);
1071         newMessage->setContent(resultingData);
1072         newMessage->parse();
1073         return newMessage;
1074     } else {
1075         return {};
1076     }
1077 }
1078 
1079 QList<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
1080 {
1081     QList<KMime::Content *> result;
1082     for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) {
1083         const auto contents = it.value();
1084         for (auto content : contents) {
1085             if (KMime::isAttachment(content)) {
1086                 result.push_back(content);
1087             } else {
1088                 result += content->attachments();
1089             }
1090         }
1091     }
1092     return result;
1093 }
1094 }
1095 
1096 #include "moc_nodehelper.cpp"