diff --git a/messageviewer/src/CMakeLists.txt b/messageviewer/src/CMakeLists.txt index 2301d75c..d88772ad 100644 --- a/messageviewer/src/CMakeLists.txt +++ b/messageviewer/src/CMakeLists.txt @@ -1,429 +1,431 @@ add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_definitions(-DTRANSLATION_DOMAIN=\"libmessageviewer\") add_subdirectory(messagepartthemes/grantlee) # KCFG files: # The main messageviewer.kcfg is configured by CMake and put in the build directory. if(KDEPIM_ENTERPRISE_BUILD) set(LEGACY_MANGLE_FROM_TO_HEADERS true) set(LEGACY_BODY_INVITES true) set(EXCHANGE_COMPATIBLE_INVITATIONS true) else() set(LEGACY_MANGLE_FROM_TO_HEADERS false) set(LEGACY_BODY_INVITES false) set(EXCHANGE_COMPATIBLE_INVITATIONS false) endif() configure_file(settings/messageviewer.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/messageviewer.kcfg) include(CheckIncludeFiles) find_package(Inotify) set_package_properties(Inotify PROPERTIES PURPOSE "Filesystem alteration notifications using inotify") if(Inotify_FOUND) set(HAVE_SYS_INOTIFY_H 1) else() set(HAVE_SYS_INOTIFY_H 0) endif() configure_file(config-messageviewer.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-messageviewer.h) # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) if(BUILD_TESTING) add_subdirectory(scamdetection/autotests) add_subdirectory(scamdetection/tests) add_subdirectory(viewerplugins/tests/) add_subdirectory(htmlwriter/autotests) add_subdirectory(viewer/webengine/tests) add_subdirectory(messagepartthemes/default/autotests) add_subdirectory(viewer/webengine/autotests) endif() add_subdirectory(pics) add_subdirectory(kconf_update) add_subdirectory(about) add_subdirectory(messageviewerheaderplugins) if(DEBUG_SIGNATURE) add_definitions(-DDEBUG_SIGNATURE) endif() set(libmessageviewer_mailviewer_SRCS viewer/webengine/mailwebengineview.cpp viewer/webengine/mailwebenginepage.cpp viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp viewer/webengine/mailwebenginescript.cpp ) set(libmessageviewer_viewer_SRCS + viewer/attachmentstrategy.cpp viewer/csshelper.cpp viewer/csshelperbase.cpp viewer/editorwatcher.cpp viewer/objecttreeemptysource.cpp viewer/objecttreeviewersource.cpp viewer/viewer.cpp viewer/viewer_p.cpp viewer/messagedisplayformatattribute.cpp viewer/urlhandlermanager.cpp viewer/mimeparttree/mimeparttreeview.cpp viewer/mimeparttree/mimetreemodel.cpp ) set(libmessageviewer_widgets_SRCS widgets/attachmentdialog.cpp widgets/configurewidget.cpp widgets/printingsettings.cpp widgets/htmlstatusbar.cpp widgets/vcardviewer.cpp widgets/invitationsettings.cpp widgets/openattachmentfolderwidget.cpp widgets/mailsourceviewtextbrowserwidget.cpp widgets/submittedformwarningwidget.cpp ) set(libmessageviewer_widgets_webengine_SRCS widgets/mailsourcewebengineviewer.cpp ) set(libmessageviewer_header_SRCS header/contactdisplaymessagememento.cpp header/headerstrategy.cpp header/richheaderstrategy.cpp header/headerstyle.cpp header/grantleeheaderstyle.cpp header/plainheaderstyle.cpp header/headerstyle_util.cpp header/grantleeheaderformatter.cpp header/grantleeheaderteststyle.cpp header/kxface.cpp header/headerstyleplugin.cpp header/headerstylepluginmanager.cpp header/headerstyleinterface.cpp header/headerstylemenumanager.cpp ) set(libmessageviewer_scamdetection_SRCS scamdetection/scamdetectionwarningwidget.cpp scamdetection/scamdetectiondetailsdialog.cpp scamdetection/scamattribute.cpp scamdetection/scamcheckshorturl.cpp scamdetection/scamexpandurljob.cpp scamdetection/scamcheckshorturlmanager.cpp ) set(libmessageviewer_scamdetection_webengine_SRCS scamdetection/scamdetectionwebengine.cpp ) set(libmessageviewer_findbar_SRCS findbar/findbarsourceview.cpp ) set(libmessageviewer_utils_SRCS utils/iconnamecache.cpp utils/markmessagereadhandler.cpp utils/messageviewerutil.cpp utils/mimetype.cpp ) set(libmessageviewer_htmlwriter_webengine_SRCS htmlwriter/webengineparthtmlwriter.cpp htmlwriter/webengineembedpart.cpp ) set(libmessageviewer_antispam_SRCS antispam/spamheaderanalyzer.cpp antispam/antispamconfig.cpp ) set(libmessageviewer_job_SRCS job/attachmenteditjob.cpp job/modifymessagedisplayformatjob.cpp ) set(libmessageviewer_viewerplugins_SRCS viewerplugins/viewerpluginmanager.cpp viewerplugins/viewerplugin.cpp viewerplugins/viewerplugininterface.cpp viewerplugins/viewerplugintoolmanager.cpp ) set(libmessageviewer_messagepartthemes_default_SRCS messagepartthemes/default/converthtmltoplaintext.cpp messagepartthemes/default/defaultrenderer.cpp messagepartthemes/default/htmlblock.cpp messagepartthemes/default/messagepartrenderermanager.cpp messagepartthemes/default/plugins/attachmentmessagepartrenderer.cpp messagepartthemes/default/plugins/messagepartrenderer.cpp messagepartthemes/default/plugins/textmessagepartrenderer.cpp messagepartthemes/default/plugins/quotehtml.cpp messagepartthemes/default/messagepartrenderbase.cpp messagepartthemes/default/messagepartrenderplugin.cpp messagepartthemes/default/messagepartrendererfactory.cpp ) set(libmessageviewer_SRCS ${libmessageviewer_messagepartthemes_default_SRCS} ${libmessageviewer_htmlwriter_webengine_SRCS} ${libmessageviewer_messagepartthemes_SRCS} ${libmessageviewer_scamdetection_webengine_SRCS} ${libmessageviewer_widgets_webengine_SRCS} ${libmessageviewer_viewer_SRCS} ${libmessageviewer_widgets_SRCS} ${libmessageviewer_header_SRCS} ${libmessageviewer_scamdetection_SRCS} ${libmessageviewer_findbar_SRCS} ${libmessageviewer_utils_SRCS} ${libmessageviewer_antispam_SRCS} ${libmessageviewer_job_SRCS} ${libmessageviewer_viewerplugins_SRCS} settings/messageviewersettings.cpp ${libmessageviewer_mailviewer_SRCS} ) qt5_add_resources(libmessageviewer_SRCS messagepartthemes.qrc) ecm_qt_declare_logging_category(libmessageviewer_SRCS HEADER messageviewer_debug.h IDENTIFIER MESSAGEVIEWER_LOG CATEGORY_NAME org.kde.pim.messageviewer) kconfig_add_kcfg_files(libmessageviewer_SRCS settings/globalsettings_messageviewer.kcfgc ) ki18n_wrap_ui(libmessageviewer_SRCS ui/settings.ui ui/invitationsettings.ui ui/printingsettings.ui ) add_library(KF5MessageViewer ${libmessageviewer_SRCS}) generate_export_header(KF5MessageViewer BASE_NAME messageviewer) add_library(KF5::MessageViewer ALIAS KF5MessageViewer) target_include_directories(KF5MessageViewer INTERFACE "$") target_link_libraries(KF5MessageViewer PUBLIC KF5::MessageCore KF5::PimCommon KF5::AkonadiCore KF5::AkonadiMime KF5::Contacts KF5::Libkleo KF5::MimeTreeParser PRIVATE KF5::SyntaxHighlighting KF5::ItemViews Qt5::Network KF5::WebEngineViewer KF5::LibkdepimAkonadi KF5::GrantleeTheme KF5::KaddressbookGrantlee Grantlee5::Templates KF5::MailTransportAkonadi KF5::Mime KF5::Mbox KF5::PimTextEdit KF5::Gravatar KF5::IconThemes KF5::I18n KF5::KIOFileWidgets KF5::KIOWidgets KF5::WindowSystem KF5::XmlGui Grantlee5::TextDocument Grantlee5::Templates Qt5::PrintSupport QGpgme ) set_target_properties(KF5MessageViewer PROPERTIES VERSION ${MESSAGEVIEWER_VERSION_STRING} SOVERSION ${MESSAGEVIEWER_SOVERSION} EXPORT_NAME MessageViewer ) install(TARGETS KF5MessageViewer EXPORT KF5MessageViewerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) ecm_generate_headers(MessageViewer_Camelcasewebengine_HEADERS HEADER_NAMES MailWebEnginePage MailWebEngineView REQUIRED_HEADERS MessageViewer_webengine_HEADERS PREFIX MessageViewer RELATIVE viewer/webengine ) ecm_generate_headers(MessageViewer_Camelcasescam_HEADERS HEADER_NAMES ScamExpandUrlJob ScamCheckShortUrlManager ScamCheckShortUrl REQUIRED_HEADERS MessageViewer_scam_HEADERS PREFIX MessageViewer RELATIVE scamdetection ) ecm_generate_headers(MessageViewer_Camelcaseviewer_HEADERS HEADER_NAMES + AttachmentStrategy Viewer CSSHelperBase CSSHelper ObjectTreeEmptySource EditorWatcher Stl_Util REQUIRED_HEADERS MessageViewer_viewer_HEADERS PREFIX MessageViewer RELATIVE viewer ) ecm_generate_headers(MessageViewer_Camelcasewidgets_HEADERS HEADER_NAMES InvitationSettings PrintingSettings ConfigureWidget REQUIRED_HEADERS MessageViewer_widgets_HEADERS PREFIX MessageViewer RELATIVE widgets ) ecm_generate_headers(MessageViewer_Camelcaseutils_HEADERS HEADER_NAMES IconNameCache MarkMessageReadHandler MessageViewerUtil MimeType REQUIRED_HEADERS MessageViewer_utils_HEADERS PREFIX MessageViewer RELATIVE utils ) ecm_generate_headers(MessageViewer_Camelcaseantispam_HEADERS HEADER_NAMES SpamHeaderAnalyzer REQUIRED_HEADERS MessageViewer_antispam_HEADERS PREFIX MessageViewer RELATIVE antispam ) ecm_generate_headers(MessageViewer_Camelcaseinterfaces_HEADERS HEADER_NAMES BodyPartURLHandler URLHandler REQUIRED_HEADERS MessageViewer_interfaces_HEADERS PREFIX MessageViewer RELATIVE interfaces ) ecm_generate_headers(MessageViewer_Camelcasesettings_HEADERS HEADER_NAMES MessageViewerSettings REQUIRED_HEADERS MessageViewer_settings_HEADERS PREFIX MessageViewer RELATIVE settings ) ecm_generate_headers(MessageViewer_Camelcaseheader_HEADERS HEADER_NAMES HeaderStrategy GrantleeHeaderTestStyle GrantleeHeaderStyle HeaderStyle KXFace HeaderStyle_Util HeaderStylePlugin HeaderStyleInterface PlainHeaderStyle RichHeaderStrategy HeaderStylePluginManager HeaderStyleMenuManager REQUIRED_HEADERS MessageViewer_header_HEADERS PREFIX MessageViewer RELATIVE header ) ecm_generate_headers(MessageViewer_Camelcaseviewerplugin_HEADERS HEADER_NAMES ViewerPluginManager ViewerPlugin ViewerPluginInterface ViewerPluginToolManager REQUIRED_HEADERS MessageViewer_viewerplugin_HEADERS PREFIX MessageViewer RELATIVE viewerplugins ) ecm_generate_headers(MessageViewer_Camelcaserenderer_HEADERS HEADER_NAMES HtmlBlock MessagePartRendererBase MessagePartRendererManager MessagePartRenderPlugin REQUIRED_HEADERS MessageViewer_renderer_HEADERS PREFIX MessageViewer RELATIVE messagepartthemes/default ) ecm_generate_pri_file(BASE_NAME MessageViewer LIB_NAME KF5MessageViewer DEPS "PimCommon MessageCore AkonadiCore AkonadiMime Contacts Libkleo MimeTreeParser" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageViewer ) install(FILES ${MessageViewer_Camelcasewebengine_HEADERS} ${MessageViewer_Camelcaseheader_HEADERS} ${MessageViewer_Camelcaseviewerplugin_HEADERS} ${MessageViewer_Camelcasesettings_HEADERS} ${MessageViewer_Camelcaseutils_HEADERS} ${MessageViewer_Camelcaseinterfaces_HEADERS} ${MessageViewer_Camelcaseviewer_HEADERS} ${MessageViewer_Camelcasewidgets_HEADERS} ${MessageViewer_Camelcaseantispam_HEADERS} ${MessageViewer_Camelfindbar_HEADERS} ${MessageViewer_Camelcasescam_HEADERS} ${MessageViewer_Camelcaserenderer_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageViewer COMPONENT Devel ) install(FILES ${MessageViewer_webengine_HEADERS} ${MessageViewer_scam_HEADERS} ${MessageViewer_viewerplugin_HEADERS} ${MessageViewer_settings_HEADERS} ${MessageViewer_header_HEADERS} ${MessageViewer_utils_HEADERS} ${MessageViewer_interfaces_HEADERS} ${MessageViewer_HEADERS} ${MessageViewer_viewer_HEADERS} ${MessageViewer_widgets_HEADERS} ${MessageViewer_antispam_HEADERS} ${MessageViewer_findbar_HEADERS} ${MessageViewer_renderer_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/messageviewer_export.h ${CMAKE_CURRENT_BINARY_DIR}/globalsettings_messageviewer.h ${CMAKE_CURRENT_BINARY_DIR}/messageviewer_debug.h ${CMAKE_CURRENT_BINARY_DIR}/config-messageviewer.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/messageviewer COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(FILES header/data/messageviewer_header_themes.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) install(FILES notify/messageviewer.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} ) install(FILES scamdetection/data/longurlServices.json DESTINATION ${KDE_INSTALL_DATADIR}/messageviewer ) diff --git a/messageviewer/src/messagepartthemes/default/autotests/setupenv.h b/messageviewer/src/messagepartthemes/default/autotests/setupenv.h index 3248d8dd..0ee24d10 100644 --- a/messageviewer/src/messagepartthemes/default/autotests/setupenv.h +++ b/messageviewer/src/messagepartthemes/default/autotests/setupenv.h @@ -1,186 +1,186 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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. */ #ifndef __MESSAGEVIEWER_TESTS_SETUPENV_H__ #define __MESSAGEVIEWER_TESTS_SETUPENV_H__ #include -#include #include #include +#include #include namespace MessageViewer { namespace Test { /** * setup a environment variables for tests: * * set LC_ALL to C * * set KDEHOME */ void setupEnv(); // We can't use EmptySource, since we need to control some emelnets of the source for tests to also test // loadExternal and htmlMail. class ObjectTreeSource : public MessageViewer::EmptySource { public: ObjectTreeSource(MimeTreeParser::HtmlWriter *writer, MessageViewer::CSSHelperBase *cssHelper) : mWriter(writer) , mCSSHelper(cssHelper) , mAttachmentStrategy(QStringLiteral("smart")) , mHtmlLoadExternal(false) , mDecryptMessage(false) , mShowSignatureDetails(false) , mShowExpandQuotesMark(false) , mPreferredMode(MimeTreeParser::Util::Html) , mQuoteLevel(1) { } MimeTreeParser::HtmlWriter *htmlWriter() override { return mWriter; } CSSHelperBase *cssHelper() override { return mCSSHelper; } bool htmlLoadExternal() const override { return mHtmlLoadExternal; } void setHtmlLoadExternal(bool loadExternal) { mHtmlLoadExternal = loadExternal; } void setAttachmentStrategy(const QString &strategy) { mAttachmentStrategy = strategy; } - const MimeTreeParser::AttachmentStrategy *attachmentStrategy() override + const AttachmentStrategy *attachmentStrategy() override { - return MimeTreeParser::AttachmentStrategy::create(mAttachmentStrategy); + return AttachmentStrategy::create(mAttachmentStrategy); } bool autoImportKeys() const override { return true; } bool showEmoticons() const override { return false; } void setShowExpandQuotesMark(bool b) { mShowExpandQuotesMark = b; } bool showExpandQuotesMark() const override { return mShowExpandQuotesMark; } const MimeTreeParser::BodyPartFormatterFactory *bodyPartFormatterFactory() override { return &mBodyPartFormatterFactory; } bool decryptMessage() const override { return mDecryptMessage; } void setAllowDecryption(bool allowDecryption) { mDecryptMessage = allowDecryption; } void setShowSignatureDetails(bool showSignatureDetails) { mShowSignatureDetails = showSignatureDetails; } bool showSignatureDetails() const override { return mShowSignatureDetails; } void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) override { Q_UNUSED(mode); Q_UNUSED(availableModes); } MimeTreeParser::Util::HtmlMode preferredMode() const override { return mPreferredMode; } void setPreferredMode(MimeTreeParser::Util::HtmlMode mode) { mPreferredMode = mode; } int levelQuote() const override { return mQuoteLevel; } void setLevelQuote(int level) { mQuoteLevel = level; } const QTextCodec *overrideCodec() override { return nullptr; } QString createMessageHeader(KMime::Message *message) override { Q_UNUSED(message); return QString(); //do nothing } private: MimeTreeParser::HtmlWriter *mWriter; MessageViewer::CSSHelperBase *mCSSHelper; QString mAttachmentStrategy; MimeTreeParser::BodyPartFormatterFactory mBodyPartFormatterFactory; bool mHtmlLoadExternal; bool mDecryptMessage; bool mShowSignatureDetails; bool mShowExpandQuotesMark; MimeTreeParser::Util::HtmlMode mPreferredMode; int mQuoteLevel; }; } } #endif //__MESSAGEVIEWER_TESTS_SETUPENV_H__ diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp index 6838e324..fa95930f 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp @@ -1,1032 +1,1045 @@ /* Copyright (c) 2016 Sandro Knauß 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 "defaultrenderer.h" #include "defaultrenderer_p.h" #include "messageviewer_debug.h" #include "converthtmltoplaintext.h" #include "messagepartrendererbase.h" #include "messagepartrendererfactory.h" #include "htmlblock.h" #include "utils/iconnamecache.h" #include "utils/mimetype.h" +#include "viewer/attachmentstrategy.h" #include "viewer/csshelperbase.h" #include "messagepartrenderermanager.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; using namespace MessageViewer; Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient) Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(const QGpgME::Protocol *) static const int SIG_FRAME_COL_UNDEF = 99; #define SIG_FRAME_COL_RED -1 #define SIG_FRAME_COL_YELLOW 0 #define SIG_FRAME_COL_GREEN 1 QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos) { // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. showKeyInfos = true; QString result; if (cryptProto) { if (cryptProto == QGpgME::openpgp()) { // process enum according to it's definition to be read in // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h switch (status_code) { case 0: // GPGME_SIG_STAT_NONE result = i18n("Error: Signature not verified"); break; case 1: // GPGME_SIG_STAT_GOOD result = i18n("Good signature"); break; case 2: // GPGME_SIG_STAT_BAD result = i18n("Bad signature"); break; case 3: // GPGME_SIG_STAT_NOKEY result = i18n("No public key to verify the signature"); break; case 4: // GPGME_SIG_STAT_NOSIG result = i18n("No signature found"); break; case 5: // GPGME_SIG_STAT_ERROR result = i18n("Error verifying the signature"); break; case 6: // GPGME_SIG_STAT_DIFF result = i18n("Different results for signatures"); break; /* PENDING(khz) Verify exact meaning of the following values: case 7: // GPGME_SIG_STAT_GOOD_EXP return i18n("Signature certificate is expired"); break; case 8: // GPGME_SIG_STAT_GOOD_EXPKEY return i18n("One of the certificate's keys is expired"); break; */ default: result.clear(); // do *not* return a default text here ! break; } } else if (cryptProto == QGpgME::smime()) { // process status bits according to SigStatus_... // definitions in kdenetwork/libkdenetwork/cryptplug.h if (summary == GpgME::Signature::None) { result = i18n("No status information available."); frameColor = SIG_FRAME_COL_YELLOW; showKeyInfos = false; return result; } if (summary & GpgME::Signature::Valid) { result = i18n("Good signature."); // Note: // Here we are work differently than KMail did before! // // The GOOD case ( == sig matching and the complete // certificate chain was verified and is valid today ) // by definition does *not* show any key // information but just states that things are OK. // (khz, according to LinuxTag 2002 meeting) frameColor = SIG_FRAME_COL_GREEN; showKeyInfos = false; return result; } // we are still there? OK, let's test the different cases: // we assume green, test for yellow or red (in this order!) frameColor = SIG_FRAME_COL_GREEN; QString result2; if (summary & GpgME::Signature::KeyExpired) { // still is green! result2 = i18n("One key has expired."); } if (summary & GpgME::Signature::SigExpired) { // and still is green! result2 += i18n("The signature has expired."); } // test for yellow: if (summary & GpgME::Signature::KeyMissing) { result2 += i18n("Unable to verify: key missing."); // if the signature certificate is missing // we cannot show information on it showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlMissing) { result2 += i18n("CRL not available."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlTooOld) { result2 += i18n("Available CRL is too old."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::BadPolicy) { result2 += i18n("A policy was not met."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::SysError) { result2 += i18n("A system error occurred."); // if a system error occurred // we cannot trust any information // that was given back by the plug-in showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } // test for red: if (summary & GpgME::Signature::KeyRevoked) { // this is red! result2 += i18n("One key has been revoked."); frameColor = SIG_FRAME_COL_RED; } if (summary & GpgME::Signature::Red) { if (result2.isEmpty()) { // Note: // Here we are work differently than KMail did before! // // The BAD case ( == sig *not* matching ) // by definition does *not* show any key // information but just states that things are BAD. // // The reason for this: In this case ALL information // might be falsificated, we can NOT trust the data // in the body NOT the signature - so we don't show // any key/signature information at all! // (khz, according to LinuxTag 2002 meeting) showKeyInfos = false; } frameColor = SIG_FRAME_COL_RED; } else { result.clear(); } if (SIG_FRAME_COL_GREEN == frameColor) { result = i18n("Good signature."); } else if (SIG_FRAME_COL_RED == frameColor) { result = i18n("Bad signature."); } else { result.clear(); } if (!result2.isEmpty()) { if (!result.isEmpty()) { result.append(QLatin1String("
")); } result.append(result2); } } /* // add i18n support for 3rd party plug-ins here: else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) { } */ } return result; } /** Checks whether @p str contains external references. To be precise, we only check whether @p str contains 'xxx="http[s]:' where xxx is not href. Obfuscated external references are ignored on purpose. */ bool containsExternalReferences(const QString &str, const QString &extraHead) { const bool hasBaseInHeader = extraHead.contains(QStringLiteral( "= 0 || httpsPos >= 0) { // pos = index of next occurrence of "http: or "https: whichever comes first int pos = (httpPos < httpsPos) ? ((httpPos >= 0) ? httpPos : httpsPos) : ((httpsPos >= 0) ? httpsPos : httpPos); // look backwards for "href" if (pos > 5) { int hrefPos = str.lastIndexOf(QLatin1String("href"), pos - 5, Qt::CaseInsensitive); // if no 'href' is found or the distance between 'href' and '"http[s]:' // is larger than 7 (7 is the distance in 'href = "http[s]:') then // we assume that we have found an external reference if ((hrefPos == -1) || (pos - hrefPos > 7)) { // HTML messages created by KMail itself for now contain the following: // // Make sure not to show an external references warning for this string int dtdPos = str.indexOf(QLatin1String( "http://www.w3.org/TR/html4/loose.dtd"), pos + 1); if (dtdPos != (pos + 1)) { return true; } } } // find next occurrence of "http: or "https: if (pos == httpPos) { httpPos = str.indexOf(QLatin1String("\"http:"), httpPos + 6, Qt::CaseInsensitive); } else { httpsPos = str.indexOf(QLatin1String("\"https:"), httpsPos + 7, Qt::CaseInsensitive); } } return false; } // FIXME this used to go through the full webkit parser to extract the body and head blocks // until we have that back, at least attempt to fix some of the damage // yes, "parsing" HTML with regexps is very very wrong, but it's still better than not filtering // this at all... QString processHtml(const QString &htmlSource, QString &extraHead) { auto s = htmlSource.trimmed(); s = s.replace(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive), QString()).trimmed(); // head s = s.replace(QRegExp(QStringLiteral("^"), Qt::CaseInsensitive), QString()).trimmed(); if (s.startsWith(QLatin1String("", Qt::CaseInsensitive))) { const auto idx = s.indexOf(QLatin1String(""), Qt::CaseInsensitive); if (idx < 0) { return htmlSource; } extraHead = s.mid(6, idx - 6); s = s.mid(idx + 7).trimmed(); } // body s = s.replace(QRegExp(QStringLiteral("]*>"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive), QString()).trimmed(); return s; } -DefaultRendererPrivate::DefaultRendererPrivate(const MessagePart::Ptr &msgPart, CSSHelperBase *cssHelper, HtmlWriter *writer, const MessagePartRendererFactory *rendererFactory, bool showOnlyOneMimePart) - : mMsgPart(msgPart) - , mCSSHelper(cssHelper) +DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory) + : mCSSHelper(cssHelper) , mRendererFactory(rendererFactory) - , mShowOnlyOneMimePart(showOnlyOneMimePart) { - renderFactory(mMsgPart, writer); } DefaultRendererPrivate::~DefaultRendererPrivate() { } CSSHelperBase *DefaultRendererPrivate::cssHelper() const { return mCSSHelper; } Interface::ObjectTreeSource *DefaultRendererPrivate::source() const { return mMsgPart->source(); } void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { foreach (const auto &m, msgPart->subParts()) { renderFactory(m, htmlWriter); } } void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr rBlock; HTMLBlock::Ptr aBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; HTMLBlock::Ptr rBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter) { if (!mp->hasSubParts()) { return; } Grantlee::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral(":/encapsulatedrfc822messagepart.html")); Grantlee::Context c = MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("link", mp->nodeHelper()->asHREF(mp->mMessage.data(), QStringLiteral("body"))); c.insert(QStringLiteral("msgHeader"), mp->source()->createMessageHeader(mp->mMessage.data())); c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderSubParts(mp, htmlWriter); })); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/htmlmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); auto preferredMode = mp->source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); const bool isPrinting = mp->source()->isPrinting(); block.setProperty("htmlMail", isHtmlPreferred); block.setProperty("loadExternal", mp->source()->htmlLoadExternal()); block.setProperty("isPrinting", isPrinting); { QString extraHead; //laurent: FIXME port to async method webengine QString bodyText = processHtml(mp->mBodyHTML, extraHead); if (isHtmlPreferred) { mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true); htmlWriter->extraHead(extraHead); } block.setProperty("containsExternalReferences", containsExternalReferences(bodyText, extraHead)); c.insert(QStringLiteral("content"), bodyText); } { ConvertHtmlToPlainText convert; convert.setHtmlString(mp->mBodyHTML); QString plaintext = convert.generatePlainText(); plaintext.replace(QLatin1Char('\n'), QStringLiteral("
")); c.insert(QStringLiteral("plaintext"), plaintext); } mp->source()->setHtmlMode(Util::Html, QList() << Util::Html); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/encryptedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node || mp->hasSubParts()) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->content() && mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->mCryptoProto)); if (!mp->mDecryptRecipients.empty()) { c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->mDecryptRecipients)); } c.insert(QStringLiteral("block"), &block); block.setProperty("inProgress", metaData.inProgress); block.setProperty("isDecrypted", mp->decryptMessage()); block.setProperty("isDecryptable", metaData.isDecryptable); block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral( "document-decrypt"), KIconLoader::Small)).url()); block.setProperty("errorText", metaData.errorText); block.setProperty("noSecKey", mp->mNoSecKey); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); auto cryptoProto = mp->mCryptoProto; const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime()); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/signedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto)); c.insert(QStringLiteral("block"), &block); block.setProperty("inProgress", metaData.inProgress); block.setProperty("errorText", metaData.errorText); block.setProperty("detailHeader", mp->source()->showSignatureDetails()); block.setProperty("printing", false); block.setProperty("addr", metaData.signerMailAddresses.join(QLatin1Char(','))); block.setProperty("technicalProblem", metaData.technicalProblem); block.setProperty("keyId", metaData.keyId); if (metaData.creationTime.isValid()) { //should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475 block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat)); } block.setProperty("isGoodSignature", metaData.isGoodSignature); block.setProperty("isSMIME", isSMIME); if (metaData.keyTrust == GpgME::Signature::Unknown) { block.setProperty("keyTrust", QStringLiteral("unknown")); } else if (metaData.keyTrust == GpgME::Signature::Marginal) { block.setProperty("keyTrust", QStringLiteral("marginal")); } else if (metaData.keyTrust == GpgME::Signature::Full) { block.setProperty("keyTrust", QStringLiteral("full")); } else if (metaData.keyTrust == GpgME::Signature::Ultimate) { block.setProperty("keyTrust", QStringLiteral("ultimate")); } else { block.setProperty("keyTrust", QStringLiteral("untrusted")); } QString startKeyHREF; { QString keyWithWithoutURL; if (cryptoProto) { startKeyHREF = QStringLiteral("") .arg(cryptoProto->displayName(), cryptoProto->name(), QString::fromLatin1(metaData.keyId)); keyWithWithoutURL = QStringLiteral("%1%2").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral( "0x") + metaData.keyId))); } else { keyWithWithoutURL = QStringLiteral("0x") + QString::fromUtf8(metaData.keyId); } block.setProperty("keyWithWithoutURL", keyWithWithoutURL); } bool onlyShowKeyURL = false; bool showKeyInfos = false; bool cannotCheckSignature = true; QString signer = metaData.signer; QString statusStr; QString mClass; QString greenCaseWarning; if (metaData.inProgress) { mClass = QStringLiteral("signInProgress"); } else { const QStringList &blockAddrs(metaData.signerMailAddresses); // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. int frameColor = SIG_FRAME_COL_UNDEF; statusStr = sigStatusToString(cryptoProto, metaData.status_code, metaData.sigSummary, frameColor, showKeyInfos); // if needed fallback to english status text // that was reported by the plugin if (statusStr.isEmpty()) { statusStr = metaData.status; } if (metaData.technicalProblem) { frameColor = SIG_FRAME_COL_YELLOW; } switch (frameColor) { case SIG_FRAME_COL_RED: cannotCheckSignature = false; break; case SIG_FRAME_COL_YELLOW: cannotCheckSignature = true; break; case SIG_FRAME_COL_GREEN: cannotCheckSignature = false; break; } // temporary hack: always show key information! showKeyInfos = true; if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) { switch (frameColor) { case SIG_FRAME_COL_RED: mClass = QStringLiteral("signErr"); onlyShowKeyURL = true; break; case SIG_FRAME_COL_YELLOW: if (metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { mClass = QStringLiteral("signOkKeyBad"); } break; case SIG_FRAME_COL_GREEN: mClass = QStringLiteral("signOkKeyOk"); // extra hint for green case // that email addresses in DN do not match fromAddress QString msgFrom(KEmailAddress::extractEmailAddress(mp->mFromAddress)); QString certificate; if (metaData.keyId.isEmpty()) { certificate = i18n("certificate"); } else { certificate = startKeyHREF + i18n("certificate") + QStringLiteral(""); } if (!blockAddrs.empty()) { if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n( "Sender's mail address is not stored in the %1 used for signing.", certificate) +QStringLiteral("
") +i18n("sender: ") +msgFrom +QStringLiteral("
") +i18n("stored: "); // We cannot use Qt's join() function here but // have to join the addresses manually to // extract the mail addresses (without '<''>') // before including it into our string: bool bStart = true; QStringList::ConstIterator end(blockAddrs.constEnd()); for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != end; ++it) { if (!bStart) { greenCaseWarning.append(QStringLiteral(",
   ")); } bStart = false; greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it)); } } } else { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n("No mail address is stored in the %1 used for signing, " "so we cannot compare it to the sender's address %2.", certificate, msgFrom); } break; } if (showKeyInfos && !cannotCheckSignature) { if (metaData.signer.isEmpty()) { signer.clear(); } else { if (!blockAddrs.empty()) { const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first()); signer = QStringLiteral("%2").arg(QLatin1String(QUrl :: toPercentEncoding( address . path())), signer); } } } } else { if (metaData.signer.isEmpty() || metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { // HTMLize the signer's user id and create mailto: link signer = MessageCore::StringUtil::quoteHtmlChars(signer, true); signer = QStringLiteral("%1").arg(signer); if (metaData.isGoodSignature) { if (metaData.keyTrust < GpgME::Signature::Marginal) { mClass = QStringLiteral("signOkKeyBad"); } else { mClass = QStringLiteral("signOkKeyOk"); } } else { mClass = QStringLiteral("signErr"); } } } } block.setProperty("onlyShowKeyURL", onlyShowKeyURL); block.setProperty("showKeyInfos", showKeyInfos); block.setProperty("cannotCheckSignature", cannotCheckSignature); block.setProperty("signer", signer); block.setProperty("statusStr", statusStr); block.setProperty("signClass", mClass); block.setProperty("greenCaseWarning", greenCaseWarning); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isSigned || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSigned(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isEncrypted || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderEncrypted(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } auto mode = mp->preferredMode(); if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) { foreach (const auto m, mp->availableModes()) { if (m != MimeTreeParser::Util::MultipartPlain) { mode = m; break; } } } MimeMessagePart::Ptr part(mp->mChildParts.first()); if (mp->mChildParts.contains(mode)) { part = mp->mChildParts[mode]; } render(part, htmlWriter); } void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const GpgME::ImportResult &importResult(mp->mImportResult); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/certmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString())); block.setProperty("nImp", importResult.numImported()); block.setProperty("nUnc", importResult.numUnchanged()); block.setProperty("nSKImp", importResult.numSecretKeysImported()); block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged()); QVariantList keylist; const auto imports = importResult.imports(); auto end(imports.end()); for (auto it = imports.begin(); it != end; ++it) { QObject *key(new QObject(mp.data())); key->setProperty("error", QString::fromLocal8Bit((*it).error().asString())); key->setProperty("status", (*it).status()); key->setProperty("fingerprint", QLatin1String((*it).fingerprint())); keylist << QVariant::fromValue(key); } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { if (!mRendererFactory) { return false; } for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) { if (r->render(msgPart, htmlWriter, this)) { return true; } } return false; } void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { const QString className = QString::fromUtf8(msgPart->metaObject()->className()); if (renderWithFactory(msgPart, htmlWriter)) { return; } if (className == QStringLiteral("MimeTreeParser::MessagePartList")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::MimeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncapsulatedRfc822MessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::HtmlMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::SignedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncryptedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::AlternativeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::CertMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else { qCWarning(MESSAGEVIEWER_LOG) << "We got a unkonwn classname, using default behaviour for " << className; } } bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart) { auto mp = msgPart.dynamicCast(); auto content = msgPart->content(); if (!mp) { auto _mp = msgPart.dynamicCast(); if (_mp) { return msgPart->nodeHelper()->isNodeDisplayedHidden(content); } else { return false; } } if (mShowOnlyOneMimePart) { return false; } - const AttachmentStrategy *const as = msgPart->source()->attachmentStrategy(); + const AttachmentStrategy *const as = mAttachmentStrategy; const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); QByteArray mediaType("text"); if (content->contentType(false) && !content->contentType()->mediaType().isEmpty() && !content->contentType()->subType().isEmpty()) { mediaType = content->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!mp->neverDisplayInline()) { if (as) { defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon; } } // neither image nor text -> show as icon if (!mp->isImage() && !isTextPart) { defaultAsIcon = true; } bool hidden(false); if (isTextPart) { hidden = defaultHidden; } else { if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") { hidden = true; } else { hidden = defaultHidden && content->parent(); hidden |= defaultAsIcon && defaultHidden; } } msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden); return hidden; } MimeTreeParser::IconType MimeTreeParser::DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr& msgPart) { auto mp = msgPart.dynamicCast(); auto content = msgPart->content(); if (!mp) { return MimeTreeParser::IconType::NoIcon; } - const AttachmentStrategy *const as = msgPart->source()->attachmentStrategy(); + const AttachmentStrategy *const as = mAttachmentStrategy; const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None); const bool showOnlyOneMimePart(mShowOnlyOneMimePart); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); QByteArray mediaType("text"); if (content->contentType(false) && !content->contentType()->mediaType().isEmpty() && !content->contentType()->subType().isEmpty()) { mediaType = content->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!mp->neverDisplayInline()) { if (as) { defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon; } } if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) { defaultAsIcon = false; } // neither image nor text -> show as icon if (!mp->isImage() && !isTextPart) { defaultAsIcon = true; } if (isTextPart) { if (as && as->defaultDisplay(content) != AttachmentStrategy::Inline) { return MimeTreeParser::IconExternal; } return MimeTreeParser::NoIcon; } else { if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") { return MimeTreeParser::IconInline; } if (defaultHidden && !showOnlyOneMimePart && content->parent()) { return MimeTreeParser::IconInline; } if (defaultAsIcon) { return MimeTreeParser::IconExternal; } else if (mp->isImage()) { return MimeTreeParser::IconInline; } } return MimeTreeParser::NoIcon; } -DefaultRenderer::DefaultRenderer(const MimeTreeParser::MessagePart::Ptr &msgPart, CSSHelperBase *cssHelper, MimeTreeParser::HtmlWriter *writer, bool showOnlyOneMimePart) - : d(new MimeTreeParser::DefaultRendererPrivate(msgPart, cssHelper, writer, MessagePartRendererFactory::instance(), showOnlyOneMimePart)) +DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper) + : d(new MimeTreeParser::DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance())) { } DefaultRenderer::~DefaultRenderer() { delete d; } + +void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart) +{ + d->mShowOnlyOneMimePart = onlyOneMimePart; +} + +void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy) +{ + d->mAttachmentStrategy = strategy; +} + +void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, MimeTreeParser::HtmlWriter *writer) +{ + d->mMsgPart = msgPart; + d->renderFactory(d->mMsgPart, writer); +} diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.h b/messageviewer/src/messagepartthemes/default/defaultrenderer.h index ba68059b..8663efbd 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer.h +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.h @@ -1,45 +1,49 @@ /* Copyright (c) 2016 Sandro Knauß 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. */ #ifndef __MESSAGEVIEWER_DEFAULTRENDERER_H__ #define __MESSAGEVIEWER_DEFAULTRENDERER_H__ #include namespace MimeTreeParser { class DefaultRendererPrivate; class HtmlWriter; class MessagePart; typedef QSharedPointer MessagePartPtr; } namespace MessageViewer { +class AttachmentStrategy; class CSSHelperBase; class DefaultRenderer { public: - DefaultRenderer(const MimeTreeParser::MessagePartPtr &msgPart, CSSHelperBase *cssHelder, MimeTreeParser::HtmlWriter *writer, bool showOnlyOneMimePart); + DefaultRenderer(CSSHelperBase *cssHelder); ~DefaultRenderer(); + void setShowOnlyOneMimePart(bool onlyOneMimePart); + void setAttachmentStrategy(const AttachmentStrategy *strategy); + void render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *writer); private: MimeTreeParser::DefaultRendererPrivate *d; }; } #endif //__MIMETREEPARSER_MAILRENDERER_H__ diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer_p.h b/messageviewer/src/messagepartthemes/default/defaultrenderer_p.h index a2fe7acc..654bf79a 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer_p.h +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer_p.h @@ -1,72 +1,74 @@ /* Copyright (c) 2016 Sandro Knauß 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. */ #ifndef __MESSAGEVIEWER_DEFAULTRENDERER_P_H__ #define __MESSAGEVIEWER_DEFAULTRENDERER_P_H__ #include "defaultrenderer.h" #include "messagepartrendererbase.h" #include using namespace MimeTreeParser; using namespace MessageViewer; namespace MessageViewer { +class AttachmentStrategy; class MessagePartRendererFactory; class CSSHelperBase; class HtmlWriter; } namespace MimeTreeParser { class DefaultRendererPrivate : public MessageViewer::RenderContext { public: - DefaultRendererPrivate(const MessagePart::Ptr &msgPart, CSSHelperBase *cssHelper, HtmlWriter *writer, const MessagePartRendererFactory *rendererFactory, bool showOnlyOneMimePart); + DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory); ~DefaultRendererPrivate(); CSSHelperBase *cssHelper() const override; Interface::ObjectTreeSource *source() const; void renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) override; void render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter); void render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter); void render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter); bool renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *writer) override; using RenderContext::renderWithFactory; void renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter); bool isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart) override; MimeTreeParser::IconType displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart) override; - bool mShowOnlyOneMimePart; + bool mShowOnlyOneMimePart = false; MessagePart::Ptr mMsgPart; CSSHelperBase *mCSSHelper = nullptr; const MessageViewer::MessagePartRendererFactory *mRendererFactory = nullptr; + const MessageViewer::AttachmentStrategy *mAttachmentStrategy = nullptr; }; } #endif diff --git a/mimetreeparser/src/attachmentstrategy.cpp b/messageviewer/src/viewer/attachmentstrategy.cpp similarity index 94% rename from mimetreeparser/src/attachmentstrategy.cpp rename to messageviewer/src/viewer/attachmentstrategy.cpp index 5ed4a359..64008c0c 100644 --- a/mimetreeparser/src/attachmentstrategy.cpp +++ b/messageviewer/src/viewer/attachmentstrategy.cpp @@ -1,370 +1,370 @@ /* -*- c++ -*- attachmentstrategy.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "attachmentstrategy.h" -#include "nodehelper.h" -#include "utils/util.h" +#include +#include #include #include -#include "mimetreeparser_debug.h" +#include "messageviewer_debug.h" -using namespace MimeTreeParser; +using namespace MessageViewer; static AttachmentStrategy::Display smartDisplay(KMime::Content *node) { const auto cd = node->contentDisposition(false); if (cd && cd->disposition() == KMime::Headers::CDinline) { // explict "inline" disposition: return AttachmentStrategy::Inline; } if (cd && cd->disposition() == KMime::Headers::CDattachment) { // explicit "attachment" disposition: return AttachmentStrategy::AsIcon; } const auto ct = node->contentType(false); if (ct && ct->isText() && ct->name().trimmed().isEmpty() && (!cd || cd->filename().trimmed().isEmpty())) { // text/* w/o filename parameter: return AttachmentStrategy::Inline; } return AttachmentStrategy::AsIcon; } // // IconicAttachmentStrategy: // show everything but the first text/plain body as icons // class IconicAttachmentStrategy : public AttachmentStrategy { friend class AttachmentStrategy; protected: IconicAttachmentStrategy() : AttachmentStrategy() { } virtual ~IconicAttachmentStrategy() { } public: const char *name() const override { return "iconic"; } bool inlineNestedMessages() const override { return false; } Display defaultDisplay(KMime::Content *node) const override { if (node->contentType()->isText() && node->contentDisposition()->filename().trimmed().isEmpty() && node->contentType()->name().trimmed().isEmpty()) { // text/* w/o filename parameter: return Inline; } return AsIcon; } }; // // SmartAttachmentStrategy: // in addition to Iconic, show all body parts // with content-disposition == "inline" and // all text parts without a filename or name parameter inline // class SmartAttachmentStrategy : public AttachmentStrategy { friend class AttachmentStrategy; protected: SmartAttachmentStrategy() : AttachmentStrategy() { } virtual ~SmartAttachmentStrategy() { } public: const char *name() const override { return "smart"; } bool inlineNestedMessages() const override { return true; } Display defaultDisplay(KMime::Content *node) const override { return smartDisplay(node); } }; // // InlinedAttachmentStrategy: // show everything possible inline // class InlinedAttachmentStrategy : public AttachmentStrategy { friend class AttachmentStrategy; protected: InlinedAttachmentStrategy() : AttachmentStrategy() { } virtual ~InlinedAttachmentStrategy() { } public: const char *name() const override { return "inlined"; } bool inlineNestedMessages() const override { return true; } Display defaultDisplay(KMime::Content *) const override { return Inline; } }; // // HiddenAttachmentStrategy // show nothing except the first text/plain body part _at all_ // class HiddenAttachmentStrategy : public AttachmentStrategy { friend class AttachmentStrategy; protected: HiddenAttachmentStrategy() : AttachmentStrategy() { } virtual ~HiddenAttachmentStrategy() { } public: const char *name() const override { return "hidden"; } bool inlineNestedMessages() const override { return false; } Display defaultDisplay(KMime::Content *node) const override { if (node->contentType()->isText() && node->contentDisposition()->filename().trimmed().isEmpty() && node->contentType()->name().trimmed().isEmpty()) { // text/* w/o filename parameter: return Inline; } if (!node->parent()) { return Inline; } if (node->parent() && node->parent()->contentType()->isMultipart() && node->parent()->contentType()->subType() == "related") { return Inline; } return None; } }; class HeaderOnlyAttachmentStrategy : public AttachmentStrategy { friend class AttachmentStrategy; protected: HeaderOnlyAttachmentStrategy() : AttachmentStrategy() { } virtual ~HeaderOnlyAttachmentStrategy() { } public: const char *name() const override { return "headerOnly"; } bool inlineNestedMessages() const override { return true; } Display defaultDisplay(KMime::Content *node) const override { - if (NodeHelper::isInEncapsulatedMessage(node)) { + if (MimeTreeParser::NodeHelper::isInEncapsulatedMessage(node)) { return smartDisplay(node); } - if (!Util::labelForContent(node).isEmpty() && QIcon::hasThemeIcon(Util::iconNameForContent(node)) && !Util::isTypeBlacklisted(node)) { + if (!MimeTreeParser::Util::labelForContent(node).isEmpty() && QIcon::hasThemeIcon(MimeTreeParser::Util::iconNameForContent(node)) && !MimeTreeParser::Util::isTypeBlacklisted(node)) { return None; } return smartDisplay(node); } bool requiresAttachmentListInHeader() const override { return true; } }; // // AttachmentStrategy abstract base: // AttachmentStrategy::AttachmentStrategy() { } AttachmentStrategy::~AttachmentStrategy() { } const AttachmentStrategy *AttachmentStrategy::create(Type type) { switch (type) { case Iconic: return iconic(); case Smart: return smart(); case Inlined: return inlined(); case Hidden: return hidden(); case HeaderOnly: return headerOnly(); } - qCCritical(MIMETREEPARSER_LOG) << "Unknown attachment startegy ( type ==" + qCCritical(MESSAGEVIEWER_LOG) << "Unknown attachment startegy ( type ==" << (int)type << ") requested!"; return nullptr; // make compiler happy } const AttachmentStrategy *AttachmentStrategy::create(const QString &type) { const QString lowerType = type.toLower(); if (lowerType == QLatin1String("iconic")) { return iconic(); } //if ( lowerType == "smart" ) return smart(); // not needed, see below if (lowerType == QLatin1String("inlined")) { return inlined(); } if (lowerType == QLatin1String("hidden")) { return hidden(); } if (lowerType == QLatin1String("headeronly")) { return headerOnly(); } // don't kFatal here, b/c the strings are user-provided // (KConfig), so fail gracefully to the default: return smart(); } static const AttachmentStrategy *iconicStrategy = nullptr; static const AttachmentStrategy *smartStrategy = nullptr; static const AttachmentStrategy *inlinedStrategy = nullptr; static const AttachmentStrategy *hiddenStrategy = nullptr; static const AttachmentStrategy *headerOnlyStrategy = nullptr; const AttachmentStrategy *AttachmentStrategy::iconic() { if (!iconicStrategy) { iconicStrategy = new IconicAttachmentStrategy(); } return iconicStrategy; } const AttachmentStrategy *AttachmentStrategy::smart() { if (!smartStrategy) { smartStrategy = new SmartAttachmentStrategy(); } return smartStrategy; } const AttachmentStrategy *AttachmentStrategy::inlined() { if (!inlinedStrategy) { inlinedStrategy = new InlinedAttachmentStrategy(); } return inlinedStrategy; } const AttachmentStrategy *AttachmentStrategy::hidden() { if (!hiddenStrategy) { hiddenStrategy = new HiddenAttachmentStrategy(); } return hiddenStrategy; } const AttachmentStrategy *AttachmentStrategy::headerOnly() { if (!headerOnlyStrategy) { headerOnlyStrategy = new HeaderOnlyAttachmentStrategy(); } return headerOnlyStrategy; } bool AttachmentStrategy::requiresAttachmentListInHeader() const { return false; } diff --git a/mimetreeparser/src/attachmentstrategy.h b/messageviewer/src/viewer/attachmentstrategy.h similarity index 92% rename from mimetreeparser/src/attachmentstrategy.h rename to messageviewer/src/viewer/attachmentstrategy.h index 606fc6f5..f9fa558d 100644 --- a/mimetreeparser/src/attachmentstrategy.h +++ b/messageviewer/src/viewer/attachmentstrategy.h @@ -1,88 +1,88 @@ /* -*- c++ -*- attachmentstrategy.h This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ -#ifndef __MIMETREEPARSER_ATTACHMENTSTRATEGY_H__ -#define __MIMETREEPARSER_ATTACHMENTSTRATEGY_H__ +#ifndef __MESSAGEVIEWER_ATTACHMENTSTRATEGY_H__ +#define __MESSAGEVIEWER_ATTACHMENTSTRATEGY_H__ -#include "mimetreeparser_export.h" +#include "messageviewer_export.h" class QString; namespace KMime { class Content; } -namespace MimeTreeParser { -class MIMETREEPARSER_EXPORT AttachmentStrategy +namespace MessageViewer { +class MESSAGEVIEWER_EXPORT AttachmentStrategy { protected: AttachmentStrategy(); virtual ~AttachmentStrategy(); public: // // Factory methods: // enum Type { Iconic, Smart, Inlined, Hidden, HeaderOnly }; static const AttachmentStrategy *create(Type type); static const AttachmentStrategy *create(const QString &type); static const AttachmentStrategy *iconic(); static const AttachmentStrategy *smart(); static const AttachmentStrategy *inlined(); static const AttachmentStrategy *hidden(); static const AttachmentStrategy *headerOnly(); // // Navigation methods: // virtual const char *name() const = 0; // // Bahavioural: // enum Display { None, AsIcon, Inline }; virtual bool inlineNestedMessages() const = 0; virtual Display defaultDisplay(KMime::Content *node) const = 0; virtual bool requiresAttachmentListInHeader() const; }; } #endif // __MIMETREEPARSER_ATTACHMENTSTRATEGY_H__ diff --git a/messageviewer/src/viewer/objecttreeemptysource.cpp b/messageviewer/src/viewer/objecttreeemptysource.cpp index 9b7144e2..73af51e7 100644 --- a/messageviewer/src/viewer/objecttreeemptysource.cpp +++ b/messageviewer/src/viewer/objecttreeemptysource.cpp @@ -1,146 +1,149 @@ /* 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 "objecttreeemptysource.h" +#include "viewer/attachmentstrategy.h" #include "viewer/viewer_p.h" #include "viewer/csshelperbase.h" -#include #include #include #include "messagepartthemes/default/defaultrenderer.h" using namespace MessageViewer; namespace MessageViewer { class EmptySourcePrivate { public: EmptySourcePrivate() : mAllowDecryption(false) { } bool mAllowDecryption; }; } EmptySource::EmptySource() : MimeTreeParser::Interface::ObjectTreeSource() , d(new MessageViewer::EmptySourcePrivate) { } EmptySource::~EmptySource() { delete d; } bool EmptySource::decryptMessage() const { return d->mAllowDecryption; } bool EmptySource::htmlLoadExternal() const { return false; } bool EmptySource::showSignatureDetails() const { return false; } void EmptySource::setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) { Q_UNUSED(mode); Q_UNUSED(availableModes); } MimeTreeParser::Util::HtmlMode EmptySource::preferredMode() const { return MimeTreeParser::Util::Html; } void EmptySource::setAllowDecryption(bool allowDecryption) { d->mAllowDecryption = allowDecryption; } int EmptySource::levelQuote() const { return 1; } const QTextCodec *EmptySource::overrideCodec() { return nullptr; } QString EmptySource::createMessageHeader(KMime::Message *message) { Q_UNUSED(message); return QString(); //do nothing } -const MimeTreeParser::AttachmentStrategy *EmptySource::attachmentStrategy() +const AttachmentStrategy *EmptySource::attachmentStrategy() { - return MimeTreeParser::AttachmentStrategy::smart(); + return AttachmentStrategy::smart(); } MimeTreeParser::HtmlWriter *EmptySource::htmlWriter() { return nullptr; } CSSHelperBase *EmptySource::cssHelper() { return nullptr; } bool EmptySource::autoImportKeys() const { return true; } bool EmptySource::showEmoticons() const { return false; } bool EmptySource::showExpandQuotesMark() const { return false; } const MimeTreeParser::BodyPartFormatterFactory *EmptySource::bodyPartFormatterFactory() { return MimeTreeParser::BodyPartFormatterFactory::instance(); } bool EmptySource::isPrinting() const { return false; } void EmptySource::render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *htmlWriter, bool showOnlyOneMimePart) { - DefaultRenderer(msgPart, cssHelper(), htmlWriter, showOnlyOneMimePart); + auto renderer = DefaultRenderer(cssHelper()); + renderer.setShowOnlyOneMimePart(showOnlyOneMimePart); + renderer.setAttachmentStrategy(attachmentStrategy()); + renderer.render(msgPart, htmlWriter); } diff --git a/messageviewer/src/viewer/objecttreeemptysource.h b/messageviewer/src/viewer/objecttreeemptysource.h index 3d498de6..c2bf0711 100644 --- a/messageviewer/src/viewer/objecttreeemptysource.h +++ b/messageviewer/src/viewer/objecttreeemptysource.h @@ -1,61 +1,62 @@ /* 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 MAILVIEWER_OBJECTTREEEMPTYSOURCE_H #define MAILVIEWER_OBJECTTREEEMPTYSOURCE_H #include #include "messageviewer_export.h" class QString; namespace MessageViewer { +class AttachmentStrategy; class CSSHelperBase; /** An ObjectTreeSource that does not work on anything */ class EmptySourcePrivate; class MESSAGEVIEWER_EXPORT EmptySource : public MimeTreeParser::Interface::ObjectTreeSource { public: EmptySource(); ~EmptySource(); bool decryptMessage() const override; bool htmlLoadExternal() const override; bool showSignatureDetails() const override; void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) override; MimeTreeParser::Util::HtmlMode preferredMode() const override; void setAllowDecryption(bool allowDecryption); int levelQuote() const override; const QTextCodec *overrideCodec() override; QString createMessageHeader(KMime::Message *message) override; - const MimeTreeParser::AttachmentStrategy *attachmentStrategy() override; + virtual const AttachmentStrategy *attachmentStrategy(); MimeTreeParser::HtmlWriter *htmlWriter() override; virtual CSSHelperBase *cssHelper(); bool autoImportKeys() const override; bool showEmoticons() const override; bool showExpandQuotesMark() const override; const MimeTreeParser::BodyPartFormatterFactory *bodyPartFormatterFactory() override; void render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *htmlWriter, bool showOnlyOneMimePart) override; bool isPrinting() const override; private: EmptySourcePrivate *const d; }; } #endif diff --git a/messageviewer/src/viewer/objecttreeviewersource.cpp b/messageviewer/src/viewer/objecttreeviewersource.cpp index 2e47e85a..acd00298 100644 --- a/messageviewer/src/viewer/objecttreeviewersource.cpp +++ b/messageviewer/src/viewer/objecttreeviewersource.cpp @@ -1,135 +1,138 @@ /* 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 "objecttreeviewersource.h" #include "csshelper.h" #include #include #include "messagepartthemes/default/defaultrenderer.h" #include "viewer/viewer_p.h" #include "widgets/htmlstatusbar.h" #include "settings/messageviewersettings.h" using namespace MessageViewer; MailViewerSource::MailViewerSource(ViewerPrivate *viewer) : MimeTreeParser::Interface::ObjectTreeSource() , mViewer(viewer) { } MailViewerSource::~MailViewerSource() { } bool MailViewerSource::decryptMessage() const { return mViewer->decryptMessage(); } bool MailViewerSource::htmlLoadExternal() const { return mViewer->htmlLoadExternal(); } bool MailViewerSource::showSignatureDetails() const { return mViewer->mShowSignatureDetails; } void MailViewerSource::setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) { mViewer->mColorBar->setAvailableModes(availableModes); mViewer->mColorBar->setMode(mode); } MimeTreeParser::Util::HtmlMode MailViewerSource::preferredMode() const { switch (mViewer->displayFormatMessageOverwrite()) { case MessageViewer::Viewer::UseGlobalSetting: case MessageViewer::Viewer::Unknown: return mViewer->htmlMailGlobalSetting() ? MimeTreeParser::Util::Html : MimeTreeParser::Util ::Normal; case MessageViewer::Viewer::Html: return MimeTreeParser::Util::MultipartHtml; case MessageViewer::Viewer::Text: return MimeTreeParser::Util::MultipartPlain; case MessageViewer::Viewer::ICal: return MimeTreeParser::Util::MultipartIcal; } Q_ASSERT(true); return MimeTreeParser::Util::Html; } int MailViewerSource::levelQuote() const { return mViewer->mLevelQuote; } const QTextCodec *MailViewerSource::overrideCodec() { return mViewer->overrideCodec(); } QString MailViewerSource::createMessageHeader(KMime::Message *message) { return mViewer->writeMsgHeader(message); } -const MimeTreeParser::AttachmentStrategy *MailViewerSource::attachmentStrategy() +const AttachmentStrategy *MailViewerSource::attachmentStrategy() { return mViewer->attachmentStrategy(); } MimeTreeParser::HtmlWriter *MailViewerSource::htmlWriter() { return mViewer->htmlWriter(); } bool MailViewerSource::autoImportKeys() const { return MessageViewer::MessageViewerSettings::self()->autoImportKeys(); } bool MailViewerSource::showEmoticons() const { return mViewer->showEmoticons(); } bool MailViewerSource::showExpandQuotesMark() const { return MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark(); } const MimeTreeParser::BodyPartFormatterFactory *MailViewerSource::bodyPartFormatterFactory() { return MimeTreeParser::BodyPartFormatterFactory::instance(); } bool MailViewerSource::isPrinting() const { return mViewer->mPrinting; } void MailViewerSource::render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *htmlWriter, bool showOnlyOneMimePart) { - DefaultRenderer(msgPart, mViewer->cssHelper(), htmlWriter, showOnlyOneMimePart); + auto renderer = DefaultRenderer(mViewer->cssHelper()); + renderer.setShowOnlyOneMimePart(showOnlyOneMimePart); + renderer.setAttachmentStrategy(attachmentStrategy()); + renderer.render(msgPart, htmlWriter); } diff --git a/messageviewer/src/viewer/objecttreeviewersource.h b/messageviewer/src/viewer/objecttreeviewersource.h index d8961795..eb95ab31 100644 --- a/messageviewer/src/viewer/objecttreeviewersource.h +++ b/messageviewer/src/viewer/objecttreeviewersource.h @@ -1,58 +1,59 @@ /* 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 MAILVIEWER_OBJECTTREEVIEWERSOURCE_H #define MAILVIEWER_OBJECTTREEVIEWERSOURCE_H #include class QString; namespace MessageViewer { +class AttachmentStrategy; class ViewerPrivate; /** An ObjectTreeParser source working on a MailViewer object */ class MailViewerSource : public MimeTreeParser::Interface::ObjectTreeSource { public: explicit MailViewerSource(ViewerPrivate *viewer); ~MailViewerSource(); bool decryptMessage() const override; bool htmlLoadExternal() const override; bool showSignatureDetails() const override; void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) override; MimeTreeParser::Util::HtmlMode preferredMode() const override; int levelQuote() const override; const QTextCodec *overrideCodec() override; QString createMessageHeader(KMime::Message *message) override; - const MimeTreeParser::AttachmentStrategy *attachmentStrategy() override; + const AttachmentStrategy *attachmentStrategy(); MimeTreeParser::HtmlWriter *htmlWriter() override; bool autoImportKeys() const override; bool showEmoticons() const override; bool showExpandQuotesMark() const override; const MimeTreeParser::BodyPartFormatterFactory *bodyPartFormatterFactory() override; void render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *htmlWriter, bool showOnlyOneMimePart) override; bool isPrinting() const override; private: ViewerPrivate *mViewer = nullptr; }; } #endif diff --git a/messageviewer/src/viewer/viewer.cpp b/messageviewer/src/viewer/viewer.cpp index cf87ab60..8a66cd77 100644 --- a/messageviewer/src/viewer/viewer.cpp +++ b/messageviewer/src/viewer/viewer.cpp @@ -1,688 +1,688 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (C) 2013-2017 Laurent Montel 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. */ // define this to copy all html that is written to the readerwindow to // filehtmlwriter.out in the current working directory //#define KMAIL_READER_HTML_DEBUG 1 #include "viewer.h" #include "viewer_p.h" #include "widgets/configurewidget.h" #include "csshelper.h" #include "settings/messageviewersettings.h" #include "viewer/webengine/mailwebengineview.h" #include #include #include "viewer/mimeparttree/mimetreemodel.h" #include "viewer/mimeparttree/mimeparttreeview.h" #include "widgets/zoomactionmenu.h" #include #include #include #include #include #include #include namespace MessageViewer { class AbstractMessageLoadedHandler::Private { public: Akonadi::Session *mSession = nullptr; }; AbstractMessageLoadedHandler::AbstractMessageLoadedHandler() : d(new Private) { } AbstractMessageLoadedHandler::~AbstractMessageLoadedHandler() { delete d; } void AbstractMessageLoadedHandler::setSession(Akonadi::Session *session) { d->mSession = session; } Akonadi::Session *AbstractMessageLoadedHandler::session() const { return d->mSession; } Viewer::Viewer(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QWidget(aParent) , d_ptr(new ViewerPrivate(this, mainWindow, actionCollection)) { initialize(); } Viewer::~Viewer() { //the d_ptr is automatically deleted } void Viewer::initialize() { connect(d_ptr, &ViewerPrivate::displayPopupMenu, this, &Viewer::displayPopupMenu); connect(d_ptr, &ViewerPrivate::popupMenu, this, &Viewer::popupMenu); connect(d_ptr, SIGNAL(urlClicked(Akonadi::Item,QUrl)), SIGNAL(urlClicked(Akonadi::Item,QUrl))); connect(d_ptr, &ViewerPrivate::requestConfigSync, this, &Viewer::requestConfigSync); connect(d_ptr, &ViewerPrivate::makeResourceOnline, this, &Viewer::makeResourceOnline); connect(d_ptr, &ViewerPrivate::showReader, this, &Viewer::showReader); connect(d_ptr, &ViewerPrivate::showMessage, this, &Viewer::showMessage); connect(d_ptr, &ViewerPrivate::replyMessageTo, this, &Viewer::replyMessageTo); connect(d_ptr, &ViewerPrivate::showStatusBarMessage, this, &Viewer::showStatusBarMessage); connect(d_ptr, &ViewerPrivate::itemRemoved, this, &Viewer::itemRemoved); connect(d_ptr, &ViewerPrivate::changeDisplayMail, this, &Viewer::slotChangeDisplayMail); connect(d_ptr, &ViewerPrivate::moveMessageToTrash, this, &Viewer::moveMessageToTrash); connect(d_ptr, &ViewerPrivate::pageIsScrolledToBottom, this, &Viewer::pageIsScrolledToBottom); connect(d_ptr, &ViewerPrivate::printingFinished, this, &Viewer::printingFinished); setMessage(KMime::Message::Ptr(), MimeTreeParser::Delayed); } void Viewer::changeEvent(QEvent *event) { Q_D(Viewer); if (event->type() == QEvent::FontChange) { d->slotGeneralFontChanged(); } QWidget::changeEvent(event); } void Viewer::setMessage(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); if (message == d->message()) { return; } d->setMessage(message, updateMode); } void Viewer::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); if (d->messageItem() == item) { return; } if (!item.isValid() || item.loadedPayloadParts().contains(Akonadi::MessagePart::Body)) { d->setMessageItem(item, updateMode); } else { Akonadi::ItemFetchJob *job = createFetchJob(item); connect(job, SIGNAL(result(KJob*)), d, SLOT(itemFetchResult(KJob*))); d->displaySplashPage(i18n("Loading message...")); } } QString Viewer::messagePath() const { Q_D(const Viewer); return d->mMessagePath; } void Viewer::setMessagePath(const QString &path) { Q_D(Viewer); d->mMessagePath = path; } void Viewer::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain) { Q_D(Viewer); d->displaySplashPage(templateName, data, domain); } void Viewer::enableMessageDisplay() { Q_D(Viewer); d->enableMessageDisplay(); } void Viewer::printMessage(const Akonadi::Item &msg) { Q_D(Viewer); d->printMessage(msg); } void Viewer::printPreviewMessage(const Akonadi::Item &message) { Q_D(Viewer); d->printPreviewMessage(message); } void Viewer::printPreview() { Q_D(Viewer); d->slotPrintPreview(); } void Viewer::print() { Q_D(Viewer); d->slotPrintMessage(); } void Viewer::resizeEvent(QResizeEvent *) { Q_D(Viewer); if (!d->mResizeTimer.isActive()) { // // Combine all resize operations that are requested as long a // the timer runs. // d->mResizeTimer.start(100); } } void Viewer::closeEvent(QCloseEvent *e) { Q_D(Viewer); QWidget::closeEvent(e); d->writeConfig(); } void Viewer::slotAttachmentSaveAs() { Q_D(Viewer); d->slotAttachmentSaveAs(); } void Viewer::slotAttachmentSaveAll() { Q_D(Viewer); d->slotAttachmentSaveAll(); } void Viewer::slotSaveMessage() { Q_D(Viewer); d->slotSaveMessage(); } void Viewer::slotScrollUp() { Q_D(Viewer); d->mViewer->scrollUp(10); } void Viewer::slotScrollDown() { Q_D(Viewer); d->mViewer->scrollDown(10); } void Viewer::atBottom() { Q_D(const Viewer); d->mViewer->isScrolledToBottom(); } void Viewer::slotJumpDown() { Q_D(Viewer); d->mViewer->scrollPageDown(100); } void Viewer::slotScrollPrior() { Q_D(Viewer); d->mViewer->scrollPageUp(80); } void Viewer::slotScrollNext() { Q_D(Viewer); d->mViewer->scrollPageDown(80); } QString Viewer::selectedText() const { Q_D(const Viewer); return d->mViewer->selectedText(); } Viewer::DisplayFormatMessage Viewer::displayFormatMessageOverwrite() const { Q_D(const Viewer); return d->displayFormatMessageOverwrite(); } void Viewer::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format) { Q_D(Viewer); d->setDisplayFormatMessageOverwrite(format); } void Viewer::setHtmlLoadExtOverride(bool override) { Q_D(Viewer); d->setHtmlLoadExtOverride(override); } bool Viewer::htmlLoadExtOverride() const { Q_D(const Viewer); return d->htmlLoadExtOverride(); } bool Viewer::htmlMail() const { Q_D(const Viewer); return d->htmlMail(); } bool Viewer::htmlLoadExternal() const { Q_D(const Viewer); return d->htmlLoadExternal(); } bool Viewer::isFixedFont() const { Q_D(const Viewer); return d->mUseFixedFont; } void Viewer::setUseFixedFont(bool useFixedFont) { Q_D(Viewer); d->setUseFixedFont(useFixedFont); } QWidget *Viewer::mainWindow() { Q_D(Viewer); return d->mMainWindow; } void Viewer::setDecryptMessageOverwrite(bool overwrite) { Q_D(Viewer); d->setDecryptMessageOverwrite(overwrite); } KMime::Message::Ptr Viewer::message() const { Q_D(const Viewer); return d->mMessage; } Akonadi::Item Viewer::messageItem() const { Q_D(const Viewer); return d->mMessageItem; } bool Viewer::event(QEvent *e) { Q_D(Viewer); if (e->type() == QEvent::PaletteChange) { delete d->mCSSHelper; d->mCSSHelper = new CSSHelper(d->mViewer); d->update(MimeTreeParser::Force); e->accept(); return true; } return QWidget::event(e); } void Viewer::slotFind() { Q_D(Viewer); d->slotFind(); } -const MimeTreeParser::AttachmentStrategy *Viewer::attachmentStrategy() const +const AttachmentStrategy *Viewer::attachmentStrategy() const { Q_D(const Viewer); return d->attachmentStrategy(); } -void Viewer::setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy) +void Viewer::setAttachmentStrategy(const AttachmentStrategy *strategy) { Q_D(Viewer); d->setAttachmentStrategy(strategy); } QString Viewer::overrideEncoding() const { Q_D(const Viewer); return d->overrideEncoding(); } void Viewer::setOverrideEncoding(const QString &encoding) { Q_D(Viewer); d->setOverrideEncoding(encoding); } CSSHelper *Viewer::cssHelper() const { Q_D(const Viewer); return d->cssHelper(); } KToggleAction *Viewer::toggleFixFontAction() const { Q_D(const Viewer); return d->mToggleFixFontAction; } bool Viewer::mimePartTreeIsEmpty() const { Q_D(const Viewer); return d->mimePartTreeIsEmpty(); } KToggleAction *Viewer::toggleMimePartTreeAction() const { Q_D(const Viewer); return d->mToggleMimePartTreeAction; } QAction *Viewer::selectAllAction() const { Q_D(const Viewer); return d->mSelectAllAction; } QAction *Viewer::viewSourceAction() const { Q_D(const Viewer); return d->mViewSourceAction; } QAction *Viewer::copyURLAction() const { Q_D(const Viewer); return d->mCopyURLAction; } QAction *Viewer::copyAction() const { Q_D(const Viewer); return d->mCopyAction; } QAction *Viewer::speakTextAction() const { Q_D(const Viewer); return d->mSpeakTextAction; } QAction *Viewer::copyImageLocation() const { Q_D(const Viewer); return d->mCopyImageLocation; } QAction *Viewer::saveAsAction() const { Q_D(const Viewer); return d->mSaveMessageAction; } QAction *Viewer::urlOpenAction() const { Q_D(const Viewer); return d->mUrlOpenAction; } bool Viewer::printingMode() const { Q_D(const Viewer); return d->printingMode(); } void Viewer::setPrinting(bool enable) { Q_D(Viewer); d->setPrinting(enable); } void Viewer::writeConfig(bool force) { Q_D(Viewer); d->writeConfig(force); } QUrl Viewer::urlClicked() const { Q_D(const Viewer); return d->mClickedUrl; } QUrl Viewer::imageUrlClicked() const { Q_D(const Viewer); return d->imageUrl(); } void Viewer::update(MimeTreeParser::UpdateMode updateMode) { Q_D(Viewer); d->update(updateMode); } void Viewer::setMessagePart(KMime::Content *aMsgPart) { Q_D(Viewer); d->setMessagePart(aMsgPart); } void Viewer::clear(MimeTreeParser::UpdateMode updateMode) { setMessage(KMime::Message::Ptr(), updateMode); } void Viewer::slotShowMessageSource() { Q_D(Viewer); d->slotShowMessageSource(); } void Viewer::readConfig() { Q_D(Viewer); d->readConfig(); } QAbstractItemModel *Viewer::messageTreeModel() const { #ifndef QT_NO_TREEVIEW return d_func()->mMimePartTree->mimePartModel(); #else return nullptr; #endif } Akonadi::ItemFetchJob *Viewer::createFetchJob(const Akonadi::Item &item) { Q_D(Viewer); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, d->mSession); job->fetchScope().fetchAllAttributes(); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->fetchScope().fetchFullPayload(true); job->fetchScope().setFetchRelations(true); // needed to know if we have notes or not job->fetchScope().fetchAttribute(); return job; } void Viewer::addMessageLoadedHandler(AbstractMessageLoadedHandler *handler) { Q_D(Viewer); if (!handler) { return; } handler->setSession(d->mSession); d->mMessageLoadedHandlers.insert(handler); } void Viewer::removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler) { Q_D(Viewer); d->mMessageLoadedHandlers.remove(handler); } void Viewer::deleteMessage() { Q_D(Viewer); Q_EMIT deleteMessage(d->messageItem()); } void Viewer::selectAll() { Q_D(Viewer); d->selectAll(); } void Viewer::copySelectionToClipboard() { Q_D(Viewer); d->slotCopySelectedText(); } void Viewer::setZoomFactor(qreal zoomFactor) { Q_D(Viewer); d->mZoomActionMenu->setZoomFactor(zoomFactor); } void Viewer::slotZoomReset() { Q_D(Viewer); d->mZoomActionMenu->slotZoomReset(); } void Viewer::slotZoomIn() { Q_D(Viewer); d->mZoomActionMenu->slotZoomIn(); } void Viewer::slotZoomOut() { Q_D(Viewer); d->mZoomActionMenu->slotZoomOut(); } QAction *Viewer::findInMessageAction() const { Q_D(const Viewer); return d->mFindInMessageAction; } void Viewer::slotChangeDisplayMail(Viewer::DisplayFormatMessage mode, bool loadExternal) { setHtmlLoadExtOverride(loadExternal); setDisplayFormatMessageOverwrite(mode); update(MimeTreeParser::Force); } QAction *Viewer::saveMessageDisplayFormatAction() const { Q_D(const Viewer); return d->mSaveMessageDisplayFormat; } QAction *Viewer::resetMessageDisplayFormatAction() const { Q_D(const Viewer); return d->mResetMessageDisplayFormat; } KToggleAction *Viewer::disableEmoticonAction() const { Q_D(const Viewer); return d->mDisableEmoticonAction; } void Viewer::saveMainFrameScreenshotInFile(const QString &filename) { Q_D(Viewer); return d->saveMainFrameScreenshotInFile(filename); } KActionMenu *Viewer::shareServiceUrlMenu() const { Q_D(const Viewer); return d->mShareServiceUrlMenu; } HeaderStylePlugin *Viewer::headerStylePlugin() const { Q_D(const Viewer); return d->mHeaderStylePlugin; } void Viewer::setPluginName(const QString &pluginName) { Q_D(Viewer); return d->setPluginName(pluginName); } void Viewer::showOpenAttachmentFolderWidget(const QUrl &url) { Q_D(Viewer); d->showOpenAttachmentFolderWidget(url); } QList Viewer::viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features) { Q_D(Viewer); return d->viewerPluginActionList(features); } QList Viewer::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const { Q_D(const Viewer); return d->interceptorUrlActions(result); } void Viewer::runJavaScript(const QString &code) { Q_D(Viewer); d->mViewer->page()->runJavaScript(code, WebEngineViewer::WebEngineManageScript::scriptWordId()); } void Viewer::setPrintElementBackground(bool printElementBackground) { Q_D(Viewer); d->mViewer->setPrintElementBackground(printElementBackground); } } diff --git a/messageviewer/src/viewer/viewer.h b/messageviewer/src/viewer/viewer.h index f0568b4b..1a97bea6 100644 --- a/messageviewer/src/viewer/viewer.h +++ b/messageviewer/src/viewer/viewer.h @@ -1,405 +1,402 @@ /* This file is part of KMail, the KDE mail client. Copyright (c) 1997 Markus Wuebben 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 MESSAGEVIEWER_H #define MESSAGEVIEWER_H #include "messageviewer_export.h" #include "config-messageviewer.h" #include "messageviewer/viewerplugininterface.h" #include #include #include namespace Akonadi { class Item; class ItemFetchJob; } -namespace MimeTreeParser { -class AttachmentStrategy; -} - class KActionCollection; class QAction; class KToggleAction; class KActionMenu; class QAbstractItemModel; class QCloseEvent; class QEvent; class QResizeEvent; namespace WebEngineViewer { class WebHitTestResult; } namespace MessageViewer { class WebHitTestResult; +class AttachmentStrategy; class HeaderStylePlugin; class CSSHelper; class ViewerPrivate; /** * An interface to plug in a handler that is called when * an message item has been loaded into the view. */ class Viewer; class MESSAGEVIEWER_EXPORT AbstractMessageLoadedHandler { public: AbstractMessageLoadedHandler(); virtual ~AbstractMessageLoadedHandler(); /** * This method is called whenever a message item has been loaded * into the view. * * @param item The message item that has been loaded. */ virtual void setItem(const Akonadi::Item &item) = 0; protected: Akonadi::Session *session() const; private: void setSession(Akonadi::Session *session); friend class Viewer; class Private; Private *const d; }; //TODO(Andras) once only those methods are public that really need to be public, probably export the whole class instead of just some methods /** * This is the main widget for the viewer. * See the documentation of ViewerPrivate for implementation details. * See Mainpage.dox for an overview of the classes in the messageviewer library. */ class MESSAGEVIEWER_EXPORT Viewer : public QWidget { Q_OBJECT Q_DECLARE_PRIVATE(Viewer) public: /** * Create a mail viewer widget * @param parent parent widget * @param mainWindow the application's main window * @param actionCollection the action collection where the widget's actions will belong to * @param f window flags */ explicit Viewer(QWidget *parent, QWidget *widget = nullptr, KActionCollection *actionCollection = nullptr); virtual ~Viewer(); /** * Returns the current message displayed in the viewer. */ KMime::Message::Ptr message() const; /** * Returns the current message item displayed in the viewer. */ Akonadi::Item messageItem() const; enum DisplayFormatMessage { UseGlobalSetting = 0, Text = 1, Html = 2, Unknown = 3, ICal = 4 }; enum AttachmentAction { Open = 1, OpenWith = 2, View = 3, Save = 4, Properties = 5, Delete = 6, Edit = 7, Copy = 8, ScrollTo = 9, ReplyMessageToAuthor = 10, ReplyMessageToAll = 11 }; enum ResourceOnlineMode { AllResources = 0, SelectedResource = 1 }; /** * Set the message that shall be shown. * @param msg - the message to be shown. If 0, an empty page is displayed. * @param updateMode - update the display immediately or not. See UpdateMode. */ void setMessage(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** * Set the Akonadi item that will be displayed. * @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data), * an empty page is shown. * @param updateMode - update the display immediately or not. See UpdateMode. */ void setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** * The path to the message in terms of Akonadi collection hierarchy. */ QString messagePath() const; /** * Set the path to the message in terms of Akonadi collection hierarchy. */ void setMessagePath(const QString &path); /** * Instead of settings a message to be shown sets a message part * to be shown */ void setMessagePart(KMime::Content *aMsgPart); /** * Convenience method to clear the reader and discard the current message. Sets the internal message pointer * returned by message() to 0. * @param updateMode - update the display immediately or not. See UpdateMode. */ void clear(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); void update(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** * Sets a message as the current one and print it immediately. * @param message the message to display and print */ void printMessage(const Akonadi::Item &msg); void printPreviewMessage(const Akonadi::Item &message); /** Print the currently displayed message */ void print(); void printPreview(); /** Get the html override setting */ Viewer::DisplayFormatMessage displayFormatMessageOverwrite() const; /** Override default html mail setting */ void setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format); /** Get the load external references override setting */ bool htmlLoadExtOverride() const; /** Override default load external references setting */ void setHtmlLoadExtOverride(bool override); /** Is html mail to be supported? Takes into account override */ bool htmlMail() const; /** Is loading ext. references to be supported? Takes into account override */ bool htmlLoadExternal() const; /** * Display a generic HTML splash page instead of a message. * @param templateName - the template to be loaded * @param data - data for the template */ void displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain = QByteArray()); /** Enable the displaying of messages again after an splash (or other) page was displayed */ void enableMessageDisplay(); /** Returns true if the message view is scrolled to the bottom. */ void atBottom(); bool isFixedFont() const; void setUseFixedFont(bool useFixedFont); QWidget *mainWindow(); /** Enforce message decryption. */ void setDecryptMessageOverwrite(bool overwrite = true); /** * Initiates a delete, by sending a signal to delete the message item */ void deleteMessage(); - const MimeTreeParser::AttachmentStrategy *attachmentStrategy() const; - void setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy); + const AttachmentStrategy *attachmentStrategy() const; + void setAttachmentStrategy(const AttachmentStrategy *strategy); QString overrideEncoding() const; void setOverrideEncoding(const QString &encoding); CSSHelper *cssHelper() const; void setPrinting(bool enable); void selectAll(); void copySelectionToClipboard(); void setZoomFactor(qreal zoomFactor); KToggleAction *toggleFixFontAction() const; KToggleAction *toggleMimePartTreeAction() const; QAction *selectAllAction() const; QAction *copyURLAction() const; QAction *copyAction() const; QAction *urlOpenAction() const; QAction *speakTextAction() const; QAction *copyImageLocation() const; QAction *viewSourceAction() const; QAction *findInMessageAction() const; QAction *saveAsAction() const; QAction *saveMessageDisplayFormatAction() const; QAction *resetMessageDisplayFormatAction() const; KToggleAction *disableEmoticonAction() const; KActionMenu *shareServiceUrlMenu() const; HeaderStylePlugin *headerStylePlugin() const; void setPluginName(const QString &pluginName); void writeConfig(bool withSync = true); QUrl urlClicked() const; QUrl imageUrlClicked() const; void readConfig(); /** A QAIM tree model of the message structure. */ QAbstractItemModel *messageTreeModel() const; /** * Create an item fetch job that is suitable for using to fetch the message item that will * be displayed on this viewer. * It will set the correct fetch scope. * You still need to connect to the job's result signal. */ Akonadi::ItemFetchJob *createFetchJob(const Akonadi::Item &item); /** * Adds a @p handler for actions that will be executed when the message * has been loaded into the view. */ void addMessageLoadedHandler(AbstractMessageLoadedHandler *handler); /** * Removes the @p handler for actions that will be executed when the message * has been loaded into the view. */ void removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler); QString selectedText() const; void setExternalWindow(bool b); void saveMainFrameScreenshotInFile(const QString &filename); bool mimePartTreeIsEmpty() const; void showOpenAttachmentFolderWidget(const QUrl &url); QList viewerPluginActionList( MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features); QList interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const; void runJavaScript(const QString &code); void setPrintElementBackground(bool printElementBackground); bool printingMode() const; Q_SIGNALS: void moveMessageToTrash(); void pageIsScrolledToBottom(bool); /** * Emitted when a status bar message is shown. Note that the status bar message is also set to * KPIM::BroadcastStatus in addition. */ void showStatusBarMessage(const QString &message); /** The user presses the right mouse button. 'url' may be 0. */ void popupMenu(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &mousePos); void displayPopupMenu(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &mousePos); /** * The message viewer handles some types of urls itself, most notably http(s) * and ftp(s). When it can't handle the url it will Q_EMIT this signal. */ void urlClicked(const Akonadi::Item &, const QUrl &); void requestConfigSync(); /// Emitted when the content should be shown in a separate window void showReader(KMime::Content *aMsgPart, bool aHTML, const QString &encoding); /// Emitted when the message should be shown in a separate window void showMessage(const KMime::Message::Ptr &message, const QString &encoding); void replyMessageTo(const KMime::Message::Ptr &message, bool replyToAll); void deleteMessage(const Akonadi::Item &); /// Emitted when the item, previously set with setMessageItem, has been removed. void itemRemoved(); void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode); void printingFinished(); private: void initialize(); public Q_SLOTS: /** * HTML Widget scrollbar and layout handling. * * Scrolling always happens in the direction of the slot that is called. I.e. * the methods take the absolute value of */ void slotScrollUp(); void slotScrollDown(); void slotScrollPrior(); void slotScrollNext(); void slotJumpDown(); void slotFind(); void slotSaveMessage(); void slotAttachmentSaveAs(); void slotAttachmentSaveAll(); void slotShowMessageSource(); void slotZoomIn(); void slotZoomOut(); void slotZoomReset(); void slotChangeDisplayMail(Viewer::DisplayFormatMessage, bool); protected: /** Some necessary event handling. */ void closeEvent(QCloseEvent *) override; void resizeEvent(QResizeEvent *) override; /** Watch for palette changes */ bool event(QEvent *e) override; void changeEvent(QEvent *event) override; ViewerPrivate *const d_ptr; }; } #endif diff --git a/messageviewer/src/viewer/viewer_p.cpp b/messageviewer/src/viewer/viewer_p.cpp index c3c00d8c..e2e6db2b 100644 --- a/messageviewer/src/viewer/viewer_p.cpp +++ b/messageviewer/src/viewer/viewer_p.cpp @@ -1,3244 +1,3244 @@ /* Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2010 Torgny Nyblom Copyright (C) 2011-2017 Laurent Montel 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 "viewer_p.h" #include "viewer.h" #include "messageviewer_debug.h" #include "utils/mimetype.h" #include "viewer/objecttreeemptysource.h" #include "viewer/objecttreeviewersource.h" #include "messagedisplayformatattribute.h" #include "utils/iconnamecache.h" #include "scamdetection/scamdetectionwarningwidget.h" #include "scamdetection/scamattribute.h" #include "viewer/mimeparttree/mimeparttreeview.h" #include "widgets/openattachmentfolderwidget.h" #include "messageviewer/headerstyle.h" #include "messageviewer/headerstrategy.h" #include "kpimtextedit/slidecontainer.h" #include "Gravatar/GravatarCache" #include "gravatarsettings.h" #include "job/attachmenteditjob.h" #include "job/modifymessagedisplayformatjob.h" #include "config-messageviewer.h" #include "webengine/mailwebenginescript.h" #include "viewerplugins/viewerplugintoolmanager.h" #include #include "htmlwriter/webengineembedpart.h" #include #include // link() //KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include //libkdepim #include "Libkdepim/BroadcastStatus" #include #include #include #include #include #include #include #include //own includes #include "widgets/attachmentdialog.h" #include "csshelper.h" #include "settings/messageviewersettings.h" #include "widgets/htmlstatusbar.h" +#include "viewer/attachmentstrategy.h" #include "viewer/mimeparttree/mimetreemodel.h" #include "viewer/urlhandlermanager.h" #include "messageviewer/messageviewerutil.h" #include "utils/messageviewerutil_p.h" #include "widgets/vcardviewer.h" #include #include "viewer/webengine/mailwebengineview.h" #include "htmlwriter/webengineparthtmlwriter.h" #include #include #include "header/headerstylemenumanager.h" #include "widgets/submittedformwarningwidget.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include
#include #include #include #include #include using namespace boost; using namespace MailTransport; using namespace MessageViewer; using namespace MessageCore; static QAtomicInt _k_attributeInitialized; template struct InvokeWrapper { R *receiver; void (C::*memberFun)(Arg); void operator()(Arg result) { (receiver->*memberFun)(result); } }; template InvokeWrapper invoke(R *receiver, void (C::*memberFun)(Arg)) { InvokeWrapper wrapper = {receiver, memberFun}; return wrapper; } ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QObject(aParent) , mNodeHelper(new MimeTreeParser::NodeHelper) , mViewer(nullptr) , mFindBar(nullptr) , mAttachmentStrategy(nullptr) , mUpdateReaderWinTimer(nullptr) , mResizeTimer(nullptr) , mOldGlobalOverrideEncoding(QStringLiteral("---")) , mMsgDisplay(true) , // init with dummy value mCSSHelper(nullptr) , mMainWindow(mainWindow) , mActionCollection(actionCollection) , mCopyAction(nullptr) , mCopyURLAction(nullptr) , mUrlOpenAction(nullptr) , mSelectAllAction(nullptr) , mScrollUpAction(nullptr) , mScrollDownAction(nullptr) , mScrollUpMoreAction(nullptr) , mScrollDownMoreAction(nullptr) , mHeaderOnlyAttachmentsAction(nullptr) , mSelectEncodingAction(nullptr) , mToggleFixFontAction(nullptr) , mToggleDisplayModeAction(nullptr) , mToggleMimePartTreeAction(nullptr) , mSpeakTextAction(nullptr) , mCanStartDrag(false) , mHtmlWriter(nullptr) , mDecrytMessageOverwrite(false) , mShowSignatureDetails(false) , mShowAttachmentQuicklist(true) , mForceEmoticons(true) , mRecursionCountForDisplayMessage(0) , mCurrentContent(nullptr) , mMessagePartNode(nullptr) , q(aParent) , mSession(new Akonadi::Session("MessageViewer-" + QByteArray::number(reinterpret_cast(this)), this)) , mPreviouslyViewedItem(-1) , mScamDetectionWarning(nullptr) , mOpenAttachmentFolderWidget(nullptr) , mSliderContainer(nullptr) , mShareServiceManager(nullptr) , mHeaderStylePlugin(nullptr) , mHeaderStyleMenuManager(nullptr) , mViewerPluginToolManager(nullptr) , mZoomActionMenu(nullptr) , mCurrentPrinter(nullptr) , mPhishingDatabase(nullptr) { mMimePartTree = nullptr; if (!mainWindow) { mMainWindow = aParent; } if (_k_attributeInitialized.testAndSetAcquire(0, 1)) { Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); } mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this); mPhishingDatabase->initialize(); connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ViewerPrivate::slotCheckedUrlFinished); mShareServiceManager = new PimCommon::ShareServiceUrlManager(this); mDisplayFormatMessageOverwrite = MessageViewer::Viewer::UseGlobalSetting; mHtmlLoadExtOverride = false; mHtmlLoadExternalGlobalSetting = false; mHtmlMailGlobalSetting = false; mUpdateReaderWinTimer.setObjectName(QStringLiteral("mUpdateReaderWinTimer")); mResizeTimer.setObjectName(QStringLiteral("mResizeTimer")); mPrinting = false; createWidgets(); createActions(); initHtmlWidget(); readConfig(); mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; mResizeTimer.setSingleShot(true); connect(&mResizeTimer, &QTimer::timeout, this, &ViewerPrivate::slotDelayedResize); mUpdateReaderWinTimer.setSingleShot(true); connect(&mUpdateReaderWinTimer, &QTimer::timeout, this, &ViewerPrivate::updateReaderWin); connect(mNodeHelper, &MimeTreeParser::NodeHelper::update, this, &ViewerPrivate::update); connect(mColorBar, &HtmlStatusBar::clicked, this, &ViewerPrivate::slotToggleHtmlMode); // FIXME: Don't use the full payload here when attachment loading on demand is used, just // like in KMMainWidget::slotMessageActivated(). mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor")); mMonitor.setSession(mSession); Akonadi::ItemFetchScope fs; fs.fetchFullPayload(); fs.fetchAttribute(); fs.fetchAttribute(); fs.fetchAttribute(); mMonitor.setItemFetchScope(fs); connect(&mMonitor, &Akonadi::Monitor::itemChanged, this, &ViewerPrivate::slotItemChanged); connect(&mMonitor, &Akonadi::Monitor::itemRemoved, this, &ViewerPrivate::slotClear); connect(&mMonitor, &Akonadi::Monitor::itemMoved, this, &ViewerPrivate::slotItemMoved); } ViewerPrivate::~ViewerPrivate() { MessageViewer::MessageViewerSettings::self()->save(); delete mHtmlWriter; mHtmlWriter = nullptr; delete mViewer; mViewer = nullptr; delete mCSSHelper; mNodeHelper->forceCleanTempFiles(); qDeleteAll(mListMailSourceViewer); delete mNodeHelper; } //----------------------------------------------------------------------------- KMime::Content *ViewerPrivate::nodeFromUrl(const QUrl &url) const { return mNodeHelper->fromHREF(mMessage, url); } void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url) { if (!node) { return; } if (node->contentType(false)) { if (node->contentType()->mimeType() == "text/x-moz-deleted") { return; } if (node->contentType()->mimeType() == "message/external-body") { if (node->contentType()->hasParameter(QStringLiteral("url"))) { KRun::RunFlags flags; flags |= KRun::RunExecutables; const QString url = node->contentType()->parameter(QStringLiteral("url")); KRun::runUrl(QUrl(url), QStringLiteral("text/html"), q, flags); return; } } } const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { // the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation // in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree. KMime::Message::Ptr m = KMime::Message::Ptr(new KMime::Message); m->setContent(node->parent()->bodyAsMessage()->encodedContent()); m->parse(); atmViewMsg(m); return; } // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower())); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { showVCard(node); return; } // special case treatment on mac and windows QUrl atmUrl = url; if (url.isEmpty()) { atmUrl = mNodeHelper->tempFileUrlFromNode(node); } if (Util::handleUrlWithQDesktopServices(atmUrl)) { return; } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { mimetype = MimeTreeParser::Util::mimetype( url.isLocalFile() ? url.toLocalFile() : url.fileName()); } KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); const QString filenameText = MimeTreeParser::NodeHelper::fileName(node); QPointer dialog = new AttachmentDialog(mMainWindow, filenameText, offer, QLatin1String( "askSave_") + mimetype.name()); const int choice = dialog->exec(); delete dialog; if (choice == AttachmentDialog::Save) { QUrl currentUrl; if (Util::saveContents(mMainWindow, KMime::Content::List() << node, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } else if (choice == AttachmentDialog::Open) { // Open if (offer) { attachmentOpenWith(node, offer); } else { attachmentOpen(node); } } else if (choice == AttachmentDialog::OpenWith) { attachmentOpenWith(node); } else { // Cancel qCDebug(MESSAGEVIEWER_LOG) << "Canceled opening attachment"; } } bool ViewerPrivate::deleteAttachment(KMime::Content *node, bool showWarning) { if (!node) { return true; } KMime::Content *parent = node->parent(); if (!parent) { return true; } QList extraNodes = mNodeHelper->extraContents(mMessage.data()); if (extraNodes.contains(node->topLevel())) { KMessageBox::error(mMainWindow, i18n( "Deleting an attachment from an encrypted or old-style mailman message is not supported."), i18n("Delete Attachment")); return true; //cancelled } if (showWarning && KMessageBox::warningContinueCancel(mMainWindow, i18n( "Deleting an attachment might invalidate any digital signature on this message."), i18n("Delete Attachment"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral( "DeleteAttachmentSignatureWarning")) != KMessageBox::Continue) { return false; //cancelled } //don't confuse the model #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif QString filename; QString name; QByteArray mimetype; if (node->contentDisposition(false)) { filename = node->contentDisposition()->filename(); } if (node->contentType(false)) { name = node->contentType()->name(); mimetype = node->contentType()->mimeType(); } // text/plain part: KMime::Content *deletePart = new KMime::Content(parent); deletePart->contentType()->setMimeType("text/x-moz-deleted"); deletePart->contentType()->setName(QStringLiteral("Deleted: %1").arg(name), "utf8"); deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment); deletePart->contentDisposition()->setFilename(QStringLiteral("Deleted: %1").arg(name)); deletePart->contentType()->setCharset("utf-8"); deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray bodyMessage = QByteArrayLiteral( "\nYou deleted an attachment from this message. The original MIME headers for the attachment were:"); bodyMessage += ("\nContent-Type: ") + mimetype; bodyMessage += ("\nname=\"") + name.toUtf8() + "\""; bodyMessage += ("\nfilename=\"") + filename.toUtf8() + "\""; deletePart->setBody(bodyMessage); parent->replaceContent(node, deletePart); parent->assemble(); KMime::Message *modifiedMessage = mNodeHelper->messageWithExtraContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->mimePartModel()->setRoot(modifiedMessage); #endif mMessageItem.setPayloadFromData(modifiedMessage->encodedContent()); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession); job->disableRevisionCheck(); connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult); return true; } void ViewerPrivate::itemModifiedResult(KJob *job) { if (job->error()) { qCDebug(MESSAGEVIEWER_LOG) << "Item update failed:" << job->errorString(); } else { setMessageItem(mMessageItem, MimeTreeParser::Force); } } void ViewerPrivate::editAttachment(KMime::Content *node, bool showWarning) { MessageViewer::AttachmentEditJob *job = new MessageViewer::AttachmentEditJob(mSession, this); connect(job, &AttachmentEditJob::refreshMessage, this, &ViewerPrivate::slotRefreshMessage); job->setMainWindow(mMainWindow); job->setMessageItem(mMessageItem); job->setMessage(mMessage); job->addAttachment(node, showWarning); job->canDeleteJob(); } void ViewerPrivate::scrollToAnchor(const QString &anchor) { mViewer->scrollToAnchor(anchor); } void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent) { const KService::List offers = KFileItemActions::associatedApplications( QStringList() << contentTypeStr, QString()); if (!offers.isEmpty()) { QMenu *menu = topMenu; QActionGroup *actionGroup = new QActionGroup(menu); if (fromCurrentContent) { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithActionCurrentContent); } else { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithAction); } if (offers.count() > 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } //qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu; KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for (; it != end; ++it) { QAction *act = MessageViewer::Util::createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } QAction *openWithAct = new QAction(menu); openWithAct->setText(openWithActionName); if (fromCurrentContent) { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } menu->addAction(openWithAct); } else { // no app offers -> Open With... QAction *act = new QAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); if (fromCurrentContent) { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } topMenu->addAction(act); } } void ViewerPrivate::slotOpenWithDialogCurrentContent() { if (!mCurrentContent) { return; } attachmentOpenWith(mCurrentContent); } void ViewerPrivate::slotOpenWithDialog() { auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first()); } } void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act) { if (!mCurrentContent) { return; } KService::Ptr app = act->data().value(); attachmentOpenWith(mCurrentContent, app); } void ViewerPrivate::slotOpenWithAction(QAction *act) { KService::Ptr app = act->data().value(); auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first(), app); } } void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos) { Q_UNUSED(name); prepareHandleAttachment(node); QMenu *menu = new QMenu(); bool deletedAttachment = false; if (node->contentType(false)) { deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted"); } const QString contentTypeStr = QLatin1String(node->contentType()->mimeType()); QSignalMapper *attachmentMapper = new QSignalMapper(menu); connect(attachmentMapper, SIGNAL(mapped(int)), this, SLOT(slotHandleAttachment(int))); QAction *action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Open); if (!deletedAttachment) { createOpenWithMenu(menu, contentTypeStr, true); } QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid()) { const QStringList parentMimeType = mimetype.parentMimeTypes(); if ((contentTypeStr == QLatin1String("text/plain")) || (contentTypeStr == QLatin1String("image/png")) || (contentTypeStr == QLatin1String("image/jpeg")) || parentMimeType.contains(QStringLiteral("text/plain")) || parentMimeType.contains(QStringLiteral("image/png")) || parentMimeType.contains(QStringLiteral("image/jpeg")) ) { action = menu->addAction(i18nc("to view something", "View")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::View); } } #if 0 //Reimplement in the future const bool attachmentInHeader = mViewer->isAttachmentInjectionPoint(globalPos); const bool hasScrollbar = mViewer->hasVerticalScrollBar(); if (attachmentInHeader && hasScrollbar) { action = menu->addAction(i18n("Scroll To")); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ScrollTo); } #endif action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "Save As...")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Save); action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Copy); const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid() && (mMessageItem.parentCollection().rights() != Akonadi::Collection::ReadOnly) && !isEncapsulatedMessage; if (MessageViewer::MessageViewerSettings::self()->allowAttachmentEditing()) { action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n( "Edit Attachment")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Edit); action->setEnabled(canChange); } action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Attachment")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Delete); action->setEnabled(canChange && !deletedAttachment); #if 0 menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply To Author")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ReplyMessageToAuthor); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n( "Reply To All")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ReplyMessageToAll); #endif menu->addSeparator(); action = menu->addAction(i18n("Properties")); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Properties); menu->exec(globalPos); delete menu; } void ViewerPrivate::prepareHandleAttachment(KMime::Content *node) { mCurrentContent = node; } QString ViewerPrivate::createAtmFileLink(const QString &atmFileName) const { QFileInfo atmFileInfo(atmFileName); // tempfile name is /TMP/attachmentsRANDOM/atmFileInfo.fileName()" const QString tmpPath = QDir::tempPath() + QLatin1Char('/') + QLatin1String("attachments"); QDir().mkpath(tmpPath); QTemporaryDir *linkDir = new QTemporaryDir(tmpPath); QString linkPath = linkDir->path() + QLatin1Char('/') + atmFileInfo.fileName(); QFile *linkFile = new QFile(linkPath); linkFile->open(QIODevice::ReadWrite); const QString linkName = linkFile->fileName(); delete linkFile; delete linkDir; if (::link(QFile::encodeName(atmFileName).constData(), QFile::encodeName(linkName).constData()) == 0) { return linkName; // success } return QString(); } KService::Ptr ViewerPrivate::getServiceOffer(KMime::Content *content) { const QString fileName = mNodeHelper->writeNodeToTempFile(content); const QString contentTypeStr = QLatin1String(content->contentType()->mimeType()); // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { attachmentView(content); return KService::Ptr(nullptr); } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { /*TODO(Andris) port when on-demand loading is done && msgPart.isComplete() */ mimetype = MimeTreeParser::Util::mimetype(fileName); } return KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); } KMime::Content::List ViewerPrivate::selectedContents() { return mMimePartTree->selectedContents(); } void ViewerPrivate::attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer) { QString name = mNodeHelper->writeNodeToTempFile(node); // Make sure that it will not deleted when we switch from message. QTemporaryDir *tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX")); if (tmpDir->isValid()) { tmpDir->setAutoRemove(false); const QString path = tmpDir->path(); delete tmpDir; QFile f(name); const QUrl tmpFileName = QUrl::fromLocalFile(name); const QString newPath = path + QLatin1Char('/') + tmpFileName.fileName(); if (!f.copy(newPath)) { qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name << " to " << path; } else { name = newPath; } f.close(); } else { delete tmpDir; } QList lst; const QFileDevice::Permissions perms = QFile::permissions(name); QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser); const QUrl url = QUrl::fromLocalFile(name); lst.append(url); if (offer) { if ((!KRun::runService(*offer, lst, nullptr, true))) { QFile::remove(url.toLocalFile()); } } else { if ((!KRun::displayOpenWithDialog(lst, mMainWindow, true))) { QFile::remove(url.toLocalFile()); } } } void ViewerPrivate::attachmentOpen(KMime::Content *node) { KService::Ptr offer = getServiceOffer(node); if (!offer) { qCDebug(MESSAGEVIEWER_LOG) << "got no offer"; return; } attachmentOpenWith(node, offer); } bool ViewerPrivate::showEmoticons() const { return mForceEmoticons; } MimeTreeParser::HtmlWriter *ViewerPrivate::htmlWriter() const { return mHtmlWriter; } CSSHelper *ViewerPrivate::cssHelper() const { return mCSSHelper; } MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const { return mNodeHelper; } Viewer *ViewerPrivate::viewer() const { return q; } Akonadi::Item ViewerPrivate::messageItem() const { return mMessageItem; } KMime::Message::Ptr ViewerPrivate::message() const { return mMessage; } bool ViewerPrivate::decryptMessage() const { if (!MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) { return mDecrytMessageOverwrite; } else { return true; } } void ViewerPrivate::displaySplashPage(const QString &message) { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("icon"), QStringLiteral("kmail") }, { QStringLiteral("name"), i18n("KMail") }, { QStringLiteral("subtitle"), i18n("The KDE Mail Client") }, { QStringLiteral("message"), message } }); } void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain) { mMsgDisplay = false; adjustLayout(); GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/")); GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default")); if (theme.isValid()) { mViewer->setHtml(theme.render(templateName, data, domain), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/'))); } else { qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme"; } mViewer->show(); } void ViewerPrivate::enableMessageDisplay() { if (mMsgDisplay) { return; } mMsgDisplay = true; adjustLayout(); } void ViewerPrivate::displayMessage() { showHideMimeTree(); mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); if (mMessageItem.hasAttribute()) { const MessageViewer::MessageDisplayFormatAttribute *const attr = mMessageItem.attribute(); setHtmlLoadExtOverride(attr->remoteContent()); setDisplayFormatMessageOverwrite(attr->messageFormat()); } htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont)); if (!mMainWindow) { q->setWindowTitle(mMessage->subject()->asUnicodeString()); } // Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker. // It is updated right after parseMsg() instead. mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate); if (mMessageItem.hasAttribute()) { //TODO: Insert link to clear error so that message might be resent const ErrorAttribute *const attr = mMessageItem.attribute(); Q_ASSERT(attr); if (!mForegroundError.isValid()) { const KColorScheme scheme = KColorScheme(QPalette::Active, KColorScheme::View); mForegroundError = scheme.foreground(KColorScheme::NegativeText).color(); mBackgroundError = scheme.background(KColorScheme::NegativeBackground).color(); } htmlWriter()->write(QStringLiteral( "
%4
").arg( mBackgroundError. name(), mForegroundError . name(), mForegroundError . name(), attr->message().toHtmlEscaped())); htmlWriter()->write(QStringLiteral("

")); } parseContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data())); #endif mColorBar->update(); htmlWriter()->write(QStringLiteral("")); connect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered, Qt::UniqueConnection); const QString html = attachmentInjectionHtml(); const QString js = html.isEmpty() ? QString() : MessageViewer::MailWebEngineScript::injectAttachments(html, QStringLiteral( "attachmentInjectionPoint")); mViewer->addScript(js, QStringLiteral("attachment_injection"), QWebEngineScript::DocumentReady); htmlWriter()->end(); } void ViewerPrivate::parseContent(KMime::Content *content) { assert(content != nullptr); // Check if any part of this message is a v-card // v-cards can be either text/x-vcard or text/directory, so we need to check // both. KMime::Content *vCardContent = findContentByType(content, "text/x-vcard"); if (!vCardContent) { vCardContent = findContentByType(content, "text/directory"); } bool hasVCard = false; if (vCardContent) { // ### FIXME: We should only do this if the vCard belongs to the sender, // ### i.e. if the sender's email address is contained in the vCard. const QByteArray vCard = vCardContent->decodedContent(); KContacts::VCardConverter t; if (!t.parseVCards(vCard).isEmpty()) { hasVCard = true; mNodeHelper->writeNodeToTempFile(vCardContent); } } KMime::Message *message = dynamic_cast(content); if (message) { htmlWriter()->write(writeMsgHeader(message, hasVCard ? vCardContent : nullptr, true)); } // Pass control to the OTP now, which does the real work mNodeHelper->removeTempFiles(); mNodeHelper->setNodeUnprocessed(mMessage.data(), true); MailViewerSource otpSource(this); MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper); //TODO: needs to end up in renderer: mMessage.data() != content /* show only single node */); otp.setAllowAsync(!mPrinting); otp.parseObjectTree(content, mMessage.data() != content /* parse/show only single node */); // TODO: Setting the signature state to nodehelper is not enough, it should actually // be added to the store, so that the message list correctly displays the signature state // of messages that were parsed at least once // store encrypted/signed status information in the KMMessage // - this can only be done *after* calling parseObjectTree() MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState( content); MimeTreeParser::KMMsgSignatureState signatureState = mNodeHelper->overallSignatureState(content); mNodeHelper->setEncryptionState(content, encryptionState); // Don't reset the signature state to "not signed" (e.g. if one canceled the // decryption of a signed messages which has already been decrypted before). if (signatureState != MimeTreeParser::KMMsgNotSigned || mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) { mNodeHelper->setSignatureState(content, signatureState); } showHideMimeTree(); } QString ViewerPrivate::writeMsgHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel) { if (!headerStylePlugin()) { qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMsgHeader() without a header style set!"; return {}; } QString href; if (vCardNode) { href = mNodeHelper->asHREF(vCardNode, QStringLiteral("body")); } headerStylePlugin()->headerStyle()->setHeaderStrategy(headerStylePlugin()->headerStrategy()); headerStylePlugin()->headerStyle()->setVCardName(href); headerStylePlugin()->headerStyle()->setPrinting(mPrinting); headerStylePlugin()->headerStyle()->setTopLevel(topLevel); headerStylePlugin()->headerStyle()->setAllowAsync(true); headerStylePlugin()->headerStyle()->setSourceObject(this); headerStylePlugin()->headerStyle()->setNodeHelper(mNodeHelper); headerStylePlugin()->headerStyle()->setMessagePath(mMessagePath); if (mMessageItem.isValid()) { Akonadi::MessageStatus status; status.setStatusFromFlags(mMessageItem.flags()); headerStylePlugin()->headerStyle()->setMessageStatus(status); headerStylePlugin()->headerStyle()->setCollectionName( mMessageItem.parentCollection().displayName()); } else { headerStylePlugin()->headerStyle()->setCollectionName(QString()); headerStylePlugin()->headerStyle()->setReadOnlyMessage(true); } return headerStylePlugin()->headerStyle()->format(aMsg); } void ViewerPrivate::showVCard(KMime::Content *msgPart) { const QByteArray vCard = msgPart->decodedContent(); VCardViewer *vcv = new VCardViewer(mMainWindow, vCard); vcv->setAttribute(Qt::WA_DeleteOnClose); vcv->show(); } void ViewerPrivate::initHtmlWidget() { if (!htmlWriter()) { mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr); mHtmlWriter = mPartHtmlWriter; } connect(mViewer->page(), &QWebEnginePage::linkHovered, this, &ViewerPrivate::slotUrlOn); connect(mViewer, &MailWebEngineView::openUrl, this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection); connect(mViewer, &MailWebEngineView::popupMenu, this, &ViewerPrivate::slotUrlPopup); connect(mViewer, &MailWebEngineView::wheelZoomChanged, this, &ViewerPrivate::slotWheelZoomChanged); connect(mViewer, &MailWebEngineView::messageMayBeAScam, this, &ViewerPrivate::slotMessageMayBeAScam); connect(mViewer, &MailWebEngineView::formSubmittedForbidden, this, &ViewerPrivate::slotFormSubmittedForbidden); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::showDetails, mViewer, &MailWebEngineView::slotShowDetails); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::moveMessageToTrash, this, &ViewerPrivate::moveMessageToTrash); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::messageIsNotAScam, this, &ViewerPrivate::slotMessageIsNotAScam); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::addToWhiteList, this, &ViewerPrivate::slotAddToWhiteList); connect(mViewer, &MailWebEngineView::pageIsScrolledToBottom, this, &ViewerPrivate::pageIsScrolledToBottom); } void ViewerPrivate::slotWheelZoomChanged(int numSteps) { if (mZoomActionMenu) { const qreal factor = mZoomActionMenu->zoomFactor() + numSteps * 10; if (factor >= 10 && factor <= 300) { mZoomActionMenu->setZoomFactor(factor); mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0); } } } void ViewerPrivate::readConfig() { delete mCSSHelper; mCSSHelper = new CSSHelper(mViewer); mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } mHtmlMailGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail(); mHtmlLoadExternalGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlLoadExternal(); readGravatarConfig(); if (mHeaderStyleMenuManager) { mHeaderStyleMenuManager->readConfig(); } - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::create(MessageViewer:: + setAttachmentStrategy(AttachmentStrategy::create(MessageViewer:: MessageViewerSettings::self()-> attachmentStrategy())); KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy()); if (raction) { raction->setChecked(true); } adjustLayout(); readGlobalOverrideCodec(); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); if (mMessage) { update(); } mColorBar->update(); } void ViewerPrivate::readGravatarConfig() { Gravatar::GravatarCache::self()->setMaximumSize( Gravatar::GravatarSettings::self()->gravatarCacheSize()); if (!Gravatar::GravatarSettings::self()->gravatarSupportEnabled()) { Gravatar::GravatarCache::self()->clear(); } } void ViewerPrivate::slotGeneralFontChanged() { delete mCSSHelper; mCSSHelper = new CSSHelper(mViewer); if (mMessage) { update(); } } void ViewerPrivate::writeConfig(bool sync) { MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mUseFixedFont); if (attachmentStrategy()) { MessageViewer::MessageViewerSettings::self()->setAttachmentStrategy(QLatin1String( attachmentStrategy() ->name())); } saveSplitterSizes(); if (sync) { Q_EMIT requestConfigSync(); } } -const MimeTreeParser::AttachmentStrategy *ViewerPrivate::attachmentStrategy() const +const AttachmentStrategy *ViewerPrivate::attachmentStrategy() const { return mAttachmentStrategy; } -void ViewerPrivate::setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy) +void ViewerPrivate::setAttachmentStrategy(const AttachmentStrategy *strategy) { if (mAttachmentStrategy == strategy) { return; } - mAttachmentStrategy = strategy ? strategy : MimeTreeParser::AttachmentStrategy::smart(); + mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart(); update(MimeTreeParser::Force); } QString ViewerPrivate::overrideEncoding() const { return mOverrideEncoding; } void ViewerPrivate::setOverrideEncoding(const QString &encoding) { if (encoding == mOverrideEncoding) { return; } mOverrideEncoding = encoding; if (mSelectEncodingAction) { if (encoding.isEmpty()) { mSelectEncodingAction->setCurrentItem(0); } else { const QStringList encodings = mSelectEncodingAction->items(); int i = 0; for (QStringList::const_iterator it = encodings.constBegin(), end = encodings.constEnd(); it != end; ++it, ++i) { if (MimeTreeParser::NodeHelper::encodingForName(*it) == encoding) { mSelectEncodingAction->setCurrentItem(i); break; } } if (i == encodings.size()) { // the value of encoding is unknown => use Auto qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding" << encoding << ". Using Auto instead."; mSelectEncodingAction->setCurrentItem(0); mOverrideEncoding.clear(); } } } update(MimeTreeParser::Force); } void ViewerPrivate::setPrinting(bool enable) { mPrinting = enable; } bool ViewerPrivate::printingMode() const { return mPrinting; } void ViewerPrivate::printMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::printPreviewMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::resetStateForNewMessage() { mClickedUrl.clear(); mImageUrl.clear(); enableMessageDisplay(); // just to make sure it's on mMessage.reset(); mNodeHelper->clear(); mMessagePartNode = nullptr; #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif mViewer->clearRelativePosition(); mViewer->hideAccessKeys(); setShowSignatureDetails(false); mFindBar->closeBar(); mViewerPluginToolManager->closeAllTools(); mScamDetectionWarning->setVisible(false); mOpenAttachmentFolderWidget->setVisible(false); if (mPrinting) { if (MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettings()) { if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) { mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; } else { mLevelQuote = -1; } } else { mLevelQuote = -1; } } else { mDisplayFormatMessageOverwrite = (mDisplayFormatMessageOverwrite == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting : MessageViewer::Viewer::Unknown; } } void ViewerPrivate::setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode) { mViewerPluginToolManager->updateActions(mMessageItem); mMessage = message; if (message) { mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); } #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(message.data())); update(updateMode); #endif } void ViewerPrivate::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); foreach (const Akonadi::Item::Id monitoredId, mMonitor.itemsMonitoredEx()) { mMonitor.setItemMonitored(Akonadi::Item(monitoredId), false); } Q_ASSERT(mMonitor.itemsMonitoredEx().isEmpty()); mMessageItem = item; if (mMessageItem.isValid()) { mMonitor.setItemMonitored(mMessageItem, true); } if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } setMessageInternal(mMessageItem.payload(), updateMode); } void ViewerPrivate::setMessage(const KMime::Message::Ptr &aMsg, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); Akonadi::Item item; item.setMimeType(KMime::Message::mimeType()); item.setPayload(aMsg); mMessageItem = item; setMessageInternal(aMsg, updateMode); } void ViewerPrivate::setMessagePart(KMime::Content *node) { // Cancel scheduled updates of the reader window, as that would stop the // timer of the HTML writer, which would make viewing attachment not work // anymore as not all HTML is written to the HTML part. // We're updating the reader window here ourselves anyway. mUpdateReaderWinTimer.stop(); if (node) { mMessagePartNode = node; if (node->bodyIsMessage()) { mMainWindow->setWindowTitle(node->bodyAsMessage()->subject()->asUnicodeString()); } else { QString windowTitle = MimeTreeParser::NodeHelper::fileName(node); if (windowTitle.isEmpty()) { windowTitle = node->contentDescription()->asUnicodeString(); } if (!windowTitle.isEmpty()) { mMainWindow->setWindowTitle(i18n("View Attachment: %1", windowTitle)); } } htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont)); parseContent(node); htmlWriter()->write(QStringLiteral("")); htmlWriter()->end(); } } void ViewerPrivate::showHideMimeTree() { #ifndef QT_NO_TREEVIEW if (mimePartTreeIsEmpty()) { mMimePartTree->hide(); return; } bool showMimeTree = false; if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always) { mMimePartTree->show(); showMimeTree = true; } else { // don't rely on QSplitter maintaining sizes for hidden widgets: saveSplitterSizes(); mMimePartTree->hide(); showMimeTree = false; } if (mToggleMimePartTreeAction && (mToggleMimePartTreeAction->isChecked() != showMimeTree)) { mToggleMimePartTreeAction->setChecked(showMimeTree); } #endif } void ViewerPrivate::atmViewMsg(const KMime::Message::Ptr &message) { Q_ASSERT(message); Q_EMIT showMessage(message, overrideEncoding()); } void ViewerPrivate::adjustLayout() { #ifndef QT_NO_TREEVIEW const int mimeH = MessageViewer::MessageViewerSettings::self()->mimePaneHeight(); const int messageH = MessageViewer::MessageViewerSettings::self()->messagePaneHeight(); QList splitterSizes; splitterSizes << messageH << mimeH; mSplitter->addWidget(mMimePartTree); mSplitter->setSizes(splitterSizes); if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always && mMsgDisplay) { mMimePartTree->show(); } else { mMimePartTree->hide(); } #endif if (MessageViewer::MessageViewerSettings::self()->showColorBar() && mMsgDisplay) { mColorBar->show(); } else { mColorBar->hide(); } } void ViewerPrivate::saveSplitterSizes() const { #ifndef QT_NO_TREEVIEW if (!mSplitter || !mMimePartTree) { return; } if (mMimePartTree->isHidden()) { return; // don't rely on QSplitter maintaining sizes for hidden widgets. } MessageViewer::MessageViewerSettings::self()->setMimePaneHeight(mSplitter->sizes().at(1)); MessageViewer::MessageViewerSettings::self()->setMessagePaneHeight(mSplitter->sizes().at(0)); #endif } void ViewerPrivate::createWidgets() { //TODO: Make a MDN bar similar to Mozillas password bar and show MDNs here as soon as a // MDN enabled message is shown. QVBoxLayout *vlay = new QVBoxLayout(q); vlay->setMargin(0); mSplitter = new QSplitter(Qt::Vertical, q); connect(mSplitter, &QSplitter::splitterMoved, this, &ViewerPrivate::saveSplitterSizes); mSplitter->setObjectName(QStringLiteral("mSplitter")); mSplitter->setChildrenCollapsible(false); vlay->addWidget(mSplitter); #ifndef QT_NO_TREEVIEW mMimePartTree = new MimePartTreeView(mSplitter); connect(mMimePartTree, &QAbstractItemView::activated, this, &ViewerPrivate::slotMimePartSelected); connect(mMimePartTree, &QWidget::customContextMenuRequested, this, &ViewerPrivate::slotMimeTreeContextMenuRequested); #endif mBox = new QWidget(mSplitter); QHBoxLayout *mBoxHBoxLayout = new QHBoxLayout(mBox); mBoxHBoxLayout->setMargin(0); mColorBar = new HtmlStatusBar(mBox); mBoxHBoxLayout->addWidget(mColorBar); QWidget *readerBox = new QWidget(mBox); QVBoxLayout *readerBoxVBoxLayout = new QVBoxLayout(readerBox); readerBoxVBoxLayout->setMargin(0); mBoxHBoxLayout->addWidget(readerBox); mColorBar->setObjectName(QStringLiteral("mColorBar")); mColorBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); mSubmittedFormWarning = new SubmittedFormWarningWidget(readerBox); mSubmittedFormWarning->setObjectName(QStringLiteral("submittedformwarning")); readerBoxVBoxLayout->addWidget(mSubmittedFormWarning); mScamDetectionWarning = new ScamDetectionWarningWidget(readerBox); mScamDetectionWarning->setObjectName(QStringLiteral("scandetectionwarning")); readerBoxVBoxLayout->addWidget(mScamDetectionWarning); mOpenAttachmentFolderWidget = new OpenAttachmentFolderWidget(readerBox); mOpenAttachmentFolderWidget->setObjectName(QStringLiteral("openattachementfolderwidget")); readerBoxVBoxLayout->addWidget(mOpenAttachmentFolderWidget); mTextToSpeechWidget = new KPIMTextEdit::TextToSpeechWidget(readerBox); mTextToSpeechWidget->setObjectName(QStringLiteral("texttospeechwidget")); readerBoxVBoxLayout->addWidget(mTextToSpeechWidget); mViewer = new MailWebEngineView(mActionCollection, readerBox); mViewer->setViewer(this); readerBoxVBoxLayout->addWidget(mViewer); mViewer->setObjectName(QStringLiteral("mViewer")); mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(readerBox, this); mViewerPluginToolManager->setActionCollection(mActionCollection); mViewerPluginToolManager->setPluginName(QStringLiteral("messageviewer")); mViewerPluginToolManager->setServiceTypeName(QStringLiteral("MessageViewer/ViewerPlugin")); if (!mViewerPluginToolManager->initializePluginList()) { qCDebug(MESSAGEVIEWER_LOG) << " Impossible to initialize plugins"; } mViewerPluginToolManager->createView(); connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this, &ViewerPrivate::slotActivatePlugin); mSliderContainer = new KPIMTextEdit::SlideContainer(readerBox); mSliderContainer->setObjectName(QStringLiteral("slidercontainer")); readerBoxVBoxLayout->addWidget(mSliderContainer); mFindBar = new WebEngineViewer::FindBarWebEngineView(mViewer, q); connect(mFindBar, &WebEngineViewer::FindBarWebEngineView::hideFindBar, mSliderContainer, &KPIMTextEdit::SlideContainer::slideOut); mSliderContainer->setContent(mFindBar); #ifndef QT_NO_TREEVIEW mSplitter->setStretchFactor(mSplitter->indexOf(mMimePartTree), 0); #endif } void ViewerPrivate::slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin) { mHeaderStylePlugin = plugin; update(MimeTreeParser::Force); } void ViewerPrivate::slotStyleUpdated() { update(MimeTreeParser::Force); } void ViewerPrivate::createActions() { KActionCollection *ac = mActionCollection; mHeaderStyleMenuManager = new MessageViewer::HeaderStyleMenuManager(ac, this); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleChanged, this, &ViewerPrivate::slotStyleChanged); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleUpdated, this, &ViewerPrivate::slotStyleUpdated); if (!ac) { return; } mZoomActionMenu = new WebEngineViewer::ZoomActionMenu(this); connect(mZoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, mViewer, &MailWebEngineView::slotZoomChanged); mZoomActionMenu->setActionCollection(ac); mZoomActionMenu->createZoomActions(); // attachment style KActionMenu *attachmentMenu = new KActionMenu(i18nc("View->", "&Attachments"), this); ac->addAction(QStringLiteral("view_attachments"), attachmentMenu); addHelpTextAction(attachmentMenu, i18n("Choose display style of attachments")); QActionGroup *group = new QActionGroup(this); KToggleAction *raction = new KToggleAction(i18nc("View->attachments->", "&As Icons"), this); ac->addAction(QStringLiteral("view_attachments_as_icons"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotIconicAttachments); addHelpTextAction(raction, i18n("Show all attachments as icons. Click to see them.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Smart"), this); ac->addAction(QStringLiteral("view_attachments_smart"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotSmartAttachments); addHelpTextAction(raction, i18n("Show attachments as suggested by sender.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Inline"), this); ac->addAction(QStringLiteral("view_attachments_inline"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotInlineAttachments); addHelpTextAction(raction, i18n("Show all attachments inline (if possible)")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Hide"), this); ac->addAction(QStringLiteral("view_attachments_hide"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotHideAttachments); addHelpTextAction(raction, i18n("Do not show attachments in the message viewer")); group->addAction(raction); attachmentMenu->addAction(raction); mHeaderOnlyAttachmentsAction = new KToggleAction(i18nc("View->attachments->", "In Header Only"), this); ac->addAction(QStringLiteral("view_attachments_headeronly"), mHeaderOnlyAttachmentsAction); connect(mHeaderOnlyAttachmentsAction, &QAction::triggered, this, &ViewerPrivate::slotHeaderOnlyAttachments); addHelpTextAction(mHeaderOnlyAttachmentsAction, i18n("Show Attachments only in the header of the mail")); group->addAction(mHeaderOnlyAttachmentsAction); attachmentMenu->addAction(mHeaderOnlyAttachmentsAction); // Set Encoding submenu mSelectEncodingAction = new KSelectAction(QIcon::fromTheme(QStringLiteral( "character-set")), i18n("&Set Encoding"), this); mSelectEncodingAction->setToolBarMode(KSelectAction::MenuMode); ac->addAction(QStringLiteral("encoding"), mSelectEncodingAction); connect(mSelectEncodingAction, SIGNAL(triggered(int)), SLOT(slotSetEncoding())); QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false); encodings.prepend(i18n("Auto")); mSelectEncodingAction->setItems(encodings); mSelectEncodingAction->setCurrentItem(0); // // Message Menu // // copy selected text to clipboard mCopyAction = ac->addAction(KStandardAction::Copy, QStringLiteral("kmail_copy")); mCopyAction->setText(i18n("Copy Text")); connect(mCopyAction, &QAction::triggered, this, &ViewerPrivate::slotCopySelectedText); connect(mViewer, &MailWebEngineView::selectionChanged, this, &ViewerPrivate::viewerSelectionChanged); viewerSelectionChanged(); // copy all text to clipboard mSelectAllAction = new QAction(i18n("Select All Text"), this); ac->addAction(QStringLiteral("mark_all_text"), mSelectAllAction); connect(mSelectAllAction, &QAction::triggered, this, &ViewerPrivate::selectAll); ac->setDefaultShortcut(mSelectAllAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A)); // copy Email address to clipboard mCopyURLAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link Address"), this); ac->addAction(QStringLiteral("copy_url"), mCopyURLAction); connect(mCopyURLAction, &QAction::triggered, this, &ViewerPrivate::slotUrlCopy); // open URL mUrlOpenAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n( "Open URL"), this); ac->addAction(QStringLiteral("open_url"), mUrlOpenAction); connect(mUrlOpenAction, &QAction::triggered, this, &ViewerPrivate::slotOpenUrl); // use fixed font mToggleFixFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); ac->addAction(QStringLiteral("toggle_fixedfont"), mToggleFixFontAction); connect(mToggleFixFontAction, &QAction::triggered, this, &ViewerPrivate::slotToggleFixedFont); ac->setDefaultShortcut(mToggleFixFontAction, QKeySequence(Qt::Key_X)); // Show message structure viewer mToggleMimePartTreeAction = new KToggleAction(i18n("Show Message Structure"), this); ac->addAction(QStringLiteral("toggle_mimeparttree"), mToggleMimePartTreeAction); connect(mToggleMimePartTreeAction, &QAction::toggled, this, &ViewerPrivate::slotToggleMimePartTree); ac->setDefaultShortcut(mToggleMimePartTreeAction, QKeySequence(Qt::Key_D + Qt::CTRL + Qt::ALT)); mViewSourceAction = new QAction(i18n("&View Source"), this); ac->addAction(QStringLiteral("view_source"), mViewSourceAction); connect(mViewSourceAction, &QAction::triggered, this, &ViewerPrivate::slotShowMessageSource); ac->setDefaultShortcut(mViewSourceAction, QKeySequence(Qt::Key_V)); mSaveMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "&Save message..."), this); ac->addAction(QStringLiteral("save_message"), mSaveMessageAction); connect(mSaveMessageAction, &QAction::triggered, this, &ViewerPrivate::slotSaveMessage); //Laurent: conflict with kmail shortcut //mSaveMessageAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); mSaveMessageDisplayFormat = new QAction(i18n("&Save Display Format"), this); ac->addAction(QStringLiteral("save_message_display_format"), mSaveMessageDisplayFormat); connect(mSaveMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotSaveMessageDisplayFormat); mResetMessageDisplayFormat = new QAction(i18n("&Reset Display Format"), this); ac->addAction(QStringLiteral("reset_message_display_format"), mResetMessageDisplayFormat); connect(mResetMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotResetMessageDisplayFormat); // // Scroll actions // mScrollUpAction = new QAction(i18n("Scroll Message Up"), this); ac->setDefaultShortcut(mScrollUpAction, QKeySequence(Qt::Key_Up)); ac->addAction(QStringLiteral("scroll_up"), mScrollUpAction); connect(mScrollUpAction, &QAction::triggered, q, &Viewer::slotScrollUp); mScrollDownAction = new QAction(i18n("Scroll Message Down"), this); ac->setDefaultShortcut(mScrollDownAction, QKeySequence(Qt::Key_Down)); ac->addAction(QStringLiteral("scroll_down"), mScrollDownAction); connect(mScrollDownAction, &QAction::triggered, q, &Viewer::slotScrollDown); mScrollUpMoreAction = new QAction(i18n("Scroll Message Up (More)"), this); ac->setDefaultShortcut(mScrollUpMoreAction, QKeySequence(Qt::Key_PageUp)); ac->addAction(QStringLiteral("scroll_up_more"), mScrollUpMoreAction); connect(mScrollUpMoreAction, &QAction::triggered, q, &Viewer::slotScrollPrior); mScrollDownMoreAction = new QAction(i18n("Scroll Message Down (More)"), this); ac->setDefaultShortcut(mScrollDownMoreAction, QKeySequence(Qt::Key_PageDown)); ac->addAction(QStringLiteral("scroll_down_more"), mScrollDownMoreAction); connect(mScrollDownMoreAction, &QAction::triggered, q, &Viewer::slotScrollNext); // // Actions not in menu // // Toggle HTML display mode. mToggleDisplayModeAction = new KToggleAction(i18n("Toggle HTML Display Mode"), this); ac->addAction(QStringLiteral("toggle_html_display_mode"), mToggleDisplayModeAction); ac->setDefaultShortcut(mToggleDisplayModeAction, QKeySequence(Qt::SHIFT + Qt::Key_H)); connect(mToggleDisplayModeAction, &QAction::triggered, this, &ViewerPrivate::slotToggleHtmlMode); addHelpTextAction(mToggleDisplayModeAction, i18n("Toggle display mode between HTML and plain text")); // Load external reference QAction *loadExternalReferenceAction = new QAction(i18n("Load external references"), this); ac->addAction(QStringLiteral("load_external_reference"), loadExternalReferenceAction); ac->setDefaultShortcut(loadExternalReferenceAction, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_R)); connect(loadExternalReferenceAction, &QAction::triggered, this, &ViewerPrivate::slotLoadExternalReference); addHelpTextAction(loadExternalReferenceAction, i18n("Load external references from the Internet for this message.")); mSpeakTextAction = new QAction(i18n("Speak Text"), this); mSpeakTextAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); ac->addAction(QStringLiteral("speak_text"), mSpeakTextAction); connect(mSpeakTextAction, &QAction::triggered, this, &ViewerPrivate::slotSpeakText); mCopyImageLocation = new QAction(i18n("Copy Image Location"), this); mCopyImageLocation->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization"))); ac->addAction(QStringLiteral("copy_image_location"), mCopyImageLocation); ac->setShortcutsConfigurable(mCopyImageLocation, false); connect(mCopyImageLocation, &QAction::triggered, this, &ViewerPrivate::slotCopyImageLocation); mFindInMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n( "&Find in Message..."), this); ac->addAction(QStringLiteral("find_in_messages"), mFindInMessageAction); connect(mFindInMessageAction, &QAction::triggered, this, &ViewerPrivate::slotFind); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); mShareServiceUrlMenu = mShareServiceManager->menu(); ac->addAction(QStringLiteral("shareservice_menu"), mShareServiceUrlMenu); connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ViewerPrivate::slotServiceUrlSelected); mDisableEmoticonAction = new KToggleAction(i18n("Disable Emoticon"), this); ac->addAction(QStringLiteral("disable_emoticon"), mDisableEmoticonAction); connect(mDisableEmoticonAction, &QAction::triggered, this, &ViewerPrivate::slotToggleEmoticons); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); } void ViewerPrivate::showContextMenu(KMime::Content *content, const QPoint &pos) { #ifndef QT_NO_TREEVIEW if (!content) { return; } if (content->contentType(false)) { if (content->contentType()->mimeType() == "text/x-moz-deleted") { return; } } const bool isAttachment = !content->contentType()->isMultipart() && !content->isTopLevel(); const bool isRoot = (content == mMessage.data()); const auto hasAttachments = KMime::hasAttachment(mMessage.data()); QMenu popup; if (!isRoot) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save &As..."), this, &ViewerPrivate::slotAttachmentSaveAs); if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open"), this, &ViewerPrivate::slotAttachmentOpen); if (selectedContents().count() == 1) { createOpenWithMenu(&popup, QLatin1String(content->contentType()->mimeType()), false); } else { popup.addAction(i18n("Open With..."), this, &ViewerPrivate::slotAttachmentOpenWith); } popup.addAction(i18nc("to view something", "View"), this, &ViewerPrivate::slotAttachmentView); } } if (hasAttachments) { popup.addAction(i18n("Save All Attachments..."), this, &ViewerPrivate::slotAttachmentSaveAll); } // edit + delete only for attachments if (!isRoot) { if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"), this, &ViewerPrivate::slotAttachmentCopy); #if 0 //FIXME Laurent Comment for the moment it crash see Bug 287177 popup.addAction(QIcon::fromTheme("edit-delete"), i18n("Delete Attachment"), this, SLOT(slotAttachmentDelete())); #endif if (MessageViewer::MessageViewerSettings::self()->allowAttachmentEditing()) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Attachment"), this, &ViewerPrivate::slotAttachmentEdit); } } if (!content->isTopLevel()) { popup.addAction(i18n("Properties"), this, &ViewerPrivate::slotAttachmentProperties); } } popup.exec(mMimePartTree->viewport()->mapToGlobal(pos)); #endif } KToggleAction *ViewerPrivate::actionForAttachmentStrategy( - const MimeTreeParser::AttachmentStrategy *as) + const AttachmentStrategy *as) { if (!mActionCollection) { return nullptr; } QString actionName; - if (as == MimeTreeParser::AttachmentStrategy::iconic()) { + if (as == AttachmentStrategy::iconic()) { actionName = QStringLiteral("view_attachments_as_icons"); - } else if (as == MimeTreeParser::AttachmentStrategy::smart()) { + } else if (as == AttachmentStrategy::smart()) { actionName = QStringLiteral("view_attachments_smart"); - } else if (as == MimeTreeParser::AttachmentStrategy::inlined()) { + } else if (as == AttachmentStrategy::inlined()) { actionName = QStringLiteral("view_attachments_inline"); - } else if (as == MimeTreeParser::AttachmentStrategy::hidden()) { + } else if (as == AttachmentStrategy::hidden()) { actionName = QStringLiteral("view_attachments_hide"); - } else if (as == MimeTreeParser::AttachmentStrategy::headerOnly()) { + } else if (as == AttachmentStrategy::headerOnly()) { actionName = QStringLiteral("view_attachments_headeronly"); } if (actionName.isEmpty()) { return nullptr; } else { return static_cast(mActionCollection->action(actionName)); } } void ViewerPrivate::readGlobalOverrideCodec() { // if the global character encoding wasn't changed then there's nothing to do if (MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding() == mOldGlobalOverrideEncoding) { return; } setOverrideEncoding(MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding()); mOldGlobalOverrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); } const QTextCodec *ViewerPrivate::overrideCodec() const { if (mOverrideEncoding.isEmpty() || mOverrideEncoding == QLatin1String("Auto")) { // Auto return nullptr; } else { return ViewerPrivate::codecForName(mOverrideEncoding.toLatin1()); } } static QColor nextColor(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv((h + 50) % 360, qMax(s, 64), v); } QString ViewerPrivate::renderAttachments(KMime::Content *node, const QColor &bgColor) const { if (!node) { return QString(); } QString html; KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (child) { QString subHtml = renderAttachments(child, nextColor(bgColor)); if (!subHtml.isEmpty()) { QString margin; if (node != mMessage.data() || headerStylePlugin()->hasMargin()) { margin = QStringLiteral("padding:2px; margin:2px; "); } QString align = headerStylePlugin()->alignment(); const QByteArray mediaTypeLower = node->contentType()->mediaType().toLower(); const bool result = (mediaTypeLower == "message" || mediaTypeLower == "multipart" || node == mMessage.data()); if (result) { html += QStringLiteral("
").arg(bgColor.name()). arg(margin).arg(align); } html += subHtml; if (result) { html += QLatin1String("
"); } } } else { Util::AttachmentDisplayInfo info = Util::attachmentDisplayInfo(node); if (info.displayInHeader) { html += QLatin1String(" "); } } Q_FOREACH (KMime::Content *extraNode, mNodeHelper->extraContents(node)) { html += renderAttachments(extraNode, bgColor); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(node); if (next) { html += renderAttachments(next, nextColor(bgColor)); } return html; } KMime::Content *ViewerPrivate::findContentByType(KMime::Content *content, const QByteArray &type) { const auto list = content->contents(); for (KMime::Content *c : list) { if (c->contentType()->mimeType() == type) { return c; } } return nullptr; } //----------------------------------------------------------------------------- const QTextCodec *ViewerPrivate::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } void ViewerPrivate::update(MimeTreeParser::UpdateMode updateMode) { // Avoid flicker, somewhat of a cludge if (updateMode == MimeTreeParser::Force) { // stop the timer to avoid calling updateReaderWin twice mUpdateReaderWinTimer.stop(); saveRelativePosition(); updateReaderWin(); } else if (mUpdateReaderWinTimer.isActive()) { mUpdateReaderWinTimer.setInterval(150); } else { mUpdateReaderWinTimer.start(0); } } void ViewerPrivate::slotOpenUrl() { slotUrlOpen(); } void ViewerPrivate::slotUrlOpen(const QUrl &url) { if (!url.isEmpty()) { mClickedUrl = url; } // First, let's see if the URL handler manager can handle the URL. If not, try KRun for some // known URLs, otherwise fallback to emitting a signal. // That signal is caught by KMail, and in case of mailto URLs, a composer is shown. if (URLHandlerManager::instance()->handleClick(mClickedUrl, this)) { return; } Q_EMIT urlClicked(mMessageItem, mClickedUrl); } void ViewerPrivate::checkPhishingUrl() { if (!PimCommon::NetworkUtil::self()->lowBandwidth() && MessageViewer::MessageViewerSettings::self()->checkPhishingUrl() && (mClickedUrl.scheme() != QLatin1String("mailto"))) { mPhishingDatabase->checkUrl(mClickedUrl); } else { executeRunner(mClickedUrl); } } void ViewerPrivate::executeRunner(const QUrl &url) { if (!MessageViewer::Util::handleUrlWithQDesktopServices(url)) { KRun *runner = new KRun(url, viewer()); // will delete itself runner->setRunExecutables(false); } } void ViewerPrivate::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status) { switch (status) { case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork: KMessageBox::error(mMainWindow, i18n("The network is broken."), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl: KMessageBox::error(mMainWindow, i18n("The URL %1 is not valid.", url.toString()), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::Ok: break; case WebEngineViewer::CheckPhishingUrlUtil::MalWare: if (!urlIsAMalwareButContinue()) { return; } break; case WebEngineViewer::CheckPhishingUrlUtil::Unknown: qCWarning(MESSAGEVIEWER_LOG) << "WebEngineViewer::slotCheckedUrlFinished unknown error "; break; } executeRunner(url); } bool ViewerPrivate::urlIsAMalwareButContinue() { if (KMessageBox::No == KMessageBox::warningYesNo(mMainWindow, i18n( "This web site is a malware, do you want to continue to show it?"), i18n("Malware"))) { return false; } return true; } void ViewerPrivate::slotUrlOn(const QString &link) { // The "link" we get here is not URL-encoded, and therefore there is no way QUrl could // parse it correctly. To workaround that, we use QWebFrame::hitTestContent() on the mouse position // to get the URL before WebKit managed to mangle it. QUrl url(link); const QString protocol = url.scheme(); if (protocol == QLatin1String("kmail") || protocol == QLatin1String("x-kmail") || protocol == QLatin1String("attachment") || (protocol.isEmpty() && url.path().isEmpty())) { mViewer->setAcceptDrops(false); } else { mViewer->setAcceptDrops(true); } mViewer->setLinkHovered(url); if (link.trimmed().isEmpty()) { KPIM::BroadcastStatus::instance()->reset(); Q_EMIT showStatusBarMessage(QString()); return; } QString msg = URLHandlerManager::instance()->statusBarMessage(url, this); if (msg.isEmpty()) { msg = link; } KPIM::BroadcastStatus::instance()->setTransientStatusMsg(msg); Q_EMIT showStatusBarMessage(msg); } void ViewerPrivate::slotUrlPopup(const WebEngineViewer::WebHitTestResult &result) { if (!mMsgDisplay) { return; } mClickedUrl = result.linkUrl(); mImageUrl = result.imageUrl(); const QPoint aPos = mViewer->mapToGlobal(result.pos()); if (URLHandlerManager::instance()->handleContextMenuRequest(mClickedUrl, aPos, this)) { return; } if (!mActionCollection) { return; } if (mClickedUrl.scheme() == QLatin1String("mailto")) { mCopyURLAction->setText(i18n("Copy Email Address")); } else { mCopyURLAction->setText(i18n("Copy Link Address")); } Q_EMIT displayPopupMenu(mMessageItem, result, aPos); Q_EMIT popupMenu(mMessageItem, mClickedUrl, mImageUrl, aPos); } void ViewerPrivate::slotLoadExternalReference() { if (mColorBar->isNormal() || htmlLoadExtOverride()) { return; } setHtmlLoadExtOverride(true); update(MimeTreeParser::Force); } Viewer::DisplayFormatMessage translateToDisplayFormat(MimeTreeParser::Util::HtmlMode mode) { switch (mode) { case MimeTreeParser::Util::Normal: return Viewer::Unknown; case MimeTreeParser::Util::Html: return Viewer::Html; case MimeTreeParser::Util::MultipartPlain: return Viewer::Text; case MimeTreeParser::Util::MultipartHtml: return Viewer::Html; case MimeTreeParser::Util::MultipartIcal: return Viewer::ICal; } return Viewer::Unknown; } void ViewerPrivate::slotToggleHtmlMode() { const auto availableModes = mColorBar->availableModes(); const int availableModeSize(availableModes.size()); if (mColorBar->isNormal() || availableModeSize < 2) { return; } mScamDetectionWarning->setVisible(false); const MimeTreeParser::Util::HtmlMode mode = mColorBar->mode(); const int pos = (availableModes.indexOf(mode) + 1) % availableModeSize; setDisplayFormatMessageOverwrite(translateToDisplayFormat(availableModes[pos])); update(MimeTreeParser::Force); mColorBar->setAvailableModes(availableModes); } void ViewerPrivate::slotFind() { if (mViewer->hasSelection()) { mFindBar->setText(mViewer->selectedText()); } mSliderContainer->slideIn(); mFindBar->focusAndSetCursor(); } void ViewerPrivate::slotToggleFixedFont() { mUseFixedFont = !mUseFixedFont; update(MimeTreeParser::Force); } void ViewerPrivate::slotToggleMimePartTree() { if (mToggleMimePartTreeAction->isChecked()) { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2( MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always); } else { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2( MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Never); } showHideMimeTree(); } void ViewerPrivate::slotShowMessageSource() { if (!mMessage) { return; } mNodeHelper->messageWithExtraContent(mMessage.data()); QPointer viewer = new MailSourceWebEngineViewer; // deletes itself upon close mListMailSourceViewer.append(viewer); viewer->setWindowTitle(i18n("Message as Plain Text")); const QString rawMessage = QString::fromLatin1(mMessage->encodedContent()); viewer->setRawSource(rawMessage); viewer->setDisplayedSource(mViewer->page()); if (mUseFixedFont) { viewer->setFixedFont(); } // Well, there is no widget to be seen here, so we have to use QCursor::pos() // Update: (GS) I'm not going to make this code behave according to Xinerama // configuration because this is quite the hack. if (QApplication::desktop()->isVirtualDesktop()) { #ifndef QT_NO_CURSOR int scnum = QApplication::desktop()->screenNumber(QCursor::pos()); #else int scnum = 0; #endif viewer->resize(QApplication::desktop()->screenGeometry(scnum).width() / 2, 2 * QApplication::desktop()->screenGeometry(scnum).height() / 3); } else { viewer->resize(QApplication::desktop()->geometry().width() / 2, 2 * QApplication::desktop()->geometry().height() / 3); } viewer->show(); } void ViewerPrivate::updateReaderWin() { if (!mMsgDisplay) { return; } if (mRecursionCountForDisplayMessage + 1 > 1) { // This recursion here can happen because the ObjectTreeParser in parseMsg() can exec() an // eventloop. // This happens in two cases: // 1) The ContactSearchJob started by FancyHeaderStyle::format // 2) Various modal passphrase dialogs for decryption of a message (bug 96498) // // While the exec() eventloop is running, it is possible that a timer calls updateReaderWin(), // and not aborting here would confuse the state terribly. qCWarning(MESSAGEVIEWER_LOG) << "Danger, recursion while displaying a message!"; return; } mRecursionCountForDisplayMessage++; mViewer->setAllowExternalContent(htmlLoadExternal()); htmlWriter()->reset(); //TODO: if the item doesn't have the payload fetched, try to fetch it? Maybe not here, but in setMessageItem. if (mMessage) { if (MessageViewer::MessageViewerSettings::self()->showColorBar()) { mColorBar->show(); } else { mColorBar->hide(); } displayMessage(); } else if (mMessagePartNode) { setMessagePart(mMessagePartNode); } else { mColorBar->hide(); #ifndef QT_NO_TREEVIEW mMimePartTree->hide(); #endif htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont) + QLatin1String("")); htmlWriter()->end(); } mRecursionCountForDisplayMessage--; } void ViewerPrivate::slotMimePartSelected(const QModelIndex &index) { #ifndef QT_NO_TREEVIEW KMime::Content *content = static_cast(index.internalPointer()); if (!mMimePartTree->mimePartModel()->parent(index).isValid() && index.row() == 0) { update(MimeTreeParser::Force); } else { setMessagePart(content); } #endif } void ViewerPrivate::slotIconicAttachments() { - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::iconic()); + setAttachmentStrategy(AttachmentStrategy::iconic()); } void ViewerPrivate::slotSmartAttachments() { - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::smart()); + setAttachmentStrategy(AttachmentStrategy::smart()); } void ViewerPrivate::slotInlineAttachments() { - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::inlined()); + setAttachmentStrategy(AttachmentStrategy::inlined()); } void ViewerPrivate::slotHideAttachments() { - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::hidden()); + setAttachmentStrategy(AttachmentStrategy::hidden()); } void ViewerPrivate::slotHeaderOnlyAttachments() { - setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::headerOnly()); + setAttachmentStrategy(AttachmentStrategy::headerOnly()); } void ViewerPrivate::attachmentView(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { atmViewMsg(atmNode->parent()->bodyAsMessage()); } else if ((qstricmp(atmNode->contentType()->mediaType().constData(), "text") == 0) && ((qstricmp(atmNode->contentType()->subType().constData(), "x-vcard") == 0) || (qstricmp(atmNode->contentType()->subType().constData(), "directory") == 0))) { setMessagePart(atmNode); } else { Q_EMIT showReader(atmNode, htmlMail(), overrideEncoding()); } } } void ViewerPrivate::slotDelayedResize() { mSplitter->setGeometry(0, 0, q->width(), q->height()); } void ViewerPrivate::slotPrintPreview() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); if (!mMessage) { return; } //Need to delay QTimer::singleShot(1 * 1000, this, &ViewerPrivate::slotDelayPrintPreview); } void ViewerPrivate::slotDelayPrintPreview() { QPrintPreviewDialog *dialog = new QPrintPreviewDialog(q); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->resize(800, 750); connect(dialog, &QPrintPreviewDialog::paintRequested, this, [=](QPrinter *printing) { QApplication::setOverrideCursor(Qt::WaitCursor); mViewer->execPrintPreviewPage(printing, 10*1000); QApplication::restoreOverrideCursor(); }); dialog->open(this, SIGNAL(printingFinished())); } void ViewerPrivate::slotOpenInBrowser() { WebEngineViewer::WebEngineExportHtmlPageJob *job = new WebEngineViewer::WebEngineExportHtmlPageJob; job->setEngineView(mViewer); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed, this, &ViewerPrivate::slotExportHtmlPageFailed); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::success, this, &ViewerPrivate::slotExportHtmlPageSuccess); job->start(); } void ViewerPrivate::slotExportHtmlPageSuccess(const QString &filename) { const QUrl url(QUrl::fromLocalFile(filename)); KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(url, QStringLiteral("text/html"), q, flags); Q_EMIT printingFinished(); } void ViewerPrivate::slotExportHtmlPageFailed() { qCDebug(MESSAGEVIEWER_LOG) << " Export HTML failed"; Q_EMIT printingFinished(); } void ViewerPrivate::slotPrintMessage() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); if (!mMessage) { return; } if (mCurrentPrinter) { return; } mCurrentPrinter = new QPrinter(); QPointer dialog = new QPrintDialog(mCurrentPrinter, mMainWindow); dialog->setWindowTitle(i18n("Print Document")); if (dialog->exec() != QDialog::Accepted) { slotHandlePagePrinted(false); delete dialog; return; } delete dialog; mViewer->page()->print(mCurrentPrinter, invoke(this, &ViewerPrivate::slotHandlePagePrinted)); } void ViewerPrivate::slotHandlePagePrinted(bool result) { Q_UNUSED(result); delete mCurrentPrinter; mCurrentPrinter = nullptr; Q_EMIT printingFinished(); } void ViewerPrivate::slotSetEncoding() { if (mSelectEncodingAction) { if (mSelectEncodingAction->currentItem() == 0) { // Auto mOverrideEncoding.clear(); } else { mOverrideEncoding = MimeTreeParser::NodeHelper::encodingForName( mSelectEncodingAction->currentText()); } update(MimeTreeParser::Force); } } HeaderStylePlugin *ViewerPrivate::headerStylePlugin() const { return mHeaderStylePlugin; } QString ViewerPrivate::attachmentInjectionHtml() { const QColor background = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); QString html = renderAttachments(mMessage.data(), background); if (html.isEmpty()) { return QString(); } const QString listVisibility = !mShowAttachmentQuicklist ? QStringLiteral( "style=\"display:none;\"") : QString(); html = QStringLiteral("
").arg(listVisibility) + html + QStringLiteral("
"); const QString urlHandleShow = QStringLiteral("kmail:showAttachmentQuicklist"); const QString imgSrcShow = QStringLiteral("quicklistClosed.png"); const QString urlHandleHide = QStringLiteral("kmail:hideAttachmentQuicklist"); const QString imgSrcHide = QStringLiteral("quicklistOpened.png"); //TODO make it as a virtual method QString link; QString textAlign = QStringLiteral("right"); const bool isFancyTheme = (headerStylePlugin()->name() == QStringLiteral("fancy")); if (isFancyTheme) { textAlign = QStringLiteral("left"); } const QString visibility = QStringLiteral("style=\"display:none;\""); link += QStringLiteral(""); html.prepend(link); if (isFancyTheme) { html.prepend(QStringLiteral("
%1 
").arg(i18n( "Attachments:"))); } return html; } void ViewerPrivate::executeCustomScriptsAfterLoading() { disconnect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading); // inject attachments in header view // we have to do that after the otp has run so we also see encrypted parts toggleFullAddressList(); mViewer->scrollToRelativePosition(mViewer->relativePosition()); mViewer->clearRelativePosition(); } void ViewerPrivate::slotSettingsChanged() { update(MimeTreeParser::Force); } void ViewerPrivate::slotMimeTreeContextMenuRequested(const QPoint &pos) { #ifndef QT_NO_TREEVIEW QModelIndex index = mMimePartTree->indexAt(pos); if (index.isValid()) { KMime::Content *content = static_cast(index.internalPointer()); showContextMenu(content, pos); } #endif } void ViewerPrivate::slotAttachmentOpenWith() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpenWith(content); } #endif } void ViewerPrivate::slotAttachmentOpen() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpen(content); } #endif } void ViewerPrivate::showOpenAttachmentFolderWidget(const QUrl &url) { mOpenAttachmentFolderWidget->setFolder(url); mOpenAttachmentFolderWidget->slotShowWarning(); } bool ViewerPrivate::mimePartTreeIsEmpty() const { #ifndef QT_NO_TREEVIEW return mMimePartTree->model()->rowCount() == 0; #else return false; #endif } void ViewerPrivate::setPluginName(const QString &pluginName) { mHeaderStyleMenuManager->setPluginName(pluginName); } QList ViewerPrivate::viewerPluginActionList( ViewerPluginInterface::SpecificFeatureTypes features) { if (mViewerPluginToolManager) { return mViewerPluginToolManager->viewerPluginActionList(features); } return QList(); } void ViewerPrivate::slotActivatePlugin(ViewerPluginInterface *interface) { interface->setMessage(mMessage); interface->setMessageItem(mMessageItem); interface->setUrl(mClickedUrl); interface->setCurrentCollection(mMessageItem.parentCollection()); const QString text = mViewer->selectedText(); if (!text.isEmpty()) { interface->setText(text); } interface->execute(); } void ViewerPrivate::slotAttachmentSaveAs() { const auto contents = selectedContents(); QUrl currentUrl; if (Util::saveAttachments(contents, mMainWindow, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } void ViewerPrivate::slotAttachmentSaveAll() { const auto contents = mMessage->attachments(); QUrl currentUrl; if (Util::saveAttachments(contents, mMainWindow, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } void ViewerPrivate::slotAttachmentView() { const auto contents = selectedContents(); for (KMime::Content *content : contents) { attachmentView(content); } } void ViewerPrivate::slotAttachmentProperties() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } for (KMime::Content *content : contents) { attachmentProperties(content); } } void ViewerPrivate::attachmentProperties(KMime::Content *content) { MessageCore::AttachmentPropertiesDialog *dialog = new MessageCore::AttachmentPropertiesDialog( content, mMainWindow); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void ViewerPrivate::slotAttachmentCopy() { #ifndef QT_NO_CLIPBOARD attachmentCopy(selectedContents()); #endif } void ViewerPrivate::attachmentCopy(const KMime::Content::List &contents) { #ifndef QT_NO_CLIPBOARD if (contents.isEmpty()) { return; } QList urls; for (KMime::Content *content : contents) { auto url = QUrl::fromLocalFile(mNodeHelper->writeNodeToTempFile(content)); if (!url.isValid()) { continue; } urls.append(url); } if (urls.isEmpty()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(urls); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); #endif } void ViewerPrivate::slotAttachmentDelete() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } bool showWarning = true; for (KMime::Content *content : contents) { if (!deleteAttachment(content, showWarning)) { return; } showWarning = false; } update(); } void ViewerPrivate::slotAttachmentEdit() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } MessageViewer::AttachmentEditJob *job = new MessageViewer::AttachmentEditJob(mSession, this); connect(job, &AttachmentEditJob::refreshMessage, this, &ViewerPrivate::slotRefreshMessage); job->setMainWindow(mMainWindow); job->setMessageItem(mMessageItem); job->setMessage(mMessage); bool showWarning = true; for (KMime::Content *content : contents) { if (!job->addAttachment(content, showWarning)) { break; } showWarning = false; } job->canDeleteJob(); } void ViewerPrivate::slotLevelQuote(int l) { if (mLevelQuote != l) { mLevelQuote = l; update(MimeTreeParser::Force); } } void ViewerPrivate::slotHandleAttachment(int choice) { if (!mCurrentContent) { return; } switch (choice) { case Viewer::Delete: deleteAttachment(mCurrentContent); break; case Viewer::Edit: editAttachment(mCurrentContent); break; case Viewer::Properties: attachmentProperties(mCurrentContent); break; case Viewer::Save: { QUrl currentUrl; if (Util::saveContents(mMainWindow, KMime::Content::List() << mCurrentContent, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } break; } case Viewer::OpenWith: attachmentOpenWith(mCurrentContent); break; case Viewer::Open: attachmentOpen(mCurrentContent); break; case Viewer::View: attachmentView(mCurrentContent); break; case Viewer::Copy: attachmentCopy(KMime::Content::List() << mCurrentContent); break; case Viewer::ScrollTo: scrollToAttachment(mCurrentContent); break; case Viewer::ReplyMessageToAuthor: replyMessageToAuthor(mCurrentContent); break; case Viewer::ReplyMessageToAll: replyMessageToAll(mCurrentContent); break; } } void ViewerPrivate::replyMessageToAuthor(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), false); } } } void ViewerPrivate::replyMessageToAll(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), true); } } } void ViewerPrivate::slotSpeakText() { const QString text = mViewer->selectedText(); if (!text.isEmpty()) { mTextToSpeechWidget->say(text); } } QUrl ViewerPrivate::imageUrl() const { QUrl url; if (mImageUrl.scheme() == QLatin1String("cid")) { url = QUrl(MessageViewer::WebEngineEmbedPart::self()->contentUrl(mImageUrl.path())); } else { url = mImageUrl; } return url; } void ViewerPrivate::slotCopyImageLocation() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setText(imageUrl().url()); #endif } void ViewerPrivate::slotCopySelectedText() { #ifndef QT_NO_CLIPBOARD QString selection = mViewer->selectedText(); selection.replace(QChar::Nbsp, QLatin1Char(' ')); QApplication::clipboard()->setText(selection); #endif } void ViewerPrivate::viewerSelectionChanged() { mActionCollection->action(QStringLiteral("kmail_copy"))->setEnabled( !mViewer->selectedText().isEmpty()); } void ViewerPrivate::selectAll() { mViewer->selectAll(); } void ViewerPrivate::slotUrlCopy() { #ifndef QT_NO_CLIPBOARD QClipboard *clip = QApplication::clipboard(); if (mClickedUrl.scheme() == QLatin1String("mailto")) { // put the url into the mouse selection and the clipboard const QString address = KEmailAddress::decodeMailtoUrl(mClickedUrl); clip->setText(address, QClipboard::Clipboard); clip->setText(address, QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard.")); } else { // put the url into the mouse selection and the clipboard clip->setText(mClickedUrl.url(), QClipboard::Clipboard); clip->setText(mClickedUrl.url(), QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("URL copied to clipboard.")); } #endif } void ViewerPrivate::slotSaveMessage() { if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } Util::saveMessageInMbox(Akonadi::Item::List() << mMessageItem, mMainWindow); } void ViewerPrivate::saveRelativePosition() { mViewer->saveRelativePosition(); } //TODO(Andras) inline them bool ViewerPrivate::htmlMail() const { if (mDisplayFormatMessageOverwrite == Viewer::UseGlobalSetting) { return mHtmlMailGlobalSetting; } else { return mDisplayFormatMessageOverwrite == Viewer::Html; } } bool ViewerPrivate::htmlLoadExternal() const { return (mHtmlLoadExternalGlobalSetting && !mHtmlLoadExtOverride) || (!mHtmlLoadExternalGlobalSetting && mHtmlLoadExtOverride); } void ViewerPrivate::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format) { mDisplayFormatMessageOverwrite = format; // keep toggle display mode action state in sync. if (mToggleDisplayModeAction) { mToggleDisplayModeAction->setChecked(htmlMail()); } } bool ViewerPrivate::htmlMailGlobalSetting() const { return mHtmlMailGlobalSetting; } Viewer::DisplayFormatMessage ViewerPrivate::displayFormatMessageOverwrite() const { return mDisplayFormatMessageOverwrite; } void ViewerPrivate::setHtmlLoadExtOverride(bool override) { mHtmlLoadExtOverride = override; } bool ViewerPrivate::htmlLoadExtOverride() const { return mHtmlLoadExtOverride; } void ViewerPrivate::setDecryptMessageOverwrite(bool overwrite) { mDecrytMessageOverwrite = overwrite; } bool ViewerPrivate::showSignatureDetails() const { return mShowSignatureDetails; } void ViewerPrivate::setShowSignatureDetails(bool showDetails) { mShowSignatureDetails = showDetails; } void ViewerPrivate::setFullToAddressList(bool showFullTo) { mViewer->executeHideShowToAddressScripts(showFullTo); } void ViewerPrivate::setFullCcAddressList(bool showFullCc) { mViewer->executeHideShowCcAddressScripts(showFullCc); } void ViewerPrivate::setShowAttachmentQuicklist(bool showAttachmentQuicklist) { mShowAttachmentQuicklist = showAttachmentQuicklist; mViewer->executeHideShowAttachmentsScripts(mShowAttachmentQuicklist); } void ViewerPrivate::setHideEncryptionDetails(bool encDetails) { mViewer->executeHideShowEncryptionDetails(encDetails); } void ViewerPrivate::scrollToAttachment(KMime::Content *node) { const QString indexStr = node->index().toString(); // The anchors for this are created in ObjectTreeParser::parseObjectTree() mViewer->scrollToAnchor(QLatin1String("att") + indexStr); // Remove any old color markings which might be there const KMime::Content *root = node->topLevel(); const int totalChildCount = Util::allContents(root).size(); for (int i = 0; i < totalChildCount + 1; ++i) { mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv%1").arg(i + 1)); } // Don't mark hidden nodes, that would just produce a strange yellow line if (mNodeHelper->isNodeDisplayedHidden(node)) { return; } // Now, color the div of the attachment in yellow, so that the user sees what happened. // We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser, // find and modify that now. mViewer->markAttachment(QLatin1String("attachmentDiv") + indexStr, QStringLiteral("border:2px solid %1").arg(cssHelper()->pgpWarnColor(). name())); } void ViewerPrivate::setUseFixedFont(bool useFixedFont) { mUseFixedFont = useFixedFont; if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } } void ViewerPrivate::toggleFullAddressList() { toggleFullAddressList(QStringLiteral("To")); toggleFullAddressList(QStringLiteral("Cc")); } QString ViewerPrivate::recipientsQuickListLinkHtml(const QString &field) { const QString urlHandleShow = QLatin1String("kmail:hideFull") + field + QLatin1String( "AddressList"); const QString imgSrcShow = QStringLiteral("quicklistOpened.png"); const QString urlHandleHide = QLatin1String("kmail:showFull") + field + QLatin1String( "AddressList"); const QString imgSrcHide = QStringLiteral("quicklistClosed.png"); const QString visibility = QStringLiteral("style=\"display:none;\""); return QStringLiteral("") +QStringLiteral("").arg(urlHandleShow).arg(field) +QStringLiteral("\"%2\"").arg(QUrl::fromLocalFile(MessageViewer:: IconNameCache:: instance()-> iconPathFromLocal( imgSrcShow)).url(), /*altTextShow*/ QString()) +QStringLiteral("") +QStringLiteral("").arg(urlHandleHide).arg(field). arg(visibility) +QStringLiteral("\"%2\"").arg(QUrl::fromLocalFile(MessageViewer:: IconNameCache:: instance()-> iconPathFromLocal( imgSrcHide)).url(), /*altTextHide*/ QString()) +QStringLiteral("") +QStringLiteral(""); } void ViewerPrivate::toggleFullAddressList(const QString &field) { if (field == QLatin1String("To") || (field == QLatin1String("Cc"))) { mViewer->toggleFullAddressList(field, bind(&ViewerPrivate::recipientsQuickListLinkHtml, this, field)); } } void ViewerPrivate::itemFetchResult(KJob *job) { if (job->error()) { displaySplashPage(i18n("Message loading failed: %1.", job->errorText())); } else { Akonadi::ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { displaySplashPage(i18n("Message not found.")); } else { setMessageItem(fetch->items().constFirst()); } } } void ViewerPrivate::slotItemChanged(const Akonadi::Item &item, const QSet &parts) { if (item.id() != messageItem().id()) { qCDebug(MESSAGEVIEWER_LOG) << "Update for an already forgotten item. Weird."; return; } if (parts.contains("PLD:RFC822")) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &) { // clear the view after the current item has been moved somewhere else (e.g. to trash) if (item.id() == messageItem().id()) { slotClear(); } } void ViewerPrivate::slotClear() { q->clear(MimeTreeParser::Force); Q_EMIT itemRemoved(); } void ViewerPrivate::slotMessageRendered() { if (!mMessageItem.isValid()) { return; } /** * This slot might be called multiple times for the same message if * some asynchronous mementos are involved in rendering. Therefor we * have to make sure we execute the MessageLoadedHandlers only once. */ if (mMessageItem.id() == mPreviouslyViewedItem) { return; } mPreviouslyViewedItem = mMessageItem.id(); for (AbstractMessageLoadedHandler *handler : qAsConst(mMessageLoadedHandlers)) { handler->setItem(mMessageItem); } } void ViewerPrivate::setZoomFactor(qreal zoomFactor) { mZoomActionMenu->setWebViewerZoomFactor(zoomFactor); } void ViewerPrivate::goOnline() { Q_EMIT makeResourceOnline(Viewer::AllResources); } void ViewerPrivate::goResourceOnline() { Q_EMIT makeResourceOnline(Viewer::SelectedResource); } void ViewerPrivate::slotSaveMessageDisplayFormat() { if (mMessageItem.isValid()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageFormat(displayFormatMessageOverwrite()); job->setMessageItem(mMessageItem); job->setRemoteContent(htmlLoadExtOverride()); job->start(); } } void ViewerPrivate::slotResetMessageDisplayFormat() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageItem(mMessageItem); job->setResetFormat(true); job->start(); } } } void ViewerPrivate::slotMessageMayBeAScam() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { const MessageViewer::ScamAttribute *const attr = mMessageItem.attribute(); if (attr && !attr->isAScam()) { return; } } if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); const QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } } } mScamDetectionWarning->slotShowWarning(); } void ViewerPrivate::slotMessageIsNotAScam() { if (mMessageItem.isValid()) { MessageViewer::ScamAttribute *attr = mMessageItem.attribute( Akonadi::Item::AddIfMissing); attr->setIsAScam(false); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession); modify->setIgnorePayload(true); modify->disableRevisionCheck(); connect(modify, &KJob::result, this, &ViewerPrivate::slotModifyItemDone); } } void ViewerPrivate::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(MESSAGEVIEWER_LOG) << " Error trying to change attribute:" << job->errorText(); } } void ViewerPrivate::saveMainFrameScreenshotInFile(const QString &filename) { mViewer->saveMainFrameScreenshotInFile(filename); } void ViewerPrivate::slotAddToWhiteList() { if (mMessageItem.isValid()) { if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } lst << email; MessageViewer::MessageViewerSettings::self()->setScamDetectionWhiteList(lst); MessageViewer::MessageViewerSettings::self()->save(); } } } void ViewerPrivate::slotFormSubmittedForbidden() { mSubmittedFormWarning->showWarning(); } void ViewerPrivate::addHelpTextAction(QAction *act, const QString &text) { act->setStatusTip(text); act->setToolTip(text); act->setWhatsThis(text); } void ViewerPrivate::slotRefreshMessage(const Akonadi::Item &item) { if (item.id() == mMessageItem.id()) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotServiceUrlSelected( PimCommon::ShareServiceUrlManager::ServiceType serviceType) { const QUrl url = mShareServiceManager->generateServiceUrl(mClickedUrl.toString(), QString(), serviceType); mShareServiceManager->openUrl(url); } QList ViewerPrivate::interceptorUrlActions( const WebEngineViewer::WebHitTestResult &result) const { return mViewer->interceptorUrlActions(result); } void ViewerPrivate::setPrintElementBackground(bool printElementBackground) { mViewer->setPrintElementBackground(printElementBackground); } void ViewerPrivate::slotToggleEmoticons() { mForceEmoticons = !mForceEmoticons; headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons); update(MimeTreeParser::Force); } diff --git a/messageviewer/src/viewer/viewer_p.h b/messageviewer/src/viewer/viewer_p.h index b53e5f21..fdee845e 100644 --- a/messageviewer/src/viewer/viewer_p.h +++ b/messageviewer/src/viewer/viewer_p.h @@ -1,694 +1,694 @@ /* Copyright (c) 1997 Markus Wuebben 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 MAILVIEWER_P_H #define MAILVIEWER_P_H #include #include "config-messageviewer.h" #include "viewer.h" //not so nice, it is actually for the enums from MailViewer #include "PimCommon/ShareServiceUrlManager" #include "messageviewer/viewerplugininterface.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KIO { class Job; } class QAction; class KActionCollection; class KSelectAction; class KToggleAction; class QMenu; class KActionMenu; class QPoint; class QSplitter; class QModelIndex; class QPrinter; namespace KPIMTextEdit { class SlideContainer; class TextToSpeechWidget; } namespace PimCommon { class ShareServiceUrlManager; } namespace MimeTreeParser { -class AttachmentStrategy; class HtmlWriter; class ObjectTreeParser; } namespace WebEngineViewer { class WebHitTestResult; class FindBarWebEngineView; class ZoomActionMenu; class LocalDataBaseManager; } namespace MessageViewer { +class AttachmentStrategy; class HeaderStylePlugin; class CSSHelper; class MailWebEngineView; class WebEnginePartHtmlWriter; class HtmlStatusBar; class ScamDetectionWarningWidget; class MimePartTreeView; class OpenAttachmentFolderWidget; class HeaderStyleMenuManager; class ViewerPluginToolManager; class ViewerPluginInterface; class SubmittedFormWarningWidget; class MailSourceWebEngineViewer; /** \brief Private class for the Viewer, the main widget in the messageviewer library. This class creates all subwidgets, like the MailWebView, the HtmlStatusBar and the FindBarMailWebView. Also, ViewerPrivate creates and exposes all actions. \par Displaying a message Before displaying a message, a message needs to be set. This can be done in two ways, with setMessageItem() and with setMessage(). setMessageItem() is the preferred way, as the viewer can then remember the Akonadi::Item belonging to the message. The Akonadi::Item is needed when modifying the message, for example when editing or deleting an attachment. Sometimes passing an Akonadi::Item to the viewer is not possible, for example when double-clicking an attached message, in which case a new KMime::Message is constructed out of the attachment, and a separate window is opened for it. In this case, the KMime::Message has no associated Akonadi::Item. If there is an Akonadi::Item available, it will be monitored for changes and the viewer automatically updated on external changes. Once a message is set, update() is called. update() can also be called after the message has already been displayed. As an example, this is the case when the user decides to decrypt the message. The decryption can happen async, and once the decryption is finished, update() is called to display the now decrypted content. See the documentation of MimeTreeParser::ObjectTreeParser on how exactly decryption is handled. update() is just a thin wrapper that calls updateReaderWin(). The only difference is that update() has a timer that prevents too many slow calls to updateReaderWin() in a short time frame. updateReaderWin() again is only a thin wrapper that resets some state and then calls displayMessage(). displayMessage() itself is again a thin wrapper, which starts the MimeTreeParser::HtmlWriter and then calls parseMsg(). Finally, parseMsg() does the real work. It uses MimeTreeParser::ObjectTreeParser ::parseObjectTree() to let the MimeTreeParser::ObjectTreeParser parse the message and generate the HTML code for it. As mentioned before, it can happen that the MimeTreeParser::ObjectTreeParser needs to do some operation that happens async, for example decrypting. In this case, the MimeTreeParser::ObjectTreeParser will create a BodyPartMemento, which basically is a wrapper around the job that does the async operation. Once the async operation is finished. the BodyPartMemento will trigger an update() of ViewerPrivate, so that MimeTreeParser::ObjectTreeParser ::parseObjectTree() gets called again and the MimeTreeParser::ObjectTreeParser then can generate HTML which has the decrypted content of the message. Again, see the documentation of MimeTreeParser::ObjectTreeParser for the details. Additionally, parseMsg() does some evil hack for saving unencrypted messages should the config option for that be set. \par Displaying a MIME part of the message The viewer can show only a part of the message, for example by clicking on a MIME part in the message structure viewer or by double-clicking an attached message. In this case, setMessagePart() is called. There are two of these functions. One even has special handling for images, special handling for binary attachments and special handling of attached messages. In the last case, a new KMime::Message is constructed and set as the main message with setMessage(). \par Attachment Handling Some of those actions are actions that operate on a single attachment. For those, there is usually a slot, like slotAttachmentCopy(). These actions are triggered from the attachment context menu, which is shown in showAttachmentPopup(). The actions are connected to slotHandleAttachment() when they are activated. The action to edit an attachment uses the EditorWatcher to detect when editing with an external editor is finished. Upon finishing, slotAttachmentEditDone() is called, which then creates an ItemModifyJob to store the changes of the attachment. A map of currently active EditorWatcher and their KMime::Content is available in mEditorWatchers. For most attachment actions, the attachment is first written to a temp file. The action is then executed on this temp file. Writing the attachment to a temp file is done with MimeTreeParser::NodeHelper::writeNodeToTempFile(). This method is called before opening or copying an attachment or when rendering the attachment list. The MimeTreeParser::ObjectTreeParser also calls MimeTreeParser::NodeHelper::writeNodeToTempFile() in some places. Once the temp file is written, MimeTreeParser::NodeHelper::tempFileUrlFromNode() can be used to get the file name of the temp file for a specific MIME part. This is for example used by the handler for 'attachment:' URLs, AttachmentURLHandler. Since URLs for attachments are in the "attachment:" scheme, dragging them as-is to outside applications wouldn't work, since other applications don't understand this scheme. Therefore, the viewer has special handling for dragging URLs: In eventFilter(), drags are detected, and the URL handler is called to deal with the drag. The attachment URL handler then starts a drag with the file:// URL of the temp file of the attachment, which it gets with MimeTreeParser::NodeHelper::tempFileUrlFromNode(). TODO: How are attachment handled that are loaded on demand? How does prepareHandleAttachment() work? TODO: This temp file handling is a big mess and could use a rewrite, especially in the face of load on demand. There shouldn't be the need to write out tempfiles until really needed. Some header styles display an attachment list in the header. The HTML code for the attachment list cannot be generated by the HeaderStyle itself, since that does not know about all attachments. Therefore, the attachment list needs to be created by ViewerPrivate. For this, the HeaderStyle writes out a placeholder for the attachment list when it creates the HTML for the header. Once the MimeTreeParser::ObjectTreeParser is finished with the message, injectAttachments() is called. injectAttachments() searches for the placeholder and replaces that with the real HTML code for the attachments. One of the attachment actions is to scoll to the attachment. That action is only available when right-clicking the header. The action scrolls to the attachment in the body and draws a yellow frame around the attachment. This is done in scrollToAttachment(). The attachment in the body and the div which is used for the colored frame are both created by the MimeTreeParser::ObjectTreeParser . \par Misc ViewerPrivate holds the MimeTreeParser::NodeHelper, which is passed on to the MimeTreeParser::ObjectTreeParser when it needs it. It also holds the HeaderStyle, HeaderStrategy, MimeTreeParser::AttachmentStrategy, CSSHelper, MimeTreeParser::HtmlWriter and more, some of them again passed to the MimeTreeParser::ObjectTreeParser when it needs it. @author andras@kdab.net */ class ViewerPrivate : public QObject { Q_OBJECT public: ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection); virtual ~ViewerPrivate(); /** Returns message part from given URL or null if invalid. The URL's path is a KMime::ContentIndex path, or an index for the extra nodes, followed by : and the ContentIndex path. */ KMime::Content *nodeFromUrl(const QUrl &url) const; /** Open the attachment pointed to the node. * @param fileName - if not empty, use this file to load the attachment content */ void openAttachment(KMime::Content *node, const QUrl &url); /** Delete the attachment the @param node points to. Returns false if the user cancelled the deletion, true in all other cases (including failure to delete the attachment!) */ bool deleteAttachment(KMime::Content *node, bool showWarning = true); void attachmentProperties(KMime::Content *node); void attachmentCopy(const KMime::Content::List &contents); /** Edit the attachment the @param node points to. Returns false if the user cancelled the editing, true in all other cases! */ void editAttachment(KMime::Content *node, bool showWarning = true); void scrollToAnchor(const QString &anchor); void showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &p); /** * Sets the current attachment ID and the current attachment temporary filename * to the given values. * Call this so that slotHandleAttachment() knows which attachment to handle. */ void prepareHandleAttachment(KMime::Content *node); QString createAtmFileLink(const QString &atmFileName) const; KService::Ptr getServiceOffer(KMime::Content *content); KMime::Content::List selectedContents(); void attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer = KService::Ptr()); void attachmentOpen(KMime::Content *node); /** Return the MimeTreeParser::HtmlWriter connected to the MailWebView we use */ MimeTreeParser::HtmlWriter *htmlWriter() const; HeaderStylePlugin *headerStylePlugin() const; CSSHelper *cssHelper() const; MimeTreeParser::NodeHelper *nodeHelper() const; Viewer *viewer() const; Akonadi::Item messageItem() const; KMime::Message::Ptr message() const; /** Returns whether the message should be decryted. */ bool decryptMessage() const; /** Display a generic HTML splash page instead of a message. */ void displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain = QByteArray()); void displaySplashPage(const QString &message); /** Enable the displaying of messages again after an splash (or other) page was displayed */ void enableMessageDisplay(); /** Feeds the HTML viewer with the contents of the given message. HTML begin/end parts are written around the message. */ void displayMessage(); /** Parse the given content and generate HTML out of it for display */ void parseContent(KMime::Content *content); /** Creates a nice mail header depending on the current selected header style. */ QString writeMsgHeader(KMime::Message *aMsg, KMime::Content *vCardNode = nullptr, bool topLevel = false); /** show window containing information about a vCard. */ void showVCard(KMime::Content *msgPart); void saveMainFrameScreenshotInFile(const QString &filename); private: /** HTML initialization. */ void initHtmlWidget(); void createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent); public: /** Read settings from app's config file. */ void readConfig(); /** Write settings to app's config file. Calls sync() if withSync is true. */ void writeConfig(bool withSync = true); /** Get/set the message attachment strategy. */ - const MimeTreeParser::AttachmentStrategy *attachmentStrategy() const; - void setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy); + const AttachmentStrategy *attachmentStrategy() const; + void setAttachmentStrategy(const AttachmentStrategy *strategy); /** Get selected override character encoding. @return The encoding selected by the user or an empty string if auto-detection is selected. */ QString overrideEncoding() const; /** Set the override character encoding. */ void setOverrideEncoding(const QString &encoding); /** Set printing mode */ void setPrinting(bool enable); bool printingMode() const; /** Print message. */ void printMessage(const Akonadi::Item &msg); void printPreviewMessage(const Akonadi::Item &message); void resetStateForNewMessage(); void setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode); /** Set the Akonadi item that will be displayed. * @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data), * an empty page is shown. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** Set the message that shall be shown. * @param msg - the message to be shown. If 0, an empty page is displayed. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessage(const KMime::Message::Ptr &msg, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** Instead of settings a message to be shown sets a message part to be shown */ void setMessagePart(KMime::Content *node); /** Show or hide the Mime Tree Viewer if configuration is set to smart mode. */ void showHideMimeTree(); /** View message part of type message/RFC822 in extra viewer window. */ void atmViewMsg(const KMime::Message::Ptr &message); void adjustLayout(); void createWidgets(); void createActions(); void showContextMenu(KMime::Content *content, const QPoint &point); - KToggleAction *actionForAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *); + KToggleAction *actionForAttachmentStrategy(const AttachmentStrategy *); /** Read override codec from configuration */ void readGlobalOverrideCodec(); /** Get codec corresponding to the currently selected override character encoding. @return The override codec or 0 if auto-detection is selected. */ const QTextCodec *overrideCodec() const; QString renderAttachments(KMime::Content *node, const QColor &bgColor) const; KMime::Content *findContentByType(KMime::Content *content, const QByteArray &type); //TODO(Andras) move to MimeTreeParser::NodeHelper /** Return a QTextCodec for the specified charset. * This function is a bit more tolerant, than QTextCodec::codecForName */ static const QTextCodec *codecForName(const QByteArray &_str); //TODO(Andras) move to a utility class? /** Saves the relative position of the scroll view. Call this before calling update() if you want to preserve the current view. */ void saveRelativePosition(); bool htmlMail() const; bool htmlLoadExternal() const; bool htmlMailGlobalSetting() const; /** Get the html override setting */ Viewer::DisplayFormatMessage displayFormatMessageOverwrite() const; /** Override default html mail setting */ void setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format); /** Get the load external references override setting */ bool htmlLoadExtOverride() const; /** Override default load external references setting */ void setHtmlLoadExtOverride(bool override); /** Enforce message decryption. */ void setDecryptMessageOverwrite(bool overwrite = true); /** Show signature details. */ bool showSignatureDetails() const; /** Show signature details. */ void setShowSignatureDetails(bool showDetails = true); /* show or hide the list that points to the attachments */ void setShowAttachmentQuicklist(bool showAttachmentQuicklist = true); /* show or hide encryption details */ void setHideEncryptionDetails(bool encDetails = true); void scrollToAttachment(KMime::Content *node); void setUseFixedFont(bool useFixedFont); void attachmentView(KMime::Content *atmNode); void setFullToAddressList(bool showFullTo); void setFullCcAddressList(bool showFullCc); /** Show/Hide the field with id "field" */ void toggleFullAddressList(const QString &field); void setZoomFactor(qreal zoomFactor); void goOnline(); void goResourceOnline(); void showOpenAttachmentFolderWidget(const QUrl &url); bool mimePartTreeIsEmpty() const; void setPluginName(const QString &pluginName); QList viewerPluginActionList( MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features); QList interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const; void setPrintElementBackground(bool printElementBackground); bool showEmoticons() const; void checkPhishingUrl(); void executeRunner(const QUrl &url); QUrl imageUrl() const; private Q_SLOTS: void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface); void slotModifyItemDone(KJob *job); void slotMessageMayBeAScam(); void slotMessageIsNotAScam(); void slotAddToWhiteList(); void slotFormSubmittedForbidden(); /** Show hide all fields specified inside this function */ void toggleFullAddressList(); void itemFetchResult(KJob *job); void slotItemChanged(const Akonadi::Item &item, const QSet &partIdentifiers); void slotItemMoved(const Akonadi::Item &, const Akonadi::Collection &, const Akonadi::Collection &); void itemModifiedResult(KJob *job); void slotClear(); void slotMessageRendered(); void slotOpenWithAction(QAction *act); void slotOpenWithActionCurrentContent(QAction *act); void slotOpenWithDialog(); void slotOpenWithDialogCurrentContent(); void saveSplitterSizes() const; void slotRefreshMessage(const Akonadi::Item &item); void slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType serviceType); void slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin); void slotStyleUpdated(); void slotWheelZoomChanged(int numSteps); void slotOpenInBrowser(); void slotExportHtmlPageFailed(); void slotExportHtmlPageSuccess(const QString &filename); void slotHandlePagePrinted(bool result); void slotToggleEmoticons(); public Q_SLOTS: /** An URL has been activate with a click. */ void slotUrlOpen(const QUrl &url = QUrl()); void slotOpenUrl(); /** The mouse has moved on or off an URL. */ void slotUrlOn(const QString &link); /** The user presses the right mouse button on an URL. */ void slotUrlPopup(const WebEngineViewer::WebHitTestResult &result); /** The user selected "Find" from the menu. */ void slotFind(); /** The user toggled the "Fixed Font" flag from the view menu. */ void slotToggleFixedFont(); void slotToggleMimePartTree(); /** Show the message source */ void slotShowMessageSource(); /** Refresh the reader window */ void updateReaderWin(); void slotMimePartSelected(const QModelIndex &index); void slotIconicAttachments(); void slotSmartAttachments(); void slotInlineAttachments(); void slotHideAttachments(); void slotHeaderOnlyAttachments(); /** Some attachment operations. */ void slotDelayedResize(); /** Print message. Called on as a response of finished() signal of mPartHtmlWriter after rendering is finished. In the very end it deletes the KMReaderWin window that was created for the purpose of rendering. */ void slotPrintMessage(); void slotPrintPreview(); void slotSetEncoding(); void executeCustomScriptsAfterLoading(); void slotSettingsChanged(); void slotMimeTreeContextMenuRequested(const QPoint &pos); void slotAttachmentOpenWith(); void slotAttachmentOpen(); void slotAttachmentSaveAs(); void slotAttachmentSaveAll(); void slotAttachmentView(); void slotAttachmentProperties(); void slotAttachmentCopy(); void slotAttachmentDelete(); void slotAttachmentEdit(); void slotLevelQuote(int l); /** Toggle display mode between HTML and plain text. */ void slotToggleHtmlMode(); void slotLoadExternalReference(); /** * Does an action for the current attachment. * The action is defined by the KMHandleAttachmentCommand::AttachmentAction * enum. * prepareHandleAttachment() needs to be called before calling this to set the * correct attachment ID. */ void slotHandleAttachment(int action); /** Copy the selected text to the clipboard */ void slotCopySelectedText(); void viewerSelectionChanged(); /** Select message body. */ void selectAll(); /** Copy URL in mUrlCurrent to clipboard. Removes "mailto:" at beginning of URL before copying. */ void slotUrlCopy(); void slotSaveMessage(); /** Re-parse the current message. */ void update(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); void slotSpeakText(); void slotCopyImageLocation(); void slotSaveMessageDisplayFormat(); void slotResetMessageDisplayFormat(); void slotGeneralFontChanged(); Q_SIGNALS: void showStatusBarMessage(const QString &message); void popupMenu(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &mousePos); void displayPopupMenu(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &mousePos); void urlClicked(const Akonadi::Item &msg, const QUrl &url); void requestConfigSync(); void showReader(KMime::Content *aMsgPart, bool aHTML, const QString &encoding); void showMessage(const KMime::Message::Ptr &message, const QString &encoding); void replyMessageTo(const KMime::Message::Ptr &message, bool replyToAll); void itemRemoved(); void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode); void changeDisplayMail(Viewer::DisplayFormatMessage, bool); void moveMessageToTrash(); void pageIsScrolledToBottom(bool); void printingFinished(); private: QString attachmentInjectionHtml(); QString recipientsQuickListLinkHtml(const QString &); Akonadi::Relation relatedNoteRelation() const; void addHelpTextAction(QAction *act, const QString &text); void readGravatarConfig(); void replyMessageToAuthor(KMime::Content *atmNode); void replyMessageToAll(KMime::Content *atmNode); bool urlIsAMalwareButContinue(); void slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status); MimeTreeParser::NodeHelper *mNodeHelper = nullptr; void slotDelayPrintPreview(); public: bool mHtmlMailGlobalSetting; bool mHtmlLoadExternalGlobalSetting; bool mHtmlLoadExtOverride; KMime::Message::Ptr mMessage; //the current message, if it was set manually Akonadi::Item mMessageItem; //the message item from Akonadi // widgets: QSplitter *mSplitter = nullptr; QWidget *mBox = nullptr; HtmlStatusBar *mColorBar = nullptr; #ifndef QT_NO_TREEVIEW MimePartTreeView *mMimePartTree = nullptr; #endif MailWebEngineView *mViewer = nullptr; WebEngineViewer::FindBarWebEngineView *mFindBar = nullptr; - const MimeTreeParser::AttachmentStrategy *mAttachmentStrategy = nullptr; + const AttachmentStrategy *mAttachmentStrategy = nullptr; QTimer mUpdateReaderWinTimer; QTimer mResizeTimer; QString mOverrideEncoding; QString mOldGlobalOverrideEncoding; // used to detect changes of the global override character encoding QString mPicsPath; /// This is true if the viewer currently is displaying a message. Can be false, for example when /// the splash/busy page is displayed. bool mMsgDisplay; CSSHelper *mCSSHelper = nullptr; bool mUseFixedFont; bool mPrinting; QWidget *mMainWindow = nullptr; KActionCollection *mActionCollection = nullptr; QAction *mCopyAction = nullptr; QAction *mCopyURLAction = nullptr; QAction *mUrlOpenAction = nullptr; QAction *mSelectAllAction = nullptr; QAction *mScrollUpAction = nullptr; QAction *mScrollDownAction = nullptr; QAction *mScrollUpMoreAction = nullptr; QAction *mScrollDownMoreAction = nullptr; QAction *mViewSourceAction = nullptr; QAction *mSaveMessageAction = nullptr; QAction *mFindInMessageAction = nullptr; QAction *mSaveMessageDisplayFormat = nullptr; QAction *mResetMessageDisplayFormat = nullptr; KToggleAction *mDisableEmoticonAction = nullptr; KToggleAction *mHeaderOnlyAttachmentsAction = nullptr; KSelectAction *mSelectEncodingAction = nullptr; KToggleAction *mToggleFixFontAction = nullptr; KToggleAction *mToggleDisplayModeAction = nullptr; KToggleAction *mToggleMimePartTreeAction = nullptr; QAction *mSpeakTextAction = nullptr; QAction *mCopyImageLocation = nullptr; QUrl mHoveredUrl; QUrl mClickedUrl; QUrl mImageUrl; QPoint mLastClickPosition; bool mCanStartDrag; MimeTreeParser::HtmlWriter *mHtmlWriter; /** Used only to be able to connect and disconnect finished() signal in printMsg() and slotPrintMsg() since mHtmlWriter points only to abstract non-QObject class. */ QPointer mPartHtmlWriter; int mLevelQuote; bool mDecrytMessageOverwrite; bool mShowSignatureDetails; bool mShowAttachmentQuicklist; bool mForceEmoticons; int mRecursionCountForDisplayMessage; KMime::Content *mCurrentContent = nullptr; KMime::Content *mMessagePartNode = nullptr; QString mMessagePath; QColor mForegroundError; QColor mBackgroundError; Viewer *const q; Akonadi::Session *mSession = nullptr; Akonadi::Monitor mMonitor; QSet mMessageLoadedHandlers; Akonadi::Item::Id mPreviouslyViewedItem; MessageViewer::ScamDetectionWarningWidget *mScamDetectionWarning = nullptr; MessageViewer::OpenAttachmentFolderWidget *mOpenAttachmentFolderWidget = nullptr; MessageViewer::SubmittedFormWarningWidget *mSubmittedFormWarning = nullptr; KPIMTextEdit::TextToSpeechWidget *mTextToSpeechWidget = nullptr; Viewer::DisplayFormatMessage mDisplayFormatMessageOverwrite; KPIMTextEdit::SlideContainer *mSliderContainer = nullptr; PimCommon::ShareServiceUrlManager *mShareServiceManager = nullptr; KActionMenu *mShareServiceUrlMenu = nullptr; MessageViewer::HeaderStylePlugin *mHeaderStylePlugin = nullptr; MessageViewer::HeaderStyleMenuManager *mHeaderStyleMenuManager = nullptr; MessageViewer::ViewerPluginToolManager *mViewerPluginToolManager = nullptr; WebEngineViewer::ZoomActionMenu *mZoomActionMenu = nullptr; QPrinter *mCurrentPrinter = nullptr; QList > mListMailSourceViewer; WebEngineViewer::LocalDataBaseManager *mPhishingDatabase = nullptr; }; } #endif diff --git a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp b/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp index 14e3a00b..67ff3c27 100644 --- a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp +++ b/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp @@ -1,89 +1,89 @@ /* Copyright (C) 2016-2017 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 "testjquerysupportmailwebengine.h" #include #include #include #include #include #include #include #include #include -#include +#include #include TestJQuerySupportMailWebEngine::TestJQuerySupportMailWebEngine(QWidget *parent) : QWidget(parent) { QVBoxLayout *vboxLayout = new QVBoxLayout(this); viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(this)); vboxLayout->addWidget(viewer); viewer->setMessage(readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"))); viewer->setPluginName(QStringLiteral("enterprise")); - viewer->setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::headerOnly()); + viewer->setAttachmentStrategy(MessageViewer::AttachmentStrategy::headerOnly()); mEditor = new QTextEdit(this); mEditor->setAcceptRichText(false); mEditor->setPlainText(QStringLiteral( "qt.jQuery('img').each( function () { qt.jQuery(this).css('-webkit-transition', '-webkit-transform 2s'); qt.jQuery(this).css('-webkit-transform', 'rotate(180deg)') } ); undefined")); vboxLayout->addWidget(mEditor); QPushButton *executeQuery = new QPushButton(QStringLiteral("Execute Query"), this); connect(executeQuery, &QPushButton::clicked, this, &TestJQuerySupportMailWebEngine::slotExecuteQuery); vboxLayout->addWidget(executeQuery); } TestJQuerySupportMailWebEngine::~TestJQuerySupportMailWebEngine() { } KMime::Message::Ptr TestJQuerySupportMailWebEngine::readAndParseMail(const QString &mailFile) { QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); file.open(QIODevice::ReadOnly); QByteArray ba = file.readAll(); qDebug() << ba; const QByteArray data = ba; Q_ASSERT(!data.isEmpty()); KMime::Message::Ptr msg(new KMime::Message); msg->setContent(data); msg->parse(); return msg; } void TestJQuerySupportMailWebEngine::slotExecuteQuery() { const QString code = mEditor->toPlainText(); if (!code.isEmpty()) { viewer->runJavaScript(code); } } int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); TestJQuerySupportMailWebEngine *testWebEngine = new TestJQuerySupportMailWebEngine; testWebEngine->show(); const int ret = app.exec(); return ret; } diff --git a/mimetreeparser/autotests/setupenv.h b/mimetreeparser/autotests/setupenv.h index d08e614f..a950f904 100644 --- a/mimetreeparser/autotests/setupenv.h +++ b/mimetreeparser/autotests/setupenv.h @@ -1,175 +1,164 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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. */ #ifndef MESSAGECORE_TESTS_UTIL_H #define MESSAGECORE_TESTS_UTIL_H #include -#include #include #include #include #include namespace MimeTreeParser { namespace Test { /** * setup a environment variables for tests: * * set LC_ALL to C * * set KDEHOME */ void setupEnv(); // We can't use EmptySource, since we need to control some emelnets of the source for tests to also test // loadExternal and htmlMail. class TestObjectTreeSource : public MimeTreeParser::Interface::ObjectTreeSource { public: TestObjectTreeSource(MimeTreeParser::HtmlWriter *writer) : mWriter(writer) , mAttachmentStrategy(QStringLiteral("smart")) , mPreferredMode(Util::Html) , mHtmlLoadExternal(false) , mDecryptMessage(false) { } MimeTreeParser::HtmlWriter *htmlWriter() override { return mWriter; } bool htmlLoadExternal() const override { return mHtmlLoadExternal; } void setHtmlLoadExternal(bool loadExternal) { mHtmlLoadExternal = loadExternal; } - void setAttachmentStrategy(QString strategy) - { - mAttachmentStrategy = strategy; - } - - const AttachmentStrategy *attachmentStrategy() override - { - return AttachmentStrategy::create(mAttachmentStrategy); - } - bool autoImportKeys() const override { return true; } bool showEmoticons() const override { return false; } bool showExpandQuotesMark() const override { return false; } const BodyPartFormatterFactory *bodyPartFormatterFactory() override { return &mBodyPartFormatterFactory; } bool decryptMessage() const override { return mDecryptMessage; } void setAllowDecryption(bool allowDecryption) { mDecryptMessage = allowDecryption; } void setShowSignatureDetails(bool showSignatureDetails) { mShowSignatureDetails = showSignatureDetails; } bool showSignatureDetails() const override { return mShowSignatureDetails; } void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) override { Q_UNUSED(mode); Q_UNUSED(availableModes); } MimeTreeParser::Util::HtmlMode preferredMode() const override { return mPreferredMode; } void setPreferredMode(MimeTreeParser::Util::HtmlMode mode) { mPreferredMode = mode; } int levelQuote() const override { return 1; } const QTextCodec *overrideCodec() override { return nullptr; } QString createMessageHeader(KMime::Message *message) override { Q_UNUSED(message); return QString(); //do nothing } void render(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter, bool showOnlyOneMimePart) override { Q_UNUSED(msgPart); Q_UNUSED(htmlWriter); Q_UNUSED(showOnlyOneMimePart); } bool isPrinting() const override { return false; } private: MimeTreeParser::HtmlWriter *mWriter = nullptr; QString mAttachmentStrategy; BodyPartFormatterFactory mBodyPartFormatterFactory; MimeTreeParser::Util::HtmlMode mPreferredMode; bool mHtmlLoadExternal = false; bool mDecryptMessage = false; bool mShowSignatureDetails = false; }; } } #endif diff --git a/mimetreeparser/src/CMakeLists.txt b/mimetreeparser/src/CMakeLists.txt index 52ff33a5..02b751e6 100644 --- a/mimetreeparser/src/CMakeLists.txt +++ b/mimetreeparser/src/CMakeLists.txt @@ -1,176 +1,174 @@ add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_definitions(-DTRANSLATION_DOMAIN=\"libmimetreeparser\") # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) set(libmimetreeparser_main_SRCS bodyformatter/applicationpgpencrypted.cpp bodyformatter/applicationpkcs7mime.cpp bodyformatter/encrypted.cpp bodyformatter/mailman.cpp bodyformatter/multipartalternative.cpp bodyformatter/multipartencrypted.cpp bodyformatter/multipartmixed.cpp bodyformatter/multipartsigned.cpp bodyformatter/textplain.cpp bodyformatter/texthtml.cpp bodyformatter/utils.cpp interfaces/bodypartformatter.cpp interfaces/objecttreesource.cpp interfaces/bodypart.cpp interfaces/htmlwriter.cpp job/qgpgmejobexecutor.cpp utils/util.cpp - attachmentstrategy.cpp bodypartformatter.cpp bodypartformatterfactory.cpp cryptohelper.cpp nodehelper.cpp objecttreeparser.cpp messagepart.cpp partnodebodypart.cpp memento/cryptobodypartmemento.cpp memento/decryptverifybodypartmemento.cpp memento/verifydetachedbodypartmemento.cpp memento/verifyopaquebodypartmemento.cpp ) set(libmimetreeparser_extra_SRCS #HTML Writer htmlwriter/bufferedhtmlwriter.cpp htmlwriter/filehtmlwriter.cpp ) set(mimetreeparser_temporaryfile_SRCS temporaryfile/attachmenttemporaryfilesdirs.cpp ) ecm_generate_headers(MimeTreeParser_Camelcasemain_HEADERS HEADER_NAMES - AttachmentStrategy BodyPartFormatterFactory Enums MessagePart NodeHelper ObjectTreeParser PartMetaData PartNodeBodyPart REQUIRED_HEADERS MimeTreeParser_main_HEADERS PREFIX MimeTreeParser ) ecm_generate_headers(MimeTreeParser_Camelcaseutils_HEADERS HEADER_NAMES Util REQUIRED_HEADERS MimeTreeParser_utils_HEADERS PREFIX MimeTreeParser RELATIVE utils ) ecm_generate_headers(MimeTreeParser_Camelcaseinterfaces_HEADERS HEADER_NAMES BodyPartFormatter BodyPart HtmlWriter ObjectTreeSource REQUIRED_HEADERS MimeTreeParser_interfaces_HEADERS PREFIX MimeTreeParser RELATIVE interfaces ) ecm_generate_headers(MimeTreeParser_Camelcasehtmlwriter_HEADERS HEADER_NAMES BufferedHtmlWriter FileHtmlWriter REQUIRED_HEADERS MimeTreeParser_htmlwriter_HEADERS PREFIX MimeTreeParser RELATIVE htmlwriter ) ecm_generate_headers(MimeTreeParser_Camelcasetemporaryfile_HEADERS HEADER_NAMES AttachmentTemporaryFilesDirs REQUIRED_HEADERS MimeTreeParser_temporaryfile_HEADERS PREFIX MimeTreeParser RELATIVE temporaryfile ) install(FILES ${MimeTreeParser_Camelcasehtmlwriter_HEADERS} ${MimeTreeParser_Camelcaseutils_HEADERS} ${MimeTreeParser_Camelcaseinterfaces_HEADERS} ${MimeTreeParser_Camelcasemain_HEADERS} ${MimeTreeParser_Camelcasetemporaryfile_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MimeTreeParser COMPONENT Devel ) install(FILES ${MimeTreeParser_htmlwriter_HEADERS} ${MimeTreeParser_utils_HEADERS} ${MimeTreeParser_interfaces_HEADERS} ${MimeTreeParser_main_HEADERS} ${MimeTreeParser_temporaryfile_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_export.h ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_debug.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/mimetreeparser COMPONENT Devel ) ecm_generate_pri_file(BASE_NAME MimeTreeParser LIB_NAME KF5MimeTreeParser FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MimeTreeParser ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR} ) set(libmimetreeparser_SRCS ${libmimetreeparser_main_SRCS} ${libmimetreeparser_extra_SRCS} ${mimetreeparser_temporaryfile_SRCS} ) ecm_qt_declare_logging_category(libmimetreeparser_SRCS HEADER mimetreeparser_debug.h IDENTIFIER MIMETREEPARSER_LOG CATEGORY_NAME org.kde.pim.mimetreeparser) add_library(KF5MimeTreeParser ${libmimetreeparser_SRCS} ) generate_export_header(KF5MimeTreeParser BASE_NAME mimetreeparser) add_library(KF5::MimeTreeParser ALIAS KF5MimeTreeParser) set(mimetreeparser_LINK_LIBRARIES ) target_link_libraries(KF5MimeTreeParser PRIVATE QGpgme KF5::Codecs KF5::I18n KF5::CoreAddons KF5::Mime Qt5::Gui ) install(TARGETS KF5MimeTreeParser EXPORT KF5MimeTreeParserTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) set_target_properties(KF5MimeTreeParser PROPERTIES VERSION ${MIMETREEPARSER_VERSION_STRING} SOVERSION ${MIMETREEPARSER_SOVERSION} EXPORT_NAME MimeTreeParser ) target_include_directories(KF5MimeTreeParser INTERFACE "$") diff --git a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp index 650aa638..5d1889cf 100644 --- a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp +++ b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp @@ -1,170 +1,169 @@ /* Copyright (c) 2016 Sandro Knauß 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 "applicationpkcs7mime.h" #include "utils.h" -#include "attachmentstrategy.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include #include #include "mimetreeparser_debug.h" using namespace MimeTreeParser; const ApplicationPkcs7MimeBodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::self; const Interface::BodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::create() { if (!self) { self = new ApplicationPkcs7MimeBodyPartFormatter(); } return self; } MessagePart::Ptr ApplicationPkcs7MimeBodyPartFormatter::process(Interface::BodyPart &part) const { KMime::Content *node = part.content(); if (node->head().isEmpty()) { return MessagePart::Ptr(); } const auto smimeCrypto = QGpgME::smime(); if (!smimeCrypto) { return MessagePart::Ptr(); } // we are also registered for octet-stream, in that case stop here if that's not a part for us const auto mt = node->contentType()->mimeType(); const auto isCorrectMimeType = mt == QByteArrayLiteral("application/pkcs7-mime") || mt == QByteArrayLiteral("application/x-pkcs7-mime"); const auto hasCorrectName = mt == QByteArrayLiteral("application/octet-stream") && (node->contentType()->name().endsWith(QLatin1String("p7m")) || node->contentType()->name().endsWith(QLatin1String("p7s")) || node->contentType()->name().endsWith(QLatin1String("p7c"))); if (!isCorrectMimeType && !hasCorrectName) { return {}; } const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower(); if (smimeType == QLatin1String("certs-only")) { part.processResult()->setNeverDisplayInline(true); CertMessagePart::Ptr mp(new CertMessagePart(part.objectTreeParser(), node, smimeCrypto, part.source()->autoImportKeys())); return mp; } bool isSigned = (smimeType == QLatin1String("signed-data")); bool isEncrypted = (smimeType == QLatin1String("enveloped-data")); // Analyze "signTestNode" node to find/verify a signature. // If zero part.objectTreeParser() verification was successfully done after // decrypting via recursion by insertAndParseNewChildNode(). KMime::Content *signTestNode = isEncrypted ? nullptr : node; // We try decrypting the content // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. MessagePart::Ptr mp; if (!isSigned) { if (isEncrypted) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?"; } auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(part.objectTreeParser(), node->decodedText(), smimeCrypto, part.nodeHelper()->fromAsString(node), node)); mp = _mp; _mp->setIsEncrypted(true); _mp->setDecryptMessage(part.source()->decryptMessage()); PartMetaData *messagePart(_mp->partMetaData()); if (!part.source()->decryptMessage()) { isEncrypted = true; signTestNode = nullptr; // PENDING(marc) to be abs. sure, we'd need to have to look at the content } else { _mp->startDecryption(); if (messagePart->isDecryptable) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !"; isEncrypted = true; part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); signTestNode = nullptr; } else { // decryption failed, which could be because the part was encrypted but // decryption failed, or because we didn't know if it was encrypted, tried, // and failed. If the message was not actually encrypted, we continue // assuming it's signed if (_mp->passphraseError() || (smimeType.isEmpty() && messagePart->isEncrypted)) { isEncrypted = true; signTestNode = nullptr; } if (isEncrypted) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO encryption found"; } } } if (isEncrypted) { part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); } } // We now try signature verification if necessarry. if (signTestNode) { if (isSigned) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data"; } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - opaque signed data ?"; } const QTextCodec *aCodec(part.objectTreeParser()->codecFor(signTestNode)); const QByteArray signaturetext = signTestNode->decodedContent(); auto _mp = SignedMessagePart::Ptr(new SignedMessagePart(part.objectTreeParser(), aCodec->toUnicode(signaturetext), smimeCrypto, part.nodeHelper()->fromAsString(node), signTestNode)); mp = _mp; _mp->startVerificationDetached(signaturetext, nullptr, QByteArray()); if (_mp->isSigned()) { if (!isSigned) { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - signature found - opaque signed data !"; } if (signTestNode != node) { part.nodeHelper()->setSignatureState(node, KMMsgFullySigned); } } else { qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO signature found :-("; } } return mp; } diff --git a/mimetreeparser/src/bodyformatter/texthtml.cpp b/mimetreeparser/src/bodyformatter/texthtml.cpp index aeb78ca1..4409ac03 100644 --- a/mimetreeparser/src/bodyformatter/texthtml.cpp +++ b/mimetreeparser/src/bodyformatter/texthtml.cpp @@ -1,47 +1,46 @@ /* Copyright (c) 2016 Sandro Knauß 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 "texthtml.h" -#include "attachmentstrategy.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include "mimetreeparser_debug.h" using namespace MimeTreeParser; const TextHtmlBodyPartFormatter *TextHtmlBodyPartFormatter::self; const Interface::BodyPartFormatter *TextHtmlBodyPartFormatter::create() { if (!self) { self = new TextHtmlBodyPartFormatter(); } return self; } MessagePart::Ptr TextHtmlBodyPartFormatter::process(Interface::BodyPart &part) const { KMime::Content *node = part.content(); HtmlMessagePart::Ptr mp(new HtmlMessagePart(part.objectTreeParser(), node, part.source())); return mp; } diff --git a/mimetreeparser/src/bodyformatter/textplain.cpp b/mimetreeparser/src/bodyformatter/textplain.cpp index fe9059cb..d58fae22 100644 --- a/mimetreeparser/src/bodyformatter/textplain.cpp +++ b/mimetreeparser/src/bodyformatter/textplain.cpp @@ -1,65 +1,64 @@ /* Copyright (c) 2016 Sandro Knauß 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 "textplain.h" -#include "attachmentstrategy.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include "mimetreeparser_debug.h" using namespace MimeTreeParser; const TextPlainBodyPartFormatter *TextPlainBodyPartFormatter::self; const Interface::BodyPartFormatter *TextPlainBodyPartFormatter::create() { if (!self) { self = new TextPlainBodyPartFormatter(); } return self; } MessagePart::Ptr TextPlainBodyPartFormatter::process(Interface::BodyPart &part) const { KMime::Content *node = part.content(); const bool isFirstTextPart = (node->topLevel()->textContent() == node); QString label = NodeHelper::fileName(node); const bool bDrawFrame = !isFirstTextPart && !label.isEmpty(); TextMessagePart::Ptr mp; if (isFirstTextPart) { mp = TextMessagePart::Ptr(new TextMessagePart(part.objectTreeParser(), node, bDrawFrame, part.source()->decryptMessage())); } else { mp = TextMessagePart::Ptr(new AttachmentMessagePart(part.objectTreeParser(), node, bDrawFrame, part.source()->decryptMessage())); } part.processResult()->setInlineSignatureState(mp->signatureState()); part.processResult()->setInlineEncryptionState(mp->encryptionState()); part.nodeHelper()->setNodeDisplayedEmbedded(node, true); return mp; } diff --git a/mimetreeparser/src/bodypartformatter.cpp b/mimetreeparser/src/bodypartformatter.cpp index b9ee426d..8a01d810 100644 --- a/mimetreeparser/src/bodypartformatter.cpp +++ b/mimetreeparser/src/bodypartformatter.cpp @@ -1,189 +1,188 @@ /* -*- c++ -*- bodypartformatter.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "mimetreeparser_debug.h" #include "bodyformatter/applicationpgpencrypted.h" #include "bodyformatter/applicationpkcs7mime.h" #include "bodyformatter/encrypted.h" #include "bodyformatter/mailman.h" #include "bodyformatter/multipartalternative.h" #include "bodyformatter/multipartmixed.h" #include "bodyformatter/multipartencrypted.h" #include "bodyformatter/multipartsigned.h" #include "bodyformatter/texthtml.h" #include "bodyformatter/textplain.h" #include "interfaces/bodypartformatter.h" #include "interfaces/bodypart.h" #include "interfaces/htmlwriter.h" #include "bodypartformatterfactory.h" #include "bodypartformatterfactory_p.h" -#include "attachmentstrategy.h" #include "objecttreeparser.h" #include "messagepart.h" #include #include using namespace MimeTreeParser; namespace { class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const AnyTypeBodyPartFormatter *self; public: MessagePart::Ptr process(Interface::BodyPart &part) const override { KMime::Content *node = part.content(); const auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(part.objectTreeParser(), node, false, part.source()->decryptMessage())); part.processResult()->setInlineSignatureState(mp->signatureState()); part.processResult()->setInlineEncryptionState(mp->encryptionState()); part.processResult()->setNeverDisplayInline(true); mp->setNeverDisplayInline(true); mp->setIsImage(false); return mp; } static const MimeTreeParser::Interface::BodyPartFormatter *create() { if (!self) { self = new AnyTypeBodyPartFormatter(); } return self; } }; const AnyTypeBodyPartFormatter *AnyTypeBodyPartFormatter::self = nullptr; class ImageTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const ImageTypeBodyPartFormatter *self; public: static const MimeTreeParser::Interface::BodyPartFormatter *create() { if (!self) { self = new ImageTypeBodyPartFormatter(); } return self; } MessagePart::Ptr process(Interface::BodyPart &part) const override { KMime::Content *node = part.content(); auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(part.objectTreeParser(), node, false, part.source()->decryptMessage())); mp->setIsImage(true); part.processResult()->setInlineSignatureState(mp->signatureState()); part.processResult()->setInlineEncryptionState(mp->encryptionState()); auto preferredMode = part.source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); if (node->parent() && node->parent()->contentType()->subType() == "related" && isHtmlPreferred) { QString fileName = mp->temporaryFilePath(); QString href = QUrl::fromLocalFile(fileName).url(); QByteArray cid = node->contentID()->identifier(); if (part.objectTreeParser()->htmlWriter()) { part.objectTreeParser()->htmlWriter()->embedPart(cid, href); } part.nodeHelper()->setNodeDisplayedEmbedded(node, true); part.nodeHelper()->setNodeDisplayedHidden(node, true); return mp; } // Show it inline if showOnlyOneMimePart(), which means the user clicked the image // in the message structure viewer manually, and therefore wants to see the full image if (false && !part.processResult()->neverDisplayInline()) { part.nodeHelper()->setNodeDisplayedEmbedded(node, true); } return mp; } }; const ImageTypeBodyPartFormatter *ImageTypeBodyPartFormatter::self = nullptr; class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { static const MessageRfc822BodyPartFormatter *self; public: MessagePart::Ptr process(Interface::BodyPart &) const override; static const MimeTreeParser::Interface::BodyPartFormatter *create(); }; const MessageRfc822BodyPartFormatter *MessageRfc822BodyPartFormatter::self; const MimeTreeParser::Interface::BodyPartFormatter *MessageRfc822BodyPartFormatter::create() { if (!self) { self = new MessageRfc822BodyPartFormatter(); } return self; } MessagePart::Ptr MessageRfc822BodyPartFormatter::process(Interface::BodyPart &part) const { const KMime::Message::Ptr message = part.content()->bodyAsMessage(); return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(part.objectTreeParser(), part.content(), message)); } } // anon namespace void BodyPartFormatterFactoryPrivate::messageviewer_create_builtin_bodypart_formatters() { insert(QStringLiteral("application/pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create()); insert(QStringLiteral("application/x-pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create()); insert(QStringLiteral("application/pgp-encrypted"), ApplicationPGPEncryptedBodyPartFormatter::create()); insert(QStringLiteral("application/octet-stream"), ApplicationPkcs7MimeBodyPartFormatter::create()); insert(QStringLiteral("application/octet-stream"), AnyTypeBodyPartFormatter::create()); insert(QStringLiteral("text/pgp"), EncryptedBodyPartFormatter::create()); insert(QStringLiteral("text/html"), TextHtmlBodyPartFormatter::create()); insert(QStringLiteral("text/rtf"), AnyTypeBodyPartFormatter::create()); insert(QStringLiteral("text/plain"), MailmanBodyPartFormatter::create()); insert(QStringLiteral("text/plain"), TextPlainBodyPartFormatter::create()); insert(QStringLiteral("image/png"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("image/jpeg"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("image/gif"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("image/svg+xml"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("image/bmp"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("image/vnd.microsoft.icon"), ImageTypeBodyPartFormatter::create()); insert(QStringLiteral("message/rfc822"), MessageRfc822BodyPartFormatter::create()); insert(QStringLiteral("multipart/alternative"), MultiPartAlternativeBodyPartFormatter::create()); insert(QStringLiteral("multipart/encrypted"), MultiPartEncryptedBodyPartFormatter::create()); insert(QStringLiteral("multipart/signed"), MultiPartSignedBodyPartFormatter::create()); insert(QStringLiteral("multipart/mixed"), MultiPartMixedBodyPartFormatter::create()); } diff --git a/mimetreeparser/src/interfaces/objecttreesource.h b/mimetreeparser/src/interfaces/objecttreesource.h index 625ade2a..f0fcfe3c 100644 --- a/mimetreeparser/src/interfaces/objecttreesource.h +++ b/mimetreeparser/src/interfaces/objecttreesource.h @@ -1,101 +1,97 @@ /* 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_OBJECTTREESOURCE_IF_H__ #define __MIMETREEPARSER_OBJECTTREESOURCE_IF_H__ #include "mimetreeparser_export.h" #include #include #include class QTextCodec; namespace MimeTreeParser { class HtmlWriter; -class AttachmentStrategy; class BodyPartFormatterFactory; class MessagePart; typedef QSharedPointer MessagePartPtr; } namespace MimeTreeParser { namespace Interface { /** * Interface for object tree sources. * @author Andras Mantia */ class MIMETREEPARSER_EXPORT ObjectTreeSource { public: ObjectTreeSource(); virtual ~ObjectTreeSource(); /** * Sets the type of mail that is currently displayed. Applications can display this * information to the user, for example KMail displays a HTML status bar. * Note: This is not called when the mode is "Normal". */ virtual void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList &availableModes) = 0; /** Return the mode that is the preferred to display */ virtual MimeTreeParser::Util::HtmlMode preferredMode() const = 0; /** Return true if an encrypted mail should be decrypted */ virtual bool decryptMessage() const = 0; /** Return true if external sources should be loaded in a html mail */ virtual bool htmlLoadExternal() const = 0; /** Return true to include the signature details in the generated html */ virtual bool showSignatureDetails() const = 0; virtual int levelQuote() const = 0; /** The override codec that should be used for the mail */ virtual const QTextCodec *overrideCodec() = 0; virtual QString createMessageHeader(KMime::Message *message) = 0; - /** Return the wanted attachment startegy */ - virtual const AttachmentStrategy *attachmentStrategy() = 0; - /** Return the html write object */ virtual HtmlWriter *htmlWriter() = 0; /** should keys be imported automatically **/ virtual bool autoImportKeys() const = 0; virtual bool showEmoticons() const = 0; virtual bool showExpandQuotesMark() const = 0; virtual const BodyPartFormatterFactory *bodyPartFormatterFactory() = 0; virtual void render(const MessagePartPtr &msgPart, HtmlWriter *htmlWriter, bool showOnlyOneMimePart) = 0; virtual bool isPrinting() const = 0; private: Q_DISABLE_COPY(ObjectTreeSource) }; } } #endif diff --git a/mimetreeparser/src/messagepart.cpp b/mimetreeparser/src/messagepart.cpp index f3933217..e4ef633b 100644 --- a/mimetreeparser/src/messagepart.cpp +++ b/mimetreeparser/src/messagepart.cpp @@ -1,1311 +1,1310 @@ /* Copyright (c) 2015 Sandro Knauß 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 "messagepart.h" #include "mimetreeparser_debug.h" -#include "attachmentstrategy.h" #include "cryptohelper.h" #include "objecttreeparser.h" #include "job/qgpgmejobexecutor.h" #include "memento/cryptobodypartmemento.h" #include "memento/decryptverifybodypartmemento.h" #include "memento/verifydetachedbodypartmemento.h" #include "memento/verifyopaquebodypartmemento.h" #include "bodyformatter/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; //------MessagePart----------------------- namespace MimeTreeParser { class MessagePartPrivate { public: MessagePart *mParentPart = nullptr; QVector mBlocks; KMime::Content *mNode = nullptr; KMime::Content *mAttachmentNode = nullptr; QString mText; PartMetaData mMetaData; bool mRoot = false; }; } MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text) : mOtp(otp) , d(new MessagePartPrivate) { d->mText = text; } MessagePart::~MessagePart() = default; MessagePart *MessagePart::parentPart() const { return d->mParentPart; } void MessagePart::setParentPart(MessagePart *parentPart) { d->mParentPart = parentPart; } QString MessagePart::htmlContent() const { return text(); } QString MessagePart::plaintextContent() const { return text(); } PartMetaData *MessagePart::partMetaData() const { return &d->mMetaData; } Interface::BodyPartMemento *MessagePart::memento() const { return nodeHelper()->bodyPartMemento(content(), "__plugin__"); } void MessagePart::setMemento(Interface::BodyPartMemento *memento) { nodeHelper()->setBodyPartMemento(content(), "__plugin__", memento); } KMime::Content *MessagePart::content() const { return d->mNode; } void MessagePart::setContent(KMime::Content *node) { d->mNode = node; } KMime::Content *MessagePart::attachmentContent() const { return d->mAttachmentNode; } void MessagePart::setAttachmentContent(KMime::Content *node) { d->mAttachmentNode = node; } bool MessagePart::isAttachment() const { return d->mAttachmentNode; } QString MessagePart::attachmentIndex() const { return attachmentContent()->index().toString(); } QString MessagePart::attachmentLink() const { return mOtp->nodeHelper()->asHREF(content(), QStringLiteral("body")); } QString MessagePart::makeLink(const QString &path) const { // FIXME: use a PRNG for the first arg, instead of a serial number static int serial = 0; if (path.isEmpty()) { return {}; } return QStringLiteral("x-kmail:/bodypart/%1/%2/%3") .arg(serial++).arg(content()->index().toString()) .arg(QString::fromLatin1(QUrl::toPercentEncoding(path, "/"))); } void MessagePart::setIsRoot(bool root) { d->mRoot = root; } bool MessagePart::isRoot() const { return d->mRoot; } QString MessagePart::text() const { return d->mText; } void MessagePart::setText(const QString &text) { d->mText = text; } bool MessagePart::isHtml() const { return false; } Interface::ObjectTreeSource *MessagePart::source() const { Q_ASSERT(mOtp); return mOtp->mSource; } NodeHelper *MessagePart::nodeHelper() const { Q_ASSERT(mOtp); return mOtp->nodeHelper(); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); d->mRoot = subMessagePart->isRoot(); foreach (const auto &part, subMessagePart->subParts()) { appendSubPart(part); } } QString MessagePart::renderInternalText() const { QString text; foreach (const auto &mp, subParts()) { text += mp->text(); } return text; } void MessagePart::fix() const { foreach (const auto &mp, subParts()) { const auto m = mp.dynamicCast(); if (m) { m->fix(); } } } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); d->mBlocks.append(messagePart); } const QVector &MessagePart::subParts() const { return d->mBlocks; } bool MessagePart::hasSubParts() const { return !d->mBlocks.isEmpty(); } void MessagePart::clearSubParts() { d->mBlocks.clear(); } //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp) : MessagePart(otp, QString()) { } MessagePartList::~MessagePartList() { } QString MessagePartList::text() const { return renderInternalText(); } QString MessagePartList::plaintextContent() const { return QString(); } QString MessagePartList::htmlContent() const { return QString(); } //-----TextMessageBlock---------------------- TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage) : MessagePartList(otp) , mDrawFrame(drawFrame) , mDecryptMessage(decryptMessage) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); parseContent(); } TextMessagePart::~TextMessagePart() { } bool TextMessagePart::decryptMessage() const { return mDecryptMessage; } void TextMessagePart::parseContent() { const auto aCodec = mOtp->codecFor(content()); const QString &fromAddress = mOtp->nodeHelper()->fromAsString(content()); mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; const auto blocks = prepareMessageForDecryption(content()->decodedContent()); const auto cryptProto = QGpgME::openpgp(); if (!blocks.isEmpty()) { /* The (overall) signature/encrypted status is broken * if one unencrypted part is at the beginning or in the middle * because mailmain adds an unencrypted part at the end this should not break the overall status * * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than * the status is set to unencryped */ bool fullySignedOrEncrypted = true; bool fullySignedOrEncryptedTmp = true; for (const auto &block : blocks) { if (!fullySignedOrEncryptedTmp) { fullySignedOrEncrypted = false; } if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { fullySignedOrEncryptedTmp = false; appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text())))); } else if (block.type() == PgpMessageBlock) { EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); mp->setDecryptMessage(decryptMessage()); mp->setIsEncrypted(true); appendSubPart(mp); if (!decryptMessage()) { continue; } mp->startDecryption(block.text(), aCodec); if (mp->partMetaData()->inProgress) { continue; } } else if (block.type() == ClearsignedBlock) { SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); appendSubPart(mp); mp->startVerification(block.text(), aCodec); } else { continue; } const auto mp = subParts().last().staticCast(); const PartMetaData *messagePart(mp->partMetaData()); if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { mp->setText(aCodec->toUnicode(block.text())); } if (messagePart->isEncrypted) { mEncryptionState = KMMsgPartiallyEncrypted; } if (messagePart->isSigned) { mSignatureState = KMMsgPartiallySigned; } } //Do we have an fully Signed/Encrypted Message? if (fullySignedOrEncrypted) { if (mSignatureState == KMMsgPartiallySigned) { mSignatureState = KMMsgFullySigned; } if (mEncryptionState == KMMsgPartiallyEncrypted) { mEncryptionState = KMMsgFullyEncrypted; } } } } KMMsgEncryptionState TextMessagePart::encryptionState() const { return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { return mSignatureState; } bool TextMessagePart::showLink() const { return !temporaryFilePath().isEmpty(); } bool TextMessagePart::showTextFrame() const { return mDrawFrame; } void TextMessagePart::setShowTextFrame(bool showFrame) { mDrawFrame = showFrame; } QString TextMessagePart::label() const { const QString name = content()->contentType()->name(); QString label = name.isEmpty() ? NodeHelper::fileName(content()) : name; if (label.isEmpty()) { label = i18nc("display name for an unnamed attachment", "Unnamed"); } return label; } QString TextMessagePart::comment() const { const QString comment = content()->contentDescription()->asUnicodeString(); if (comment == label()) { return {}; } return comment; } QString TextMessagePart::temporaryFilePath() const { return nodeHelper()->writeNodeToTempFile(content()); } //-----AttachmentMessageBlock---------------------- AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage) : TextMessagePart(otp, node, drawFrame, decryptMessage) , mIsImage(false) , mNeverDisplayInline(false) { } AttachmentMessagePart::~AttachmentMessagePart() { } bool AttachmentMessagePart::neverDisplayInline() const { return mNeverDisplayInline; } void AttachmentMessagePart::setNeverDisplayInline(bool displayInline) { mNeverDisplayInline = displayInline; } bool AttachmentMessagePart::isImage() const { return mIsImage; } void AttachmentMessagePart::setIsImage(bool image) { mIsImage = image; } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source) : MessagePart(otp, QString()) , mSource(source) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); const QByteArray partBody(node->decodedContent()); mBodyHTML = mOtp->codecFor(node)->toUnicode(partBody); mCharset = NodeHelper::charset(node); } HtmlMessagePart::~HtmlMessagePart() { } void HtmlMessagePart::fix() const { mOtp->mHtmlContent += mBodyHTML; mOtp->mHtmlContentCharset = mCharset; } QString HtmlMessagePart::text() const { return mBodyHTML; } bool HtmlMessagePart::isHtml() const { return true; } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString()) , mOnlyOneMimePart(onlyOneMimePart) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); parseInternal(node, mOnlyOneMimePart); } MimeMessagePart::~MimeMessagePart() { } QString MimeMessagePart::text() const { return renderInternalText(); } QString MimeMessagePart::plaintextContent() const { return QString(); } QString MimeMessagePart::htmlContent() const { return QString(); } //-----AlternativeMessagePart---------------------- AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode) : MessagePart(otp, QString()) , mPreferredMode(preferredMode) { setContent(node); KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar"); KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html"); KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain"); if (!dataHtml) { // If we didn't find the HTML part as the first child of the multipart/alternative, it might // be that this is a HTML message with images, and text/plain and multipart/related are the // immediate children of this multipart/alternative node. // In this case, the HTML node is a child of multipart/related. dataHtml = findTypeInDirectChilds(node, "multipart/related"); // Still not found? Stupid apple mail actually puts the attachments inside of the // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed // here. // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden // when displaying plain text. if (!dataHtml) { dataHtml = findTypeInDirectChilds(node, "multipart/mixed"); } } if (dataIcal) { mChildNodes[Util::MultipartIcal] = dataIcal; } if (dataText) { mChildNodes[Util::MultipartPlain] = dataText; } if (dataHtml) { mChildNodes[Util::MultipartHtml] = dataHtml; } if (mChildNodes.isEmpty()) { qCWarning(MIMETREEPARSER_LOG) << "no valid nodes"; return; } QMapIterator i(mChildNodes); while (i.hasNext()) { i.next(); mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true)); } } AlternativeMessagePart::~AlternativeMessagePart() { } Util::HtmlMode AlternativeMessagePart::preferredMode() const { return mPreferredMode; } QList AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(Util::MultipartPlain)) { return mChildParts[Util::MultipartPlain]->text(); } return QString(); } void AlternativeMessagePart::fix() const { if (mChildParts.contains(Util::MultipartPlain)) { mChildParts[Util::MultipartPlain]->fix(); } const auto mode = preferredMode(); if (mode != Util::MultipartPlain && mChildParts.contains(mode)) { mChildParts[mode]->fix(); } } bool AlternativeMessagePart::isHtml() const { return mChildParts.contains(Util::MultipartHtml); } QString AlternativeMessagePart::plaintextContent() const { return text(); } QString AlternativeMessagePart::htmlContent() const { if (mChildParts.contains(Util::MultipartHtml)) { return mChildParts[Util::MultipartHtml]->text(); } else { return plaintextContent(); } } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport) : MessagePart(otp, QString()) , mAutoImport(autoImport) , mCryptoProto(cryptoProto) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); if (!mAutoImport) { return; } const QByteArray certData = node->decodedContent(); QGpgME::ImportJob *import = mCryptoProto->importJob(); QGpgMEJobExecutor executor; mImportResult = executor.exec(import, certData); } CertMessagePart::~CertMessagePart() { } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = true; partMetaData()->isGoodSignature = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { partMetaData()->isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return partMetaData()->isSigned; } bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode) { NodeHelper *nodeHelper = mOtp->nodeHelper(); partMetaData()->isSigned = false; partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; const QByteArray mementoName = "verification"; CryptoBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(content(), mementoName)); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { if (!signature.isEmpty()) { QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob(); if (job) { m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data); } } else { QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob(); if (job) { m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data); } } if (m) { if (mOtp->allowAsync()) { QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (m->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } } else { m->exec(); } nodeHelper->setBodyPartMemento(content(), mementoName, m); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { partMetaData()->inProgress = false; mOtp->mHasPendingAsyncJobs = false; } if (m && !partMetaData()->inProgress) { if (!signature.isEmpty()) { mVerifiedText = data; } setVerificationResult(m, textNode); } if (!m && !partMetaData()->inProgress) { QString errorMsg; QString cryptPlugLibName; QString cryptPlugDisplayName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); cryptPlugDisplayName = mCryptoProto->displayName(); } if (!mCryptoProto) { if (cryptPlugDisplayName.isEmpty()) { errorMsg = i18n("No appropriate crypto plug-in was found."); } else { errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName); } } else { errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName); } partMetaData()->errorText = i18n("The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1", errorMsg); } return partMetaData()->isSigned; } static int signatureToStatus(const GpgME::Signature &sig) { switch (sig.status().code()) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } } QString prettifyDN(const char *uid) { return QGpgME::DN(uid).prettyDN(); } void SignedMessagePart::sigStatusToMetaData() { GpgME::Key key; if (partMetaData()->isSigned) { GpgME::Signature signature = mSignatures.front(); partMetaData()->status_code = signatureToStatus(signature); partMetaData()->isGoodSignature = partMetaData()->status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags partMetaData()->sigSummary = signature.summary(); if (partMetaData()->isGoodSignature && !key.keyID()) { // Search for the key by its fingerprint so that we can check for // trust etc. QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } delete job; } } if (key.keyID()) { partMetaData()->keyId = key.keyID(); } if (partMetaData()->keyId.isEmpty()) { partMetaData()->keyId = signature.fingerprint(); } partMetaData()->keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { partMetaData()->signer = prettifyDN(key.userID(0).id()); } for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) { // The following if /should/ always result in TRUE but we // won't trust implicitely the plugin that gave us these data. if (key.userID(iMail).email()) { QString email = QString::fromUtf8(key.userID(iMail).email()); // ### work around gpgme 0.3.QString text() const override;x / cryptplug bug where the // ### email addresses are specified as angle-addr, not addr-spec: if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { email = email.mid(1, email.length() - 2); } if (!email.isEmpty()) { partMetaData()->signerMailAddresses.append(email); } } } if (signature.creationTime()) { partMetaData()->creationTime.setTime_t(signature.creationTime()); } else { partMetaData()->creationTime = QDateTime(); } if (partMetaData()->signer.isEmpty()) { if (key.numUserIDs() > 0 && key.userID(0).name()) { partMetaData()->signer = prettifyDN(key.userID(0).name()); } if (!partMetaData()->signerMailAddresses.empty()) { if (partMetaData()->signer.isEmpty()) { partMetaData()->signer = partMetaData()->signerMailAddresses.front(); } else { partMetaData()->signer += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); } } } } } void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec) { startVerificationDetached(text, nullptr, QByteArray()); if (!content() && partMetaData()->isSigned) { setText(aCodec->toUnicode(mVerifiedText)); } } void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; if (textNode) { parseInternal(textNode, false); } okVerify(text, signature, textNode); if (!partMetaData()->isSigned) { partMetaData()->creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode) { { const auto vm = dynamic_cast(m); if (vm) { mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } partMetaData()->auditLogError = m->auditLogError(); partMetaData()->auditLog = m->auditLogAsHtml(); partMetaData()->isSigned = !mSignatures.empty(); if (partMetaData()->isSigned) { sigStatusToMetaData(); if (content()) { mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned); if (!textNode) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (!mVerifiedText.isEmpty()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("signed data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } } } QString SignedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString SignedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } //-----CryptMessageBlock--------------------- EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mPassphraseError(false) , mNoSecKey(false) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mDecryptMessage(false) { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = false; partMetaData()->isGoodSignature = false; partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } EncryptedMessagePart::~EncryptedMessagePart() { } void EncryptedMessagePart::setDecryptMessage(bool decrypt) { mDecryptMessage = decrypt; } bool EncryptedMessagePart::decryptMessage() const { return mDecryptMessage; } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { partMetaData()->isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return partMetaData()->isEncrypted; } bool EncryptedMessagePart::isDecryptable() const { return partMetaData()->isDecryptable; } bool EncryptedMessagePart::passphraseError() const { return mPassphraseError; } void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); startDecryption(content); if (!partMetaData()->inProgress && partMetaData()->isDecryptable) { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { _mp->setText(aCodec->toUnicode(mDecryptedData)); } else { setText(aCodec->toUnicode(mDecryptedData)); } } else { setText(aCodec->toUnicode(mDecryptedData)); } } } bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) { mPassphraseError = false; partMetaData()->inProgress = false; partMetaData()->errorText.clear(); partMetaData()->auditLogError = GpgME::Error(); partMetaData()->auditLog.clear(); bool bDecryptionOk = false; bool cannotDecrypt = false; NodeHelper *nodeHelper = mOtp->nodeHelper(); Q_ASSERT(decryptMessage()); // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(&data, "decryptverify")); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob(); if (!job) { cannotDecrypt = true; } else { const QByteArray ciphertext = data.decodedContent(); DecryptVerifyBodyPartMemento *newM = new DecryptVerifyBodyPartMemento(job, ciphertext); if (mOtp->allowAsync()) { QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (newM->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } nodeHelper->setBodyPartMemento(&data, "decryptverify", newM); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; m = nullptr; } if (m) { const QByteArray &plainText = m->plainText(); const GpgME::DecryptionResult &decryptResult = m->decryptResult(); const GpgME::VerificationResult &verifyResult = m->verifyResult(); partMetaData()->isSigned = verifyResult.signatures().size() > 0; if (verifyResult.signatures().size() > 0) { auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content())); subPart->setVerificationResult(m, nullptr); appendSubPart(subPart); } mDecryptRecipients.clear(); bDecryptionOk = !decryptResult.error(); // std::stringstream ss; // ss << decryptResult << '\n' << verifyResult; // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); for (const auto &recipient : decryptResult.recipients()) { if (!recipient.status()) { bDecryptionOk = true; } GpgME::Key key; QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(recipient.keyID())), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << recipient.keyID(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << recipient.keyID(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << recipient.keyID(); } else { key = found_keys[0]; } } mDecryptRecipients.push_back(std::make_pair(recipient, key)); } if (!bDecryptionOk && partMetaData()->isSigned) { //Only a signed part partMetaData()->isEncrypted = false; bDecryptionOk = true; mDecryptedData = plainText; } else { mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { partMetaData()->keyId = decryptResult.recipient(0).keyID(); } if (bDecryptionOk) { mDecryptedData = plainText; } else { mNoSecKey = true; foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); } if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly mPassphraseError = true; } } } } if (!bDecryptionOk) { QString cryptPlugLibName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); } if (!mCryptoProto) { partMetaData()->errorText = i18n("No appropriate crypto plug-in was found."); } else if (cannotDecrypt) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); } else if (!passphraseError()) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("
") + i18n("Error: %1", partMetaData()->errorText); } } return bDecryptionOk; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { if (!content() && !data) { return; } if (!data) { data = content(); } partMetaData()->isEncrypted = true; bool bOkDecrypt = okDecryptMIME(*data); if (partMetaData()->inProgress) { return; } partMetaData()->isDecryptable = bOkDecrypt; if (!partMetaData()->isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } if (partMetaData()->isEncrypted && !decryptMessage()) { partMetaData()->isDecryptable = true; } if (content() && !partMetaData()->isSigned) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (decryptMessage()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("encrypted data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } QString EncryptedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::text() const { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { return _mp->text(); } else { return MessagePart::text(); } } else { return MessagePart::text(); } } EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) : MessagePart(otp, QString()) , mMessage(message) { setContent(node); partMetaData()->isEncrypted = false; partMetaData()->isSigned = false; partMetaData()->isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true); mOtp->nodeHelper()->setPartMetaData(node, *partMetaData()); if (!mMessage) { qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists, // since the user can click the link and expect to have normal attachment operations there. mOtp->nodeHelper()->writeNodeToTempFile(message.data()); parseInternal(message.data(), false); } EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() { } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } void EncapsulatedRfc822MessagePart::fix() const { } diff --git a/mimetreeparser/src/objecttreeparser.cpp b/mimetreeparser/src/objecttreeparser.cpp index 794a180c..3d3f1040 100644 --- a/mimetreeparser/src/objecttreeparser.cpp +++ b/mimetreeparser/src/objecttreeparser.cpp @@ -1,357 +1,356 @@ /* objecttreeparser.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2015 Sandro Knauß KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ // MessageViewer includes #include "objecttreeparser.h" -#include "attachmentstrategy.h" #include "bodypartformatterfactory.h" #include "nodehelper.h" #include "messagepart.h" #include "partnodebodypart.h" #include "mimetreeparser_debug.h" #include "bodyformatter/utils.h" #include "interfaces/bodypartformatter.h" #include "interfaces/htmlwriter.h" #include "utils/util.h" #include #include // KDE includes // Qt includes #include #include using namespace MimeTreeParser; ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser) : mSource(topLevelParser->mSource) , mNodeHelper(topLevelParser->mNodeHelper) , mHtmlWriter(topLevelParser->mHtmlWriter) , mTopLevelContent(topLevelParser->mTopLevelContent) , mHasPendingAsyncJobs(false) , mAllowAsync(topLevelParser->mAllowAsync) { init(); } ObjectTreeParser::ObjectTreeParser(Interface::ObjectTreeSource *source, MimeTreeParser::NodeHelper *nodeHelper) : mSource(source) , mNodeHelper(nodeHelper) , mHtmlWriter(nullptr) , mTopLevelContent(nullptr) , mHasPendingAsyncJobs(false) , mAllowAsync(false) { init(); } void ObjectTreeParser::init() { Q_ASSERT(mSource); if (!mNodeHelper) { mNodeHelper = new NodeHelper(); mDeleteNodeHelper = true; } else { mDeleteNodeHelper = false; } } ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other) : mSource(other.mSource) , mNodeHelper(other.nodeHelper()) , //TODO(Andras) hm, review what happens if mDeleteNodeHelper was true in the source mHtmlWriter(other.mHtmlWriter) , mTopLevelContent(other.mTopLevelContent) , mHasPendingAsyncJobs(other.hasPendingAsyncJobs()) , mAllowAsync(other.allowAsync()) , mDeleteNodeHelper(false) { } ObjectTreeParser::~ObjectTreeParser() { if (mDeleteNodeHelper) { delete mNodeHelper; mNodeHelper = nullptr; } } void ObjectTreeParser::setAllowAsync(bool allow) { Q_ASSERT(!mHasPendingAsyncJobs); mAllowAsync = allow; } bool ObjectTreeParser::allowAsync() const { return mAllowAsync; } bool ObjectTreeParser::hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } QString ObjectTreeParser::plainTextContent() const { return mPlainTextContent; } QString ObjectTreeParser::htmlContent() const { return mHtmlContent; } void ObjectTreeParser::copyContentFrom(const ObjectTreeParser *other) { mPlainTextContent += other->plainTextContent(); mHtmlContent += other->htmlContent(); if (!other->plainTextContentCharset().isEmpty()) { mPlainTextContentCharset = other->plainTextContentCharset(); } if (!other->htmlContentCharset().isEmpty()) { mHtmlContentCharset = other->htmlContentCharset(); } } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree(KMime::Content *node, bool parseOnlySingleNode) { mTopLevelContent = node; mParsedPart = parseObjectTreeInternal(node, parseOnlySingleNode); if (mParsedPart) { mParsedPart->fix(); if (auto mp = toplevelTextNode(mParsedPart)) { if (auto _mp = mp.dynamicCast()) { extractNodeInfos(_mp->content(), true); } else if (auto _mp = mp.dynamicCast()) { if (_mp->mChildNodes.contains(Util::MultipartPlain)) { extractNodeInfos(_mp->mChildNodes[Util::MultipartPlain], true); } } setPlainTextContent(mp->text()); } if (htmlWriter()) { mSource->render(mParsedPart, htmlWriter(), parseOnlySingleNode); } } } MessagePartPtr ObjectTreeParser::parsedPart() const { return mParsedPart; } MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mimeType) { const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(QString::fromUtf8(mimeType)); Q_ASSERT(!formatters.empty()); for (auto formatter : formatters) { PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper); mNodeHelper->setNodeDisplayedEmbedded(node, true); const MessagePart::Ptr result = formatter->process(part); if (!result) { continue; } result->setAttachmentContent(node); return result; } Q_UNREACHABLE(); return {}; } MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart) { if (!node) { return MessagePart::Ptr(); } // reset pending async jobs state (we'll rediscover pending jobs as we go) mHasPendingAsyncJobs = false; // reset "processed" flags for... if (onlyOneMimePart) { // ... this node and all descendants mNodeHelper->setNodeUnprocessed(node, false); if (!node->contents().isEmpty()) { mNodeHelper->setNodeUnprocessed(node, true); } } else if (!node->parent()) { // ...this node and all it's siblings and descendants mNodeHelper->setNodeUnprocessed(node, true); } const bool isRoot = node->isTopLevel(); auto parsedPart = MessagePart::Ptr(new MessagePartList(this)); parsedPart->setIsRoot(isRoot); KMime::Content *parent = node->parent(); auto contents = parent ? parent->contents() : KMime::Content::List(); if (contents.isEmpty()) { contents.append(node); } int i = contents.indexOf(node); if (i < 0) { return parsedPart; } else { for (; i < contents.size(); ++i) { node = contents.at(i); if (mNodeHelper->nodeProcessed(node)) { continue; } ProcessResult processResult(mNodeHelper); QByteArray mimeType("text/plain"); if (node->contentType(false) && !node->contentType()->mimeType().isEmpty()) { mimeType = node->contentType()->mimeType(); } // unfortunately there's many emails where we can't trust the attachment mimetype // so try to see if we can find something better if (mimeType == "application/octet-stream") { NodeHelper::magicSetType(node); mimeType = node->contentType()->mimeType(); } const auto mp = processType(node, processResult, mimeType); Q_ASSERT(mp); parsedPart->appendSubPart(mp); mNodeHelper->setNodeProcessed(node, false); // adjust signed/encrypted flags if inline PGP was found processResult.adjustCryptoStatesOfNode(node); if (onlyOneMimePart) { break; } } } return parsedPart; } KMMsgSignatureState ProcessResult::inlineSignatureState() const { return mInlineSignatureState; } void ProcessResult::setInlineSignatureState(KMMsgSignatureState state) { mInlineSignatureState = state; } KMMsgEncryptionState ProcessResult::inlineEncryptionState() const { return mInlineEncryptionState; } void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state) { mInlineEncryptionState = state; } bool ProcessResult::neverDisplayInline() const { return mNeverDisplayInline; } void ProcessResult::setNeverDisplayInline(bool display) { mNeverDisplayInline = display; } void ProcessResult::adjustCryptoStatesOfNode(const KMime::Content *node) const { if ((inlineSignatureState() != KMMsgNotSigned) || (inlineEncryptionState() != KMMsgNotEncrypted)) { mNodeHelper->setSignatureState(node, inlineSignatureState()); mNodeHelper->setEncryptionState(node, inlineEncryptionState()); } } void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart) { if (isFirstTextPart) { mPlainTextContent += curNode->decodedText(); mPlainTextContentCharset += NodeHelper::charset(curNode); } } void ObjectTreeParser::setPlainTextContent(const QString &plainTextContent) { mPlainTextContent = plainTextContent; } const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const { Q_ASSERT(node); if (mSource->overrideCodec()) { return mSource->overrideCodec(); } return mNodeHelper->codec(node); } QByteArray ObjectTreeParser::plainTextContentCharset() const { return mPlainTextContentCharset; } QByteArray ObjectTreeParser::htmlContentCharset() const { return mHtmlContentCharset; } HtmlWriter *ObjectTreeParser::htmlWriter() const { if (mHtmlWriter) { return mHtmlWriter; } return mSource->htmlWriter(); } MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const { return mNodeHelper; } diff --git a/mimetreeparser/src/objecttreeparser.h b/mimetreeparser/src/objecttreeparser.h index 7359838d..1d5a8b19 100644 --- a/mimetreeparser/src/objecttreeparser.h +++ b/mimetreeparser/src/objecttreeparser.h @@ -1,378 +1,376 @@ /* objecttreeparser.h This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail 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 In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __MIMETREEPARSER_OBJECTTREEPARSER_H__ #define __MIMETREEPARSER_OBJECTTREEPARSER_H__ #include "mimetreeparser_export.h" #include "mimetreeparser/nodehelper.h" #include "mimetreeparser/objecttreesource.h" #include class QString; namespace KMime { class Content; } namespace MimeTreeParser { class PartMetaData; class ViewerPrivate; class HtmlWriter; -class AttachmentStrategy; class NodeHelper; class MessagePart; class MimeMessagePart; typedef QSharedPointer MessagePartPtr; typedef QSharedPointer MimeMessagePartPtr; class MIMETREEPARSER_EXPORT ProcessResult { public: explicit ProcessResult(NodeHelper *nodeHelper, KMMsgSignatureState inlineSignatureState = KMMsgNotSigned, KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted, bool neverDisplayInline = false) : mInlineSignatureState(inlineSignatureState) , mInlineEncryptionState(inlineEncryptionState) , mNeverDisplayInline(neverDisplayInline) , mNodeHelper(nodeHelper) { } KMMsgSignatureState inlineSignatureState() const; void setInlineSignatureState(KMMsgSignatureState state); KMMsgEncryptionState inlineEncryptionState() const; void setInlineEncryptionState(KMMsgEncryptionState state); bool neverDisplayInline() const; void setNeverDisplayInline(bool display); void adjustCryptoStatesOfNode(const KMime::Content *node) const; private: KMMsgSignatureState mInlineSignatureState; KMMsgEncryptionState mInlineEncryptionState; bool mNeverDisplayInline : 1; NodeHelper *mNodeHelper; }; /** \brief Parses messages and generates HTML display code out of them \par Introduction First, have a look at the documentation in Mainpage.dox and at the documentation of ViewerPrivate to understand the broader picture. Just a note on the terminology: 'Node' refers to a MIME part here, which in KMime is a KMime::Content. \par Basics The ObjectTreeParser basically has two modes: Generating the HTML code for the Viewer, or only extracting the plainTextContent() for situations where only the message text is needed, for example when inline forwarding a message. The mode depends on the Interface::ObjectTreeSource passed to the constructor: If Interface::ObjectTreeSource::htmlWriter() is not 0, then the HTML code generation mode is used. Basically, all the ObjectTreeParser does is going through the tree of MIME parts and operating on those nodes. Operating here means creating the HTML code for the node or extracting the textual content from it. This process is started with parseObjectTree(), where we loop over the subnodes of the current root node. For each of those subnodes, we try to find a BodyPartFormatter that can handle the type of the node. This can either be an internal function, such as processMultiPartAlternativeSubtype() or processTextHtmlSubtype(), or it can be an external plugin. More on external plugins later. When no matching formatter is found, defaultHandling() is called for that node. \par Multipart Nodes Those nodes that are of type multipart have subnodes. If one of those children needs to be processed normally, the processMultipartXXX() functions call stdChildHandling() for the node that should be handled normally. stdChildHandling() creates its own ObjectTreeParser, which is a clone of the current ObjectTreeParser, and processes the node. stdChildHandling() is not called for all children of the multipart node, for example processMultiPartAlternativeSubtype() only calls it on one of the children, as the other one doesn't need to be displayed. Similary, processMultiPartSignedSubtype() doesn't call stdChildHandling() for the signature node, only for the signed node. \par Processed and Unprocessed Nodes When a BodyPartFormatter has finished processing a node, it is processed. Nodes are set to being not processed at the beginning of parseObjectTree(). The processed state of a node is saved in a list in NodeHelper, see NodeHelper::setNodeProcessed(), NodeHelper::nodeProcessed() and the other related helper functions. It is the responsibility of the BodyPartFormatter to correctly call setNodeProcessed() and the related functions. This is important so that processing the same node twice can be prevented. The check that prevents duplicate processing is in parseObjectTree(). An example where duplicate processing would happen if we didn't check for it is in stdChildHandling(), which is for example called from processMultiPartAlternativeSubtype(). Let's say the setting is to prefer HTML over plain text. In this case, processMultiPartAlternativeSubtype() would call stdChildHandling() on the HTML node, which would create a new ObjectTreeParser and call parseObjectTree() on it. parseObjectTree() processes the node and all its siblings, and one of the siblings is the plain text node, which shouldn't be processed! Therefore processMultiPartAlternativeSubtype() sets the plain text node as been processed already. \par Plain Text Output Various nodes have plain text that should be displayed. This plain text is usually processed though writeBodyString() first. That method checks if the provided text is an inline PGP text and decrypts it if necessary. It also pushes the text through quotedHTML(), which does a number of things like coloring quoted lines or detecting links and creating real link tags for them. \par Modifying the Message The ObjectTreeParser does not only parse its message, in some circumstances it also modifies it before displaying. This is for example the case when displaying a decrypted message: The original message only contains a binary blob of crypto data, and processMultiPartEncryptedSubtype() decrypts that blob. After decryption, the current node is replaced with the decrypted node, which happens in insertAndParseNewChildNode(). \par Crypto Operations For signature and decryption handling, there are functions which help with generating the HTML code for the signature header and footer. These are writeDeferredDecryptionBlock(), writeSigstatFooter() and writeSigstatHeader(). As the name writeDeferredDecryptionBlock() suggests, a setting can cause the message to not be decrypted unless the user clicks a link. Whether the message should be decrypted or not can be controlled by Interface::ObjectTreeSource::decryptMessage(). When the user clicks the decryption link, the URLHandler for 'kmail:' URLs sets that variable to true and triggers an update of the Viewer, which will cause parseObjectTree() to be called again. \par Async Crypto Operations The above case describes decryption the message in place. However, decryption and also verifying of the signature can take a long time, so synchronous decryption and verifing would cause the Viewer to block. Therefore it is possible to run these operations in async mode, see allowAsync(). In the first run of the async mode, all the ObjectTreeParser does is starting the decrypt or the verify job, and informing the user that the operation is in progress with writeDecryptionInProgressBlock() or with writeSigstatHeader(). Then, it creates and associates a BodyPartMemento with the current node, for example a VerifyDetachedBodyPartMemento. Each node can have multiple mementos associated with it, which are differeniated by name. NodeHelper::setBodyPartMemento() and NodeHelper::bodyPartMemento() provide means to store and retrieve these mementos. A memento is basically a thin wrapper around the crypto job, it stores the job pointer, the job input data and the job result. Mementos can be used for any async situation, not just for crypto jobs, but I'll describe crypto jobs here. So in the first run of decrypting or verifying a message, the BodyPartFormatter only starts the crypto job, creates the BodyPartMemento and writes the HTML code that tells the user that the operation is in progress. parseObjectTree() thus finishes without waiting for anything, and the message is displayed. At some point, the crypto jobs then finish, which will cause slotResult() of the BodyPartMemento to be called. slotResult() then saves the result to some member variable and calls BodyPartMemento::notify(), which in the end will trigger an update of the Viewer. That update will, in ViewerPrivate::parseMsg(), create a new ObjectTreeParser and call parseObjectTree() on it. This is where the second run begins. The functions that deal with decrypting of verifying, like processMultiPartSignedSubtype() or processMultiPartEncryptedSubtype() will look if they find a BodyPartMemento that is associated with the current node. Now it finds that memento, since it was created in the first run. It checks if the memento's job has finished, and if so, the result can be written out (either the decrypted data or the verified signature). When dealing with encrypted nodes, new nodes are created with the decrypted data. It is important to note that the original MIME tree is never modified, and remains the same as the original one. The method createAndParseTempNode is called with the newly decrypted data, and it generates a new temporary node to store the decrypted data. When these nodes are created, it is important to keep track of them as otherwise some mementos that are added to the newly created temporary nodes will be constantly regenerated. As the regeneration triggers a viewer update when complete, it results in an infinite refresh loop. The function NodeHelper::linkAsPermanentDecrypted will create a link between the newly created node and the original parent. Conversely, the function NodeHelper::attachExtraContent will create a link in the other direction, from the parent node to the newly created temporary node. When generating some mementos for nodes that may be temporary nodes (for example, contact photo mementos), the function NodeHelper::setBodyPartMementoForPermanentParent is used. This will save the given body part memento for the closest found permanent parent node, rather than the transient node itself. Then when checking for the existence of a certain memento in a node, NodeHelper::findPermanentParentBodyPartMemento will check to see if any parent of the given temporary node is a permanent (encrypted) node that has been used to generate the asked-for node. To conclude: For async operations, parseObjectTree() is called twice: The first call starts the crypto operation and creates the BodyPartMemento, the second calls sees that the BodyPartMemento is there and can use its result for writing out the HTML. \par PartMetaData and ProcessResult For crypto operations, the class PartMetaData is used a lot, mainly to pass around info about the crypto state of a node. A PartMetaData can also be associated with a node by using NodeHelper::setPartMetaData(). The only user of that however is MessageAnalyzer::processPart() of the Nepomuk E-Mail Feeder, which also uses the ObjectTreeParser to analyze the message. You'll notice that a ProcessResult is passed to each formatter. The formatter is supposed to modify the ProcessResult to tell the callers something about the state of the nodes that were processed. One example for its use is to tell the caller about the crypto state of the node. \par BodyPartFormatter Plugins As mentioned way earlier, BodyPartFormatter can either be plugins or be internal. bodypartformatter.cpp contains some trickery so that the processXXX() methods of the ObjectTreeParser are called from a BodyPartFormatter associated with them, see the CREATE_BODY_PART_FORMATTER macro. The BodyPartFormatter code is work in progress, it was supposed to be refactored, but that has not yet happened at the time of writing. Therefore the code can seem a bit chaotic. External plugins are loaded with loadPlugins() in bodypartformatterfactory.cpp. External plugins can only use the classes in the interfaces/ directory, they include BodyPart, BodyPartMemento, BodyPartFormatterPlugin, BodyPartFormatter, BodyPartURLHandler, HtmlWriter and URLHandler. Therefore external plugins have powerful capabilities, which are needed for example in the iCal formatter or in the vCard formatter. \par Special HTML tags As also mentioned in the documentation of ViewerPrivate, the ObjectTreeParser writes out special links that are only understood by the viewer, for example 'kmail:' URLs or 'attachment:' URLs. Also, some special HTML tags are created, which the Viewer later uses for post-processing. For example a div with the id 'attachmentInjectionPoint', or a div with the id 'attachmentDiv', which is used to mark an attachment in the body with a yellow border when the user clicks the attachment in the header. Finally, parseObjectTree() creates an anchor with the id 'att%1', which is used in the Viewer to scroll to the attachment. */ class MIMETREEPARSER_EXPORT ObjectTreeParser { /** * @internal * Copies the context of @p other, but not it's rawDecryptedBody, plainTextContent or htmlContent. */ ObjectTreeParser(const ObjectTreeParser &other); public: explicit ObjectTreeParser(Interface::ObjectTreeSource *source, NodeHelper *nodeHelper = nullptr); explicit ObjectTreeParser(const ObjectTreeParser *topLevelParser); virtual ~ObjectTreeParser(); void setAllowAsync(bool allow); bool allowAsync() const; bool hasPendingAsyncJobs() const; /** * The text of the message, ie. what would appear in the * composer's text editor if this was edited or replied to. * This is usually the content of the first text/plain MIME part. */ QString plainTextContent() const; /** * Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part. * * Not to be consfused with the HTML code that the message viewer widget displays, that HTML * is written out by htmlWriter() and a totally different pair of shoes. */ QString htmlContent() const; /** * The original charset of MIME part the plain text was extracted from. * * If there were more than one text/plain MIME parts in the mail, the this is the charset * of the last MIME part processed. */ QByteArray plainTextContentCharset() const; QByteArray htmlContentCharset() const; HtmlWriter *htmlWriter() const; NodeHelper *nodeHelper() const; /** Parse beginning at a given node and recursively parsing the children of that node and it's next sibling. */ void parseObjectTree(KMime::Content *node, bool parseOnlySingleNode=false); MessagePartPtr parsedPart() const; private: void extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart); void setPlainTextContent(const QString &plainTextContent); /** * Does the actual work for parseObjectTree. Unlike parseObjectTree(), this does not change the * top-level content. */ MessagePartPtr parseObjectTreeInternal(KMime::Content *node, bool mOnlyOneMimePart); MessagePartPtr processType(KMime::Content *node, MimeTreeParser::ProcessResult &processResult, const QByteArray &mimeType); private: /** ctor helper */ void init(); const QTextCodec *codecFor(KMime::Content *node) const; void copyContentFrom(const ObjectTreeParser *other); private: Interface::ObjectTreeSource *mSource; NodeHelper *mNodeHelper; HtmlWriter *mHtmlWriter; QByteArray mPlainTextContentCharset; QByteArray mHtmlContentCharset; QString mPlainTextContent; QString mHtmlContent; KMime::Content *mTopLevelContent; MessagePartPtr mParsedPart; /// Show only one mime part means that the user has selected some node in the message structure /// viewer that is not the root, which means the user wants to only see the selected node and its /// children. If that is the case, this variable is set to true. /// The code needs to behave differently if this is set. For example, it should not process the /// siblings. Also, consider inline images: Normally, those nodes are completely hidden, as the /// HTML node embedds them. However, when showing only the node of the image, one has to show them, /// as their is no HTML node in which they are displayed. There are many more cases where this /// variable needs to be obeyed. /// This variable is set to false again when processing the children in stdChildHandling(), as /// the children can be completely displayed again. bool mShowOnlyOneMimePart; bool mHasPendingAsyncJobs; bool mAllowAsync; - const AttachmentStrategy *mAttachmentStrategy; // DataUrl Icons cache QString mCollapseIcon; QString mExpandIcon; bool mDeleteNodeHelper; friend class PartNodeBodyPart; friend class MessagePart; friend class EncryptedMessagePart; friend class SignedMessagePart; friend class TextMessagePart; friend class HtmlMessagePart; friend class MultiPartSignedBodyPartFormatter; friend class ApplicationPkcs7MimeBodyPartFormatter; }; } #endif // __MIMETREEPARSER_OBJECTTREEPARSER_H__