File indexing completed on 2025-01-19 04:46:51

0001 /*
0002     SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ms_tnef_debug.h"
0008 
0009 #include <MessageViewer/MessagePartRenderPlugin>
0010 #include <MessageViewer/MessagePartRendererBase>
0011 #include <MessageViewer/MessagePartRendererManager>
0012 #include <MessageViewer/MimeType>
0013 
0014 #include <MessageCore/StringUtil>
0015 
0016 #include <MessageViewer/HtmlWriter>
0017 #include <MimeTreeParser/MessagePart>
0018 #include <MimeTreeParser/NodeHelper>
0019 
0020 #include <KCalendarCore/Event>
0021 #include <KCalendarCore/ICalFormat>
0022 #include <KCalendarCore/Incidence>
0023 #include <KCalendarCore/MemoryCalendar>
0024 
0025 #include <KCalUtils/IncidenceFormatter>
0026 
0027 #include <ktnef/formatter.h>
0028 #include <ktnef/ktnefattach.h>
0029 #include <ktnef/ktnefmessage.h>
0030 #include <ktnef/ktnefparser.h>
0031 
0032 #include <KTextTemplate/Template>
0033 
0034 #include <KIconLoader>
0035 #include <KLocalizedString>
0036 
0037 #include <QFile>
0038 
0039 namespace
0040 {
0041 class Formatter : public MessageViewer::MessagePartRendererBase
0042 {
0043 public:
0044     bool render(const MimeTreeParser::MessagePartPtr &msgPart, MessageViewer::HtmlWriter *htmlWriter, MessageViewer::RenderContext *context) const override
0045     {
0046         Q_UNUSED(context)
0047         auto mp = msgPart.dynamicCast<MimeTreeParser::AttachmentMessagePart>();
0048         if (!mp || context->isHiddenHint(msgPart)) {
0049             return false;
0050         }
0051 
0052         const QByteArray mimetype = mp->content()->contentType()->mimeType();
0053         if (mimetype != QByteArrayLiteral("application/vnd.ms-tnef") && mimetype != QByteArrayLiteral("application/ms-tnef")) {
0054             return false;
0055         }
0056 
0057         const auto fileName = mp->temporaryFilePath();
0058         KTnef::KTNEFParser parser;
0059         if (!parser.openFile(fileName) || !parser.message()) {
0060             qCDebug(MS_TNEF_LOG) << "Could not parse" << fileName;
0061             return false;
0062         }
0063 
0064         // Look for an invitation
0065         QString inviteStr;
0066         QFile f(fileName);
0067         QByteArray buf;
0068         if (!f.open(QIODevice::ReadOnly)) {
0069             qCWarning(MS_TNEF_LOG) << "Failed to read attachment part: " << f.errorString();
0070         } else {
0071             buf = f.readAll();
0072             f.close();
0073         }
0074         if (!buf.isEmpty()) {
0075             const KCalendarCore::MemoryCalendar::Ptr cl(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone()));
0076             KCalUtils::InvitationFormatterHelper helper;
0077             const QString invite = KTnef::formatTNEFInvitation(buf, cl, &helper);
0078             KCalendarCore::ICalFormat format;
0079             const KCalendarCore::Incidence::Ptr inc = format.fromString(invite);
0080             const KCalendarCore::Event::Ptr event = inc.dynamicCast<KCalendarCore::Event>();
0081             if (event && event->hasEndDate()) {
0082                 // no enddate => not a valid invitation
0083                 inviteStr = KCalUtils::IncidenceFormatter::extensiveDisplayStr(cl, inc);
0084             }
0085         }
0086 
0087         auto c = MessageViewer::MessagePartRendererManager::self()->createContext();
0088         c.insert(QStringLiteral("block"), msgPart.data());
0089         c.insert(QStringLiteral("showOnlyOneMimePart"), context->showOnlyOneMimePart());
0090         c.insert(QStringLiteral("content"), QVariant::fromValue<MessageViewer::KTextTemplateCallback>([&](KTextTemplate::OutputStream *stream) {
0091                      const auto tnefatts = parser.message()->attachmentList();
0092                      if (tnefatts.isEmpty() && inviteStr.isEmpty()) {
0093                          qCDebug(MS_TNEF_LOG) << "No attachments or invitation found in" << fileName;
0094                          (*stream) << QStringLiteral("&nbsp;&lt;") << i18nc("TNEF attachment has no content", "empty") << QStringLiteral("&gt;");
0095                          return;
0096                      }
0097 
0098                      if (!inviteStr.isEmpty()) {
0099                          (*stream) << inviteStr;
0100                      }
0101 
0102                      const int numberOfTnef(tnefatts.count());
0103                      for (int i = 0; i < numberOfTnef; ++i) {
0104                          KTnef::KTNEFAttach *att = tnefatts.at(i);
0105                          QString label = att->displayName();
0106                          if (label.isEmpty()) {
0107                              label = att->name();
0108                          }
0109                          label = MessageCore::StringUtil::quoteHtmlChars(label, true);
0110 
0111                          const QString dir = mp->nodeHelper()->createTempDir(QLatin1StringView("ktnef-") + QString::number(i));
0112                          if (!parser.extractFileTo(att->name(), dir)) {
0113                              qCDebug(MS_TNEF_LOG) << "No possible to extract file:" << att->name();
0114                          }
0115 
0116                          // falling back to internal TNEF attachment name if no filename is given for the attached file
0117                          // this follows the logic of KTNEFParser::extractFileTo(...)
0118                          QString attFileName = att->fileName();
0119                          if (attFileName.isEmpty()) {
0120                              attFileName = att->name();
0121                          }
0122                          mp->nodeHelper()->addTempFile(dir + QLatin1Char('/') + attFileName);
0123                          const QString href = QLatin1StringView("file:") + dir + QLatin1Char('/') + attFileName;
0124 
0125                          const QString iconName =
0126                              QUrl::fromLocalFile(MessageViewer::Util::iconPathForMimetype(att->mimeTag(), KIconLoader::Desktop, attFileName)).url();
0127 
0128                          (*stream) << QStringLiteral("<div><a href=\"") << href << QStringLiteral("\"><img src=\"") << iconName
0129                                    << QStringLiteral("\" border=\"0\" style=\"max-width: 100%\"/>") << label << QStringLiteral("</a></div><br/>");
0130                      }
0131                  }));
0132 
0133         auto t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("textmessagepart.html"));
0134         KTextTemplate::OutputStream s(htmlWriter->stream());
0135         t->render(&s, &c);
0136         return true;
0137     }
0138 };
0139 
0140 class Plugin : public QObject, public MessageViewer::MessagePartRenderPlugin
0141 {
0142     Q_OBJECT
0143     Q_INTERFACES(MessageViewer::MessagePartRenderPlugin)
0144     Q_PLUGIN_METADATA(IID "com.kde.messageviewer.bodypartformatter" FILE "application_ms-tnef.json")
0145 public:
0146     MessageViewer::MessagePartRendererBase *renderer(int index) override
0147     {
0148         return index == 0 ? new Formatter() : nullptr;
0149     }
0150 };
0151 }
0152 
0153 #include "application_ms-tnef.moc"