diff --git a/messageviewer/src/header/grantleeheaderformatter.cpp b/messageviewer/src/header/grantleeheaderformatter.cpp index 22c0777e..e9dfb6fd 100644 --- a/messageviewer/src/header/grantleeheaderformatter.cpp +++ b/messageviewer/src/header/grantleeheaderformatter.cpp @@ -1,402 +1,415 @@ /* Copyright (C) 2013-2018 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "grantleeheaderformatter.h" #include "headerstyle_util.h" #include "grantleetheme/grantleetheme.h" #include "settings/messageviewersettings.h" #include "utils/iconnamecache.h" #include "config-messageviewer.h" #include +#include #include #include #include #include #include #include #include #include using namespace MessageCore; using namespace MessageViewer; Q_DECLARE_METATYPE(const KMime::Headers::Generics::AddressList *) Q_DECLARE_METATYPE(const KMime::Headers::Generics::MailboxList *) Q_DECLARE_METATYPE(QSharedPointer) Q_DECLARE_METATYPE(const KMime::Headers::Date *) // Read-only introspection of KMime::Headers::Generics::AddressList object. namespace Grantlee { template<> inline QVariant TypeAccessor::lookUp(const KMime::Headers::Generics::AddressList *const object, const QString &property) { if (property == QStringLiteral("nameOnly")) { return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly); } else if (property == QStringLiteral("isSet")) { return !object->asUnicodeString().isEmpty(); } else if (property == QStringLiteral("fullAddress")) { return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress); } else if (property == QStringLiteral("str")) { return object->asUnicodeString(); } else if (property.startsWith(QStringLiteral("expandable"))) { const auto &name = property.mid(10); const QString val = MessageCore::StringUtil::emailAddrAsAnchor( object, MessageCore::StringUtil::DisplayFullAddress, QString(), MessageCore::StringUtil::ShowLink, MessageCore::StringUtil::ExpandableAddresses, QStringLiteral("Full") + name + QStringLiteral("AddressList")); return val; } return QVariant(); } } // Read-only introspection of KMime::Headers::Generics::MailboxList object. namespace Grantlee { template<> inline QVariant TypeAccessor::lookUp(const KMime::Headers::Generics::MailboxList *const object, const QString &property) { if (property == QStringLiteral("nameOnly")) { return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly); } else if (property == QStringLiteral("isSet")) { return !object->asUnicodeString().isEmpty(); } else if (property == QStringLiteral("fullAddress")) { return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress); } else if (property == QStringLiteral("str")) { return object->asUnicodeString(); } else if (property.startsWith(QStringLiteral("expandable"))) { const auto &name = property.mid(10); const QString val = MessageCore::StringUtil::emailAddrAsAnchor( object, MessageCore::StringUtil::DisplayFullAddress, QString(), MessageCore::StringUtil::ShowLink, MessageCore::StringUtil::ExpandableAddresses, QStringLiteral("Full") + name + QStringLiteral("AddressList")); return val; } return QVariant(); } } GRANTLEE_BEGIN_LOOKUP(QSharedPointer) if (property == QStringLiteral("nameOnly")) { return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayNameOnly); } else if (property == QStringLiteral("isSet")) { return !object->asUnicodeString().isEmpty(); } else if (property == QStringLiteral("fullAddress")) { return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayFullAddress); } else if (property == QStringLiteral("str")) { return object->asUnicodeString(); } else if (property.startsWith(QStringLiteral("expandable"))) { const auto &name = property.mid(10); const QString val = MessageCore::StringUtil::emailAddrAsAnchor( object.data(), MessageCore::StringUtil::DisplayFullAddress, QString(), MessageCore::StringUtil::ShowLink, MessageCore::StringUtil::ExpandableAddresses, QStringLiteral("Full") + name + QStringLiteral("AddressList")); return val; } GRANTLEE_END_LOOKUP namespace Grantlee { template<> inline QVariant TypeAccessor::lookUp(const KMime::Headers::Date *const object, const QString &property) { MessageViewer::HeaderStyleUtil::HeaderStyleUtilDateFormat dateFormat; if (property == QStringLiteral("str")) { return HeaderStyleUtil::dateStr(object->dateTime()); } else if (property == QStringLiteral("short")) { dateFormat = MessageViewer::HeaderStyleUtil::ShortDate; } else if (property == QStringLiteral("long")) { dateFormat = MessageViewer::HeaderStyleUtil::CustomDate; } else if (property == QStringLiteral("fancylong")) { dateFormat = MessageViewer::HeaderStyleUtil::FancyLongDate; } else if (property == QStringLiteral("fancyshort")) { dateFormat = MessageViewer::HeaderStyleUtil::FancyShortDate; } else if(property == QStringLiteral("localelong")){ dateFormat = MessageViewer::HeaderStyleUtil::LongDate; } else { return QVariant(); } return HeaderStyleUtil::strToHtml(HeaderStyleUtil::dateString(object, dateFormat)); } } class Q_DECL_HIDDEN MessageViewer::GrantleeHeaderFormatter::Private { public: Private() { Grantlee::registerMetaType(); Grantlee::registerMetaType(); Grantlee::registerMetaType>(); Grantlee::registerMetaType(); iconSize = KIconLoader::global()->currentSize(KIconLoader::Toolbar); engine = new Grantlee::Engine; templateLoader = QSharedPointer( new Grantlee::FileSystemTemplateLoader); engine->addTemplateLoader(templateLoader); } ~Private() { delete engine; } QSharedPointer templateLoader; Grantlee::Engine *engine = nullptr; MessageViewer::HeaderStyleUtil headerStyleUtil; int iconSize; }; GrantleeHeaderFormatter::GrantleeHeaderFormatter() : d(new GrantleeHeaderFormatter::Private) { } GrantleeHeaderFormatter::~GrantleeHeaderFormatter() { delete d; } QString GrantleeHeaderFormatter::toHtml( const GrantleeHeaderFormatter::GrantleeHeaderFormatterSettings &settings) const { QString errorMessage; if (!settings.theme.isValid()) { errorMessage = i18n("Grantlee theme \"%1\" is not valid.", settings.theme.name()); return errorMessage; } d->templateLoader->setTemplateDirs(QStringList() << settings.theme.absolutePath()); Grantlee::Template headerTemplate = d->engine->loadByName(settings.theme.themeFilename()); if (headerTemplate->error()) { errorMessage = headerTemplate->errorString(); return errorMessage; } return format( settings.theme.absolutePath(), headerTemplate, settings.theme.displayExtraVariables(), settings.isPrinting, settings.style, settings.message, settings.showEmoticons); } QString GrantleeHeaderFormatter::toHtml(const QStringList &displayExtraHeaders, const QString &absolutPath, const QString &filename, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool isPrinting) const { d->templateLoader->setTemplateDirs(QStringList() << absolutPath); Grantlee::Template headerTemplate = d->engine->loadByName(filename); if (headerTemplate->error()) { return headerTemplate->errorString(); } return format(absolutPath, headerTemplate, displayExtraHeaders, isPrinting, style, message); } QString GrantleeHeaderFormatter::format(const QString &absolutePath, const Grantlee::Template &headerTemplate, const QStringList &displayExtraHeaders, bool isPrinting, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool showEmoticons) const { QVariantHash headerObject; + const auto nodeHelper = style->nodeHelper(); // However, the direction of the message subject within the header is // determined according to the contents of the subject itself. Since // the "Re:" and "Fwd:" prefixes would always cause the subject to be // considered left-to-right, they are ignored when determining its // direction. const QString absoluteThemePath = QUrl::fromLocalFile(absolutePath + QLatin1Char('/')).url(); headerObject.insert(QStringLiteral("absoluteThemePath"), absoluteThemePath); headerObject.insert(QStringLiteral("applicationDir"), QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral( "ltr")); headerObject.insert(QStringLiteral("subjectDir"), d->headerStyleUtil.subjectDirectionString(message)); headerObject.insert(QStringLiteral("subjecti18n"), i18n("Subject:")); KTextToHTML::Options flags = KTextToHTML::PreserveSpaces; if (showEmoticons) { flags |= KTextToHTML::ReplaceSmileys; } headerObject.insert(QStringLiteral("subject"), d->headerStyleUtil.subjectString(message, flags)); - if (message->to(false)) { + if (nodeHelper->hasMailHeader("to", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("to",message); headerObject.insert(QStringLiteral("toi18n"), i18n("To:")); - headerObject.insert(QStringLiteral("to"), QVariant::fromValue(static_cast(message->to()))); + headerObject.insert(QStringLiteral("to"), QVariant::fromValue(static_cast(value))); } - if (message->replyTo(false)) { + if (nodeHelper->hasMailHeader("replyTo", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("replyTo",message); headerObject.insert(QStringLiteral("replyToi18n"), i18n("Reply to:")); - headerObject.insert(QStringLiteral("replyTo"), QVariant::fromValue(static_cast(message->replyTo()))); + headerObject.insert(QStringLiteral("replyTo"), QVariant::fromValue(static_cast(value))); } - if (message->cc(false)) { + if (nodeHelper->hasMailHeader("cc", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("cc",message); headerObject.insert(QStringLiteral("cci18n"), i18n("CC:")); - headerObject.insert(QStringLiteral("cc"), QVariant::fromValue(static_cast(message->cc()))); + headerObject.insert(QStringLiteral("cc"), QVariant::fromValue(static_cast(value))); } - if (message->bcc(false)) { + if (nodeHelper->hasMailHeader("bcc", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("bcc",message); headerObject.insert(QStringLiteral("bcci18n"), i18n("BCC:")); - headerObject.insert(QStringLiteral("bcc"), QVariant::fromValue(static_cast(message->bcc()))); + headerObject.insert(QStringLiteral("bcc"), QVariant::fromValue(static_cast(value))); } - headerObject.insert(QStringLiteral("fromi18n"), i18n("From:")); - headerObject.insert(QStringLiteral("from"), QVariant::fromValue(static_cast(message->from()))); + { + const auto value = nodeHelper->mailHeaderAsAddressList("from",message); + headerObject.insert(QStringLiteral("fromi18n"), i18n("From:")); + headerObject.insert(QStringLiteral("from"), QVariant::fromValue(static_cast(value))); + } //Sender headerObject.insert(QStringLiteral("senderi18n"), i18n("Sender:")); headerObject.insert(QStringLiteral("sender"), - HeaderStyleUtil::strToHtml(message->sender()->asUnicodeString())); + HeaderStyleUtil::strToHtml(nodeHelper->mailHeaderAsBase("sender", message)->asUnicodeString())); headerObject.insert(QStringLiteral("listidi18n"), i18n("List-Id:")); - if (auto hrd = message->headerByType("List-Id")) { - headerObject.insert(QStringLiteral("listid"), hrd->asUnicodeString()); + if (nodeHelper->hasMailHeader("List-Id", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("List-Id", message); + headerObject.insert(QStringLiteral("listid"), value->asUnicodeString()); } const QString spamHtml = d->headerStyleUtil.spamStatus(message); if (!spamHtml.isEmpty()) { headerObject.insert(QStringLiteral("spamstatusi18n"), i18n("Spam Status:")); headerObject.insert(QStringLiteral("spamHTML"), spamHtml); } - headerObject.insert(QStringLiteral("datei18n"), i18n("Date:")); - headerObject.insert(QStringLiteral("date"), QVariant::fromValue(static_cast(message->date()))); + { + const auto value = nodeHelper->dateHeader(message); + headerObject.insert(QStringLiteral("datei18n"), i18n("Date:")); + // TODO: rewrite that QDateTime is expected in GRANTLEE + headerObject.insert(QStringLiteral("date"), QVariant::fromValue(static_cast(message->date()))); + } - if (message->hasHeader("Resent-From")) { + if (nodeHelper->hasMailHeader("Resent-From", message)) { + const auto value = nodeHelper->mailHeaderAsAddressList("Resent-From", message); headerObject.insert(QStringLiteral("resentfromi18n"), i18n("resent from")); - headerObject.insert(QStringLiteral("resentfrom"), - QVariant::fromValue(HeaderStyleUtil::resentFromList(message))); + headerObject.insert(QStringLiteral("resentfrom"), QVariant::fromValue(static_cast(value))); } - if (message->hasHeader("Resent-To")) { - auto resentTo = HeaderStyleUtil::resentToList(message); + if (nodeHelper->hasMailHeader("Resent-To", message)) { + const auto resentTo = nodeHelper->mailHeaderAsAddressList("Resent-From", message); headerObject.insert(QStringLiteral("resenttoi18n"), i18np("receiver was", "receivers were", resentTo->mailboxes().count())); - headerObject.insert(QStringLiteral("resentto"), - QVariant::fromValue(HeaderStyleUtil::resentToList(message))); + headerObject.insert(QStringLiteral("resentto"), QVariant::fromValue(static_cast(resentTo))); } if (auto organization = message->organization(false)) { headerObject.insert(QStringLiteral("organization"), HeaderStyleUtil::strToHtml(organization->asUnicodeString())); } if (!style->vCardName().isEmpty()) { headerObject.insert(QStringLiteral("vcardname"), style->vCardName()); } if (!style->collectionName().isEmpty()) { headerObject.insert(QStringLiteral("collectionname"), style->collectionName()); } if (isPrinting) { //provide a bit more left padding when printing //kolab/issue3254 (printed mail cut at the left side) //Use it just for testing if we are in printing mode headerObject.insert(QStringLiteral("isprinting"), i18n("Printing mode")); headerObject.insert(QStringLiteral("printmode"), QStringLiteral("printmode")); } else { headerObject.insert(QStringLiteral("screenmode"), QStringLiteral("screenmode")); } // colors depend on if it is encapsulated or not QColor fontColor(Qt::white); QString linkColor = QStringLiteral("white"); const QColor activeColor = KColorScheme(QPalette::Active, KColorScheme::Selection).background().color(); QColor activeColorDark = activeColor.darker(130); // reverse colors for encapsulated if (!style->isTopLevel()) { activeColorDark = activeColor.darker(50); fontColor = QColor(Qt::black); linkColor = QStringLiteral("black"); } // 3D borders headerObject.insert(QStringLiteral("activecolordark"), activeColorDark.name()); headerObject.insert(QStringLiteral("fontcolor"), fontColor.name()); headerObject.insert(QStringLiteral("linkcolor"), linkColor); MessageViewer::HeaderStyleUtil::xfaceSettings xface = d->headerStyleUtil.xface(style, message); if (!xface.photoURL.isEmpty()) { headerObject.insert(QStringLiteral("photowidth"), xface.photoWidth); headerObject.insert(QStringLiteral("photoheight"), xface.photoHeight); headerObject.insert(QStringLiteral("photourl"), xface.photoURL); } for (QString header : qAsConst(displayExtraHeaders)) { const QByteArray baHeader = header.toLocal8Bit(); if (auto hrd = message->headerByType(baHeader.constData())) { //Grantlee doesn't support '-' in variable name => remove it. header = header.remove(QLatin1Char('-')); headerObject.insert(header, hrd->asUnicodeString()); } } headerObject.insert(QStringLiteral("vcardi18n"), i18n("[vcard]")); headerObject.insert(QStringLiteral("readOnlyMessage"), style->readOnlyMessage()); const bool messageHasAttachment = KMime::hasAttachment(message); headerObject.insert(QStringLiteral("hasAttachment"), messageHasAttachment); headerObject.insert(QStringLiteral("attachmentHtml"), style->attachmentHtml()); if (messageHasAttachment) { const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral( "mail-attachment"), KIconLoader::Toolbar); const QString html = QStringLiteral("").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize)); headerObject.insert(QStringLiteral("attachmentIcon"), html); } const bool messageIsSigned = KMime::isSigned(message); headerObject.insert(QStringLiteral("messageIsSigned"), messageIsSigned); if (messageIsSigned) { const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral( "mail-signed"), KIconLoader::Toolbar); const QString html = QStringLiteral("").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize)); headerObject.insert(QStringLiteral("signedIcon"), html); } const bool messageIsEncrypted = KMime::isEncrypted(message); headerObject.insert(QStringLiteral("messageIsEncrypted"), messageIsEncrypted); if (messageIsEncrypted) { const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral( "mail-encrypted"), KIconLoader::Toolbar); const QString html = QStringLiteral("").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize)); headerObject.insert(QStringLiteral("encryptedIcon"), html); } const bool messageHasSecurityInfo = messageIsEncrypted || messageIsSigned; headerObject.insert(QStringLiteral("messageHasSecurityInfo"), messageHasSecurityInfo); headerObject.insert(QStringLiteral("messageHasSecurityInfoI18n"), i18n("Security:")); QVariantHash mapping; mapping.insert(QStringLiteral("header"), headerObject); Grantlee::Context context(mapping); return headerTemplate->render(&context); } diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp index 41611d74..da5a6d89 100644 --- a/mimetreeparser/src/nodehelper.cpp +++ b/mimetreeparser/src/nodehelper.cpp @@ -1,1038 +1,1071 @@ /* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nodehelper.h" #include "mimetreeparser_debug.h" #include "partmetadata.h" #include "interfaces/bodypart.h" #include "temporaryfile/attachmenttemporaryfilesdirs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MimeTreeParser { QStringList replySubjPrefixes(QStringList() << QStringLiteral("Re\\s*:") << QStringLiteral("Re\\[\\d+\\]:") << QStringLiteral("Re\\d+:")); QStringList forwardSubjPrefixes(QStringList() << QStringLiteral("Fwd:") << QStringLiteral("FW:")); NodeHelper::NodeHelper() : mAttachmentFilesDir(new AttachmentTemporaryFilesDirs()) { //TODO(Andras) add methods to modify these prefixes mLocalCodec = QTextCodec::codecForLocale(); // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. // (Introduction to i18n, 6.6 Limit of Locale technology): // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP // is the standard for Internet, and Shift-JIS is the encoding // for Windows and Macintosh. if (mLocalCodec) { const QByteArray codecNameLower = mLocalCodec->name().toLower(); if (codecNameLower == "eucjp" #if defined Q_OS_WIN || defined Q_OS_MACX || codecNameLower == "shift-jis" // OK? #endif ) { mLocalCodec = QTextCodec::codecForName("jis7"); // QTextCodec *cdc = QTextCodec::codecForName("jis7"); // QTextCodec::setCodecForLocale(cdc); // KLocale::global()->setEncoding(cdc->mibEnum()); } } } NodeHelper::~NodeHelper() { if (mAttachmentFilesDir) { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } clear(); } void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.append(node); qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString(); //<< " decodedContent" << node->decodedContent(); if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeProcessed(c, true); } } } void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse) { if (!node) { return; } mProcessedNodes.removeAll(node); //avoid double addition of extra nodes, eg. encrypted attachments const QMap >::iterator it = mExtraContents.find(node); if (it != mExtraContents.end()) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); mExtraContents.erase(it); } qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node; if (recurse) { const auto contents = node->contents(); for (KMime::Content *c : contents) { setNodeUnprocessed(c, true); } } } bool NodeHelper::nodeProcessed(KMime::Content *node) const { if (!node) { return true; } return mProcessedNodes.contains(node); } static void clearBodyPartMemento(QMap &bodyPartMementoMap) { for (QMap::iterator it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end(); it != end; ++it) { Interface::BodyPartMemento *memento = it.value(); memento->detach(); delete memento; } bodyPartMementoMap.clear(); } void NodeHelper::clear() { mProcessedNodes.clear(); mEncryptionState.clear(); mSignatureState.clear(); mOverrideCodecs.clear(); std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(), &clearBodyPartMemento); mBodyPartMementoMap.clear(); QMap >::ConstIterator end(mExtraContents.constEnd()); for (QMap >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) { Q_FOREACH (KMime::Content *c, it.value()) { KMime::Content *p = c->parent(); if (p) { p->removeContent(c); } } qDeleteAll(it.value()); qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key(); } mExtraContents.clear(); mDisplayEmbeddedNodes.clear(); mDisplayHiddenNodes.clear(); } void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state) { mEncryptionState[node] = state; } KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const { return mEncryptionState.value(node, KMMsgNotEncrypted); } void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state) { mSignatureState[node] = state; } KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const { return mSignatureState.value(node, KMMsgNotSigned); } PartMetaData NodeHelper::partMetaData(KMime::Content *node) { return mPartMetaDatas.value(node, PartMetaData()); } void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData) { mPartMetaDatas.insert(node, metaData); } QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename) { QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } fname += QLatin1Char('/') + filename; QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(QByteArray()); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QString NodeHelper::writeNodeToTempFile(KMime::Content *node) { // If the message part is already written to a file, no point in doing it again. // This function is called twice actually, once from the rendering of the attachment // in the body and once for the header. QUrl existingFileName = tempFileUrlFromNode(node); if (!existingFileName.isEmpty()) { return existingFileName.toLocalFile(); } QString fname = createTempDir(persistentIndex(node)); if (fname.isEmpty()) { return QString(); } QString fileName = NodeHelper::fileName(node); // strip off a leading path int slashPos = fileName.lastIndexOf(QLatin1Char('/')); if (-1 != slashPos) { fileName = fileName.mid(slashPos + 1); } if (fileName.isEmpty()) { fileName = QStringLiteral("unnamed"); } fname += QLatin1Char('/') + fileName; qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname; QByteArray data = node->decodedContent(); if (node->contentType()->isText() && !data.isEmpty()) { // convert CRLF to LF before writing text attachments to disk data = KMime::CRLFtoLF(data); } QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString(); mAttachmentFilesDir->addTempFile(fname); return QString(); } f.write(data); mAttachmentFilesDir->addTempFile(fname); // make file read-only so that nobody gets the impression that he might // edit attached files (cf. bug #52813) f.setPermissions(QFileDevice::ReadUser); f.close(); return fname; } QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node) { if (!node) { return QUrl(); } const QString index = persistentIndex(node); foreach (const QString &path, mAttachmentFilesDir->temporaryFiles()) { const int right = path.lastIndexOf(QLatin1Char('/')); int left = path.lastIndexOf(QLatin1String(".index."), right); if (left != -1) { left += 7; } QStringRef storedIndex(&path, left, right - left); if (left != -1 && storedIndex == index) { return QUrl::fromLocalFile(path); } } return QUrl(); } QString NodeHelper::createTempDir(const QString ¶m) { QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param); tempFile->open(); const QString fname = tempFile->fileName(); delete tempFile; QFile fFile(fname); if (!(fFile.permissions() & QFileDevice::WriteUser)) { // Not there or not writable if (!QDir().mkpath(fname) || !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) { mAttachmentFilesDir->addTempDir(fname); return QString(); //failed create } } Q_ASSERT(!fname.isNull()); mAttachmentFilesDir->addTempDir(fname); return fname; } void NodeHelper::forceCleanTempFiles() { mAttachmentFilesDir->forceCleanTempFiles(); delete mAttachmentFilesDir; mAttachmentFilesDir = nullptr; } void NodeHelper::removeTempFiles() { //Don't delete it it will delete in class mAttachmentFilesDir->removeTempFiles(); mAttachmentFilesDir = new AttachmentTemporaryFilesDirs(); } void NodeHelper::addTempFile(const QString &file) { mAttachmentFilesDir->addTempFile(file); } bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node) { const KMime::Content *const topLevel = node->topLevel(); const KMime::Content *cur = node; while (cur && cur != topLevel) { const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false) && cur->parent()->contentType()->mimeType().toLower() == "message/rfc822"; if (parentIsMessage && cur->parent() != topLevel) { return true; } cur = cur->parent(); } return false; } QByteArray NodeHelper::charset(KMime::Content *node) { if (node->contentType(false)) { return node->contentType(false)->charset(); } else { return node->defaultCharset(); } } KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const { KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgEncryptionState otherState = encryptionState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) { otherState = overallEncryptionState(next->contents().at(0)); } if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) { otherState = overallEncryptionState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgEncryptionStateUnknown: break; case KMMsgNotEncrypted: if (myState == KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } else if (myState != KMMsgPartiallyEncrypted) { myState = KMMsgNotEncrypted; } break; case KMMsgPartiallyEncrypted: myState = KMMsgPartiallyEncrypted; break; case KMMsgFullyEncrypted: if (myState != KMMsgFullyEncrypted) { myState = KMMsgPartiallyEncrypted; } break; case KMMsgEncryptionProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState; return myState; } KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const { KMMsgSignatureState myState = KMMsgSignatureStateUnknown; if (!node) { return myState; } KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(const_cast(node)); if (i < 0) { //Be safe return myState; } for (; i < contents.size(); ++i) { auto next = contents.at(i); KMMsgSignatureState otherState = signatureState(next); // NOTE: children are tested ONLY when parent is not encrypted if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) { otherState = overallSignatureState(next->contents().at(0)); } if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) { otherState = overallSignatureState(extraContents(next).at(0)); } if (next == node) { myState = otherState; } switch (otherState) { case KMMsgSignatureStateUnknown: break; case KMMsgNotSigned: if (myState == KMMsgFullySigned) { myState = KMMsgPartiallySigned; } else if (myState != KMMsgPartiallySigned) { myState = KMMsgNotSigned; } break; case KMMsgPartiallySigned: myState = KMMsgPartiallySigned; break; case KMMsgFullySigned: if (myState != KMMsgFullySigned) { myState = KMMsgPartiallySigned; } break; case KMMsgSignatureProblematic: break; } } qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState; return myState; } void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode) { const QByteArray body = (aAutoDecode) ? node->decodedContent() : node->body(); QMimeDatabase db; QMimeType mime = db.mimeTypeForData(body); QString mimetype = mime.name(); node->contentType()->setMimeType(mimetype.toLatin1()); } +bool NodeHelper::hasMailHeader(const char *header, const KMime::Message *message) const +{ + return message->hasHeader(header); +} + +KMime::Headers::Base * NodeHelper::mailHeaderAsBase(const char *header, const KMime::Message *message) const +{ + return message->headerByType(header); +} + +KMime::Headers::Generics::AddressList * NodeHelper::mailHeaderAsAddressList(const char *header, KMime::Message *message) const +{ + /* works without this is maybe faster ? + if(strcmp(header, "to") == 0) { + return message->to(); + } else if(strcmp(header, "replyTo") == 0) { + return message->replyTo(); + } else if(strcmp(header, "bcc") == 0) { + return message->bcc(); + } else if(strcmp(header, "cc") == 0) { + return message->cc(); + } */ + auto addressList = new KMime::Headers::Generics::AddressList(); + const auto hrd = message->headerByType(header); + const QByteArray &data = hrd->as7BitString(false); + addressList->from7BitString(data); + return addressList; +} + +QDateTime NodeHelper::dateHeader(KMime::Message *message) const +{ + return message->date()->dateTime(); +} void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec) { if (!node) { return; } mOverrideCodecs[node] = codec; } const QTextCodec *NodeHelper::codec(KMime::Content *node) { if (!node) { return mLocalCodec; } const QTextCodec *c = mOverrideCodecs.value(node, nullptr); if (!c) { // no override-codec set for this message, try the CT charset parameter: QByteArray charset = node->contentType()->charset(); // utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead // utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients. if (charset.toLower() == "us-ascii") { charset = "utf-8"; } c = codecForName(charset); } if (!c) { // no charset means us-ascii (RFC 2045), so using local encoding should // be okay c = mLocalCodec; } return c; } const QTextCodec *NodeHelper::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } QString NodeHelper::fileName(const KMime::Content *node) { QString name = const_cast(node)->contentDisposition()->filename(); if (name.isEmpty()) { name = const_cast(node)->contentType()->name(); } name = name.trimmed(); return name; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const { const QMap< QString, QMap >::const_iterator nit = mBodyPartMementoMap.find(persistentIndex(node)); if (nit == mBodyPartMementoMap.end()) { return nullptr; } const QMap::const_iterator it = nit->find(which.toLower()); return it != nit->end() ? it.value() : nullptr; } //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento) { QMap &mementos = mBodyPartMementoMap[persistentIndex(node)]; const QByteArray whichLower = which.toLower(); const QMap::iterator it = mementos.lowerBound(whichLower); if (it != mementos.end() && it.key() == whichLower) { delete it.value(); if (memento) { it.value() = memento; } else { mementos.erase(it); } } else { mementos.insert(whichLower, memento); } } bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const { qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node); return mDisplayEmbeddedNodes.contains(node); } void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded) { qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded; if (displayedEmbedded) { mDisplayEmbeddedNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const { return mDisplayHiddenNodes.contains(node); } void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden) { if (displayedHidden) { mDisplayHiddenNodes.insert(node); } else { mDisplayEmbeddedNodes.remove(node); } } /*! Creates a persistent index string that bridges the gap between the permanent nodes and the temporary ones. Used internally for robust indexing. */ QString NodeHelper::persistentIndex(const KMime::Content *node) const { if (!node) { return QString(); } QString indexStr = node->index().toString(); if (indexStr.isEmpty()) { QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const auto &extraNodes = it.value(); for (int i = 0; i < extraNodes.size(); i++) { if (extraNodes[i] == node) { indexStr = QString::fromLatin1("e%1").arg(i); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QString::fromLatin1("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } else { const KMime::Content *const topLevel = node->topLevel(); //if the node is an extra node, prepend the index of the extra node to the url QMapIterator > it(mExtraContents); while (it.hasNext()) { it.next(); const QList &extraNodes = extraContents(it.key()); for (int i = 0; i < extraNodes.size(); ++i) { KMime::Content *const extraNode = extraNodes[i]; if (topLevel == extraNode) { indexStr.prepend(QStringLiteral("e%1:").arg(i)); const QString parentIndex = persistentIndex(it.key()); if (!parentIndex.isEmpty()) { indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr); } return indexStr; } } } } return indexStr; } KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const { KMime::Content *c = node->topLevel(); if (c) { const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts); const int pathPartsSize(pathParts.size()); for (int i = 0; i < pathPartsSize; ++i) { const QString &path = pathParts[i]; if (path.startsWith(QLatin1Char('e'))) { const QList &extraParts = mExtraContents.value(c); const int idx = path.midRef(1, -1).toInt(); c = (idx < extraParts.size()) ? extraParts[idx] : nullptr; } else { c = c->content(KMime::ContentIndex(path)); } if (!c) { break; } } } return c; } QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const { return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place); } KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const { if (url.isEmpty()) { return mMessage.data(); } if (!url.isLocalFile()) { return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path()); } else { const QString path = url.toLocalFile(); // extract from //qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2" // start of the index is something that is not a number followed by a dot: \D. // index is only made of numbers,"." and ":": ([0-9.:]+) // index is the last part of the folder name: / const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/")); //search the occurrence at most at the end if (rIndex.lastIndexIn(path) != -1) { return contentFromIndex(mMessage.data(), rIndex.cap(1)); } return mMessage.data(); } } QString NodeHelper::fixEncoding(const QString &encoding) { QString returnEncoding = encoding; // According to http://www.iana.org/assignments/character-sets, uppercase is // preferred in MIME headers const QString returnEncodingToUpper = returnEncoding.toUpper(); if (returnEncodingToUpper.contains(QLatin1String("ISO "))) { returnEncoding = returnEncodingToUpper; returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-")); } return returnEncoding; } //----------------------------------------------------------------------------- QString NodeHelper::encodingForName(const QString &descriptiveName) { QString encoding = KCharsets::charsets()->encodingForName(descriptiveName); return NodeHelper::fixEncoding(encoding); } QStringList NodeHelper::supportedEncodings(bool usAscii) { QStringList encodingNames = KCharsets::charsets()->availableEncodingNames(); QStringList encodings; QMap mimeNames; QStringList::ConstIterator constEnd(encodingNames.constEnd()); for (QStringList::ConstIterator it = encodingNames.constBegin(); it != constEnd; ++it) { QTextCodec *codec = KCharsets::charsets()->codecForName(*it); QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it); if (!mimeNames.contains(mimeName)) { encodings.append(KCharsets::charsets()->descriptionForEncoding(*it)); mimeNames.insert(mimeName, true); } } encodings.sort(); if (usAscii) { encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii"))); } return encodings; } QString NodeHelper::fromAsString(KMime::Content *node) const { if (auto topLevel = dynamic_cast(node->topLevel())) { return topLevel->from()->asUnicodeString(); } else { auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(), [node](const QList &nodes) { return nodes.contains(node); }); if (realNode != mExtraContents.cend()) { return fromAsString(realNode.key()); } } return QString(); } void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content) { qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content; mExtraContents[topLevelNode].append(content); } void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode) { qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode; mExtraContents[topLevelNode].clear(); } QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const { return mExtraContents.value(topLevelnode); } void NodeHelper::mergeExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { if (node->bodyIsMessage()) { qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node <encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent(); continue; } KMime::Content *c = new KMime::Content(node); c->setContent(extra->encodedContent()); c->parse(); node->addContent(c); } Q_FOREACH (KMime::Content *child, node->contents()) { mergeExtraNodes(child); } } void NodeHelper::cleanFromExtraNodes(KMime::Content *node) { if (!node) { return; } const QList extraNodes = extraContents(node); for (KMime::Content *extra : extraNodes) { QByteArray s = extra->encodedContent(); const auto children = node->contents(); for (KMime::Content *c : children) { if (c->encodedContent() == s) { node->removeContent(c); } } } Q_FOREACH (KMime::Content *child, node->contents()) { cleanFromExtraNodes(child); } } KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode) { /*The merge is done in several steps: 1) merge the extra nodes into topLevelNode 2) copy the modified (merged) node tree into a new node tree 3) restore the original node tree in topLevelNode by removing the extra nodes from it The reason is that extra nodes are assigned by pointer value to the nodes in the original tree. */ if (!topLevelNode) { return nullptr; } mergeExtraNodes(topLevelNode); KMime::Message *m = new KMime::Message; m->setContent(topLevelNode->encodedContent()); m->parse(); cleanFromExtraNodes(topLevelNode); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent(); // qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent(); return m; } KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const { const QList xc = extraContents(content); if (!xc.empty()) { if (xc.size() == 1) { return xc.front(); } else { qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?"; } } return nullptr; } bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel) { bool returnValue = false; if (node) { KMime::Content *curNode = node; KMime::Content *decryptedNode = nullptr; const QByteArray type = node->contentType(false) ? QByteArray(node->contentType()->mediaType()).toLower() : "text"; const QByteArray subType = node->contentType(false) ? node->contentType()->subType().toLower() : "plain"; const bool isMultipart = node->contentType(false) && node->contentType()->isMultipart(); bool isSignature = false; qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType; if (isMultipart) { if (subType == "signed") { isSignature = true; } else if (subType == "encrypted") { decryptedNode = decryptedNodeForContent(curNode); } } else if (type == "application") { if (subType == "octet-stream") { decryptedNode = decryptedNodeForContent(curNode); } else if (subType == "pkcs7-signature") { isSignature = true; } else if (subType == "pkcs7-mime") { // note: subtype pkcs7-mime can also be signed // and we do NOT want to remove the signature! if (encryptionState(curNode) != KMMsgNotEncrypted) { decryptedNode = decryptedNodeForContent(curNode); } } } if (decryptedNode) { qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header " "and then processing the children."; Q_ASSERT(addHeaders); KMime::Content headers; headers.setHead(curNode->head()); headers.parse(); if (decryptedNode->contentType(false)) { headers.contentType()->from7BitString(decryptedNode->contentType()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentTransferEncoding(false)) { headers.contentTransferEncoding()->from7BitString(decryptedNode->contentTransferEncoding()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDisposition(false)) { headers.contentDisposition()->from7BitString(decryptedNode->contentDisposition()->as7BitString(false)); } else { headers.removeHeader(); } if (decryptedNode->contentDescription(false)) { headers.contentDescription()->from7BitString(decryptedNode->contentDescription()->as7BitString(false)); } else { headers.removeHeader(); } headers.assemble(); resultingData += headers.head() + '\n'; unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1); returnValue = true; } else if (isSignature) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is."; // We can't change the nodes under the signature, as that would invalidate it. Add the signature // and its child as-is if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->encodedBody(); returnValue = false; } else if (isMultipart) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children."; // Normal multipart node, add the header and all of its children bool somethingChanged = false; if (addHeaders) { resultingData += curNode->head() + '\n'; } const QByteArray boundary = curNode->contentType()->boundary(); foreach (KMime::Content *child, curNode->contents()) { resultingData += "\n--" + boundary + '\n'; const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1); if (changed) { somethingChanged = true; } } resultingData += "\n--" + boundary + "--\n\n"; returnValue = somethingChanged; } else if (curNode->bodyIsMessage()) { qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child."; if (addHeaders) { resultingData += curNode->head() + '\n'; } returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1); } else { qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is."; if (addHeaders) { resultingData += curNode->head() + '\n'; } resultingData += curNode->body(); returnValue = false; } } qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done."; return returnValue; } KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage) { QByteArray resultingData; const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true); if (messageChanged) { #if 0 qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData; QFile bla("stripped.mbox"); bla.open(QIODevice::WriteOnly); bla.write(resultingData); bla.close(); #endif KMime::Message::Ptr newMessage(new KMime::Message); newMessage->setContent(resultingData); newMessage->parse(); return newMessage; } else { return KMime::Message::Ptr(); } } QVector NodeHelper::attachmentsOfExtraContents() const { QVector result; for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) { foreach (auto content, it.value()) { if (KMime::isAttachment(content)) { result.push_back(content); } else { result += content->attachments(); } } } return result; } } diff --git a/mimetreeparser/src/nodehelper.h b/mimetreeparser/src/nodehelper.h index e6789a3a..23a7ce65 100644 --- a/mimetreeparser/src/nodehelper.h +++ b/mimetreeparser/src/nodehelper.h @@ -1,262 +1,267 @@ /* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MIMETREEPARSER_NODEHELPER_H #define MIMETREEPARSER_NODEHELPER_H #include "mimetreeparser_export.h" #include "mimetreeparser/partmetadata.h" #include "mimetreeparser/enums.h" #include #include #include #include class QUrl; class QTextCodec; namespace MimeTreeParser { class AttachmentTemporaryFilesDirs; namespace Interface { class BodyPartMemento; } } namespace MimeTreeParser { /** * @author Andras Mantia */ class MIMETREEPARSER_EXPORT NodeHelper : public QObject { Q_OBJECT public: NodeHelper(); ~NodeHelper(); void setNodeProcessed(KMime::Content *node, bool recurse); void setNodeUnprocessed(KMime::Content *node, bool recurse); bool nodeProcessed(KMime::Content *node) const; void clear(); void forceCleanTempFiles(); void setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state); KMMsgEncryptionState encryptionState(const KMime::Content *node) const; void setSignatureState(const KMime::Content *node, const KMMsgSignatureState state); KMMsgSignatureState signatureState(const KMime::Content *node) const; KMMsgSignatureState overallSignatureState(KMime::Content *node) const; KMMsgEncryptionState overallEncryptionState(KMime::Content *node) const; void setPartMetaData(KMime::Content *node, const PartMetaData &metaData); PartMetaData partMetaData(KMime::Content *node); /** * Set the 'Content-Type' by mime-magic from the contents of the body. * If autoDecode is true the decoded body will be used for mime type * determination (this does not change the body itself). */ static void magicSetType(KMime::Content *node, bool autoDecode = true); + bool hasMailHeader(const char *header, const KMime::Message *message) const; + KMime::Headers::Base *mailHeaderAsBase(const char *header, const KMime::Message *message) const; + KMime::Headers::Generics::AddressList *mailHeaderAsAddressList(const char *header, KMime::Message *message) const; + QDateTime dateHeader(KMime::Message *message) const; + /** Attach an extra node to an existing node */ void attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content); void cleanExtraContent(KMime::Content *topLevelNode); /** Get the extra nodes attached to the @param topLevelNode and all sub-nodes of @param topLevelNode */ QList extraContents(KMime::Content *topLevelNode) const; /** Return a modified message (node tree) starting from @param topLevelNode that has the original nodes and the extra nodes. The caller has the responsibility to delete the new message. */ KMime::Message *messageWithExtraContent(KMime::Content *topLevelNode); /** Get a QTextCodec suitable for this message part */ const QTextCodec *codec(KMime::Content *node); /** Set the charset the user selected for the message to display */ void setOverrideCodec(KMime::Content *node, const QTextCodec *codec); Interface::BodyPartMemento *bodyPartMemento(KMime::Content *node, const QByteArray &which) const; void setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento); // A flag to remember if the node was embedded. This is useful for attachment nodes, the reader // needs to know if they were displayed inline or not. bool isNodeDisplayedEmbedded(KMime::Content *node) const; void setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded); // Same as above, but this time determines if the node was hidden or not bool isNodeDisplayedHidden(KMime::Content *node) const; void setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden); /** * Writes the given message part to a temporary file and returns the * name of this file or QString() if writing failed. */ QString writeNodeToTempFile(KMime::Content *node); QString writeFileToTempFile(KMime::Content *node, const QString &filename); /** * Returns the temporary file path and name where this node was saved, or an empty url * if it wasn't saved yet with writeNodeToTempFile() */ QUrl tempFileUrlFromNode(const KMime::Content *node); /** * Creates a temporary dir for saving attachments, etc. * Will be automatically deleted when another message is viewed. * @param param Optional part of the directory name. */ QString createTempDir(const QString ¶m = QString()); /** * Cleanup the attachment temp files */ void removeTempFiles(); /** * Add a file to the list of managed temporary files */ void addTempFile(const QString &file); // Get a href in the form attachment:?place=, used by ObjectTreeParser and // UrlHandlerManager. QString asHREF(const KMime::Content *node, const QString &place) const; KMime::Content *fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &href) const; /** * @return true if this node is a child or an encapsulated message */ static bool isInEncapsulatedMessage(KMime::Content *node); /** * Returns the charset for the given node. If no charset is specified * for the node, the defaultCharset() is returned. */ static QByteArray charset(KMime::Content *node); /** * Return a QTextCodec for the specified charset. * This function is a bit more tolerant, than QTextCodec::codecForName */ static const QTextCodec *codecForName(const QByteArray &_str); /** * Returns a usable filename for a node, that can be the filename from the * content disposition header, or if that one is empty, the name from the * content type header. */ static QString fileName(const KMime::Content *node); /** * Fixes an encoding received by a KDE function and returns the proper, * MIME-compilant encoding name instead. * @see encodingForName */ static QString fixEncoding(const QString &encoding); //TODO(Andras) move to a utility class? /** * Drop-in replacement for KCharsets::encodingForName(). The problem with * the KCharsets function is that it returns "human-readable" encoding names * like "ISO 8859-15" instead of valid encoding names like "ISO-8859-15". * This function fixes this by replacing whitespace with a hyphen. */ static QString encodingForName(const QString &descriptiveName); //TODO(Andras) move to a utility class? /** * Return a list of the supported encodings * @param usAscii if true, US-Ascii encoding will be prepended to the list. */ static QStringList supportedEncodings(bool usAscii); //TODO(Andras) move to a utility class? QString fromAsString(KMime::Content *node) const; KMime::Content *decryptedNodeForContent(KMime::Content *content) const; /** * This function returns the unencrypted message that is based on @p originalMessage. * All encrypted MIME parts are removed and replaced by their decrypted plain-text versions. * Encrypted parts that are within signed parts are not replaced, since that would invalidate * the signature. * * This only works if the message was run through ObjectTreeParser::parseObjectTree() with the * currrent NodeHelper before, because parseObjectTree() actually decrypts the message and stores * the decrypted nodes by calling attachExtraContent(). * * @return the unencrypted message or an invalid pointer if the original message didn't contain * a part that needed to be modified. */ KMime::Message::Ptr unencryptedMessage(const KMime::Message::Ptr &originalMessage); /** * Returns a list of attachments of attached extra content nodes. * This is mainly useful is order to get attachments of encrypted messages. * Note that this does not include attachments from the primary node tree. * @see KMime::Content::attachments(). */ QVector attachmentsOfExtraContents() const; Q_SIGNALS: void update(MimeTreeParser::UpdateMode); private: Q_DISABLE_COPY(NodeHelper) bool unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel = 1); void mergeExtraNodes(KMime::Content *node); void cleanFromExtraNodes(KMime::Content *node); /** Creates a persistent index string that bridges the gap between the permanent nodes and the temporary ones. Used internally for robust indexing. **/ QString persistentIndex(const KMime::Content *node) const; /** Translates the persistentIndex into a node back node: any node of the actually message to what the persistentIndex is interpreded **/ KMime::Content *contentFromIndex(KMime::Content *node, const QString &persistentIndex) const; private: QList mProcessedNodes; QList mNodesUnderProcess; QMap mEncryptionState; QMap mSignatureState; QSet mDisplayEmbeddedNodes; QSet mDisplayHiddenNodes; QTextCodec *mLocalCodec = nullptr; QMap mOverrideCodecs; QMap > mBodyPartMementoMap; QMap mPartMetaDatas; QMap > mExtraContents; AttachmentTemporaryFilesDirs *mAttachmentFilesDir = nullptr; friend class NodeHelperTest; }; } #endif