Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F23020426
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
52 KB
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94c0b7c..bfa85cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,134 +1,136 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(PIM_VERSION "5.23.42")
project(MimeTreeParser VERSION ${PIM_VERSION})
# ECM setup
set(KF_MIN_VERSION "5.105.0")
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${ECM_MODULE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
)
set(QT_REQUIRED_VERSION "5.15.2")
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
endif()
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(ECMGeneratePriFile)
+include(ECMQmlModule)
include(ECMSetupVersion)
include(FeatureSummary)
include(KDEGitCommitHooks)
include(KDEClangFormat)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMAddQch)
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
set(KF_MIN_VERSION "5.240.0")
set(KF_MAJOR_VERSION "6")
else()
set(KF_MAJOR_VERSION "5")
endif()
set(KPIM_MIME_VERSION "5.23.40")
ecm_setup_version(PROJECT
VARIABLE_PREFIX MIMETREEPARSER
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfigVersion.cmake"
SOVERSION 5
)
configure_file(mimetreeparser-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/mimetreeparser-version.h @ONLY)
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
if(BUILD_TESTING)
add_definitions(-DBUILD_TESTING)
endif()
########### Find packages ###########
find_package(Qt${KF_MAJOR_VERSION}Gui ${QT_MIN_VERSION} CONFIG REQUIRED)
+find_package(Qt${KF_MAJOR_VERSION}Quick ${QT_MIN_VERSION} CONFIG REQUIRED)
find_package(KF${KF_MAJOR_VERSION}I18n ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF${KF_MAJOR_VERSION}Codecs ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}Mime ${KPIM_MIME_VERSION} CONFIG REQUIRED)
find_package(Gpgme REQUIRED)
########### Targets ###########
ecm_set_disabled_deprecation_versions(QT 6.4 KF 5.105.0)
option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF)
if (USE_UNITY_CMAKE_SUPPORT)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON)
add_definitions(-DCOMPILE_WITH_UNITY_CMAKE_SUPPORT)
endif()
add_subdirectory(src)
add_subdirectory(examples)
if (BUILD_TESTING)
#add_subdirectory(autotests)
endif ()
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim${KF_MAJOR_VERSION}MimeTreeParser")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim${KF_MAJOR_VERSION}MimeTreeparser_QCH
FILE KPim${KF_MAJOR_VERSION}MimeTreeParserQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim${KF_MAJOR_VERSION}MimeTreeParserTargets.cmake NAMESPACE KPim${KF_MAJOR_VERSION}::)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser COMPONENT Devel
)
ecm_qt_install_logging_categories(
EXPORT MIMETREEPARSER
FILE mimetreeparser.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
ki18n_install(po)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/examples/qml/content/main.qml b/examples/qml/content/main.qml
index 3d336a2..022a6b2 100644
--- a/examples/qml/content/main.qml
+++ b/examples/qml/content/main.qml
@@ -1,33 +1,34 @@
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.0
import org.kde.kirigami 2.20 as Kirigami
+import org.kde.pim.mimetreeparser 1.0
import org.kde.mimetreeparser 1.0
Kirigami.ApplicationWindow {
id: root
readonly property Kirigami.Action openFileAction: Kirigami.Action {
text: i18n("Open File")
onTriggered: fileDialog.open()
}
FileDialog {
id: fileDialog
title: i18n("Choose file")
onAccepted: fileOpener.open(fileUrl)
}
FileOpener {
id: fileOpener
objectName: "FileOpener"
onMessageOpened: pageStack.currentItem.message = message
}
pageStack.initialPage: MailViewer {
}
}
diff --git a/examples/qml/resources.qrc b/examples/qml/resources.qrc
index d2e4b11..b351c4d 100644
--- a/examples/qml/resources.qrc
+++ b/examples/qml/resources.qrc
@@ -1,19 +1,9 @@
<RCC>
<!--
SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: GPL-2.0-or-later
-->
<qresource prefix="/">
<file>content/main.qml</file>
- <file>content/Banner.qml</file>
- <file>content/ErrorPart.qml</file>
- <file>content/HtmlPart.qml</file>
- <file>content/ICalPart.qml</file>
- <file>content/MailPart.qml</file>
- <file>content/MailPartModel.qml</file>
- <file>content/MailPartView.qml</file>
- <file>content/MailViewer.qml</file>
- <file>content/TextPart.qml</file>
- <file>content/AttachmentDelegate.qml</file>
</qresource>
</RCC>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 858c3fc..493306f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,133 +1,135 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
add_definitions(-DTRANSLATION_DOMAIN=\"libmimetreeparser5\")
+add_subdirectory(plugins)
+
add_library(KPim${KF_MAJOR_VERSION}MimeTreeParser)
add_library(KPim${KF_MAJOR_VERSION}::MimeTreeParser ALIAS KPim${KF_MAJOR_VERSION}MimeTreeParser)
target_sources(KPim${KF_MAJOR_VERSION}MimeTreeParser PRIVATE
crypto.h
crypto.cpp
errors.h
async.h
attachmentmodel.h
bodypartformatter.h
bodypartformatterbasefactory.h
bodypartformatterbasefactory_p.h
cryptohelper.h
enums.h
htmlutils.h
mailcrypto.h
messageparser.h
messagepart.h
mimetreeparser_debug.h
objecttreeparser.h
partmetadata.h
partmodel.h
utils.h
attachmentmodel.cpp
bodypartformatter.cpp
bodypartformatter_impl.cpp
bodypartformatterbasefactory.cpp
cryptohelper.cpp
htmlutils.cpp
mailcrypto.cpp
messageparser.cpp
messagepart.cpp
mimetreeparser_debug.cpp
objecttreeparser.cpp
partmodel.cpp
utils.cpp
)
ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}MimeTreeParser
HEADER mimetreeparser_debug.h
IDENTIFIER MIMETREEPARSER_LOG
CATEGORY_NAME org.kde.pim.mimetreeparser
DESCRIPTION "mimetreeparser (pim lib)"
EXPORT MIMETREEPARSER
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParser PROPERTIES UNITY_BUILD ON)
endif()
generate_export_header(KPim${KF_MAJOR_VERSION}MimeTreeParser BASE_NAME mimetreeparser)
target_include_directories(KPim${KF_MAJOR_VERSION}MimeTreeParser INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/AkonadiCalendar>")
target_include_directories(KPim${KF_MAJOR_VERSION}MimeTreeParser PUBLIC "$<BUILD_INTERFACE:${Akonadi-Calendar_SOURCE_DIR}/src;${Akonadi-Calendar_BINARY_DIR}/src>")
target_link_libraries(KPim${KF_MAJOR_VERSION}MimeTreeParser
PUBLIC
KPim${KF_MAJOR_VERSION}::Mime
KF${KF_MAJOR_VERSION}::I18n
Qt${KF_MAJOR_VERSION}::Gui
PRIVATE
KF${KF_MAJOR_VERSION}::Codecs
Gpgme::Gpgme
)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParser PROPERTIES
VERSION ${MIMETREEPARSER_VERSION}
SOVERSION ${MIMETREEPARSER_SOVERSION}
EXPORT_NAME MimeTreeParser
)
ecm_generate_pri_file(BASE_NAME MimeTreeParser
LIB_NAME KPim${KF_MAJOR_VERSION}MimeTreeParser
DEPS "MimeTreeParser"
FILENAME_VAR PRI_FILENAME
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(TARGETS
KPim${KF_MAJOR_VERSION}MimeTreeParser
EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
ecm_generate_headers(MimeTreeParser_CamelCase_HEADERS
HEADER_NAMES
AttachmentModel
ObjectTreeParser
MessageParser
REQUIRED_HEADERS MimeTreeParser_HEADERS
PREFIX MimeTreeParser
)
install(FILES
${MimeTreeParser_CamelCase_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser/MimeTreeParser
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_export.h
${MimeTreeParser_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser/mimetreeparser
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KPim${KF_MAJOR_VERSION}MimeTreeParser_QCH
NAME MimeTreeParser
BASE_NAME KPim${KF_MAJOR_VERSION}MimeTreeParser
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
# using only public headers, to cover only public API
SOURCES ${MimeTreeParser_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt${QT_MAJOR_VERSION}Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
MIMETREEPARSER_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
diff --git a/src/messageparser.cpp b/src/messageparser.cpp
index 82ee32e..d3cbef4 100644
--- a/src/messageparser.cpp
+++ b/src/messageparser.cpp
@@ -1,127 +1,137 @@
// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messageparser.h"
#include "objecttreeparser.h"
#include <KLocalizedString>
#include <QElapsedTimer>
#include "async.h"
#include "attachmentmodel.h"
#include "partmodel.h"
class MessagePartPrivate
{
public:
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
KMime::Message::Ptr mMessage;
};
MessageParser::MessageParser(QObject *parent)
: QObject(parent)
, d(std::unique_ptr<MessagePartPrivate>(new MessagePartPrivate))
{
}
MessageParser::~MessageParser()
{
}
KMime::Message::Ptr MessageParser::message() const
{
return d->mMessage;
}
void MessageParser::setMessage(const KMime::Message::Ptr message)
{
if (message == d->mMessage) {
return;
}
d->mMessage = message;
QElapsedTimer time;
time.start();
auto parser = std::make_shared<MimeTreeParser::ObjectTreeParser>();
parser->parseObjectTree(message.data());
qDebug() << "Message parsing took: " << time.elapsed();
parser->decryptParts();
qDebug() << "Message parsing and decryption/verification: " << time.elapsed();
d->mParser = parser;
Q_EMIT htmlChanged();
}
bool MessageParser::loaded() const
{
return bool{d->mParser};
}
QString MessageParser::structureAsString() const
{
if (!d->mParser) {
return {};
}
return d->mParser->structureAsString();
}
QAbstractItemModel *MessageParser::parts() const
{
if (!d->mParser) {
return nullptr;
}
const auto model = new PartModel(d->mParser);
return model;
}
QAbstractItemModel *MessageParser::attachments() const
{
if (!d->mParser) {
return nullptr;
}
const auto model = new AttachmentModel(d->mParser);
return model;
}
QString MessageParser::subject() const
{
- if (d->mMessage && d->mMessage->subject()) {
+ if (d->mMessage && d->mMessage->subject() && d->mMessage->subject()->asUnicodeString().length()) {
return d->mMessage->subject()->asUnicodeString();
} else {
+ if (d->mMessage) {
+ const auto contents = d->mMessage->contents();
+ for (const auto content : contents) {
+ if (const auto message = dynamic_cast<KMime::Message *>(content)) {
+ if (message->subject() && message->subject()->asUnicodeString().length()) {
+ return message->subject()->asUnicodeString();
+ }
+ }
+ }
+ }
return i18nc("displayed as subject when the subject of a mail is empty", "No Subject");
}
}
QString MessageParser::from() const
{
if (d->mMessage && d->mMessage->from()) {
return d->mMessage->from()->asUnicodeString();
} else {
return QString();
}
}
QString MessageParser::sender() const
{
if (d->mMessage && d->mMessage->sender()) {
return d->mMessage->sender()->asUnicodeString();
} else {
return QString();
}
}
QString MessageParser::to() const
{
if (d->mMessage && d->mMessage->to()) {
return d->mMessage->to()->asUnicodeString();
} else {
return i18nc("displayed when a mail has unknown sender, receiver or date", "Unknown");
}
}
QDateTime MessageParser::date() const
{
if (d->mMessage && d->mMessage->date()) {
return d->mMessage->date()->dateTime();
} else {
return QDateTime();
}
}
diff --git a/src/messagepart.cpp b/src/messagepart.cpp
index ce60a74..5246103 100644
--- a/src/messagepart.cpp
+++ b/src/messagepart.cpp
@@ -1,962 +1,964 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messagepart.h"
#include "cryptohelper.h"
#include "mimetreeparser_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <KMime/Content>
#include <QTextCodec>
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 : std::as_const(mNodesToDelete)) {
delete n;
}
}
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 {};
}
if (const auto cd = mNode->contentDisposition(false)) {
const auto name = cd->filename();
// Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways.
// Not the recommended way, but exists.
if (!name.isEmpty()) {
return name;
}
}
if (const auto ct = mNode->contentType(false)) {
return ct->name();
}
return {};
}
static KMime::Headers::ContentType *contentType(KMime::Content *node)
{
if (node) {
return node->contentType(false);
}
return nullptr;
}
QByteArray MessagePart::charset() const
{
if (!mNode) {
return "us-ascii";
}
if (auto ct = contentType(mNode)) {
return ct->charset();
}
// Per rfc2045 us-ascii is the default
return "us-ascii";
}
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();
for (const auto &part : std::as_const(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();
tempNode->contentType()->setCharset(charset());
bindLifetime(tempNode);
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString("temporary node");
}
parseInternal(tempNode);
}
QString MessagePart::renderInternalText() const
{
QString text;
for (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::Ptr> &MessagePart::subParts() const
{
return mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !mBlocks.isEmpty();
}
QVector<SignedMessagePart *> MessagePart::signatures() const
{
QVector<SignedMessagePart *> list;
if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
QVector<EncryptedMessagePart *> MessagePart::encryptions() const
{
QVector<EncryptedMessagePart *> list;
if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<EncryptedMessagePart *>(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;
}
KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const
{
if (node() && node()->hasHeader(header)) {
return node()->headerByType(header);
}
if (auto parent = parentPart()) {
return parent->header(header);
}
return nullptr;
}
//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
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();
}
void TextMessagePart::parseContent()
{
mSignatureState = KMMsgNotSigned;
mEncryptionState = KMMsgNotEncrypted;
const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
// We also get blocks for unencrypted messages
if (!blocks.isEmpty()) {
const auto aCodec = mOtp->codecFor(mNode);
const auto cryptProto = OpenPGP;
/* 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(KMime::CRLFtoLF(block.text())))));
} else if (block.type() == PgpMessageBlock) {
KMime::Content *content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false));
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();
content->contentType()->setCharset(charset());
SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false));
mp->bindLifetime(content);
mp->setIsSigned(true);
appendSubPart(mp);
} else {
continue;
}
const auto mp = subParts().last().staticCast<MessagePart>();
const PartMetaData *messagePart(mp->partMetaData());
if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
mp->setText(aCodec->toUnicode(KMime::CRLFtoLF(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)
{
}
//-----HtmlMessageBlock----------------------
HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setText(mOtp->codecFor(mNode)->toUnicode(KMime::CRLFtoLF(mNode->decodedContent())));
}
//-----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[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
}
if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) {
mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true));
}
if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) {
mChildParts[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 : parts) {
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[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr));
}
}
}
AlternativeMessagePart::~AlternativeMessagePart()
{
}
QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes()
{
return mChildParts.keys();
}
QString AlternativeMessagePart::text() const
{
if (mChildParts.contains(MultipartPlain)) {
return mChildParts[MultipartPlain]->text();
}
return QString();
}
bool AlternativeMessagePart::isHtml() const
{
return mChildParts.contains(MultipartHtml);
}
QString AlternativeMessagePart::plaintextContent() const
{
return text();
}
QString AlternativeMessagePart::htmlContent() const
{
if (mChildParts.contains(MultipartHtml)) {
return mChildParts[MultipartHtml]->text();
} else {
return plaintextContent();
}
}
QString AlternativeMessagePart::icalContent() const
{
if (mChildParts.contains(MultipartIcal)) {
return mChildParts[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 CryptoProtocol cryptoProto,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption)
: MessagePart(otp, {}, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mSignedData(signedData)
{
mMetaData.isSigned = true;
mMetaData.isGoodSignature = false;
mMetaData.status = i18n("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);
}
static void sigStatusToMetaData(PartMetaData &mMetaData, const Signature &signature)
{
mMetaData.isGoodSignature = !signature.status;
if (!mMetaData.isGoodSignature) {
qWarning() << "The signature is bad." << signature.status;
}
// save extended signature status flags
mMetaData.keyMissing = signature.result == Crypto::Signature::KeyNotFound;
mMetaData.keyExpired = signature.result == Crypto::Signature::Expired;
Key key;
if (mMetaData.isGoodSignature) {
// Search for the key by its fingerprint so that we can check for trust etc.
const auto keys = findKeys({QString::fromUtf8(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.isTrusted;
if (!key.userIds.empty()) {
mMetaData.signer = prettifyDN(key.userIds[0].id.data());
}
for (const auto &userId : key.userIds) {
QString email = QString::fromUtf8(userId.email);
// Work around cryptplug bug where email addresses are specified as angle-addr ( <person@example.org> ),
// not addr-spec ( person@example.org ):
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.data());
}
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) {
return;
}
mMetaData.isSigned = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
const auto codec = mOtp->codecFor(mSignedData);
// If we have a mNode, this is a detached signature
if (mNode) {
const auto signature = mNode->decodedContent();
// This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF)
const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent());
setVerificationResult(Crypto::verifyDetachedSignature(mProtocol, signature, signedData), signedData);
setText(codec->toUnicode(KMime::CRLFtoLF(signedData)));
} else {
QByteArray outdata;
setVerificationResult(Crypto::verifyOpaqueSignature(mProtocol, mSignedData->decodedContent(), outdata), outdata);
setText(codec->toUnicode(KMime::CRLFtoLF(outdata)));
}
if (!mMetaData.isSigned) {
mMetaData.creationTime = QDateTime();
}
}
void SignedMessagePart::setVerificationResult(const VerificationResult &result, const QByteArray &signedData)
{
const auto signatures = result.signatures;
// FIXME
// mMetaData.auditLogError = result.error;
if (!signatures.empty()) {
mMetaData.isSigned = true;
sigStatusToMetaData(mMetaData, signatures.front());
if (!signedData.isEmpty() && mParseAfterDecryption) {
parseInternal(signedData);
}
}
}
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,
KMime::Content *node,
KMime::Content *encryptedNode,
bool parseAfterDecryption)
: MessagePart(otp, text, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mEncryptedNode(encryptedNode)
{
mMetaData.isSigned = false;
mMetaData.isGoodSignature = false;
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
mMetaData.isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return mMetaData.isEncrypted;
}
bool EncryptedMessagePart::isDecryptable() const
{
return mMetaData.isDecryptable;
}
bool EncryptedMessagePart::decrypt(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) = Crypto::decryptAndVerify(mProtocol, ciphertext, plainText);
mMetaData.isSigned = verifyResult.signatures.size() > 0;
// Normalize CRLF's
plainText = KMime::CRLFtoLF(plainText);
const auto codec = mOtp->codecFor(&data);
const auto decoded = codec->toUnicode(plainText);
if (verifyResult.signatures.size() > 0) {
// We simply attach a signed message part to indicate that this content is also signed
// We're forwarding mNode to not loose the encoding information
auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mProtocol, mNode, nullptr));
subPart->setText(decoded);
subPart->setVerificationResult(verifyResult, plainText);
appendSubPart(subPart);
}
if (decryptResult.error && mMetaData.isSigned) {
// Only a signed part
mMetaData.isEncrypted = false;
mDecryptedData = plainText;
return true;
}
if (mMetaData.isEncrypted) {
mMetaData.keyId = [&] {
for (const auto &recipient : std::as_const(decryptResult.recipients)) {
if (recipient.secretKeyAvailable) {
return recipient.keyId;
}
}
return QByteArray{};
}();
}
if (!decryptResult.error) {
mDecryptedData = plainText;
setText(decoded);
} else {
mMetaData.isEncrypted = decryptResult.result != Crypto::DecryptionResult::NotEncrypted;
qWarning() << "Failed to decrypt : " << decryptResult.error;
if (decryptResult.result == Crypto::DecryptionResult::NoSecretKeyError) {
mError = NoKeyError;
mMetaData.errorText = i18n("Could not decrypt the data: no key found for recipients.");
} else if (decryptResult.result == Crypto::DecryptionResult::PassphraseError) {
mError = PassphraseError;
} else {
mError = UnknownError;
mMetaData.errorText = i18n("Could not decrypt the data.");
}
setText(QString::fromUtf8(mDecryptedData.constData()));
return false;
}
+ qWarning() << "decrypt" << text();
+
return true;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
mMetaData.isEncrypted = true;
mMetaData.isDecryptable = decrypt(*data);
if (mParseAfterDecryption && !mMetaData.isSigned) {
parseInternal(mDecryptedData);
}
}
void EncryptedMessagePart::startDecryption()
{
if (mEncryptedNode) {
startDecryption(mEncryptedNode);
} else {
startDecryption(mNode);
}
}
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<SignedMessagePart>();
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;
if (!mMessage) {
qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
return;
}
parseInternal(message.data());
}
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)
{
}
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
new file mode 100644
index 0000000..1c8a3e0
--- /dev/null
+++ b/src/plugins/CMakeLists.txt
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
+# SPDX-License-Identifier: BSD-3-Clause
+
+ecm_add_qml_module(mimetreeparser_plugin URI "org.kde.pim.mimetreeparser" VERSION 1.0)
+
+target_sources(mimetreeparser_plugin PRIVATE
+ mimetreeparserplugin.cpp
+ mimetreeparserplugin.h
+)
+
+target_link_libraries(mimetreeparser_plugin PRIVATE
+ Qt::Quick
+ KPim${KF_MAJOR_VERSION}::MimeTreeParser
+)
+
+ecm_target_qml_sources(mimetreeparser_plugin
+ SOURCES
+ qml/MailViewer.qml
+)
+
+ecm_target_qml_sources(mimetreeparser_plugin PRIVATE
+ PATH private
+ SOURCES
+ qml/private/AttachmentDelegate.qml
+ qml/private/Banner.qml
+ qml/private/ErrorPart.qml
+ qml/private/HtmlPart.qml
+ qml/private/ICalPart.qml
+ qml/private/MailPart.qml
+ qml/private/MailPartModel.qml
+ qml/private/MailPartView.qml
+ qml/private/TextPart.qml
+)
+
+ecm_finalize_qml_module(mimetreeparser_plugin
+ DESTINATION ${KDE_INSTALL_QMLDIR}
+ BUILD_SHARED_LIBS OFF
+)
diff --git a/src/plugins/mimetreeparserplugin.cpp b/src/plugins/mimetreeparserplugin.cpp
new file mode 100644
index 0000000..6e8bfe3
--- /dev/null
+++ b/src/plugins/mimetreeparserplugin.cpp
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "mimetreeparserplugin.h"
+
+#include <MimeTreeParser/MessageParser>
+#include <QQmlEngine>
+
+void MimeTreeParserPlugin::registerTypes(const char *uri)
+{
+ Q_ASSERT(uri == QByteArray("org.kde.pim.mimetreeparser"));
+
+ qmlRegisterModule(uri, 1, 0);
+ qmlRegisterType<MessageParser>(uri, 1, 0, "MessageParser");
+}
+
+void MimeTreeParserPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
+{
+ Q_UNUSED(uri);
+}
diff --git a/src/plugins/mimetreeparserplugin.h b/src/plugins/mimetreeparserplugin.h
new file mode 100644
index 0000000..b652ba4
--- /dev/null
+++ b/src/plugins/mimetreeparserplugin.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <QQmlExtensionPlugin>
+
+class MimeTreeParserPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+ void registerTypes(const char *uri) override;
+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
+};
diff --git a/examples/qml/content/MailViewer.qml b/src/plugins/qml/MailViewer.qml
similarity index 98%
rename from examples/qml/content/MailViewer.qml
rename to src/plugins/qml/MailViewer.qml
index 9e2c4bb..da2277f 100644
--- a/examples/qml/content/MailViewer.qml
+++ b/src/plugins/qml/MailViewer.qml
@@ -1,186 +1,187 @@
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2
import QtGraphicalEffects 1.15
-import org.kde.mimetreeparser 1.0
+import org.kde.pim.mimetreeparser 1.0
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kitemmodels 1.0 as KItemModels
+import './private'
Kirigami.Page {
id: root
property alias message: mailPartView.message
readonly property string subject: mailPartView.subject
readonly property string from: mailPartView.from
readonly property string sender: mailPartView.sender
readonly property string to: mailPartView.to
readonly property date dateTime: mailPartView.dateTime
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
padding: Kirigami.Units.largeSpacing * 2
title: i18n("Message viewer")
header: QQC2.ToolBar {
id: mailHeader
padding: root.padding
visible: root.from.length > 0 || root.to.length > 0 || root.subject.length > 0
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Item {
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.alternateBackgroundColor
}
Kirigami.Separator {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
Kirigami.Separator {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
}
}
GridLayout {
width: mailHeader.width - mailHeader.leftPadding - mailHeader.rightPadding
rowSpacing: Kirigami.Units.smallSpacing
columnSpacing: Kirigami.Units.smallSpacing
columns: 3
QQC2.Label {
text: i18n('Subject:')
font.bold: true
visible: root.subject.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.subject
visible: text.length > 0
elide: Text.ElideRight
Layout.fillWidth: true
}
QQC2.Label {
text: root.dateTime.toLocaleString(Qt.locale(), Locale.ShortFormat)
visible: text.length > 0
horizontalAlignment: Text.AlignRight
}
QQC2.Label {
text: i18n('From:')
font.bold: true
visible: root.from.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.from
visible: text.length > 0
elide: Text.ElideRight
Layout.fillWidth: true
Layout.columnSpan: 2
}
QQC2.Label {
text: i18n('Sender:')
font.bold: true
visible: root.sender.length > 0 && root.sender !== root.from
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
visible: root.sender.length > 0 && root.sender !== root.from
text: root.sender
elide: Text.ElideRight
Layout.fillWidth: true
Layout.columnSpan: 2
}
QQC2.Label {
text: i18n('To:')
font.bold: true
visible: root.to.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.to
elide: Text.ElideRight
visible: root.to.length > 0
Layout.fillWidth: true
Layout.columnSpan: 2
}
}
}
MailPartView {
id: mailPartView
anchors.fill: parent
}
footer: QQC2.ToolBar {
padding: root.padding
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Item {
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
top: undefined
bottom: parent.bottom
}
}
}
Flow {
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Repeater {
model: mailPartView.attachmentModel
delegate: AttachmentDelegate {
name: model.name
type: model.type
icon.name: model.iconName
clip: true
actionIcon: 'download'
actionTooltip: i18n("Save attachment")
onExecute: mailPartView.attachmentModel.saveAttachmentToDisk(mailPartView.attachmentModel.index(index, 0))
onClicked: mailPartView.attachmentModel.openAttachment(mailPartView.attachmentModel.index(index, 0))
onPublicKeyImport: mailPartView.attachmentModel.importPublicKey(mailPartView.attachmentModel.index(index, 0))
}
}
}
}
}
diff --git a/examples/qml/content/AttachmentDelegate.qml b/src/plugins/qml/private/AttachmentDelegate.qml
similarity index 100%
rename from examples/qml/content/AttachmentDelegate.qml
rename to src/plugins/qml/private/AttachmentDelegate.qml
diff --git a/examples/qml/content/Banner.qml b/src/plugins/qml/private/Banner.qml
similarity index 100%
rename from examples/qml/content/Banner.qml
rename to src/plugins/qml/private/Banner.qml
diff --git a/examples/qml/content/ErrorPart.qml b/src/plugins/qml/private/ErrorPart.qml
similarity index 100%
rename from examples/qml/content/ErrorPart.qml
rename to src/plugins/qml/private/ErrorPart.qml
diff --git a/examples/qml/content/HtmlPart.qml b/src/plugins/qml/private/HtmlPart.qml
similarity index 100%
rename from examples/qml/content/HtmlPart.qml
rename to src/plugins/qml/private/HtmlPart.qml
diff --git a/examples/qml/content/ICalPart.qml b/src/plugins/qml/private/ICalPart.qml
similarity index 100%
rename from examples/qml/content/ICalPart.qml
rename to src/plugins/qml/private/ICalPart.qml
diff --git a/examples/qml/content/MailPart.qml b/src/plugins/qml/private/MailPart.qml
similarity index 100%
rename from examples/qml/content/MailPart.qml
rename to src/plugins/qml/private/MailPart.qml
diff --git a/examples/qml/content/MailPartModel.qml b/src/plugins/qml/private/MailPartModel.qml
similarity index 100%
rename from examples/qml/content/MailPartModel.qml
rename to src/plugins/qml/private/MailPartModel.qml
diff --git a/examples/qml/content/MailPartView.qml b/src/plugins/qml/private/MailPartView.qml
similarity index 100%
rename from examples/qml/content/MailPartView.qml
rename to src/plugins/qml/private/MailPartView.qml
diff --git a/examples/qml/content/TextPart.qml b/src/plugins/qml/private/TextPart.qml
similarity index 100%
rename from examples/qml/content/TextPart.qml
rename to src/plugins/qml/private/TextPart.qml
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, May 12, 6:13 PM (13 h, 2 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
51/af/a49210b450182acd386aaf81897e
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment