Commit d849ea9d authored by Carl Schwan's avatar Carl Schwan 🚴 Committed by Claudio Cambra
Browse files

Import mimetree parser code from kube/sink



Signed-off-by: Carl Schwan's avatarCarl Schwan <carl@carlschwan.eu>
parent ea1e4dc1
......@@ -49,7 +49,7 @@ ecm_setup_version(${PROJECT_VERSION}
)
################# Find dependencies #################
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg DBus Test QuickTest)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Gui Qml QuickControls2 Svg DBus Test QuickTest WebEngineWidgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 DBusAddons I18n CalendarCore ConfigWidgets WindowSystem CoreAddons QQC2DesktopStyle Contacts ItemModels XmlGui IconThemes)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
......
......@@ -10,6 +10,24 @@ target_sources(kalendar_mail_plugin PRIVATE
mailmanager.h
mailmodel.cpp
mailmodel.h
crypto.cpp
mimetreeparser/messagepart.cpp
mimetreeparser/bodypartformatter.cpp
mimetreeparser/bodypartformatter_impl.cpp
mimetreeparser/bodypartformatterbasefactory.cpp
mimetreeparser/bodypartformatterbasefactory.cpp
mimetreeparser/cryptohelper.cpp
mimetreeparser/messagepart.cpp
mimetreeparser/mimetreeparser_debug.cpp
mimetreeparser/objecttreeparser.cpp
mimetreeparser/utils.cpp
mime/attachmentmodel.cpp
mime/htmlutils.cpp
mime/mailcrypto.cpp
mime/mailtemplates.cpp
mime/messageparser.cpp
mime/partmodel.cpp
)
ecm_target_qml_sources(kalendar_mail_plugin SOURCES
......@@ -17,15 +35,15 @@ ecm_target_qml_sources(kalendar_mail_plugin SOURCES
qml/FolderView.qml
)
#ecm_target_qml_sources(kalendar_contact_plugin
# PRIVATE PATH private SOURCES
# qml/private/ContactListItem.qml
# qml/private/ContactPage.qml
# qml/private/ContactsPage.qml
# qml/private/Header.qml
# qml/private/PhoneNumberDialog.qml
# qml/private/QrCodePage.qml
#)
ecm_target_qml_sources(kalendar_contact_plugin
PRIVATE PATH private SOURCES
qml/mailpartview/HtmlPart.qml
qml/mailpartview/ICalPart.qml
qml/mailpartview/MailPart.qml
qml/mailpartview/MailPartModel.qml
qml/mailpartview/MailPartView.qml
qml/mailpartview/TextPart.qml
)
ecm_qt_declare_logging_category(kalendar_contact_plugin
HEADER kalendar_mail_debug.h
......@@ -35,7 +53,7 @@ ecm_qt_declare_logging_category(kalendar_contact_plugin
EXPORT KALENDAR
)
target_link_libraries(kalendar_mail_plugin PRIVATE kalendar_lib KF5::MailCommon KF5::AkonadiMime)
target_link_libraries(kalendar_mail_plugin PRIVATE kalendar_lib KF5::MailCommon KF5::AkonadiMime Qt5::WebEngineWidgets)
ecm_qt_install_logging_categories(
EXPORT KALENDAR
......
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
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 "crypto.h"
#include "errors.h"
#include <gpgme.h>
#include <QDateTime>
#include <QDebug>
#include <future>
#include <utility>
using namespace Crypto;
QDebug operator<<(QDebug d, const Key &key)
{
d << key.fingerprint;
return d;
}
QDebug operator<<(QDebug d, const Error &error)
{
d << error.errorCode() << gpgme_strerror(error.errorCode());
return d;
}
namespace Crypto
{
struct Data {
Data(const QByteArray &buffer)
{
const bool copy = false;
const gpgme_error_t e = gpgme_data_new_from_mem(&data, buffer.constData(), buffer.size(), int(copy));
if (e) {
qWarning() << "Failed to copy data?" << e;
}
}
~Data()
{
gpgme_data_release(data);
}
gpgme_data_t data;
};
}
static gpgme_error_t checkEngine(CryptoProtocol protocol)
{
gpgme_check_version(nullptr);
const gpgme_protocol_t p = protocol == CMS ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP;
return gpgme_engine_check_version(p);
}
static std::pair<gpgme_error_t, gpgme_ctx_t> createForProtocol(CryptoProtocol proto)
{
if (auto e = checkEngine(proto)) {
qWarning() << "GPG Engine check failed." << e;
return std::make_pair(e, nullptr);
}
gpgme_ctx_t ctx = nullptr;
if (auto e = gpgme_new(&ctx)) {
return std::make_pair(e, nullptr);
}
switch (proto) {
case OpenPGP:
if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP)) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
break;
case CMS:
if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS)) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
break;
default:
Q_ASSERT(false);
return std::make_pair(1, nullptr);
}
// We want the output to always be ASCII armored
gpgme_set_armor(ctx, 1);
return std::make_pair(GPG_ERR_NO_ERROR, ctx);
}
namespace Crypto
{
struct Context {
Context(CryptoProtocol protocol = OpenPGP)
{
gpgme_error_t code;
std::tie(code, context) = createForProtocol(protocol);
error = Error{code};
}
~Context()
{
gpgme_release(context);
}
operator bool() const
{
return !error;
}
Error error;
gpgme_ctx_t context;
};
}
static QByteArray toBA(gpgme_data_t out)
{
size_t length = 0;
auto data = gpgme_data_release_and_get_mem(out, &length);
auto outdata = QByteArray{data, static_cast<int>(length)};
gpgme_free(data);
return outdata;
}
static std::vector<Recipient> copyRecipients(gpgme_decrypt_result_t result)
{
std::vector<Recipient> recipients;
for (gpgme_recipient_t r = result->recipients; r; r = r->next) {
recipients.push_back({QByteArray{r->keyid}, {r->status}});
}
return recipients;
}
static std::vector<Signature> copySignatures(gpgme_verify_result_t result)
{
std::vector<Signature> signatures;
for (gpgme_signature_t is = result->signatures; is; is = is->next) {
Signature sig;
sig.fingerprint = QByteArray{is->fpr};
sig.creationTime.setSecsSinceEpoch(is->timestamp);
sig.summary = is->summary;
sig.status = {is->status};
sig.validity = is->validity;
sig.validity_reason = is->validity_reason;
signatures.push_back(sig);
}
return signatures;
}
VerificationResult Crypto::verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {{}, context.error};
}
auto ctx = context.context;
auto err = gpgme_op_verify(ctx, Data{signature}.data, Data{text}.data, nullptr);
gpgme_verify_result_t res = gpgme_op_verify_result(ctx);
return {copySignatures(res), {err}};
}
VerificationResult Crypto::verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return VerificationResult{{}, context.error};
}
auto ctx = context.context;
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
auto err = gpgme_op_verify(ctx, Data{signature}.data, nullptr, out);
VerificationResult result{{}, {err}};
if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
result.signatures = copySignatures(res);
}
outdata = toBA(out);
return result;
}
std::pair<DecryptionResult, VerificationResult> Crypto::decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return std::make_pair(DecryptionResult{{}, context.error}, VerificationResult{{}, context.error});
}
auto ctx = context.context;
gpgme_data_t out;
if (gpgme_error_t e = gpgme_data_new(&out)) {
qWarning() << "Failed to allocated data" << e;
}
auto err = gpgme_op_decrypt_verify(ctx, Data{ciphertext}.data, out);
if (err) {
qWarning() << "Failed to decrypt and verify" << Error{err};
// We make sure we don't return any plain-text if the decryption failed to prevent EFAIL
if (err == GPG_ERR_DECRYPT_FAILED) {
return std::make_pair(DecryptionResult{{}, {err}}, VerificationResult{{}, {err}});
}
}
VerificationResult verificationResult{{}, {err}};
if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
verificationResult.signatures = copySignatures(res);
}
DecryptionResult decryptionResult{{}, {err}};
if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) {
decryptionResult.recipients = copyRecipients(res);
}
outdata = toBA(out);
return std::make_pair(decryptionResult, verificationResult);
}
ImportResult Crypto::importKey(CryptoProtocol protocol, const QByteArray &certData)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {0, 0, 0};
}
if (gpgme_op_import(context.context, Data{certData}.data)) {
qWarning() << "Import failed";
return {0, 0, 0};
}
if (auto result = gpgme_op_import_result(context.context)) {
return {result->considered, result->imported, result->unchanged};
} else {
return {0, 0, 0};
}
}
static KeyListResult listKeys(CryptoProtocol protocol, const std::vector<const char *> &patterns, bool secretOnly, int keyListMode, bool importKeys)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {{}, context.error};
}
auto ctx = context.context;
gpgme_set_keylist_mode(ctx, keyListMode);
KeyListResult result;
result.error = {GPG_ERR_NO_ERROR};
auto zeroTerminatedPatterns = patterns;
zeroTerminatedPatterns.push_back(nullptr);
if (patterns.size() > 1) {
if (auto err = gpgme_op_keylist_ext_start(ctx, const_cast<const char **>(zeroTerminatedPatterns.data()), int(secretOnly), 0)) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
} else if (patterns.size() == 1) {
if (auto err = gpgme_op_keylist_start(ctx, zeroTerminatedPatterns[0], int(secretOnly))) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
} else {
if (auto err = gpgme_op_keylist_start(ctx, nullptr, int(secretOnly))) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
}
std::vector<gpgme_key_t> listedKeys;
while (true) {
gpgme_key_t key;
if (auto err = gpgme_op_keylist_next(ctx, &key)) {
Error error{err};
if (error.errorCode() != GPG_ERR_EOF) {
result.error = error;
qWarning() << "Error after listing keys" << result.error;
}
break;
}
listedKeys.push_back(key);
Key k;
if (key->subkeys) {
k.keyId = QByteArray{key->subkeys->keyid};
k.shortKeyId = k.keyId.right(8);
k.fingerprint = QByteArray{key->subkeys->fpr};
}
for (gpgme_user_id_t uid = key->uids; uid; uid = uid->next) {
k.userIds.push_back(UserId{QByteArray{uid->name}, QByteArray{uid->email}, QByteArray{uid->uid}});
}
k.isExpired = key->expired;
result.keys.push_back(k);
}
gpgme_op_keylist_end(ctx);
if (importKeys && !listedKeys.empty()) {
listedKeys.push_back(nullptr);
if (auto err = gpgme_op_import_keys(ctx, const_cast<gpgme_key_t *>(listedKeys.data()))) {
qWarning() << "Error while importing keys" << Error{err};
}
}
return result;
}
/**
* Get the given `key` in the armor format.
*/
Expected<Error, QByteArray> Crypto::exportPublicKey(const Key &key)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
qDebug() << "Exporting public key:" << key.keyId;
if (auto err = gpgme_op_export(context.context, key.keyId.data(), 0, out)) {
return makeUnexpected(Error{err});
}
return toBA(out);
}
Expected<Error, QByteArray> Crypto::signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
for (const auto &signingKey : signingKeys) {
qDebug() << "Signing with " << signingKey;
// TODO do we have to free those again?
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
gpgme_signers_add(context.context, key);
}
}
gpgme_key_t *const keys = new gpgme_key_t[encryptionKeys.size() + 1];
gpgme_key_t *keys_it = keys;
for (const auto &k : encryptionKeys) {
qDebug() << "Encrypting to " << k;
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, k.fingerprint.data(), &key, /*secret*/ false)) {
qWarning() << "Failed to retrieve key " << k.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
*keys_it++ = key;
}
}
*keys_it++ = nullptr;
gpgme_data_t out;
if (auto e = gpgme_data_new(&out)) {
qWarning() << "Failed to allocate output buffer";
return makeUnexpected(Error{e});
}
gpgme_error_t err = !signingKeys.empty() ? gpgme_op_encrypt_sign(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out)
: gpgme_op_encrypt(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out);
delete[] keys;
if (err) {
const auto error = Error{err};
qWarning() << "Encryption failed:" << error;
switch (error.errorCode()) {
case GPG_ERR_UNUSABLE_PUBKEY:
for (const auto &k : encryptionKeys) {
qWarning() << "Encryption key:" << k;
}
break;
case GPG_ERR_UNUSABLE_SECKEY:
for (const auto &k : signingKeys) {
qWarning() << "Signing key:" << k;
}
break;
default:
break;
}
return makeUnexpected(error);
}
return toBA(out);
}
Expected<Error, std::pair<QByteArray, QString>> Crypto::sign(const QByteArray &content, const std::vector<Key> &signingKeys)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
for (const auto &signingKey : signingKeys) {
// TODO do we have to free those again?
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
gpgme_signers_add(context.context, key);
}
}
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
if (auto err = gpgme_op_sign(context.context, Data{content}.data, out, GPGME_SIG_MODE_DETACH)) {
qWarning() << "Signing failed:" << Error{err};
return makeUnexpected(Error{err});
}
const QByteArray algo = [&] {
if (gpgme_sign_result_t res = gpgme_op_sign_result(context.context)) {
if (gpgme_new_signature_t is = res->signatures) {
return QByteArray{gpgme_hash_algo_name(is->hash_algo)};
}
}
return QByteArray{};
}();
// RFC 3156 Section 5:
// Hash-symbols are constructed [...] by converting the text name to lower
// case and prefixing it with the four characters "pgp-".
const auto micAlg = (QStringLiteral("pgp-") + QString::fromUtf8(algo)).toLower();
return std::pair<QByteArray, QString>{toBA(out), micAlg};
}
std::vector<Key> Crypto::findKeys(const QStringList &patterns, bool findPrivate, bool remote)
{
QByteArrayList list;
std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [](const QString &s) {
return s.toUtf8();
});
std::vector<char const *> pattern;
std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [](const QByteArray &s) {
return s.constData();
});
const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL, remote);
if (res.error) {
qWarning() << "Failed to lookup keys: " << res.error;
return {};
}
qDebug() << "got keys:" << res.keys.size() << " for " << patterns;
for (const auto &key : res.keys) {
qDebug() << "isexpired:" << key.isExpired;
for (const auto &userId : key.userIds) {
qDebug() << "userID:" << userId.email;
}
}
return res.keys;
}
/*
Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
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.
*/
#pragma once
#include "errors.h"
#include <QByteArray>
#include <QVariant>
#include <QDateTime>
#include <functional>
#include <gpgme.h>
#include <memory>
namespace Crypto
{
enum CryptoProtocol { UnknownProtocol, OpenPGP, CMS };
struct UserId {
QByteArray name;
QByteArray email;
QByteArray id;
};
struct Key {
QByteArray keyId;
QByteArray shortKeyId;
QByteArray fingerprint;
bool isExpired = false;
std::vector<UserId> userIds;
};
struct Error {
gpgme_error_t error;
gpgme_err_code_t errorCode() const
{
return gpgme_err_code(error);
}
operator bool() const
{
return error != GPG_ERR_NO_ERROR;
}
};
struct Signature {
QByteArray fingerprint;