File indexing completed on 2024-12-22 05:05:18

0001 // SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
0002 // SPDX-License-Identifier: GPL-2.0-only
0003 
0004 #include "mimetreeparser_core_debug.h"
0005 
0006 #include "bodypartformatter.h"
0007 
0008 #include "bodypartformatterbasefactory.h"
0009 #include "bodypartformatterbasefactory_p.h"
0010 
0011 #include "messagepart.h"
0012 #include "objecttreeparser.h"
0013 #include "utils.h"
0014 
0015 #include <KMime/Content>
0016 #include <QGpgME/Protocol>
0017 
0018 using namespace MimeTreeParser;
0019 using namespace MimeTreeParser::Interface;
0020 
0021 namespace MimeTreeParser
0022 {
0023 class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0024 {
0025 };
0026 
0027 class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0028 {
0029 public:
0030     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0031     {
0032         return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(objectTreeParser, node, node->bodyAsMessage()));
0033     }
0034 };
0035 
0036 class HeadersBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0037 {
0038 public:
0039     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0040     {
0041         return MessagePart::Ptr(new HeadersPart(objectTreeParser, node));
0042     }
0043 };
0044 
0045 class MultiPartRelatedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0046 {
0047 public:
0048     QList<MessagePart::Ptr> processList(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0049     {
0050         if (node->contents().isEmpty()) {
0051             return {};
0052         }
0053         // We rely on the order of the parts.
0054         // Theoretically there could also be a Start parameter which would break this..
0055         // https://tools.ietf.org/html/rfc2387#section-4
0056 
0057         // We want to display attachments even if displayed inline.
0058         QList<MessagePart::Ptr> list;
0059         list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0), true)));
0060         for (int i = 1; i < node->contents().size(); i++) {
0061             auto p = node->contents().at(i);
0062             if (KMime::isAttachment(p)) {
0063                 list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, p, true)));
0064             }
0065         }
0066         return list;
0067     }
0068 };
0069 
0070 class MultiPartMixedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0071 {
0072 public:
0073     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0074     {
0075         const auto contents = node->contents();
0076         if (contents.isEmpty()) {
0077             return {};
0078         }
0079 
0080         // we need the intermediate part to preserve the headers (necessary for with protected headers using multipart mixed)
0081         auto part = MessagePart::Ptr(new MessagePart(objectTreeParser, {}, node));
0082         part->appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, contents.at(0), false)));
0083         return part;
0084     }
0085 };
0086 
0087 class ApplicationPGPEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0088 {
0089 public:
0090     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0091     {
0092         if (node->decodedContent().trimmed() != "Version: 1") {
0093             qCWarning(MIMETREEPARSER_CORE_LOG) << "Unknown PGP Version String:" << node->decodedContent().trimmed();
0094         }
0095 
0096         if (!node->parent()) {
0097             return MessagePart::Ptr();
0098         }
0099 
0100         KMime::Content *data = findTypeInDirectChildren(node->parent(), "application/octet-stream");
0101 
0102         if (!data) {
0103             return MessagePart::Ptr(); // new MimeMessagePart(objectTreeParser, node));
0104         }
0105 
0106         EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), QGpgME::openpgp(), node, data));
0107         mp->setIsEncrypted(true);
0108         return mp;
0109     }
0110 };
0111 
0112 class ApplicationPkcs7MimeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0113 {
0114 public:
0115     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0116     {
0117         if (node->head().isEmpty()) {
0118             return MessagePart::Ptr();
0119         }
0120 
0121         const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower();
0122 
0123         if (smimeType == QLatin1StringView("certs-only")) {
0124             return CertMessagePart::Ptr(new CertMessagePart(objectTreeParser, node, QGpgME::smime()));
0125         }
0126 
0127         bool isSigned = (smimeType == QLatin1StringView("signed-data"));
0128         bool isEncrypted = (smimeType == QLatin1StringView("enveloped-data"));
0129 
0130         // Analyze "signTestNode" node to find/verify a signature.
0131         // If zero part.objectTreeParser verification was successfully done after
0132         // decrypting via recursion by insertAndParseNewChildNode().
0133         KMime::Content *signTestNode = isEncrypted ? nullptr : node;
0134 
0135         // We try decrypting the content
0136         // if we either *know* that it is an encrypted message part
0137         // or there is neither signed nor encrypted parameter.
0138         MessagePart::Ptr mp;
0139         if (!isSigned) {
0140             if (isEncrypted) {
0141                 qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime     ==      S/MIME TYPE: enveloped (encrypted) data";
0142             } else {
0143                 qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime  -  type unknown  -  enveloped (encrypted) data ?";
0144             }
0145 
0146             auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, node->decodedText(), QGpgME::smime(), node));
0147             mp = _mp;
0148             _mp->setIsEncrypted(true);
0149             // PartMetaData *messagePart(_mp->partMetaData());
0150             // if (!part.source()->decryptMessage()) {
0151             // isEncrypted = true;
0152             signTestNode = nullptr; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
0153             // } else {
0154             //     _mp->startDecryption();
0155             //     if (messagePart->isDecryptable) {
0156             //         qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime  -  encryption found  -  enveloped (encrypted) data !";
0157             //         isEncrypted = true;
0158             //         part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted);
0159             //         signTestNode = nullptr;
0160 
0161             //     } else {
0162             //         // decryption failed, which could be because the part was encrypted but
0163             //         // decryption failed, or because we didn't know if it was encrypted, tried,
0164             //         // and failed. If the message was not actually encrypted, we continue
0165             //         // assuming it's signed
0166             //         if (_mp->passphraseError() || (smimeType.isEmpty() && messagePart->isEncrypted)) {
0167             //             isEncrypted = true;
0168             //             signTestNode = nullptr;
0169             //         }
0170 
0171             //         if (isEncrypted) {
0172             //             qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime  -  ERROR: COULD NOT DECRYPT enveloped data !";
0173             //         } else {
0174             //             qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime  -  NO encryption found";
0175             //         }
0176             //     }
0177             // }
0178         }
0179 
0180         // We now try signature verification if necessarry.
0181         if (signTestNode) {
0182             if (isSigned) {
0183                 qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime     ==      S/MIME TYPE: opaque signed data";
0184             } else {
0185                 qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime  -  type unknown  -  opaque signed data ?";
0186             }
0187 
0188             return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, QGpgME::smime(), nullptr, signTestNode));
0189         }
0190         return mp;
0191     }
0192 };
0193 
0194 class MultiPartAlternativeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0195 {
0196 public:
0197     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0198     {
0199         if (node->contents().isEmpty()) {
0200             return MessagePart::Ptr();
0201         }
0202 
0203         AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(objectTreeParser, node));
0204         if (mp->mChildParts.isEmpty()) {
0205             return MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
0206         }
0207         return mp;
0208     }
0209 };
0210 
0211 class MultiPartEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0212 {
0213 public:
0214     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0215     {
0216         if (node->contents().isEmpty()) {
0217             Q_ASSERT(false);
0218             return MessagePart::Ptr();
0219         }
0220 
0221         const QGpgME::Protocol *protocol = nullptr;
0222 
0223         /*
0224         ATTENTION: This code is to be replaced by the new 'auto-detect' feature. --------------------------------------
0225         */
0226         KMime::Content *data = findTypeInDirectChildren(node, "application/octet-stream");
0227         if (data) {
0228             protocol = QGpgME::openpgp();
0229         } else {
0230             data = findTypeInDirectChildren(node, "application/pkcs7-mime");
0231             if (data) {
0232                 protocol = QGpgME::smime();
0233             }
0234         }
0235         /*
0236         ---------------------------------------------------------------------------------------------------------------
0237         */
0238 
0239         if (!data) {
0240             return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
0241         }
0242 
0243         EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), protocol, node, data));
0244         mp->setIsEncrypted(true);
0245         return mp;
0246     }
0247 };
0248 
0249 class MultiPartSignedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0250 {
0251 public:
0252     static const QGpgME::Protocol *detectProtocol(const QString &protocolContentType_, const QString &signatureContentType)
0253     {
0254         auto protocolContentType = protocolContentType_;
0255         if (protocolContentType.isEmpty()) {
0256             qCWarning(MIMETREEPARSER_CORE_LOG) << "Message doesn't set the protocol for the multipart/signed content-type, "
0257                                                   "using content-type of the signature:"
0258                                                << signatureContentType;
0259             protocolContentType = signatureContentType;
0260         }
0261 
0262         const QGpgME::Protocol *protocol = nullptr;
0263         if (protocolContentType == QLatin1StringView("application/pkcs7-signature")
0264             || protocolContentType == QLatin1StringView("application/x-pkcs7-signature")) {
0265             protocol = QGpgME::smime();
0266         } else if (protocolContentType == QLatin1StringView("application/pgp-signature")
0267                    || protocolContentType == QLatin1StringView("application/x-pgp-signature")) {
0268             protocol = QGpgME::openpgp();
0269         }
0270         return protocol;
0271     }
0272 
0273     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0274     {
0275         if (node->contents().size() != 2) {
0276             qCDebug(MIMETREEPARSER_CORE_LOG) << "mulitpart/signed must have exactly two child parts!" << Qt::endl << "processing as multipart/mixed";
0277             if (!node->contents().isEmpty()) {
0278                 return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0)));
0279             } else {
0280                 return MessagePart::Ptr();
0281             }
0282         }
0283 
0284         KMime::Content *signedData = node->contents().at(0);
0285         KMime::Content *signature = node->contents().at(1);
0286         Q_ASSERT(signedData);
0287         Q_ASSERT(signature);
0288 
0289         auto protocol = detectProtocol(node->contentType()->parameter(QStringLiteral("protocol")).toLower(),
0290                                        QLatin1StringView(signature->contentType()->mimeType().toLower()));
0291 
0292         if (!protocol) {
0293             return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, signedData));
0294         }
0295 
0296         return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, protocol, signature, signedData));
0297     }
0298 };
0299 
0300 class TextHtmlBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0301 {
0302 public:
0303     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0304     {
0305         return HtmlMessagePart::Ptr(new HtmlMessagePart(objectTreeParser, node));
0306     }
0307 };
0308 
0309 class TextPlainBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
0310 {
0311 public:
0312     MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE
0313     {
0314         if (KMime::isAttachment(node)) {
0315             return AttachmentMessagePart::Ptr(new AttachmentMessagePart(objectTreeParser, node));
0316         }
0317         return TextMessagePart::Ptr(new TextMessagePart(objectTreeParser, node));
0318     }
0319 };
0320 
0321 } // anon namespace
0322 
0323 void BodyPartFormatterBaseFactoryPrivate::messageviewer_create_builtin_bodypart_formatters()
0324 {
0325     auto any = new AnyTypeBodyPartFormatter;
0326     auto textPlain = new TextPlainBodyPartFormatter;
0327     auto pkcs7 = new ApplicationPkcs7MimeBodyPartFormatter;
0328     auto pgp = new ApplicationPGPEncryptedBodyPartFormatter;
0329     auto html = new TextHtmlBodyPartFormatter;
0330     auto headers = new HeadersBodyPartFormatter;
0331     auto multipartAlternative = new MultiPartAlternativeBodyPartFormatter;
0332     auto multipartMixed = new MultiPartMixedBodyPartFormatter;
0333     auto multipartSigned = new MultiPartSignedBodyPartFormatter;
0334     auto multipartEncrypted = new MultiPartEncryptedBodyPartFormatter;
0335     auto message = new MessageRfc822BodyPartFormatter;
0336     auto multipartRelated = new MultiPartRelatedBodyPartFormatter;
0337 
0338     insert("application", "octet-stream", any);
0339     insert("application", "pgp", textPlain);
0340     insert("application", "pkcs7-mime", pkcs7);
0341     insert("application", "x-pkcs7-mime", pkcs7);
0342     insert("application", "pgp-encrypted", pgp);
0343     insert("application", "*", any);
0344 
0345     insert("text", "html", html);
0346     insert("text", "rtf", any);
0347     insert("text", "plain", textPlain);
0348     insert("text", "rfc822-headers", headers);
0349     insert("text", "*", textPlain);
0350 
0351     insert("image", "*", any);
0352 
0353     insert("message", "rfc822", message);
0354     insert("message", "*", any);
0355 
0356     insert("multipart", "alternative", multipartAlternative);
0357     insert("multipart", "encrypted", multipartEncrypted);
0358     insert("multipart", "signed", multipartSigned);
0359     insert("multipart", "related", multipartRelated);
0360     insert("multipart", "*", multipartMixed);
0361     insert("*", "*", any);
0362 }