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

0001 /*
0002     objecttreeparser.cpp
0003 
0004     This file is part of KMail, the KDE mail client.
0005     SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
0006     SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
0007     SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
0008     SPDX-FileCopyrightText: 2015 Sandro Knauß <sknauss@kde.org>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 // MessageViewer includes
0014 
0015 #include "objecttreeparser.h"
0016 
0017 #include "bodypartformatterfactory.h"
0018 #include "messagepart.h"
0019 #include "nodehelper.h"
0020 #include "partnodebodypart.h"
0021 
0022 #include "bodyformatter/utils.h"
0023 #include "interfaces/bodypartformatter.h"
0024 #include "utils/util.h"
0025 
0026 #include <KMime/Headers>
0027 #include <KMime/Message>
0028 
0029 // Qt includes
0030 #include <QByteArray>
0031 
0032 using namespace MimeTreeParser;
0033 
0034 ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser)
0035     : mSource(topLevelParser->mSource)
0036     , mNodeHelper(topLevelParser->mNodeHelper)
0037     , mTopLevelContent(topLevelParser->mTopLevelContent)
0038     , mAllowAsync(topLevelParser->mAllowAsync)
0039 {
0040     init();
0041 }
0042 
0043 ObjectTreeParser::ObjectTreeParser(Interface::ObjectTreeSource *source, MimeTreeParser::NodeHelper *nodeHelper)
0044     : mSource(source)
0045     , mNodeHelper(nodeHelper)
0046     , mTopLevelContent(nullptr)
0047     , mAllowAsync(false)
0048 {
0049     init();
0050 }
0051 
0052 void ObjectTreeParser::init()
0053 {
0054     Q_ASSERT(mSource);
0055 
0056     if (!mNodeHelper) {
0057         mNodeHelper = new NodeHelper();
0058         mDeleteNodeHelper = true;
0059     } else {
0060         mDeleteNodeHelper = false;
0061     }
0062 }
0063 
0064 ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other)
0065     : mSource(other.mSource)
0066     , mNodeHelper(other.nodeHelper())
0067     , mTopLevelContent(other.mTopLevelContent)
0068     , mHasPendingAsyncJobs(other.hasPendingAsyncJobs())
0069     , mAllowAsync(other.allowAsync())
0070     , mDeleteNodeHelper(false)
0071 {
0072 }
0073 
0074 ObjectTreeParser::~ObjectTreeParser()
0075 {
0076     if (mDeleteNodeHelper) {
0077         delete mNodeHelper;
0078         mNodeHelper = nullptr;
0079     }
0080 }
0081 
0082 void ObjectTreeParser::setAllowAsync(bool allow)
0083 {
0084     Q_ASSERT(!mHasPendingAsyncJobs);
0085     mAllowAsync = allow;
0086 }
0087 
0088 bool ObjectTreeParser::allowAsync() const
0089 {
0090     return mAllowAsync;
0091 }
0092 
0093 bool ObjectTreeParser::hasPendingAsyncJobs() const
0094 {
0095     return mHasPendingAsyncJobs;
0096 }
0097 
0098 QString ObjectTreeParser::plainTextContent() const
0099 {
0100     return mPlainTextContent;
0101 }
0102 
0103 QString ObjectTreeParser::htmlContent() const
0104 {
0105     return mHtmlContent;
0106 }
0107 
0108 //-----------------------------------------------------------------------------
0109 
0110 void ObjectTreeParser::parseObjectTree(KMime::Content *node, bool parseOnlySingleNode)
0111 {
0112     mTopLevelContent = node;
0113     mParsedPart = parseObjectTreeInternal(node, parseOnlySingleNode);
0114 
0115     if (mParsedPart) {
0116         mParsedPart->fix();
0117         if (auto mp = toplevelTextNode(mParsedPart)) {
0118             if (auto _mp = mp.dynamicCast<TextMessagePart>()) {
0119                 extractNodeInfos(_mp->content(), true);
0120             } else if (auto _mp = mp.dynamicCast<AlternativeMessagePart>()) {
0121                 if (_mp->childParts().contains(Util::MultipartPlain)) {
0122                     extractNodeInfos(_mp->childParts()[Util::MultipartPlain]->content(), true);
0123                 }
0124             }
0125             setPlainTextContent(mp->text());
0126         }
0127     }
0128 }
0129 
0130 MessagePartPtr ObjectTreeParser::parsedPart() const
0131 {
0132     return mParsedPart;
0133 }
0134 
0135 MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mimeType)
0136 {
0137     const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(QString::fromUtf8(mimeType));
0138     Q_ASSERT(!formatters.empty());
0139     for (auto formatter : formatters) {
0140         PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper);
0141         mNodeHelper->setNodeDisplayedEmbedded(node, true);
0142 
0143         const MessagePart::Ptr result = formatter->process(part);
0144         if (result.isNull()) {
0145             continue;
0146         }
0147 
0148         result->setAttachmentContent(node);
0149         return result;
0150     }
0151 
0152     Q_UNREACHABLE();
0153     return {};
0154 }
0155 
0156 MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart)
0157 {
0158     if (!node) {
0159         return {};
0160     }
0161 
0162     // reset pending async jobs state (we'll rediscover pending jobs as we go)
0163     mHasPendingAsyncJobs = false;
0164 
0165     mNodeHelper->clearOverrideHeaders();
0166 
0167     // reset "processed" flags for...
0168     if (onlyOneMimePart) {
0169         // ... this node and all descendants
0170         mNodeHelper->setNodeUnprocessed(node, false);
0171         if (!node->contents().isEmpty()) {
0172             mNodeHelper->setNodeUnprocessed(node, true);
0173         }
0174     } else if (!node->parent()) {
0175         // ...this node and all it's siblings and descendants
0176         mNodeHelper->setNodeUnprocessed(node, true);
0177     }
0178 
0179     const bool isRoot = node->isTopLevel();
0180     auto parsedPart = MessagePart::Ptr(new MessagePartList(this));
0181     parsedPart->setIsRoot(isRoot);
0182     KMime::Content *parent = node->parent();
0183     auto contents = parent ? parent->contents() : KMime::Content::List();
0184     if (contents.isEmpty()) {
0185         contents.append(node);
0186     }
0187     int i = contents.indexOf(node);
0188     if (i < 0) {
0189         return parsedPart;
0190     } else {
0191         for (; i < contents.size(); ++i) {
0192             node = contents.at(i);
0193             if (mNodeHelper->nodeProcessed(node)) {
0194                 continue;
0195             }
0196 
0197             ProcessResult processResult(mNodeHelper);
0198 
0199             QByteArray mimeType("text/plain");
0200             if (auto ct = node->contentType(false)) {
0201                 mimeType = ct->mimeType();
0202             }
0203             // unfortunately there's many emails where we can't trust the attachment mimetype
0204             // so try to see if we can find something better
0205             if (mimeType == "application/octet-stream") {
0206                 NodeHelper::magicSetType(node);
0207                 mimeType = node->contentType(false)->mimeType();
0208             }
0209 
0210             const auto mp = processType(node, processResult, mimeType);
0211             Q_ASSERT(mp);
0212             parsedPart->appendSubPart(mp);
0213             mNodeHelper->setNodeProcessed(node, false);
0214 
0215             // adjust signed/encrypted flags if inline PGP was found
0216             processResult.adjustCryptoStatesOfNode(node);
0217 
0218             if (onlyOneMimePart) {
0219                 break;
0220             }
0221         }
0222     }
0223     return parsedPart;
0224 }
0225 
0226 KMMsgSignatureState ProcessResult::inlineSignatureState() const
0227 {
0228     return mInlineSignatureState;
0229 }
0230 
0231 void ProcessResult::setInlineSignatureState(KMMsgSignatureState state)
0232 {
0233     mInlineSignatureState = state;
0234 }
0235 
0236 KMMsgEncryptionState ProcessResult::inlineEncryptionState() const
0237 {
0238     return mInlineEncryptionState;
0239 }
0240 
0241 void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state)
0242 {
0243     mInlineEncryptionState = state;
0244 }
0245 
0246 bool ProcessResult::neverDisplayInline() const
0247 {
0248     return mNeverDisplayInline;
0249 }
0250 
0251 void ProcessResult::setNeverDisplayInline(bool display)
0252 {
0253     mNeverDisplayInline = display;
0254 }
0255 
0256 void ProcessResult::adjustCryptoStatesOfNode(const KMime::Content *node) const
0257 {
0258     if ((inlineSignatureState() != KMMsgNotSigned) || (inlineEncryptionState() != KMMsgNotEncrypted)) {
0259         mNodeHelper->setSignatureState(node, inlineSignatureState());
0260         mNodeHelper->setEncryptionState(node, inlineEncryptionState());
0261     }
0262 }
0263 
0264 void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart)
0265 {
0266     if (isFirstTextPart) {
0267         mPlainTextContent += curNode->decodedText();
0268         mPlainTextContentCharset += NodeHelper::charset(curNode);
0269     }
0270 }
0271 
0272 void ObjectTreeParser::setPlainTextContent(const QString &plainTextContent)
0273 {
0274     mPlainTextContent = plainTextContent;
0275 }
0276 
0277 QByteArray ObjectTreeParser::codecNameFor(KMime::Content *node) const
0278 {
0279     Q_ASSERT(node);
0280     if (!mSource->overrideCodecName().isEmpty()) {
0281         return mSource->overrideCodecName();
0282     }
0283     return mNodeHelper->codecName(node);
0284 }
0285 
0286 QByteArray ObjectTreeParser::plainTextContentCharset() const
0287 {
0288     return mPlainTextContentCharset;
0289 }
0290 
0291 QByteArray ObjectTreeParser::htmlContentCharset() const
0292 {
0293     return mHtmlContentCharset;
0294 }
0295 
0296 MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const
0297 {
0298     return mNodeHelper;
0299 }