diff --git a/components/mailviewer/qml/ICalPart.qml b/components/mailviewer/qml/ICalPart.qml new file mode 100644 index 00000000..a9d77a48 --- /dev/null +++ b/components/mailviewer/qml/ICalPart.qml @@ -0,0 +1,87 @@ +/* + Copyright (C) 2019 Christian Mollekopf, + + 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. +*/ + +import QtQuick 2.7 +import QtQuick.Controls 2 +import QtQuick.Layouts 1 + +import org.kube.framework 1.0 as Kube + +Item { + id: root + + property string content + property bool autoLoadImages: false + + property string searchString + property int contentHeight: childrenRect.height + + Kube.InvitationController { + id: controller + Component.onCompleted: loadICal(content) + } + + ColumnLayout { + Kube.Heading { + Layout.fillWidth: true + text: qsTr("You've been invited to: \"%1\"").arg(controller.summary) + } + + Kube.SelectableLabel { + visible: controller.allDay + text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM") + (/*DateUtils.sameDay(controller.start, controller.end)*/ true ? "" : " - " + controller.end.toLocaleString(Qt.locale(), "dd. MMMM")) + } + + Kube.SelectableLabel { + visible: !controller.allDay + text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM hh:mm") + " - " + (/*DateUtils.sameDay(controller.start, controller.end)*/ true ? controller.end.toLocaleString(Qt.locale(), "hh:mm") : controller.end.toLocaleString(Qt.locale(), "dd. MMMM hh:mm")) + } + + RowLayout { + Kube.Button { + text: qsTr("Decline") + onClicked: { + controller.acceptAction.execute() + } + } + Kube.PositiveButton { + text: qsTr("Accept") + onClicked: { + controller.acceptAction.execute() + } + } + + Kube.Label { + text: qsTr("in") + } + + Kube.CalendarComboBox { + id: calendarSelector + Layout.fillWidth: true + accountId: Kube.Context.currentAccountId + contentType: "event" + onSelected: { + controller.calendar = calendar + } + } + + } + + } + +} diff --git a/components/mailviewer/qml/MailDataModel.qml b/components/mailviewer/qml/MailDataModel.qml index c78896d3..fdf4abdb 100644 --- a/components/mailviewer/qml/MailDataModel.qml +++ b/components/mailviewer/qml/MailDataModel.qml @@ -1,175 +1,179 @@ /* Copyright (C) 2016 Michael Bohlender, 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. */ import QtQuick 2.4 import QtQml.Models 2.2 import org.kube.framework 1.0 as Kube DelegateModel { id: root property string searchString: "" property bool autoLoadImages: false delegate: Item { id: partColumn width: parent.width height: childrenRect.height function getColor(securityLevel) { if (securityLevel == "good") { return Kube.Colors.positiveColor } if (securityLevel == "bad") { return Kube.Colors.negativeColor } if (securityLevel == "notsogood") { return Kube.Colors.warningColor } return Kube.Colors.lightgrey } function getDetails(signatureDetails) { var details = ""; if (signatureDetails.noSignaturesFound) { details += qsTr("This message has been signed but we failed to validate the signature.") + "\n" } else if (signatureDetails.keyMissing) { details += qsTr("This message has been signed using the key %1.").arg(signatureDetails.keyId) + "\n"; details += qsTr("The key details are not available.") + "\n"; } else { details += qsTr("This message has been signed using the key %1 by %2.").arg(signatureDetails.keyId).arg(signatureDetails.signer) + "\n"; if (signatureDetails.keyRevoked) { details += qsTr("The key was revoked.") + "\n" } if (signatureDetails.keyExpired) { details += qsTr("The key has expired.") + "\n" } if (signatureDetails.keyIsTrusted) { details += qsTr("You are trusting this key.") + "\n" } if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired && !signatureDetails.keyIsTrusted) { details += qsTr("The signature is invalid.") + "\n" } } return details } Column { id: buttons anchors.left: parent.left anchors.top: parent.top anchors.rightMargin: Kube.Units.smallSpacing spacing: Kube.Units.smallSpacing Kube.IconButton { id: encryptedButton width: Kube.Units.gridUnit height: width iconName: Kube.Icons.secure color: getColor(model.encryptionSecurityLevel) backgroundOpacity: 0.5 visible: model.encrypted tooltip: model.encryptionDetails.keyId == "" ? qsTr("This message is encrypted but we don't have the key for it.") : qsTr("This message is encrypted to the key: %1").arg(model.encryptionDetails.keyId); //FIXME make text copyable // Kube.SelectableItem { // visualParent: encryptedButton // text: parent.tooltip // } } Kube.IconButton { id: signedButton width: Kube.Units.gridUnit height: width iconName: Kube.Icons.signed color: getColor(model.signatureSecurityLevel) backgroundOpacity: 0.5 visible: model.signed tooltip: getDetails(model.signatureDetails) } } Rectangle { id: border visible: encryptedButton.hovered || signedButton.hovered anchors.topMargin: Kube.Units.smallSpacing anchors.top: buttons.bottom anchors.bottom: partLoader.bottom anchors.right: buttons.right width: Kube.Units.smallSpacing color: getColor(model.securityLevel) opacity: 0.5 } Loader { id: partLoader anchors { top: parent.top left: buttons.right leftMargin: Kube.Units.smallSpacing right: parent.right } height: item ? item.contentHeight : 0 width: parent.width Binding { target: partLoader.item property: "searchString" value: root.searchString when: partLoader.status == Loader.Ready } Binding { target: partLoader.item property: "autoLoadImages" value: root.autoLoadImages when: partLoader.status == Loader.Ready } } Component.onCompleted: { switch (model.type) { case "plain": partLoader.setSource("TextContent.qml", {"content": model.content, "embedded": model.embedded, "type": model.type }) break case "html": partLoader.setSource("HtmlContent.qml", {"content": model.content, }) break; case "error": partLoader.setSource("ErrorPart.qml", { "errorType": model.errorType, "errorString": model.errorString, }) break; case "encapsulated": partLoader.setSource("MailPart.qml", {"rootIndex": root.modelIndex(index), "model": root.model, "sender": model.sender, "date": model.date }) break; + case "ical": + partLoader.setSource("ICalPart.qml", + {"content": model.content}) + break; } } } } diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt index 4cde2d1b..aab6c894 100644 --- a/framework/src/CMakeLists.txt +++ b/framework/src/CMakeLists.txt @@ -1,109 +1,110 @@ find_package(Qt5 COMPONENTS REQUIRED Core Concurrent Quick Qml WebEngineWidgets Test WebEngine Gui) find_package(KF5Mime 4.87.0 CONFIG REQUIRED) find_package(KF5CalendarCore CONFIG REQUIRED) find_package(Sink 0.7.0 CONFIG REQUIRED) find_package(KAsync CONFIG REQUIRED) find_package(Gpgme REQUIRED) find_package(KF5Codecs CONFIG REQUIRED) find_package(KF5Contacts CONFIG REQUIRED) include(GenerateExportHeader) set(CMAKE_CXX_VISIBILITY_PRESET default) include_directories(. domain/mime/mimetreeparser domain/ domain/mime) add_library(crypto STATIC crypto.cpp ) target_link_libraries(crypto PUBLIC Qt5::Core Gpgme::Gpgme ) add_library(kubeframework SHARED settings/settings.cpp domain/maillistmodel.cpp domain/folderlistmodel.cpp domain/perioddayeventmodel.cpp domain/eventoccurrencemodel.cpp domain/eventcontroller.cpp + domain/invitationcontroller.cpp domain/todomodel.cpp domain/todocontroller.cpp domain/multidayeventmodel.cpp domain/composercontroller.cpp domain/modeltest.cpp domain/retriever.cpp domain/outboxmodel.cpp domain/identitiesmodel.cpp domain/recepientautocompletionmodel.cpp domain/settings/accountsettings.cpp domain/selector.cpp domain/completer.cpp domain/mouseproxy.cpp domain/contactcontroller.cpp domain/controller.cpp domain/peoplemodel.cpp domain/textdocumenthandler.cpp domain/mime/htmlutils.cpp domain/mime/messageparser.cpp domain/mime/attachmentmodel.cpp domain/mime/partmodel.cpp domain/mime/mailtemplates.cpp accounts/accountfactory.cpp accounts/accountsmodel.cpp fabric.cpp sinkfabric.cpp kubeimage.cpp clipboardproxy.cpp krecursivefilterproxymodel.cpp qquicktreemodeladaptor.cpp startupcheck.cpp keyring.cpp domainobjectcontroller.cpp extensionmodel.cpp viewhighlighter.cpp file.cpp logmodel.cpp entitymodel.cpp ) generate_export_header(kubeframework BASE_NAME Kube EXPORT_FILE_NAME kube_export.h) set_target_properties(kubeframework PROPERTIES ENABLE_EXPORTS 1 WINDOWS_EXPORT_ALL_SYMBOLS 1 ) target_link_libraries(kubeframework sink kube_otp mailcrypto crypto Qt5::Core Qt5::Quick Qt5::Qml Qt5::WebEngineWidgets Qt5::Test Qt5::WebEngine Qt5::Gui KF5::Codecs KF5::Contacts KF5::CalendarCore KAsync ) install(TARGETS kubeframework DESTINATION ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) add_library(frameworkplugin SHARED frameworkplugin.cpp) target_link_libraries(frameworkplugin kubeframework ) install(TARGETS frameworkplugin DESTINATION ${FRAMEWORK_INSTALL_DIR}) set(BUILD_TESTING ON) add_subdirectory(tests) add_subdirectory(domain/mime) add_subdirectory(domain/mime/tests) add_subdirectory(domain/mime/mimetreeparser) add_subdirectory(domain/settings/tests) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/framework/src/domain/invitationcontroller.cpp b/framework/src/domain/invitationcontroller.cpp new file mode 100644 index 00000000..46cf89f0 --- /dev/null +++ b/framework/src/domain/invitationcontroller.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * Copyright (C) 2018 Christian Mollekopf, + * + * 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 "invitationcontroller.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Sink::ApplicationDomain; + +InvitationController::InvitationController() + : EventController(), + action_accept{new Kube::ControllerAction{this, &InvitationController::accept}}, + action_decline{new Kube::ControllerAction{this, &InvitationController::decline}} +{ +} + +void InvitationController::loadICal(const QString &ical) +{ + auto icalEvent = KCalCore::ICalFormat().readIncidence(ical.toUtf8()).dynamicCast(); + if(!icalEvent) { + SinkWarning() << "Invalid ICal to process, ignoring..."; + return; + } + + setUid(icalEvent->uid()); + setSummary(icalEvent->summary()); + setDescription(icalEvent->description()); + setLocation(icalEvent->location()); + + setStart(icalEvent->dtStart()); + setEnd(icalEvent->dtEnd()); + setAllDay(icalEvent->allDay()); +} + +void InvitationController::accept() +{ + using namespace Sink; + using namespace Sink::ApplicationDomain; + + const auto calendar = getCalendar(); + if (!calendar) { + qWarning() << "No calendar selected"; + return; + } + + auto calcoreEvent = QSharedPointer::create(); + calcoreEvent->setUid(getUid()); + calcoreEvent->setSummary(getSummary()); + calcoreEvent->setDescription(getDescription()); + calcoreEvent->setLocation(getLocation()); + calcoreEvent->setDtStart(getStart()); + calcoreEvent->setDtEnd(getEnd()); + calcoreEvent->setAllDay(getAllDay()); + + Event event(calendar->resourceInstanceIdentifier()); + event.setIcal(KCalCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); + event.setCalendar(*calendar); + + auto job = Store::create(event) + .then([&] (const KAsync::Error &error) { + if (error) { + SinkWarning() << "Failed to save the event: " << error; + } + emit done(); + }); + + run(job); + + //TODO sendIMipMessage(calcoreEvent); +} + +void InvitationController::decline() +{ + +} + diff --git a/framework/src/domain/invitationcontroller.h b/framework/src/domain/invitationcontroller.h new file mode 100644 index 00000000..2c98144b --- /dev/null +++ b/framework/src/domain/invitationcontroller.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Michael Bohlender, + * Copyright (C) 2018 Christian Mollekopf, + * + * 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. + */ + + +#pragma once +#include "kube_export.h" + +#include +#include + +#include + +#include "eventcontroller.h" + +class KUBE_EXPORT InvitationController : public EventController +{ + Q_OBJECT + + KUBE_CONTROLLER_PROPERTY(QByteArray, Uid, uid) + + KUBE_CONTROLLER_ACTION(accept) + KUBE_CONTROLLER_ACTION(decline) + +public: + explicit InvitationController(); + + Q_INVOKABLE void loadICal(const QString &message); +}; diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.cpp b/framework/src/domain/mime/mimetreeparser/messagepart.cpp index df186632..a7c3553a 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.cpp +++ b/framework/src/domain/mime/mimetreeparser/messagepart.cpp @@ -1,1098 +1,1106 @@ /* Copyright (c) 2015 Sandro Knauß Copyright (c) 2017 Christian Mollekopf 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 "cryptohelper.h" #include "objecttreeparser.h" #include "utils.h" #include #include #include using namespace MimeTreeParser; using namespace Crypto; //------MessagePart----------------------- MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node) : mText(text) , mOtp(otp) , mParentPart(nullptr) , mNode(node) //only null for messagepartlist , mError(NoError) , mRoot(false) { } MessagePart::~MessagePart() { for (auto n : mNodesToDelete) { delete n; } } /* QByteArray MailMime::cid() const { if (!d->mNode || !d->mNode->contentID()) { return QByteArray(); } return d->mNode->contentID()->identifier(); } */ /* bool MailMime::isFirstTextPart() const { if (!d->mNode || !d->mNode->topLevel()) { return false; } return (d->mNode->topLevel()->textContent() == d->mNode); } bool MailMime::isFirstPart() const { if (!d->mNode || !d->mNode->parent()) { return false; } return (d->mNode->parent()->contents().first() == d->mNode); } bool MailMime::isTopLevelPart() const { if (!d->mNode) { return false; } return (d->mNode->topLevel() == d->mNode); } */ MessagePart::Disposition MessagePart::disposition() const { if (!mNode) { return Invalid; } const auto cd = mNode->contentDisposition(false); if (!cd) { return Invalid; } switch (cd->disposition()){ case KMime::Headers::CDinline: return Inline; case KMime::Headers::CDattachment: return Attachment; default: return Invalid; } } QString MessagePart::filename() const { if (!mNode) { return QString(); } const auto cd = mNode->contentDisposition(false); if (!cd) { return QString(); } return cd->filename(); } static KMime::Headers::ContentType *contentType(KMime::Content *node) { if (node) { return node->contentType(false); } return nullptr; } QByteArray MessagePart::charset() const { if (auto ct = contentType(mNode)) { return ct->charset(); } return mNode->defaultCharset(); } QByteArray MessagePart::mimeType() const { if (auto ct = contentType(mNode)) { return ct->mimeType(); } return {}; } bool MessagePart::isText() const { if (auto ct = contentType(mNode)) { return ct->isText(); } return false; } MessagePart::Error MessagePart::error() const { return mError; } QString MessagePart::errorString() const { return mMetaData.errorText; } PartMetaData *MessagePart::partMetaData() { return &mMetaData; } bool MessagePart::isAttachment() const { if (mNode) { return KMime::isAttachment(mNode); } return false; } KMime::Content *MessagePart::node() const { return mNode; } void MessagePart::setIsRoot(bool root) { mRoot = root; } bool MessagePart::isRoot() const { return mRoot; } QString MessagePart::text() const { return mText; } void MessagePart::setText(const QString &text) { mText = text; } bool MessagePart::isHtml() const { return false; } MessagePart *MessagePart::parentPart() const { return mParentPart; } void MessagePart::setParentPart(MessagePart *parentPart) { mParentPart = parentPart; } QString MessagePart::htmlContent() const { return text(); } QString MessagePart::plaintextContent() const { return text(); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); mRoot = subMessagePart->isRoot(); foreach (const auto &part, subMessagePart->subParts()) { appendSubPart(part); } } void MessagePart::parseInternal(const QByteArray &data) { auto tempNode = new KMime::Content(); const auto lfData = KMime::CRLFtoLF(data); //We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts, //or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this. if (lfData.contains("\n\n")) { tempNode->setContent(lfData); } else { tempNode->setBody(lfData); } tempNode->parse(); bindLifetime(tempNode); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("temporary node"); } parseInternal(tempNode); } QString MessagePart::renderInternalText() const { QString text; foreach (const auto &mp, subParts()) { text += mp->text(); } return text; } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); mBlocks.append(messagePart); } const QVector &MessagePart::subParts() const { return mBlocks; } bool MessagePart::hasSubParts() const { return !mBlocks.isEmpty(); } QVector MessagePart::signatures() const { QVector list; if (auto sig = dynamic_cast(const_cast(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast(parent)) { list << sig; } parent = parent->parentPart(); } return list; } QVector MessagePart::encryptions() const { QVector list; if (auto sig = dynamic_cast(const_cast(this))) { list << sig; } auto parent = parentPart(); while (parent) { if (auto sig = dynamic_cast(parent)) { list << sig; } parent = parent->parentPart(); } return list; } KMMsgEncryptionState MessagePart::encryptionState() const { if (!encryptions().isEmpty()) { return KMMsgFullyEncrypted; } return KMMsgNotEncrypted; } KMMsgSignatureState MessagePart::signatureState() const { if (!signatures().isEmpty()) { return KMMsgFullySigned; } return KMMsgNotSigned; } void MessagePart::bindLifetime(KMime::Content *node) { mNodesToDelete << node; } //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { } 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) : MessagePartList(otp, node), mSignatureState(KMMsgSignatureStateUnknown), mEncryptionState(KMMsgEncryptionStateUnknown) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } parseContent(); } TextMessagePart::~TextMessagePart() { } void TextMessagePart::parseContent() { const auto aCodec = mOtp->codecFor(mNode); const QString &fromAddress = mOtp->nodeHelper()->fromAsString(mNode); mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; auto body = mNode->decodedContent(); const auto blocks = prepareMessageForDecryption(body); const auto cryptProto = 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) { KMime::Content *content = new KMime::Content; content->setBody(block.text()); content->parse(); EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr, content)); mp->bindLifetime(content); mp->setIsEncrypted(true); appendSubPart(mp); } else if (block.type() == ClearsignedBlock) { KMime::Content *content = new KMime::Content; content->setBody(block.text()); content->parse(); SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr, content)); mp->bindLifetime(content); mp->setIsSigned(true); appendSubPart(mp); } 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 { if (mEncryptionState == KMMsgNotEncrypted) { return MessagePart::encryptionState(); } return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { if (mSignatureState == KMMsgNotSigned) { return MessagePart::signatureState(); } return mSignatureState; } //-----AttachmentMessageBlock---------------------- AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node) : TextMessagePart(otp, node) { } AttachmentMessagePart::~AttachmentMessagePart() { } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } const QByteArray partBody(mNode->decodedContent()); mBodyHTML = mOtp->codecFor(mNode)->toUnicode(partBody); } HtmlMessagePart::~HtmlMessagePart() { } 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(), node) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } parseInternal(mNode, onlyOneMimePart); } 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) : MessagePart(otp, QString(), node) { if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) { mChildParts[Util::MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true)); } if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) { mChildParts[Util::MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true)); } if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) { mChildParts[Util::MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true)); } else { // 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. // In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments // with html content. // // In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild. auto data = [&] { if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) { return d; } return findTypeInDirectChildren(mNode, "multipart/mixed"); }(); if (data) { QString htmlContent; const auto parts = data->contents(); for (auto p : data->contents()) { if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) { htmlContent += MimeMessagePart(mOtp, p, true).text(); } else if (KMime::isAttachment(p)) { appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true))); } } mChildParts[Util::MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr)); } } } AlternativeMessagePart::~AlternativeMessagePart() { } QList AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(Util::MultipartPlain)) { return mChildParts[Util::MultipartPlain]->text(); } return QString(); } 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(); } } +QString AlternativeMessagePart::icalContent() const +{ + if (mChildParts.contains(Util::MultipartIcal)) { + return mChildParts[Util::MultipartIcal]->text(); + } + return {}; +} + //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const CryptoProtocol cryptoProto) : MessagePart(otp, QString(), node) , mProtocol(cryptoProto) { if (!mNode) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } } CertMessagePart::~CertMessagePart() { } void CertMessagePart::import() { importKey(mProtocol, mNode->decodedContent()); } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const CryptoProtocol cryptoProto, const QString &fromAddress, KMime::Content *node, KMime::Content *signedData) : MessagePart(otp, text, node) , mProtocol(cryptoProto) , mFromAddress(fromAddress) , mSignedData(signedData) { mMetaData.isSigned = true; mMetaData.isGoodSignature = false; mMetaData.status = tr("Wrong Crypto Plug-In."); } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { mMetaData.isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return mMetaData.isSigned; } static QString prettifyDN(const char *uid) { // We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for. return QString::fromUtf8(uid); } void SignedMessagePart::sigStatusToMetaData(const Signature &signature) { mMetaData.isGoodSignature = signature.status.errorCode() == GPG_ERR_NO_ERROR; if (!mMetaData.isGoodSignature) { if (signature.status.errorCode() == GPG_ERR_NO_PUBKEY) { qWarning() << "No public key to verify signature."; } else { qWarning() << "Is no good signature" << signature.status; } } // save extended signature status flags auto summary = signature.summary; mMetaData.keyMissing = summary & GPGME_SIGSUM_KEY_MISSING; mMetaData.keyExpired = summary & GPGME_SIGSUM_KEY_EXPIRED; mMetaData.keyRevoked = summary & GPGME_SIGSUM_KEY_REVOKED; mMetaData.sigExpired = summary & GPGME_SIGSUM_SIG_EXPIRED; mMetaData.crlMissing = summary & GPGME_SIGSUM_CRL_MISSING; mMetaData.crlTooOld = summary & GPGME_SIGSUM_CRL_TOO_OLD; Key key; if (mMetaData.isGoodSignature) { // Search for the key by its fingerprint so that we can check for trust etc. const auto keys = findKeys({signature.fingerprint}); if (keys.size() > 1) { // Should not happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint; } if (keys.empty()) { // Should not happen at this point qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint; } else { key = keys[0]; } } mMetaData.keyId = key.keyId; if (mMetaData.keyId.isEmpty()) { mMetaData.keyId = signature.fingerprint; } mMetaData.keyIsTrusted = signature.validity == GPGME_VALIDITY_FULL || signature.validity == GPGME_VALIDITY_ULTIMATE; if (!key.userIds.empty()) { mMetaData.signer = prettifyDN(key.userIds[0].id); } for (const auto &userId : key.userIds) { QString email = QString::fromUtf8(userId.email); // ### work around gpgme 0.3.QString text() const Q_DECL_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()) { mMetaData.signerMailAddresses.append(email); } } mMetaData.creationTime = signature.creationTime; if (mMetaData.signer.isEmpty()) { if (!key.userIds.empty()) { mMetaData.signer = prettifyDN(key.userIds[0].name); } if (!mMetaData.signerMailAddresses.empty()) { if (mMetaData.signer.isEmpty()) { mMetaData.signer = mMetaData.signerMailAddresses.front(); } else { mMetaData.signer += QLatin1String(" <") + mMetaData.signerMailAddresses.front() + QLatin1Char('>'); } } } } void SignedMessagePart::startVerification() { if (mSignedData) { const QByteArray cleartext = KMime::LFtoCRLF(mSignedData->encodedContent()); //The case for pkcs7 if (mNode == mSignedData) { startVerificationDetached(cleartext, nullptr, {}); } else { if (mNode) { startVerificationDetached(cleartext, mSignedData, mNode->decodedContent()); } else { //The case for clearsigned above startVerificationDetached(cleartext, nullptr, {}); } } } } void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; if (textNode) { parseInternal(textNode); } mMetaData.isSigned = false; mMetaData.status = tr("Wrong Crypto Plug-In."); if (!signature.isEmpty()) { setVerificationResult(verifyDetachedSignature(mProtocol, signature, text), false, text); } else { QByteArray outdata; setVerificationResult(verifyOpaqueSignature(mProtocol, text, outdata), false, outdata); } if (!mMetaData.isSigned) { mMetaData.creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const VerificationResult &result, bool parseText, const QByteArray &plainText) { auto signatures = result.signatures; // FIXME // mMetaData.auditLogError = result.error; if (!signatures.empty()) { mMetaData.isSigned = true; sigStatusToMetaData(signatures.front()); if (mNode && parseText) { mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData); } if (!plainText.isEmpty() && parseText) { parseInternal(plainText); } } } QString SignedMessagePart::plaintextContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } QString SignedMessagePart::htmlContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } //-----CryptMessageBlock--------------------- EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const CryptoProtocol cryptoProto, const QString &fromAddress, KMime::Content *node, KMime::Content *encryptedNode) : MessagePart(otp, text, node) , mProtocol(cryptoProto) , mFromAddress(fromAddress) , mEncryptedNode(encryptedNode) { mMetaData.isSigned = false; mMetaData.isGoodSignature = false; mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mMetaData.status = tr("Wrong Crypto Plug-In."); } EncryptedMessagePart::~EncryptedMessagePart() { } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { mMetaData.isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return mMetaData.isEncrypted; } bool EncryptedMessagePart::isDecryptable() const { return mMetaData.isDecryptable; } void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); bindLifetime(content); startDecryption(content); if (mMetaData.isDecryptable) { const auto codec = aCodec ? aCodec : mOtp->codecFor(mNode); const auto decoded = codec->toUnicode(mDecryptedData); if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { _mp->setText(decoded); } else { setText(decoded); } } else { setText(decoded); } } } bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) { mError = NoError; mMetaData.errorText.clear(); //FIXME // mMetaData.auditLogError = GpgME::Error(); mMetaData.auditLog.clear(); const QByteArray ciphertext = data.decodedContent(); QByteArray plainText; DecryptionResult decryptResult; VerificationResult verifyResult; std::tie(decryptResult, verifyResult) = decryptAndVerify(mProtocol, ciphertext, plainText); mMetaData.isSigned = verifyResult.signatures.size() > 0; if (verifyResult.signatures.size() > 0) { //We simply attach a signed message part to indicate that this content is also signed auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, QString::fromUtf8(plainText), mProtocol, mFromAddress, nullptr, nullptr)); subPart->setVerificationResult(verifyResult, true, plainText); appendSubPart(subPart); } if (decryptResult.error && mMetaData.isSigned) { //Only a signed part mMetaData.isEncrypted = false; mDecryptedData = plainText; return true; } if (mMetaData.isEncrypted) { mMetaData.keyId = [&] { foreach (const auto &recipient, decryptResult.recipients) { if (recipient.status.errorCode() != GPG_ERR_NO_SECKEY) { return recipient.keyId; } } return QByteArray{}; }(); } if (!decryptResult.error) { mDecryptedData = plainText; setText(QString::fromUtf8(mDecryptedData.constData())); } else { const auto errorCode = decryptResult.error.errorCode(); mMetaData.isEncrypted = errorCode != GPG_ERR_NO_DATA; qWarning() << "Failed to decrypt : " << decryptResult.error; const bool noSecretKeyAvilable = mMetaData.keyId.isEmpty(); bool passphraseError = errorCode == GPG_ERR_CANCELED || errorCode == GPG_ERR_NO_SECKEY; //We only get a decryption failed error when we enter the wrong passphrase.... if (!passphraseError && !noSecretKeyAvilable) { passphraseError = true; } if(noSecretKeyAvilable) { mError = NoKeyError; mMetaData.errorText = tr("Could not decrypt the data: no key found for recipients."); } else if (passphraseError) { mError = PassphraseError; // mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); } else { mError = UnknownError; mMetaData.errorText = tr("Could not decrypt the data."); // + tr("Error: %1").arg(QString::fromLocal8Bit(decryptResult.error().asString())); } return false; } return true; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { if (!data) { data = mEncryptedNode; if (!data) { data = mNode; } } mMetaData.isEncrypted = true; mMetaData.isDecryptable = okDecryptMIME(*data); if (!mMetaData.isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } // if (mMetaData.isEncrypted && !decryptMessage()) { // mMetaData.isDecryptable = true; // } if (mNode && !mMetaData.isSigned) { mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData); parseInternal(mDecryptedData); } } QString EncryptedMessagePart::plaintextContent() const { if (!mNode) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::htmlContent() const { if (!mNode) { 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(), node) , mMessage(message) { mMetaData.isEncrypted = false; mMetaData.isSigned = false; mMetaData.isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setPartMetaData(mNode, mMetaData); if (!mMessage) { qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } parseInternal(message.data()); } EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() { } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } QString EncapsulatedRfc822MessagePart::from() const { if (auto from = mMessage->from(false)) { return from->asUnicodeString(); } return {}; } QDateTime EncapsulatedRfc822MessagePart::date() const { if (auto date = mMessage->date(false)) { return date->dateTime(); } return {}; } HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node) : MessagePart(otp, QString(), node) { } HeadersPart::~HeadersPart() { } diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.h b/framework/src/domain/mime/mimetreeparser/messagepart.h index ccc3a8b4..675e1510 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.h +++ b/framework/src/domain/mime/mimetreeparser/messagepart.h @@ -1,385 +1,386 @@ /* 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. */ #ifndef __MIMETREEPARSER_MESSAGEPART_H__ #define __MIMETREEPARSER_MESSAGEPART_H__ #include "util.h" #include "enums.h" #include "partmetadata.h" #include #include #include #include class QTextCodec; class PartPrivate; namespace KMime { class Content; } namespace MimeTreeParser { class ObjectTreeParser; class HTMLBlock; typedef QSharedPointer HTMLBlockPtr; class CryptoBodyPartMemento; class MultiPartAlternativeBodyPartFormatter; class SignedMessagePart; class EncryptedMessagePart; using Crypto::CryptoProtocol; using Crypto::CryptoProtocol::CMS; using Crypto::CryptoProtocol::OpenPGP; using Crypto::CryptoProtocol::UnknownProtocol; class MessagePart : public QObject { Q_OBJECT Q_PROPERTY(bool attachment READ isAttachment) Q_PROPERTY(bool root READ isRoot) Q_PROPERTY(bool isHtml READ isHtml) Q_PROPERTY(QString plaintextContent READ plaintextContent) Q_PROPERTY(QString htmlContent READ htmlContent) public: enum Disposition { Inline, Attachment, Invalid }; typedef QSharedPointer Ptr; MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node = nullptr); virtual ~MessagePart(); virtual QString text() const; void setText(const QString &text); virtual bool isAttachment() const; void setIsRoot(bool root); bool isRoot() const; void setParentPart(MessagePart *parentPart); MessagePart *parentPart() const; virtual QString plaintextContent() const; virtual QString htmlContent() const; virtual bool isHtml() const; QByteArray mimeType() const; QByteArray charset() const; QString filename() const; Disposition disposition() const; bool isText() const; enum Error { NoError = 0, PassphraseError, NoKeyError, UnknownError }; Error error() const; QString errorString() const; PartMetaData *partMetaData(); void appendSubPart(const MessagePart::Ptr &messagePart); const QVector &subParts() const; bool hasSubParts() const; KMime::Content *node() const; virtual KMMsgSignatureState signatureState() const; virtual KMMsgEncryptionState encryptionState() const; QVector signatures() const; QVector encryptions() const; void bindLifetime(KMime::Content *); protected: void parseInternal(KMime::Content *node, bool onlyOneMimePart = false); void parseInternal(const QByteArray &data); QString renderInternalText() const; QString mText; ObjectTreeParser *mOtp; PartMetaData mMetaData; MessagePart *mParentPart; KMime::Content *mNode; QVector mNodesToDelete; Error mError; private: QVector mBlocks; bool mRoot; }; class MimeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart = false); virtual ~MimeMessagePart(); QString text() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: friend class AlternativeMessagePart; friend class ::PartPrivate; }; class MessagePartList : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MessagePartList(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node = nullptr); virtual ~MessagePartList(); QString text() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; }; enum IconType { NoIcon = 0, IconExternal, IconInline }; class TextMessagePart : public MessagePartList { Q_OBJECT public: typedef QSharedPointer Ptr; TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~TextMessagePart(); KMMsgSignatureState signatureState() const Q_DECL_OVERRIDE; KMMsgEncryptionState encryptionState() const Q_DECL_OVERRIDE; private: void parseContent(); KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; friend class DefaultRendererPrivate; friend class ObjectTreeParser; friend class ::PartPrivate; }; class AttachmentMessagePart : public TextMessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~AttachmentMessagePart(); virtual bool isAttachment() const Q_DECL_OVERRIDE { return true; } }; class HtmlMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~HtmlMessagePart(); QString text() const Q_DECL_OVERRIDE; bool isHtml() const Q_DECL_OVERRIDE; private: QString mBodyHTML; QByteArray mCharset; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; class AlternativeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~AlternativeMessagePart(); QString text() const Q_DECL_OVERRIDE; bool isHtml() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; + QString icalContent() const; QList availableModes(); private: QMap mChildParts; friend class DefaultRendererPrivate; friend class ObjectTreeParser; friend class MultiPartAlternativeBodyPartFormatter; friend class ::PartPrivate; }; class CertMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const CryptoProtocol cryptoProto); virtual ~CertMessagePart(); QString text() const Q_DECL_OVERRIDE; void import(); private: const CryptoProtocol mProtocol; friend class DefaultRendererPrivate; }; class EncapsulatedRfc822MessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); virtual ~EncapsulatedRfc822MessagePart(); QString text() const Q_DECL_OVERRIDE; QString from() const; QDateTime date() const; private: const KMime::Message::Ptr mMessage; friend class DefaultRendererPrivate; }; class EncryptedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isEncrypted READ isEncrypted) public: typedef QSharedPointer Ptr; EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const CryptoProtocol protocol, const QString &fromAddress, KMime::Content *node, KMime::Content *encryptedNode = nullptr); virtual ~EncryptedMessagePart(); QString text() const Q_DECL_OVERRIDE; void setIsEncrypted(bool encrypted); bool isEncrypted() const; bool isDecryptable() const; void startDecryption(const QByteArray &text, const QTextCodec *aCodec); void startDecryption(KMime::Content *data = nullptr); QByteArray mDecryptedData; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: /** Handles the dectyptioon of a given content * returns true if the decryption was successful * if used in async mode, check if mMetaData.inProgress is true, it inicates a running decryption process. */ bool okDecryptMIME(KMime::Content &data); protected: const CryptoProtocol mProtocol; QString mFromAddress; QByteArray mVerifiedText; KMime::Content *mEncryptedNode; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; class SignedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isSigned READ isSigned) public: typedef QSharedPointer Ptr; SignedMessagePart(ObjectTreeParser *otp, const QString &text, const CryptoProtocol protocol, const QString &fromAddress, KMime::Content *node, KMime::Content *signedData); virtual ~SignedMessagePart(); void setIsSigned(bool isSigned); bool isSigned() const; void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature); void startVerification(); QByteArray mDecryptedData; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; private: void sigStatusToMetaData(const Crypto::Signature &signature); void setVerificationResult(const Crypto::VerificationResult &result, bool parseText, const QByteArray &plainText); protected: CryptoProtocol mProtocol; QString mFromAddress; KMime::Content *mSignedData; friend EncryptedMessagePart; friend class DefaultRendererPrivate; friend class ::PartPrivate; }; class HeadersPart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; HeadersPart(ObjectTreeParser *otp, KMime::Content *node); virtual ~HeadersPart(); }; } #endif //__MIMETREEPARSER_MESSAGEPART_H__ diff --git a/framework/src/domain/mime/partmodel.cpp b/framework/src/domain/mime/partmodel.cpp index a80c2e69..74b53a13 100644 --- a/framework/src/domain/mime/partmodel.cpp +++ b/framework/src/domain/mime/partmodel.cpp @@ -1,415 +1,425 @@ /* 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 "partmodel.h" #include #include "htmlutils.h" #include #include class PartModelPrivate { public: PartModelPrivate(PartModel *q_ptr, const std::shared_ptr &parser); ~PartModelPrivate() = default; void checkPart(const MimeTreeParser::MessagePart::Ptr part) { //Has to contain html, and be an alternative part (so it's not only html) if (part->isHtml() && part.dynamicCast()) { containsHtmlAndPlain = true; emit q->containsHtmlChanged(); } } //Recursively find encapsulated messages void findEncapsulated(const MimeTreeParser::EncapsulatedRfc822MessagePart::Ptr &e) { mEncapsulatedParts[e.data()] = mParser->collectContentParts(e); for (auto subPart : mEncapsulatedParts[e.data()]) { checkPart(subPart); mParents[subPart.data()] = e.data(); if (auto encapsulatedSub = subPart.dynamicCast()) { findEncapsulated(encapsulatedSub); } } } PartModel *q; QVector mParts; QHash> mEncapsulatedParts; QHash mParents; std::shared_ptr mParser; bool showHtml{false}; bool containsHtmlAndPlain{false}; }; PartModelPrivate::PartModelPrivate(PartModel *q_ptr, const std::shared_ptr &parser) : q(q_ptr) , mParser(parser) { mParts = mParser->collectContentParts(); for (auto p : mParts) { checkPart(p); if (auto e = p.dynamicCast()) { findEncapsulated(e); } } } PartModel::PartModel(std::shared_ptr parser) : d(std::unique_ptr(new PartModelPrivate(this, parser))) { } PartModel::~PartModel() { } void PartModel::setShowHtml(bool html) { if (d->showHtml != html) { beginResetModel(); d->showHtml = html; endResetModel(); emit showHtmlChanged(); } } bool PartModel::showHtml() const { return d->showHtml; } bool PartModel::containsHtml() const { return d->containsHtmlAndPlain; } QHash PartModel::roleNames() const { QHash roles; roles[TypeRole] = "type"; roles[ContentRole] = "content"; roles[IsEmbeddedRole] = "embedded"; roles[IsEncryptedRole] = "encrypted"; roles[IsSignedRole] = "signed"; roles[SecurityLevelRole] = "securityLevel"; roles[EncryptionSecurityLevelRole] = "encryptionSecurityLevel"; roles[SignatureSecurityLevelRole] = "signatureSecurityLevel"; roles[ErrorType] = "errorType"; roles[ErrorString] = "errorString"; roles[IsErrorRole] = "error"; roles[SenderRole] = "sender"; roles[SignatureDetails] = "signatureDetails"; roles[EncryptionDetails] = "encryptionDetails"; roles[DateRole] = "date"; return roles; } QModelIndex PartModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } if (parent.isValid()) { if (auto e = dynamic_cast(static_cast(parent.internalPointer()))) { const auto parts = d->mEncapsulatedParts[e]; if (row < parts.size()) { return createIndex(row, column, parts.at(row).data()); } } return QModelIndex(); } if (row < d->mParts.size()) { return createIndex(row, column, d->mParts.at(row).data()); } return QModelIndex(); } static QString addCss(const QString &s) { //Get the default font from QApplication static const auto fontFamily = QFont{}.family(); //overflow:hidden ensures no scrollbars are ever shown. static const auto css = QString(""); const auto header = QLatin1String("\n" "") + css + QLatin1String("\n\n"); return header + s + QStringLiteral(""); } SignatureInfo *encryptionInfo(MimeTreeParser::MessagePart *messagePart) { auto signatureInfo = new SignatureInfo; const auto encryptions = messagePart->encryptions(); if (encryptions.size() > 1) { qWarning() << "Can't deal with more than one encryption"; } for (const auto &p : encryptions) { signatureInfo->keyId = p->partMetaData()->keyId; } return signatureInfo; }; SignatureInfo *signatureInfo(MimeTreeParser::MessagePart *messagePart) { auto signatureInfo = new SignatureInfo; const auto signatureParts = messagePart->signatures(); if (signatureParts.size() > 1) { qWarning() << "Can't deal with more than one signature"; } for (const auto &p : signatureParts) { signatureInfo->keyId = p->partMetaData()->keyId; signatureInfo->keyMissing = p->partMetaData()->keyMissing; signatureInfo->keyExpired = p->partMetaData()->keyExpired; signatureInfo->keyRevoked = p->partMetaData()->keyRevoked; signatureInfo->sigExpired = p->partMetaData()->sigExpired; signatureInfo->crlMissing = p->partMetaData()->crlMissing; signatureInfo->crlTooOld = p->partMetaData()->crlTooOld; signatureInfo->signer = p->partMetaData()->signer; signatureInfo->signerMailAddresses = p->partMetaData()->signerMailAddresses; signatureInfo->signatureIsGood = p->partMetaData()->isGoodSignature; signatureInfo->keyIsTrusted = p->partMetaData()->keyIsTrusted; } return signatureInfo; } QVariant PartModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.internalPointer()) { const auto messagePart = static_cast(index.internalPointer()); // qWarning() << "Found message part " << messagePart->metaObject()->className() << messagePart->partMetaData()->status << messagePart->error(); Q_ASSERT(messagePart); switch(role) { case Qt::DisplayRole: return QStringLiteral("Content%1"); case SenderRole: { if (auto e = dynamic_cast(messagePart)) { return e->from(); } return {}; } case DateRole: { if (auto e = dynamic_cast(messagePart)) { return e->date(); } return {}; } case TypeRole: { if (messagePart->error()) { return "error"; } if (dynamic_cast(messagePart)) { return "encapsulated"; } if (!d->showHtml && d->containsHtmlAndPlain) { return "plain"; } + if (auto alternativePart = dynamic_cast(messagePart)) { + if (alternativePart->availableModes().contains(MimeTreeParser::Util::MultipartIcal)) { + return "ical"; + } + } //For simple html we don't need a browser auto complexHtml = [&] { if (messagePart->isHtml()) { const auto text = messagePart->htmlContent(); if (text.contains("error(); case ContentRole: { + if (auto alternativePart = dynamic_cast(messagePart)) { + if (alternativePart->availableModes().contains(MimeTreeParser::Util::MultipartIcal)) { + return alternativePart->icalContent(); + } + } if (!d->showHtml && d->containsHtmlAndPlain) { return HtmlUtils::linkify(Qt::convertFromPlainText(messagePart->isHtml() ? messagePart->plaintextContent() : messagePart->text())); } const auto text = messagePart->isHtml() ? messagePart->htmlContent() : messagePart->text(); if (messagePart->isHtml()) { return addCss(d->mParser->resolveCidLinks(text)); } else { //We assume plain //We alwas do richtext (so we get highlighted links and stuff). return HtmlUtils::linkify(Qt::convertFromPlainText(text)); } return text; } case IsEncryptedRole: return messagePart->encryptionState() != MimeTreeParser::KMMsgNotEncrypted; case IsSignedRole: return messagePart->signatureState() != MimeTreeParser::KMMsgNotSigned; case SecurityLevelRole: { auto signature = messagePart->signatureState(); auto encryption = messagePart->encryptionState(); bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned; bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted; if (messageIsSigned) { auto sigInfo = std::unique_ptr{signatureInfo(messagePart)}; if (!sigInfo->signatureIsGood) { if (sigInfo->keyMissing || sigInfo->keyExpired) { return "notsogood"; } return "bad"; } } //All good if (messageIsSigned || messageIsEncrypted) { return "good"; } //No info return "unknown"; } case EncryptionSecurityLevelRole: { auto encryption = messagePart->encryptionState(); bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted; if (messagePart->error()) { return "bad"; } //All good if (messageIsEncrypted) { return "good"; } //No info return "unknown"; } case SignatureSecurityLevelRole: { auto signature = messagePart->signatureState(); bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned; if (messageIsSigned) { auto sigInfo = std::unique_ptr{signatureInfo(messagePart)}; if (!sigInfo->signatureIsGood) { if (sigInfo->keyMissing || sigInfo->keyExpired) { return "notsogood"; } return "bad"; } return "good"; } //No info return "unknown"; } case SignatureDetails: return QVariant::fromValue(signatureInfo(messagePart)); case EncryptionDetails: return QVariant::fromValue(encryptionInfo(messagePart)); case ErrorType: return messagePart->error(); case ErrorString: { switch (messagePart->error()) { case MimeTreeParser::MessagePart::NoKeyError: return tr("No key available."); case MimeTreeParser::MessagePart::PassphraseError: return tr("Wrong passphrase."); case MimeTreeParser::MessagePart::UnknownError: break; default: break; } return messagePart->errorString(); } } } return QVariant(); } QModelIndex PartModel::parent(const QModelIndex &index) const { if (index.isValid()) { if (auto e = static_cast(index.internalPointer())) { for (const auto &p : d->mParts) { if (p.data() == e) { return QModelIndex(); } } const auto parentPart = d->mParents[e]; Q_ASSERT(parentPart); int row = 0; const auto parts = d->mEncapsulatedParts[parentPart]; for (const auto &p : parts) { if (p.data() == e) { break; } row++; } return createIndex(row, 0, parentPart); } return {}; } return {}; } int PartModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { if (auto e = dynamic_cast(static_cast(parent.internalPointer()))) { const auto parts = d->mEncapsulatedParts[e]; return parts.size(); } return 0; } return d->mParts.count(); } int PartModel::columnCount(const QModelIndex &) const { return 1; } diff --git a/framework/src/frameworkplugin.cpp b/framework/src/frameworkplugin.cpp index cd01179c..f4d5aa09 100644 --- a/framework/src/frameworkplugin.cpp +++ b/framework/src/frameworkplugin.cpp @@ -1,212 +1,214 @@ /* Copyright (c) 2016 Michael Bohlender Copyright (c) 2016 Christian Mollekopf 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 "frameworkplugin.h" #include "domain/maillistmodel.h" #include "domain/folderlistmodel.h" #include "domain/perioddayeventmodel.h" #include "domain/multidayeventmodel.h" #include "domain/eventoccurrencemodel.h" #include "domain/todomodel.h" #include "domain/composercontroller.h" #include "domain/mime/messageparser.h" #include "domain/retriever.h" #include "domain/outboxmodel.h" #include "domain/mouseproxy.h" #include "domain/contactcontroller.h" #include "domain/eventcontroller.h" +#include "domain/invitationcontroller.h" #include "domain/todocontroller.h" #include "domain/peoplemodel.h" #include "domain/textdocumenthandler.h" #include "domain/settings/accountsettings.h" #include "accounts/accountsmodel.h" #include "accounts/accountfactory.h" #include "settings/settings.h" #include "fabric.h" #include "kubeimage.h" #include "clipboardproxy.h" #include "startupcheck.h" #include "keyring.h" #include "controller.h" #include "domainobjectcontroller.h" #include "extensionmodel.h" #include "viewhighlighter.h" #include "file.h" #include "logmodel.h" #include "entitymodel.h" #include "qquicktreemodeladaptor.h" #include #include #include class KubeImageProvider : public QQuickImageProvider { public: KubeImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) { } QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE { //The platform theme plugin can overwrite our setting again once it gets loaded, //so we check on every icon load request... if (QIcon::themeName() != "kube") { QIcon::setThemeName("kube"); } const auto icon = QIcon::fromTheme(id); auto expectedSize = requestedSize; //Get the largest size that is still smaller or equal than requested //Except if we only have larger sizes, then just pick the closest one bool first = true; for (const auto s : icon.availableSizes()) { if (first && s.width() > requestedSize.width()) { expectedSize = s; break; } first = false; if (s.width() <= requestedSize.width()) { expectedSize = s; } } const auto pixmap = icon.pixmap(expectedSize); if (size) { *size = pixmap.size(); } return pixmap; } }; static QObject *fabric_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new Kube::Fabric::Fabric; } static QObject *keyring_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) auto instance = Kube::Keyring::instance(); QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership); return instance; } static QString findFile(const QString file, const QStringList importPathList) { for (const auto &path : importPathList) { const QString f = path + file; if (QFileInfo::exists(f)) { return f; } } return {}; } void FrameworkPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(uri); engine->addImageProvider(QLatin1String("kube"), new KubeImageProvider); QString kubeIcons = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kube-icons.rcc")); //For windows if (kubeIcons.isEmpty()) { const auto locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation) + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); kubeIcons = findFile(QStringLiteral("/kube/kube-icons.rcc"), locations); } //For osx if (kubeIcons.isEmpty()) { //On Mac OS we want to include Contents/Resources/ in the bundle, and that path is in AppDataLocations. QStringList iconSearchPaths; for (const auto &p : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)) { auto iconPath = p; //I'm getting broken paths reported from standardLocations if (iconPath.contains("kube.appContents")) { iconPath.replace("kube.appContents", "kube.app/Contents"); } if (iconPath.contains("kube-kolabnow.appContents")) { iconPath.replace("kube-kolabnow.appContents", "kube-kolabnow.app/Contents"); } iconSearchPaths << iconPath; } kubeIcons = findFile(QStringLiteral("/kube/kube-icons.rcc"), iconSearchPaths); } if (!QResource::registerResource(kubeIcons, "/icons/kube")) { qWarning() << "Failed to register icon resource!" << kubeIcons; qWarning() << "Searched paths: " << QStandardPaths::standardLocations(QStandardPaths::AppDataLocation) + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); Q_ASSERT(false); } else { QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/icons")); QIcon::setThemeName(QStringLiteral("kube")); } } void FrameworkPlugin::registerTypes (const char *uri) { qmlRegisterType(uri, 1, 0, "FolderListModel"); qmlRegisterType(uri, 1, 0, "MailListModel"); qmlRegisterType(uri, 1, 0, "PeriodDayEventModel"); qmlRegisterType(uri, 1, 0, "MultiDayEventModel"); qmlRegisterType(uri, 1, 0, "EventOccurrenceModel"); qmlRegisterType(uri, 1, 0, "EventController"); + qmlRegisterType(uri, 1, 0, "InvitationController"); qmlRegisterType(uri, 1, 0, "TodoModel"); qmlRegisterType(uri, 1, 0, "TodoController"); qmlRegisterType(uri, 1, 0, "ComposerController"); qmlRegisterUncreatableType(uri, 1, 0, "ListPropertyController", "abstract"); qmlRegisterUncreatableType(uri, 1, 0, "Selector", "abstract"); qmlRegisterUncreatableType(uri, 1, 0, "Completer", "abstract"); qmlRegisterType(uri, 1, 0, "ControllerAction"); qmlRegisterType(uri, 1, 0, "MessageParser"); qmlRegisterType(uri, 1, 0, "Retriever"); qmlRegisterType(uri, 1, 0, "OutboxModel"); qmlRegisterType(uri, 1, 0, "MouseProxy"); qmlRegisterType(uri, 1, 0,"ContactController"); qmlRegisterType(uri, 1, 0,"PeopleModel"); qmlRegisterType(uri, 1, 0, "TextDocumentHandler"); qmlRegisterType(uri, 1, 0, "LogModel"); qmlRegisterType(uri, 1, 0, "EntityModel"); qmlRegisterType(uri, 1, 0, "EntityLoader"); qmlRegisterType(uri, 1, 0, "CheckedEntities"); qmlRegisterType(uri, 1, 0, "CheckableEntityModel"); qmlRegisterType(uri, 1, 0, "TreeModelAdaptor"); qmlRegisterType(uri, 1, 0, "AccountFactory"); qmlRegisterType(uri, 1, 0, "AccountsModel"); qmlRegisterType(uri, 1, 0, "AccountSettings"); qmlRegisterType(uri, 1, 0, "ExtensionModel"); qmlRegisterType(uri, 1, 0, "File"); qmlRegisterType(uri, 1, 0, "Settings"); qmlRegisterType(uri, 1, 0, "Listener"); qmlRegisterType(uri, 1, 0, "DomainObjectController"); qmlRegisterSingletonType(uri, 1, 0, "Fabric", fabric_singletontype_provider); qmlRegisterType(uri, 1, 0, "KubeImage"); qmlRegisterType(uri, 1, 0, "Clipboard"); qmlRegisterType(uri, 1, 0, "StartupCheck"); qmlRegisterType(uri, 1, 0, "ViewHighlighter"); qmlRegisterSingletonType(uri, 1, 0, "Keyring", keyring_singletontype_provider); }