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 ¶m) 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"