diff --git a/.kde-ci.yml b/.kde-ci.yml index adef041..801e0f8 100644 --- a/.kde-ci.yml +++ b/.kde-ci.yml @@ -1,14 +1,15 @@ # SPDX-FileCopyrightText: None # SPDX-License-Identifier: CC0-1.0 Dependencies: - 'on': ['Linux', 'FreeBSD'] 'require': 'frameworks/extra-cmake-modules': '@latest-kf6' 'frameworks/kcalendarcore' : '@latest-kf6' 'frameworks/kwidgetsaddons' : '@latest-kf6' 'pim/kmime' : '@latest-kf6' 'pim/kmbox' : '@latest-kf6' + 'pim/libkleo' : '@latest-kf6' Options: require-passing-tests-on: ['Linux'] diff --git a/CMakeLists.txt b/CMakeLists.txt index bdfa232..0e66a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,106 +1,109 @@ # 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.240.40") project(MimeTreeParserNG VERSION ${PIM_VERSION}) # ECM setup set(KF_MIN_VERSION "5.240.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 "6.4.0") 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) set(KF_MIN_VERSION "5.240.0") set(KF_MAJOR_VERSION "6") set(KPIM_MIME_VERSION "5.240.0") +set(KPIM_MIME_VERSION "5.240.40") +set(KPIM_LIBKLEO_VERSION "5.240.40") ecm_setup_version(PROJECT VARIABLE_PREFIX MIMETREEPARSERNG VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparserng_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfigVersion.cmake" SOVERSION 6 ) configure_file(mimetreeparserng-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/mimetreeparserng-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(Qt6Gui ${QT_MIN_VERSION} CONFIG REQUIRED) find_package(KF6I18n ${KF_MIN_VERSION} CONFIG REQUIRED) find_package(KF6Codecs ${KF_MIN_VERSION} CONFIG REQUIRED) find_package(KF6CalendarCore ${KF_MIN_VERSION} CONFIG REQUIRED) find_package(KF6WidgetsAddons ${QT_MIN_VERSION} CONFIG) find_package(KPim6Mime ${KPIM_MIME_VERSION} CONFIG REQUIRED) +find_package(KPim6Libkleo ${KPIM_LIBKLEO_VERSION} CONFIG REQUIRED) find_package(Gpgme REQUIRED) find_package(Qt6Quick ${QT_MIN_VERSION} CONFIG) find_package(Qt6Widgets ${QT_MIN_VERSION} CONFIG) find_package(Qt6Core5Compat ${QT_MIN_VERSION} CONFIG) if (BUILD_TESTING) find_package(Qt6Test ${QT_MIN_VERSION} CONFIG REQUIRED) endif() ########### 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) if (BUILD_TESTING) add_subdirectory(examples) endif() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparserng_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore COMPONENT Devel ) ecm_qt_install_logging_categories( EXPORT MIMETREEPARSERNG FILE mimetreeparser2.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/fileopener.cpp b/examples/qml/fileopener.cpp index 30d4752..dac1a10 100644 --- a/examples/qml/fileopener.cpp +++ b/examples/qml/fileopener.cpp @@ -1,84 +1,124 @@ // SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include "fileopener.h" #include <KMbox/MBox> #include <MimeTreeParserCore/ObjectTreeParser> #include <QDebug> #include <QFile> #include <QMimeDatabase> #include <QMimeType> void FileOpener::open(const QUrl &url) { const auto fileName = url.fileName(); QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(fileName); if (mime.inherits(QStringLiteral("application/mbox"))) { KMBox::MBox mbox; const auto ok = mbox.load(url.toLocalFile()); if (ok) { const auto entries = mbox.entries(); if (!entries.isEmpty()) { KMime::Message::Ptr message(mbox.readMessage(entries[0])); Q_EMIT messageOpened(message); return; } } } QFile file(url.toLocalFile()); file.open(QIODevice::ReadOnly); if (!file.isOpen()) { qWarning() << "Could not open file"; return; } const auto content = file.readAll(); if (content.length() == 0) { qWarning() << "File is empty"; return; } KMime::Message::Ptr message(new KMime::Message); if (mime.inherits(QStringLiteral("application/pgp-encrypted")) || fileName.endsWith(QStringLiteral(".asc"))) { auto contentType = message->contentType(); contentType->setMimeType("multipart/encrypted"); contentType->setBoundary(KMime::multiPartBoundary()); contentType->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted")); contentType->setCategory(KMime::Headers::CCcontainer); auto cte = message->contentTransferEncoding(); cte->setEncoding(KMime::Headers::CE7Bit); cte->setDecoded(true); auto pgpEncrypted = new KMime::Content; pgpEncrypted->contentType()->setMimeType("application/pgp-encrypted"); auto contentDisposition = new KMime::Headers::ContentDisposition; contentDisposition->setDisposition(KMime::Headers::CDattachment); pgpEncrypted->appendHeader(contentDisposition); pgpEncrypted->setBody("Version: 1"); message->addContent(pgpEncrypted); auto encryptedContent = new KMime::Content; encryptedContent->contentType()->setMimeType("application/octet-stream"); contentDisposition = new KMime::Headers::ContentDisposition; contentDisposition->setDisposition(KMime::Headers::CDinline); contentDisposition->setFilename(QStringLiteral("msg.asc")); encryptedContent->appendHeader(contentDisposition); encryptedContent->setBody(content); message->addContent(encryptedContent); message->assemble(); } else { - message->setContent(content); - message->parse(); + int startOfMessage = 0; + if (content.startsWith("From ")) { + startOfMessage = content.indexOf('\n'); + if (startOfMessage == -1) { + return; + } + startOfMessage += 1; // the message starts after the '\n' + } + QVector<KMime::Message::Ptr> listMessages; + + // check for multiple messages in the file + int endOfMessage = content.indexOf("\nFrom ", startOfMessage); + while (endOfMessage != -1) { + auto msg = new KMime::Message; + msg->setContent(KMime::CRLFtoLF(content.mid(startOfMessage, endOfMessage - startOfMessage))); + msg->parse(); + if (!msg->hasContent()) { + delete msg; + msg = nullptr; + return; + } + KMime::Message::Ptr mMsg(msg); + listMessages << mMsg; + startOfMessage = endOfMessage + 1; + endOfMessage = content.indexOf("\nFrom ", startOfMessage); + } + if (endOfMessage == -1) { + endOfMessage = content.length(); + auto msg = new KMime::Message; + msg->setContent(KMime::CRLFtoLF(content.mid(startOfMessage, endOfMessage - startOfMessage))); + msg->parse(); + if (!msg->hasContent()) { + delete msg; + msg = nullptr; + return; + } + KMime::Message::Ptr mMsg(msg); + listMessages << mMsg; + } + if (listMessages.count() > 0) { + message = listMessages[0]; + } } Q_EMIT messageOpened(message); } #include "moc_fileopener.cpp" diff --git a/po/ca/mimetreeparser.po b/po/ca/mimetreeparser.po index 2e456f4..f08ba69 100644 --- a/po/ca/mimetreeparser.po +++ b/po/ca/mimetreeparser.po @@ -1,341 +1,464 @@ # Translation of mimetreeparser.po to Catalan # Copyright (C) 2023 This_file_is_part_of_KDE # This file is distributed under the license LGPL version 2.1 or # version 3 or later versions approved by the membership of KDE e.V. # # Josep M. Ferrer <txemaq@gmail.com>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 11:00+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 11:00+0200\n" "Last-Translator: Josep M. Ferrer <txemaq@gmail.com>\n" "Language-Team: Catalan <kde-i18n-ca@kde.org>\n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Lokalize 22.12.3\n" +"X-Generator: Lokalize 23.04.3\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Nom" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Mida" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Encriptat" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Signat" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Ha fallat en desar l'adjunt." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "S'ha desat l'adjunt al disc: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Ha fallat en obrir l'adjunt." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Ha fallat en desar l'adjunt per a obrir-lo." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "No s'ha trobat cap clau en aquest adjunt" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "s'ha importat una clau" msgstr[1] "s'han importat %1 claus" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "ja s'havia importat una clau" msgstr[1] "ja s'havien importat %1 claus" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "reen" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Desconegut" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Connector criptogràfic erroni." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "" -"No s'han pogut desencriptar les dades: no s'ha trobat la clau dels " -"destinataris." +msgid "No appropriate crypto plug-in was found." +msgstr "No s'ha trobat cap connector criptogràfic apropiat." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "No s'han pogut desencriptar les dades." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "El connector criptogràfic «%1» no pot desencriptar missatges." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "El connector criptogràfic «%1» no pot desencriptar les dades." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Error: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "No hi ha cap clau disponible." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Frase de pas incorrecta." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Visualitzador de missatges" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Assumpte:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "De:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Remitent:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "A:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Desa l'adjunt" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Importa la clau" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Tanca" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "S'ha produït un error: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Aquest correu conté una invitació" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "enviat per %1 a %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Aquest missatge s'ha signat emprant la clau %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Els detalls de la clau no estan disponibles." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Aquest missatge s'ha signat emprant la clau %1 de %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "S'ha revocat la clau." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "La clau ha caducat." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Esteu confiant en aquesta clau." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "La signatura no és vàlida." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "Aquest missatge està encriptat, però no es disposa de la seva clau." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Aquest missatge està encriptat amb la clau: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Aquest missatge s'ha signat conforme amb VS-NfD utilitzant la clau <a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Aquest missatge s'ha signat emprant la clau <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Aquest missatge s'ha signat conforme amb VS-NfD per %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Aquest missatge s'ha signat per %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "S'ha revocat la <a href=\"%1\">clau</a>." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "La <a href=\"%1\">clau</a> ha caducat." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"La signatura és vàlida, però no es coneix la validesa de la <a href=" +"\"%1\">clau</a>." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"La signatura és vàlida, però la <a href=\"%1\">clau</a> és de confiança " +"parcial." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" +"La signatura és vàlida i la <a href=\"%1\">clau</a> és de plena confiança." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"La signatura és vàlida i la <a href=\"%1\">clau</a> és de confiança absoluta." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" +"La signatura és vàlida, però la <a href=\"%1\">clau</a> no és de confiança." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Aquest missatge està encriptat conforme amb VS-NfD, però no es disposa de la " +"seva clau." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Aquest missatge està encriptat conforme amb VS-NfD." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Aquest missatge està encriptat." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Aquest missatge està encriptat amb les claus següents:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Clau desconeguda" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "De&sa l'adjunt com a..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Obre" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Importa la clau pública" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Invitació" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Resum:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organitzador:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Emplaçament:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "Data d'&inici:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "Data &final:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Detalls:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Correu electrònic encapsulat" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "De:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Data:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Error: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "A&ssumpte:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&De:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&A:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"No s'ha pogut iniciar el gestor de certificats. Comproveu aquesta " +"instal·lació." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "S'ha produït un error al KMail" diff --git a/po/ca@valencia/mimetreeparser.po b/po/ca@valencia/mimetreeparser.po index d260e0b..7093dc5 100644 --- a/po/ca@valencia/mimetreeparser.po +++ b/po/ca@valencia/mimetreeparser.po @@ -1,341 +1,463 @@ # Translation of mimetreeparser.po to Catalan (Valencian) # Copyright (C) 2023 This_file_is_part_of_KDE # This file is distributed under the license LGPL version 2.1 or # version 3 or later versions approved by the membership of KDE e.V. # # Josep M. Ferrer <txemaq@gmail.com>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 11:00+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 11:00+0200\n" "Last-Translator: Josep M. Ferrer <txemaq@gmail.com>\n" "Language-Team: Catalan <kde-i18n-ca@kde.org>\n" "Language: ca@valencia\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Lokalize 22.12.3\n" +"X-Generator: Lokalize 23.04.3\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Nom" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Mida" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Encriptat" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Signat" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "No s'ha pogut guardar l'adjunt." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "S'ha guardat l'adjunt al disc: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "No s'ha pogut obrir l'adjunt." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "No s'ha pogut guardar l'adjunt per a obrir-lo." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "No s'ha trobat cap clau en este adjunt" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "s'ha importat una clau" msgstr[1] "s'han importat %1 claus" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "ja s'havia importat una clau" msgstr[1] "ja s'havien importat %1 claus" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "reen" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Desconegut" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Connector criptogràfic erroni." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "" -"No s'han pogut desencriptar les dades: no s'ha trobat la clau dels " -"destinataris." +msgid "No appropriate crypto plug-in was found." +msgstr "No s'ha trobat cap connector criptogràfic apropiat." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "No s'han pogut desencriptar les dades." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "El connector criptogràfic «%1» no pot desencriptar missatges." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "El connector criptogràfic «%1» no pot desencriptar les dades." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "S'ha produït un error: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "No hi ha cap clau disponible." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Frase de pas incorrecta." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Visor de missatges" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Assumpte:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "De:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Remitent:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "A:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Guarda l'adjunt" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Importa la clau" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Tanca" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "S'ha produït un error: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Este correu conté una invitació" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "enviat per %1 a %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Este missatge s'ha signat emprant la clau %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Els detalls de la clau no estan disponibles." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Este missatge s'ha signat emprant la clau %1 de %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "S'ha revocat la clau." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "La clau ha caducat." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Esteu confiant en esta clau." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "La signatura no és vàlida." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "Este missatge està encriptat, però no es disposa de la seua clau." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Este missatge està encriptat amb la clau: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Este missatge s'ha signat conforme amb VS-NfD utilitzant la clau <a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Este missatge s'ha signat emprant la clau <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Este missatge s'ha signat conforme amb VS-NfD per %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Este missatge s'ha signat per %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "S'ha revocat la <a href=\"%1\">clau</a>." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "La <a href=\"%1\">clau</a> ha caducat." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"La signatura és vàlida, però no es coneix la validesa de la <a href=" +"\"%1\">clau</a>." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"La signatura és vàlida, però la <a href=\"%1\">clau</a> és de confiança " +"parcial." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" +"La signatura és vàlida i la <a href=\"%1\">clau</a> és de plena confiança." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"La signatura és vàlida i la <a href=\"%1\">clau</a> és de confiança absoluta." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" +"La signatura és vàlida, però la <a href=\"%1\">clau</a> no és de confiança." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Este missatge està encriptat conforme amb VS-NfD, però no es disposa de la " +"seua clau." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Este missatge està encriptat conforme amb VS-NfD." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Este missatge està encriptat." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Este missatge està encriptat amb les claus següents:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Clau desconeguda" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "Guar&da l'adjunt com a..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Obri" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Importa la clau pública" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Invitació" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Resum:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organitzador:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Emplaçament:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "Data d'&inici:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "Data &final:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Detalls:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Correu electrònic encapsulat" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "De:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Data:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "S'ha produït un error: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "A&ssumpte:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&De:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&A:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"No s'ha pogut iniciar el gestor de certificats. Comproveu esta instal·lació." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "S'ha produït un error a KMail" diff --git a/po/de/mimetreeparser.po b/po/de/mimetreeparser.po index 0a20719..e7767dc 100644 --- a/po/de/mimetreeparser.po +++ b/po/de/mimetreeparser.po @@ -1,341 +1,477 @@ # German translations for mimetreeparser package. # Copyright (C) 2023 This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # Frederik Schwarzer <schwarzer@kde.org>, 2023. # # Automatically generated, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" "PO-Revision-Date: 2023-08-13 16:14+0200\n" "Last-Translator: Frederik Schwarzer <schwarzer@kde.org>\n" "Language-Team: German <kde-i18n-de@kde.org>\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 23.11.70\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Name" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Größe" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Verschlüsselt" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Signiert" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Der Anhang kann nicht gespeichert werden." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "Anhang auf Datenträger gespeichert: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Der Anhang kann nicht geöffnet werden." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Der Anhang kann nicht zum Öffnen gespeichert werden." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "In diesem Anhang wurden keine Schlüssel gefunden" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "Ein Schlüssel importiert" msgstr[1] "%1 Schlüssel importiert" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "Ein Schlüssel war bereits importiert" msgstr[1] "%1 Schlüssel waren bereits importiert" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "fwd" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Unbekannt" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Falsches Kryptografie-Modul." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." +msgid "No appropriate crypto plug-in was found." msgstr "" -"Die Daten können nicht entschlüsselt werden: Kein Schlüssel für Empfänger " -"gefunden." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "" + +#: core/messagepart.cpp:990 +#, fuzzy, kde-format +#| msgctxt "@info" +#| msgid "Could not decrypt the data." +msgid "Crypto plug-in \"%1\" could not decrypt the data." msgstr "Die Daten können nicht entschlüsselt werden." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Fehler: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Kein Schlüssel verfügbar." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Falsche Passphrase." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Nachrichtenanzeige" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Betreff:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Von:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Absender:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "An:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Anhang speichern" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Schlüssel importieren" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Schließen" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Es ist ein Fehler aufgetreten: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Diese E-Mail enthält eine Einladung" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "Gesendet von %1 am %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Diese Nachricht wurde mit dem Schlüssel %1 signiert." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Es sind keine Details für den Schlüssel verfügbar." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Diese Nachricht wurde mit dem Schlüssel %1 von %2 signiert." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "Der Schlüssel wurde zurückgezogen." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "Der Schlüssel ist abgelaufen." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Sie vertrauen diesem Schlüssel." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "Die Signatur ist ungültig." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "Diese Nachricht ist verschlüsselt, aber es fehlt der Schlüssel." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Diese Nachricht ist mit folgendem Schlüssel verschlüsselt: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "Diese Nachricht wurde mit dem Schlüssel %1 signiert." + +#: widgets/messagecontainerwidget.cpp:73 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Diese Nachricht wurde mit dem Schlüssel %1 signiert." + +#: widgets/messagecontainerwidget.cpp:81 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Diese Nachricht wurde mit dem Schlüssel %1 signiert." + +#: widgets/messagecontainerwidget.cpp:83 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Diese Nachricht wurde mit dem Schlüssel %1 signiert." + +#: widgets/messagecontainerwidget.cpp:86 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "The key was revoked." +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "Der Schlüssel wurde zurückgezogen." + +#: widgets/messagecontainerwidget.cpp:89 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "The key has expired." +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "Der Schlüssel ist abgelaufen." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:203 +#, fuzzy, kde-format +#| msgid "This message is encrypted but we don't have the key for it." +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "Diese Nachricht ist verschlüsselt, aber es fehlt der Schlüssel." + +#: widgets/messagecontainerwidget.cpp:209 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "This message is VS-NfD compliant encrypted." +msgstr "Diese Nachricht ist mit folgendem Schlüssel verschlüsselt: %1" + +#: widgets/messagecontainerwidget.cpp:211 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "This message is encrypted." +msgstr "Diese Nachricht ist mit folgendem Schlüssel verschlüsselt: %1" + +#: widgets/messagecontainerwidget.cpp:220 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "The message is encrypted for the following keys:" +msgstr "Diese Nachricht ist mit folgendem Schlüssel verschlüsselt: %1" + +#: widgets/messagecontainerwidget.cpp:237 +#, fuzzy, kde-format +#| msgctxt "displayed when a mail has unknown sender, receiver or date" +#| msgid "Unknown" +msgid "Unknown key" +msgstr "Unbekannt" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "Anhang speichern &unter ..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Öffnen" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Öffentlichen Schlüssel importieren" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Einladung" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "Zu&sammenfassung:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organisator:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Ort:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "&Anfangsdatum:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "&Enddatum:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Details:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Eingebettete E-Mail" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Von:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Datum:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Fehler: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "&Betreff:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&Von:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&An:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "" +#~ "Die Daten können nicht entschlüsselt werden: Kein Schlüssel für Empfänger " +#~ "gefunden." diff --git a/po/es/mimetreeparser.po b/po/es/mimetreeparser.po index 9e54254..9cba205 100644 --- a/po/es/mimetreeparser.po +++ b/po/es/mimetreeparser.po @@ -1,339 +1,463 @@ # Spanish translations for mimetreeparser.po package. # Copyright (C) 2023 This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Automatically generated, 2023. # Eloy Cuadra <ecuadra@eloihr.net>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-14 17:48+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 03:59+0200\n" "Last-Translator: Eloy Cuadra <ecuadra@eloihr.net>\n" "Language-Team: Spanish <kde-l10n-es@kde.org>\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Lokalize 23.04.3\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Nombre" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Tamaño" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Cifrado" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Firmado" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." -msgstr "" +msgstr "No se ha podido guardar el adjunto." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" -msgstr "" +msgstr "Adjunto guardado en disco: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." -msgstr "" +msgstr "No se ha podido abrir el adjunto." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." -msgstr "" +msgstr "No se ha podido guardar el adjunto para su apertura." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" -msgstr "" +msgstr "No se ha encontrado ninguna clave en este adjunto" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "1 clave importada" +msgstr[1] "%1 claves importadas" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "1 clave ya estaba importada" +msgstr[1] "%1 claves ya estaban importadas" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" -msgstr "" +msgstr "fwd" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" -msgstr "" +msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Desconocido" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." -msgstr "" +msgstr "Complemento criptográfico erróneo." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "" +msgid "No appropriate crypto plug-in was found." +msgstr "No se ha encontrado ningún complemento criptográfico adecuado." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "" +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "El complemento criptográfico «%1» no puede descifrar mensajes." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "El complemento criptográfico «%1» no ha podido descifrar los datos." + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Error: %1" -#: core/partmodel.cpp:483 +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." -msgstr "" +msgstr "No hay ninguna clave disponible." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." -msgstr "" +msgstr "Contraseña larga errónea." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Visor de mensaje" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Asunto:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "De:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Remitente:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Para:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Guardar adjunto" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Importar clave" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Cerrar" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Ha ocurrido un error: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Este mensaje contiene una invitación" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "enviado por %1 el %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Este mensaje ha sido firmado usando la clave %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Los detalles de la clave no están disponibles." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Este mensaje ha sido firmado usando la clave %1 por %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "La clave ha sido revocada." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "La clave ha expirado." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." -msgstr "" +msgstr "Esta clave es de su confianza." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "La firma no es válida." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." -msgstr "" +msgstr "Este mensaje está cifrado, pero no disponemos de la clave para él." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" +msgstr "Este mensaje está cifrado para la clave: %1" + +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." msgstr "" +"Este mensaje ha sido firmado de conformidad con VS-NfD usando la clave <a " +"href=\"%1\">%2</a>." -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Este mensaje ha sido firmado usando la clave <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Este mensaje ha sido firmado de conformidad con VS-NfD por %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Este mensaje ha sido firmado por %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "La <a href=\"%1\">clave</a> ha sido revocada." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "La <a href=\"%1\">clave</a> ha expirado." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"La firma es válida, pero se desconoce la validez de la <a href=\"%1\">clave</" +"a>." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"La firma es válida y la <a href=\"%1\">clave</a> es de confianza marginal." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" +"La firma es válida y la <a href=\"%1\">clave</a> es de confianza total." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"La firma es válida y la <a href=\"%1\">clave</a> es definitivamente de " +"confianza." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" +"La firma es válida, pero la <a href=\"%1\">clave</a> no es de confianza." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Este mensaje está cifrado de conformidad con VS-NfD, pero no disponemos de " +"la clave para él." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Este mensaje está cifrado de conformidad con VS-NfD." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Este mensaje está cifrado." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "El mensaje está cifrado para las siguientes claves:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Clave desconocida" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "&Guardar adjunto como..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Abrir" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Importar clave pública" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Invitación" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Resumen:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organizador:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Ubicación:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "Fecha de &inicio:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "&Fecha final:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Detalles:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Mensaje encapsulado" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "De:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Fecha:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Error: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "A&sunto:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&De:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&Para:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"No se ha podido iniciar el gestor de certificados. Compruebe su instalación." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "Error de KMail" diff --git a/po/fr/mimetreeparser.po b/po/fr/mimetreeparser.po index 5b97495..f1bdc9d 100644 --- a/po/fr/mimetreeparser.po +++ b/po/fr/mimetreeparser.po @@ -1,332 +1,444 @@ msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" "PO-Revision-Date: 2023-08-12 10:26+0200\n" "Last-Translator: KDE Francophone <kde-francophone@kde.org>\n" "Language-Team: KDE Francophone <kde-francophone@kde.org>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "" -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "" -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "" -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "" msgstr[1] "" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "" msgstr[1] "" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "" -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." +msgid "No appropriate crypto plug-in was found." msgstr "" -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "" + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." msgstr "" -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "" -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "" -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "" -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "" -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "" -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "" -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "" -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "" -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "" -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "" -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "" -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "" diff --git a/po/it/mimetreeparser.po b/po/it/mimetreeparser.po new file mode 100644 index 0000000..e891d20 --- /dev/null +++ b/po/it/mimetreeparser.po @@ -0,0 +1,467 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the mimetreeparser package. +# Vincenzo Reale <smart2128vr@gmail.com>, 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: mimetreeparser\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-18 17:47+0200\n" +"Last-Translator: Vincenzo Reale <smart2128vr@gmail.com>\n" +"Language-Team: Italian <kde-i18n-it@kde.org>\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 23.04.3\n" + +#: core/attachmentmodel.cpp:90 +#, kde-format +msgctxt "@title:column" +msgid "Name" +msgstr "Nome" + +#: core/attachmentmodel.cpp:92 +#, kde-format +msgctxt "@title:column" +msgid "Size" +msgstr "Dimensione" + +#: core/attachmentmodel.cpp:94 +#, kde-format +msgctxt "@title:column" +msgid "Encrypted" +msgstr "Cifrato" + +#: core/attachmentmodel.cpp:96 +#, kde-format +msgctxt "@title:column" +msgid "Signed" +msgstr "Firmato" + +#: core/attachmentmodel.cpp:198 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment." +msgstr "Salvataggio dell'allegato non riuscito." + +#: core/attachmentmodel.cpp:230 +#, kde-format +msgctxt "@info" +msgid "Saved the attachment to disk: %1" +msgstr "Allegato salvato su disco:%1" + +#: core/attachmentmodel.cpp:247 +#, kde-format +msgctxt "@info" +msgid "Failed to open attachment." +msgstr "Apertura dell'allegato non riuscita." + +#: core/attachmentmodel.cpp:252 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment for opening." +msgstr "Salvataggio dell'allegato per l'apertura non riuscito" + +#: core/attachmentmodel.cpp:273 +#, kde-format +msgctxt "@info" +msgid "No keys were found in this attachment" +msgstr "Non è stata trovata alcuna chiave in questo allegato" + +#: core/attachmentmodel.cpp:276 +#, kde-format +msgctxt "@info" +msgid "one key imported" +msgid_plural "%1 keys imported" +msgstr[0] "una chiave importata" +msgstr[1] "%1 chiavi importate" + +#: core/attachmentmodel.cpp:279 +#, kde-format +msgctxt "@info" +msgid "one key was already imported" +msgid_plural "%1 keys were already imported" +msgstr[0] "una chiave è già stata importata" +msgstr[1] "%1 chiavi sono già state importate" + +#: core/mailtemplates.cpp:103 +#, kde-format +msgid "fwd" +msgstr "fwd" + +#: core/mailtemplates.cpp:132 +#, kde-format +msgid "re" +msgstr "re" + +#: core/messageparser.cpp:158 +#, kde-format +msgctxt "displayed when a mail has unknown sender, receiver or date" +msgid "Unknown" +msgstr "Sconosciuto" + +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 +#, kde-format +msgctxt "@info:status" +msgid "Wrong Crypto Plug-In." +msgstr "Estensione di cifratura errata." + +#: core/messagepart.cpp:986 +#, kde-format +msgid "No appropriate crypto plug-in was found." +msgstr "Non è stata trovata nessuna estensione crittografica valida." + +#: core/messagepart.cpp:988 +#, kde-format +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "L'estensione crittografica «%1» non può decifrare i messaggi." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "L'estensione crittografica «%1» non può decifrare i dati." + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Errore: %1" + +#: core/partmodel.cpp:487 +#, kde-format +msgctxt "@info:status" +msgid "No key available." +msgstr "Nessuna chiave disponibile." + +#: core/partmodel.cpp:489 +#, kde-format +msgctxt "@info:status" +msgid "Wrong passphrase." +msgstr "Frase segreta errata." + +#: quick/qml/MailViewer.qml:38 +#, kde-format +msgctxt "@title:window" +msgid "Message viewer" +msgstr "Visualizzatore del messaggio" + +#: quick/qml/MailViewer.qml:75 +#, kde-format +msgctxt "@label" +msgid "Subject:" +msgstr "Oggetto:" + +#: quick/qml/MailViewer.qml:96 +#, kde-format +msgctxt "@label" +msgid "From:" +msgstr "Da:" + +#: quick/qml/MailViewer.qml:112 +#, kde-format +msgctxt "@label" +msgid "Sender:" +msgstr "Mittente:" + +#: quick/qml/MailViewer.qml:128 +#, kde-format +msgctxt "@label" +msgid "To:" +msgstr "A:" + +#: quick/qml/MailViewer.qml:185 +#, kde-format +msgctxt "@action:button" +msgid "Save attachment" +msgstr "Salva allegato" + +#: quick/qml/private/AttachmentDelegate.qml:56 +#, kde-format +msgctxt "@action:button" +msgid "Import key" +msgstr "Importa chiave" + +#: quick/qml/private/Banner.qml:188 +#, kde-format +msgctxt "@action:button" +msgid "Close" +msgstr "Chiudi" + +#: quick/qml/private/ErrorPart.qml:27 +#, kde-format +msgid "An error occurred: %1" +msgstr "Si è verificato un errore: %1" + +#: quick/qml/private/ICalPart.qml:27 +#, kde-format +msgid "This mail contains an invitation" +msgstr "Questo messaggio contiene un invito" + +#: quick/qml/private/MailPart.qml:41 +#, kde-format +msgctxt "@info" +msgid "sent by %1 on %2" +msgstr "inviato da %1 il %2" + +#: quick/qml/private/MailPartModel.qml:55 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1." + +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 +#, kde-format +msgctxt "@label" +msgid "The key details are not available." +msgstr "I dettagli della chiave non sono disponibili." + +#: quick/qml/private/MailPartModel.qml:58 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1 by %2." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1 di %2." + +#: quick/qml/private/MailPartModel.qml:60 +#, kde-format +msgctxt "@label" +msgid "The key was revoked." +msgstr "La chiave è stata revocata." + +#: quick/qml/private/MailPartModel.qml:63 +#, kde-format +msgctxt "@label" +msgid "The key has expired." +msgstr "La chiave è scaduta." + +#: quick/qml/private/MailPartModel.qml:66 +#, kde-format +msgctxt "@label" +msgid "You are trusting this key." +msgstr "Ti fidi di questa chiave." + +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 +#, kde-format +msgctxt "@label" +msgid "The signature is invalid." +msgstr "La firma non è valida." + +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 +#, kde-format +msgid "This message is encrypted but we don't have the key for it." +msgstr "Questo messaggio è cifrato, ma non abbiamo la chiave relativa." + +#: quick/qml/private/MailPartModel.qml:98 +#, kde-format +msgid "This message is encrypted to the key: %1" +msgstr "Questo messaggio è cifrato con la chiave: %1" + +#: widgets/messagecontainerwidget.cpp:66 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1." + +#: widgets/messagecontainerwidget.cpp:73 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1." + +#: widgets/messagecontainerwidget.cpp:81 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "This message has been signed using the key %1." +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Questo messaggio è stato firmato utilizzando la chiave %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "The key was revoked." +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "La chiave è stata revocata." + +#: widgets/messagecontainerwidget.cpp:89 +#, fuzzy, kde-format +#| msgctxt "@label" +#| msgid "The key has expired." +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "La chiave è scaduta." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:203 +#, fuzzy, kde-format +#| msgid "This message is encrypted but we don't have the key for it." +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "Questo messaggio è cifrato, ma non abbiamo la chiave relativa." + +#: widgets/messagecontainerwidget.cpp:209 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "This message is VS-NfD compliant encrypted." +msgstr "Questo messaggio è cifrato con la chiave: %1" + +#: widgets/messagecontainerwidget.cpp:211 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "This message is encrypted." +msgstr "Questo messaggio è cifrato con la chiave: %1" + +#: widgets/messagecontainerwidget.cpp:220 +#, fuzzy, kde-format +#| msgid "This message is encrypted to the key: %1" +msgid "The message is encrypted for the following keys:" +msgstr "Questo messaggio è cifrato con la chiave: %1" + +#: widgets/messagecontainerwidget.cpp:237 +#, fuzzy, kde-format +#| msgctxt "displayed when a mail has unknown sender, receiver or date" +#| msgid "Unknown" +msgid "Unknown key" +msgstr "Sconosciuto" + +#: widgets/messageviewer.cpp:57 +#, kde-format +msgid "&Save Attachment As..." +msgstr "&Salva allegato come..." + +#: widgets/messageviewer.cpp:62 +#, kde-format +msgctxt "to open" +msgid "Open" +msgstr "Apri" + +#: widgets/messageviewer.cpp:67 +#, kde-format +msgctxt "@action:inmenu" +msgid "Import public key" +msgstr "Importa chiave pubblica" + +#: widgets/messageviewer.cpp:236 +#, kde-format +msgid "Invitation" +msgstr "Invito" + +#: widgets/messageviewer.cpp:239 +#, kde-format +msgid "&Summary:" +msgstr "&Riepilogo:" + +#: widgets/messageviewer.cpp:240 +#, kde-format +msgid "&Organizer:" +msgstr "&Organizzatore:" + +#: widgets/messageviewer.cpp:242 +#, kde-format +msgid "&Location:" +msgstr "&Posizione:" + +#: widgets/messageviewer.cpp:244 +#, kde-format +msgid "&Start date:" +msgstr "Data di &inizio:" + +#: widgets/messageviewer.cpp:246 +#, kde-format +msgid "&End date:" +msgstr "Data di &fine:" + +#: widgets/messageviewer.cpp:249 +#, kde-format +msgid "&Details:" +msgstr "&Dettagli:" + +#: widgets/messageviewer.cpp:271 +#, kde-format +msgid "Encapsulated email" +msgstr "Messaggio incapsulato" + +#: widgets/messageviewer.cpp:279 +#, kde-format +msgid "From:" +msgstr "Da:" + +#: widgets/messageviewer.cpp:280 +#, kde-format +msgid "Date:" +msgstr "Data:" + +#: widgets/messageviewer.cpp:315 +#, kde-format +msgid "&Subject:" +msgstr "&Oggetto:" + +#: widgets/messageviewer.cpp:318 +#, kde-format +msgid "&From:" +msgstr "&Da:" + +#: widgets/messageviewer.cpp:321 +#, kde-format +msgid "&To:" +msgstr "&A:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "" diff --git a/po/ka/mimetreeparser.po b/po/ka/mimetreeparser.po new file mode 100644 index 0000000..f9ed5ef --- /dev/null +++ b/po/ka/mimetreeparser.po @@ -0,0 +1,460 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the mimetreeparser package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: mimetreeparser\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 11:09+0200\n" +"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n" +"Language-Team: Georgian <kde-i18n-doc@kde.org>\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.3.2\n" + +#: core/attachmentmodel.cpp:90 +#, kde-format +msgctxt "@title:column" +msgid "Name" +msgstr "სახელი" + +#: core/attachmentmodel.cpp:92 +#, kde-format +msgctxt "@title:column" +msgid "Size" +msgstr "ზომა" + +#: core/attachmentmodel.cpp:94 +#, kde-format +msgctxt "@title:column" +msgid "Encrypted" +msgstr "დაშიფრული" + +#: core/attachmentmodel.cpp:96 +#, kde-format +msgctxt "@title:column" +msgid "Signed" +msgstr "ხელმოწერილი" + +#: core/attachmentmodel.cpp:198 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment." +msgstr "მიმაგრებული ფაილის შენახვის შეცდომა." + +#: core/attachmentmodel.cpp:230 +#, kde-format +msgctxt "@info" +msgid "Saved the attachment to disk: %1" +msgstr "მიმაგრებული ფაილი შენახულია დისკზე: %1" + +#: core/attachmentmodel.cpp:247 +#, kde-format +msgctxt "@info" +msgid "Failed to open attachment." +msgstr "მიმაგრებული ფაილის გახსნა შეუძლებელია." + +#: core/attachmentmodel.cpp:252 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment for opening." +msgstr "მიმაგრებული ფაილის გასახსნელად შენახვის შეცდომა." + +#: core/attachmentmodel.cpp:273 +#, kde-format +msgctxt "@info" +msgid "No keys were found in this attachment" +msgstr "ამ მიმაგრებულ ფაილში გასაღებები ვერ ვიპოვე" + +#: core/attachmentmodel.cpp:276 +#, kde-format +msgctxt "@info" +msgid "one key imported" +msgid_plural "%1 keys imported" +msgstr[0] "შემოტანილია 1 გასაღები" +msgstr[1] "შემოტანილია %1 გასაღები" + +#: core/attachmentmodel.cpp:279 +#, kde-format +msgctxt "@info" +msgid "one key was already imported" +msgid_plural "%1 keys were already imported" +msgstr[0] "1 გასაღები უკვე შემოტანილია" +msgstr[1] "%1 გასაღები უკვე შემოტანილია" + +#: core/mailtemplates.cpp:103 +#, kde-format +msgid "fwd" +msgstr "გადგზ" + +#: core/mailtemplates.cpp:132 +#, kde-format +msgid "re" +msgstr "პას" + +#: core/messageparser.cpp:158 +#, kde-format +msgctxt "displayed when a mail has unknown sender, receiver or date" +msgid "Unknown" +msgstr "უცნობი" + +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 +#, kde-format +msgctxt "@info:status" +msgid "Wrong Crypto Plug-In." +msgstr "კრიპტოს არასწორი დამატება." + +#: core/messagepart.cpp:986 +#, kde-format +msgid "No appropriate crypto plug-in was found." +msgstr "კრიპტოს შესაბამისი დამატება ვერ ვიპოვე." + +#: core/messagepart.cpp:988 +#, kde-format +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "კრიპტოს დამატებას \"%1\" შეტყობინებების გაშიფვრა არ შეუძლია." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "კრიპტოს დამატებას \"%1\" მონაცემების გაშიფვრა არ შეუძლია." + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "შეცდომა: %1" + +#: core/partmodel.cpp:487 +#, kde-format +msgctxt "@info:status" +msgid "No key available." +msgstr "გასაღები ხელმიუწვდომელია." + +#: core/partmodel.cpp:489 +#, kde-format +msgctxt "@info:status" +msgid "Wrong passphrase." +msgstr "არასწორი საიდუმლო ფრაზა." + +#: quick/qml/MailViewer.qml:38 +#, kde-format +msgctxt "@title:window" +msgid "Message viewer" +msgstr "შეტყობინების ნახვა" + +#: quick/qml/MailViewer.qml:75 +#, kde-format +msgctxt "@label" +msgid "Subject:" +msgstr "სათაური:" + +#: quick/qml/MailViewer.qml:96 +#, kde-format +msgctxt "@label" +msgid "From:" +msgstr "ვისგან:" + +#: quick/qml/MailViewer.qml:112 +#, kde-format +msgctxt "@label" +msgid "Sender:" +msgstr "გამგზავნი:" + +#: quick/qml/MailViewer.qml:128 +#, kde-format +msgctxt "@label" +msgid "To:" +msgstr "ვის:" + +#: quick/qml/MailViewer.qml:185 +#, kde-format +msgctxt "@action:button" +msgid "Save attachment" +msgstr "მიმაგრებული ფაილის შენახვა" + +#: quick/qml/private/AttachmentDelegate.qml:56 +#, kde-format +msgctxt "@action:button" +msgid "Import key" +msgstr "გასაღების შემოტანა" + +#: quick/qml/private/Banner.qml:188 +#, kde-format +msgctxt "@action:button" +msgid "Close" +msgstr "დახურვა" + +#: quick/qml/private/ErrorPart.qml:27 +#, kde-format +msgid "An error occurred: %1" +msgstr "შეცდომა: %1" + +#: quick/qml/private/ICalPart.qml:27 +#, kde-format +msgid "This mail contains an invitation" +msgstr "ეს ელფოსტა მოსაწვევს შეიცავს" + +#: quick/qml/private/MailPart.qml:41 +#, kde-format +msgctxt "@info" +msgid "sent by %1 on %2" +msgstr "გაგზავნილია %1-ის მიერ. თარიღი %2" + +#: quick/qml/private/MailPartModel.qml:55 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1." +msgstr "ეს შეტყობინება ხელმოწერილია გასაღებით %1." + +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 +#, kde-format +msgctxt "@label" +msgid "The key details are not available." +msgstr "გასაღების დეტალები ხელმიუწვდომელია." + +#: quick/qml/private/MailPartModel.qml:58 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1 by %2." +msgstr "ეს შეტყობინება ხელმოწერილია გასაღებით %1, %2-ის მიერ." + +#: quick/qml/private/MailPartModel.qml:60 +#, kde-format +msgctxt "@label" +msgid "The key was revoked." +msgstr "გასაღები გაუქმებულია." + +#: quick/qml/private/MailPartModel.qml:63 +#, kde-format +msgctxt "@label" +msgid "The key has expired." +msgstr "გასაღების ვადაგასულია." + +#: quick/qml/private/MailPartModel.qml:66 +#, kde-format +msgctxt "@label" +msgid "You are trusting this key." +msgstr "თქვენ ენდობით ამ გასაღებს." + +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 +#, kde-format +msgctxt "@label" +msgid "The signature is invalid." +msgstr "ხელმოწერა არასწორია." + +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 +#, kde-format +msgid "This message is encrypted but we don't have the key for it." +msgstr "შეტყობინება დაშიფრულია, მაგრამ მისი გასაღები არ გვაქვს." + +#: quick/qml/private/MailPartModel.qml:98 +#, kde-format +msgid "This message is encrypted to the key: %1" +msgstr "შეტყობინება დაშიფრულია ამ გასაღებამდე: %1" + +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"გასაღების ხელმოწერა თავსებადია VS-NfD-სთან. გამოყენებულია გასაღები<a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "ეს შეტყობინება ხელმოწერილია გასაღებით <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "ამ შეტყობინების ხელმოწერა თავსებადია VS-NfD-სთან. ავტორია %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "შეტყობინება ხელმოწერილია %1-ის მიერ." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">გასაღები</a> გაუქმებულია." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "<a href=\"%1\">გასაღები</a>ს ვადა ამოიწურა." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"ხელმოწერა სწორია, მაგრამ <a href=\"%1\">გასაღების</a> გადამოწმება " +"შეუძლებელია." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "ხელმოწერია სწორია და <a href=\"%1\">გასაღები</a> ოდნავ საეჭვოა." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "ხელმოწერია სწორია და <a href=\"%1\">გასაღები</a> სრულად სანდოა." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "ხელმოწერა სწორია და <a href=\"%1\">გასაღები</a> უპირობოდ სანდოა." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "ხელმოწერია სწორია, მაგრამ <a href=\"%1\">გასაღები</a> სანდო არაა." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"შეტყობინება დაშიფრულია VS-NfD-სთან თავსებადად, მაგრამ მისი გასაღები არ " +"გვაქვს." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "ეს შეტყობინება დაშიფრულია VS-NfD-სთან თავსებადი შიფრით." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "ეს შეტყობინება დაშიფრულია." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "შეტყობინება დაშიფრულია შემდეგი გასაღებებისთვის:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "უცნობი გასაღები" + +#: widgets/messageviewer.cpp:57 +#, kde-format +msgid "&Save Attachment As..." +msgstr "მიმაგრებული ფაილის &შენახვა, როგორც…" + +#: widgets/messageviewer.cpp:62 +#, kde-format +msgctxt "to open" +msgid "Open" +msgstr "გახსნა" + +#: widgets/messageviewer.cpp:67 +#, kde-format +msgctxt "@action:inmenu" +msgid "Import public key" +msgstr "საჯარო გასაღების შემოტანა" + +#: widgets/messageviewer.cpp:236 +#, kde-format +msgid "Invitation" +msgstr "მოწვევა" + +#: widgets/messageviewer.cpp:239 +#, kde-format +msgid "&Summary:" +msgstr "&შეჯამება:" + +#: widgets/messageviewer.cpp:240 +#, kde-format +msgid "&Organizer:" +msgstr "&ორგანიზატორი:" + +#: widgets/messageviewer.cpp:242 +#, kde-format +msgid "&Location:" +msgstr "&მდებარეობა:" + +#: widgets/messageviewer.cpp:244 +#, kde-format +msgid "&Start date:" +msgstr "&დაწყების თარიღი:" + +#: widgets/messageviewer.cpp:246 +#, kde-format +msgid "&End date:" +msgstr "&დასრულების დრო:" + +#: widgets/messageviewer.cpp:249 +#, kde-format +msgid "&Details:" +msgstr "&დეტალები:" + +#: widgets/messageviewer.cpp:271 +#, kde-format +msgid "Encapsulated email" +msgstr "ჩასმული შეტყობინება" + +#: widgets/messageviewer.cpp:279 +#, kde-format +msgid "From:" +msgstr "ვისგან:" + +#: widgets/messageviewer.cpp:280 +#, kde-format +msgid "Date:" +msgstr "თარიღი:" + +#: widgets/messageviewer.cpp:315 +#, kde-format +msgid "&Subject:" +msgstr "&სათაური:" + +#: widgets/messageviewer.cpp:318 +#, kde-format +msgid "&From:" +msgstr "&ვისგან:" + +#: widgets/messageviewer.cpp:321 +#, kde-format +msgid "&To:" +msgstr "&ვის:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "სერტიფიკატების მმართველის გაშვების შეცდომა. გადაამოწმეთ ფაილები." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "KMail-ის შეცდომა" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "მონაცემების გაშიფვრის შეცდომა: მიმღებებისთვის გასაღები ვერ ვიპოვე." diff --git a/po/ko/mimetreeparser.po b/po/ko/mimetreeparser.po new file mode 100644 index 0000000..c527175 --- /dev/null +++ b/po/ko/mimetreeparser.po @@ -0,0 +1,450 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the mimetreeparser package. +# Shinjo Park <kde@peremen.name>, 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: mimetreeparser\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 01:20+0200\n" +"Last-Translator: Shinjo Park <kde@peremen.name>\n" +"Language-Team: Korean <kde-kr@kde.org>\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 22.12.3\n" + +#: core/attachmentmodel.cpp:90 +#, kde-format +msgctxt "@title:column" +msgid "Name" +msgstr "이름" + +#: core/attachmentmodel.cpp:92 +#, kde-format +msgctxt "@title:column" +msgid "Size" +msgstr "크기" + +#: core/attachmentmodel.cpp:94 +#, kde-format +msgctxt "@title:column" +msgid "Encrypted" +msgstr "암호화됨" + +#: core/attachmentmodel.cpp:96 +#, kde-format +msgctxt "@title:column" +msgid "Signed" +msgstr "서명됨" + +#: core/attachmentmodel.cpp:198 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment." +msgstr "첨부 파일을 저장할 수 없습니다." + +#: core/attachmentmodel.cpp:230 +#, kde-format +msgctxt "@info" +msgid "Saved the attachment to disk: %1" +msgstr "첨부 파일을 디스크에 저장함: %1" + +#: core/attachmentmodel.cpp:247 +#, kde-format +msgctxt "@info" +msgid "Failed to open attachment." +msgstr "첨부 파일을 열 수 없습니다." + +#: core/attachmentmodel.cpp:252 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment for opening." +msgstr "열 첨부 파일을 저장할 수 없습니다." + +#: core/attachmentmodel.cpp:273 +#, kde-format +msgctxt "@info" +msgid "No keys were found in this attachment" +msgstr "이 첨부 파일에서 키를 찾을 수 없음" + +#: core/attachmentmodel.cpp:276 +#, kde-format +msgctxt "@info" +msgid "one key imported" +msgid_plural "%1 keys imported" +msgstr[0] "키 %1개를 가져옴" + +#: core/attachmentmodel.cpp:279 +#, kde-format +msgctxt "@info" +msgid "one key was already imported" +msgid_plural "%1 keys were already imported" +msgstr[0] "키 %1개는 이미 가져옴" + +#: core/mailtemplates.cpp:103 +#, kde-format +msgid "fwd" +msgstr "fwd" + +#: core/mailtemplates.cpp:132 +#, kde-format +msgid "re" +msgstr "re" + +#: core/messageparser.cpp:158 +#, kde-format +msgctxt "displayed when a mail has unknown sender, receiver or date" +msgid "Unknown" +msgstr "알 수 없음" + +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 +#, kde-format +msgctxt "@info:status" +msgid "Wrong Crypto Plug-In." +msgstr "잘못된 암호화 플러그인입니다." + +#: core/messagepart.cpp:986 +#, kde-format +msgid "No appropriate crypto plug-in was found." +msgstr "적절한 암호화 플러그인을 찾을 수 없습니다." + +#: core/messagepart.cpp:988 +#, kde-format +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "암호화 플러그인 \"%1\"에서 메시지를 복호화할 수 없습니다." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "암호화 플러그인 \"%1\"에서 데이터를 복호화할 수 없습니다." + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "오류: %1" + +#: core/partmodel.cpp:487 +#, kde-format +msgctxt "@info:status" +msgid "No key available." +msgstr "키를 사용할 수 없습니다." + +#: core/partmodel.cpp:489 +#, kde-format +msgctxt "@info:status" +msgid "Wrong passphrase." +msgstr "암구호가 잘못되었습니다." + +#: quick/qml/MailViewer.qml:38 +#, kde-format +msgctxt "@title:window" +msgid "Message viewer" +msgstr "메시지 뷰어" + +#: quick/qml/MailViewer.qml:75 +#, kde-format +msgctxt "@label" +msgid "Subject:" +msgstr "제목:" + +#: quick/qml/MailViewer.qml:96 +#, kde-format +msgctxt "@label" +msgid "From:" +msgstr "보낸 사람:" + +#: quick/qml/MailViewer.qml:112 +#, kde-format +msgctxt "@label" +msgid "Sender:" +msgstr "전송한 사람:" + +#: quick/qml/MailViewer.qml:128 +#, kde-format +msgctxt "@label" +msgid "To:" +msgstr "받는 사람:" + +#: quick/qml/MailViewer.qml:185 +#, kde-format +msgctxt "@action:button" +msgid "Save attachment" +msgstr "첨부 파일 저장" + +#: quick/qml/private/AttachmentDelegate.qml:56 +#, kde-format +msgctxt "@action:button" +msgid "Import key" +msgstr "키 가져오기..." + +#: quick/qml/private/Banner.qml:188 +#, kde-format +msgctxt "@action:button" +msgid "Close" +msgstr "닫기" + +#: quick/qml/private/ErrorPart.qml:27 +#, kde-format +msgid "An error occurred: %1" +msgstr "오류 발생: %1" + +#: quick/qml/private/ICalPart.qml:27 +#, kde-format +msgid "This mail contains an invitation" +msgstr "이 메일에는 초대장이 첨부되어 있습니다" + +#: quick/qml/private/MailPart.qml:41 +#, kde-format +msgctxt "@info" +msgid "sent by %1 on %2" +msgstr "%1 님이 %2에 보냄" + +#: quick/qml/private/MailPartModel.qml:55 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1." +msgstr "이 메시지는 키 %1(으)로 서명되었습니다." + +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 +#, kde-format +msgctxt "@label" +msgid "The key details are not available." +msgstr "키 정보를 알 수 없습니다." + +#: quick/qml/private/MailPartModel.qml:58 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1 by %2." +msgstr "%2 님이 키 %1(으)로 이 메시지에 서명했습니다." + +#: quick/qml/private/MailPartModel.qml:60 +#, kde-format +msgctxt "@label" +msgid "The key was revoked." +msgstr "키가 취소되었습니다." + +#: quick/qml/private/MailPartModel.qml:63 +#, kde-format +msgctxt "@label" +msgid "The key has expired." +msgstr "키가 만료되었습니다." + +#: quick/qml/private/MailPartModel.qml:66 +#, kde-format +msgctxt "@label" +msgid "You are trusting this key." +msgstr "이 키를 신뢰하고 있습니다." + +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 +#, kde-format +msgctxt "@label" +msgid "The signature is invalid." +msgstr "서명이 잘못되었습니다." + +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 +#, kde-format +msgid "This message is encrypted but we don't have the key for it." +msgstr "메시지가 암호화되어 있지만 키가 없습니다." + +#: quick/qml/private/MailPartModel.qml:98 +#, kde-format +msgid "This message is encrypted to the key: %1" +msgstr "이 메시지는 다음 키로 암호화되어 있습니다: %1" + +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"이 메시지는 VS-NfD를 준수하는 방법으로 키 <a href=\"%1\">%2</a>(으)로 서명되" +"었습니다." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "이 메시지는 키 <a href=\"%1\">%2</a>(으)로 서명되었습니다." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "%1이(가) 이 메시지에 VS-NfD를 준수하는 방법으로 서명했습니다." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "%1이(가) 메시지에 서명했습니다." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">키</a>가 취소되었습니다." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "<a href=\"%1\">키</a>가 만료되었습니다." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "서명이 올바르지만 <a href=\"%1\">키</a> 유효성을 알 수 없습니다." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "서명이 올바르지만 <a href=\"%1\">키</a>를 그럭저럭 신뢰합니다." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "서명이 올바르고 <a href=\"%1\">키</a>를 웬만큼 신뢰합니다." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "서명이 올바르고 <a href=\"%1\">키</a>를 완전히 신뢰합니다." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "서명이 올바르지만 <a href=\"%1\">키</a>를 신뢰할 수 없습니다." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "이 메시지는 VS-NfD를 준수하는 방법으로 암호화되었지만 키가 없습니다." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "이 메시지는 VS-NfD를 준수하는 방법으로 암호화되었습니다." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "이 메시지는 암호화되어 있습니다." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "이 메시지는 다음 키로 암호화되어 있습니다:" + +#: widgets/messagecontainerwidget.cpp:237 +#, fuzzy, kde-format +#| msgid "Unknow key" +msgid "Unknown key" +msgstr "알 수 없는 키" + +#: widgets/messageviewer.cpp:57 +#, kde-format +msgid "&Save Attachment As..." +msgstr "다른 이름으로 첨부 파일 저장(&S)..." + +#: widgets/messageviewer.cpp:62 +#, kde-format +msgctxt "to open" +msgid "Open" +msgstr "열기" + +#: widgets/messageviewer.cpp:67 +#, kde-format +msgctxt "@action:inmenu" +msgid "Import public key" +msgstr "공개 키 가져오기" + +#: widgets/messageviewer.cpp:236 +#, kde-format +msgid "Invitation" +msgstr "초대장" + +#: widgets/messageviewer.cpp:239 +#, kde-format +msgid "&Summary:" +msgstr "개요(&S):" + +#: widgets/messageviewer.cpp:240 +#, kde-format +msgid "&Organizer:" +msgstr "주최(&O):" + +#: widgets/messageviewer.cpp:242 +#, kde-format +msgid "&Location:" +msgstr "위치(&L):" + +#: widgets/messageviewer.cpp:244 +#, kde-format +msgid "&Start date:" +msgstr "시작 날짜(&S):" + +#: widgets/messageviewer.cpp:246 +#, kde-format +msgid "&End date:" +msgstr "종료 날짜(&E):" + +#: widgets/messageviewer.cpp:249 +#, kde-format +msgid "&Details:" +msgstr "자세히(&D):" + +#: widgets/messageviewer.cpp:271 +#, kde-format +msgid "Encapsulated email" +msgstr "캡슐화된 이메일" + +#: widgets/messageviewer.cpp:279 +#, kde-format +msgid "From:" +msgstr "보낸 사람:" + +#: widgets/messageviewer.cpp:280 +#, kde-format +msgid "Date:" +msgstr "날짜:" + +#: widgets/messageviewer.cpp:315 +#, kde-format +msgid "&Subject:" +msgstr "제목(&S):" + +#: widgets/messageviewer.cpp:318 +#, kde-format +msgid "&From:" +msgstr "보낸 사람(&F):" + +#: widgets/messageviewer.cpp:321 +#, kde-format +msgid "&To:" +msgstr "받는 사람(&T):" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "인증서 관리자를 시작할 수 없습니다. 설치 상태를 확인하십시오." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "KMail 오류" diff --git a/po/nl/mimetreeparser.po b/po/nl/mimetreeparser.po index 1482730..5416abb 100644 --- a/po/nl/mimetreeparser.po +++ b/po/nl/mimetreeparser.po @@ -1,339 +1,470 @@ # Copyright (C) YEAR This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Freek de Kruijf <freekdekruijf@kde.nl>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 09:45+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 18:13+0200\n" "Last-Translator: Freek de Kruijf <freekdekruijf@kde.nl>\n" "Language-Team: \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Lokalize 23.04.3\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Naam" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Grootte" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Versleuteld" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Gesigneerd" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Bijlage opslaan is mislukt." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "De bijlage is op schijf opgeslagen: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Bijlage openen is mislukt." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Bijlage openen voor opslaan is mislukt." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "Er zijn geen sleutels in deze bijlage gevonden" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "één sleutel geïmporteerd" msgstr[1] "" "<para><emphasis strong='true'>%1 sleutels geïmporteerd:</emphasis></para>" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "er is al één sleutel geïmporteerd" msgstr[1] "er zijn al %1 sleutels geïmporteerd" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "fwd" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Onbekend" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Onjuiste crypto-plug-in." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "" -"Kon de gegevens niet ontcijferen: geen sleutel gevonden voor ontvangers." +msgid "No appropriate crypto plug-in was found." +msgstr "Geen geschikte Crypto-plug-in gevonden." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "Kon de gegevens niet ontcijferen." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "Crypto-plug-in \"%1\" kan geen berichten ontcijferen." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "De crypto-plug-in \"%1\" kon de gegevens niet ontcijferen." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Fout: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Geen sleutel beschikbaar." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Verkeerde wachtwoordzin." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Viewer van meldingen" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Onderwerp:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Van:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Afzender:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Aan:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Bijlage opslaan" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Sleutel importeren" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Sluiten" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Er is een fout opgetreden: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Deze e-mail bevat een uitnodiging" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "verzonden door %1 op %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Dit bericht was ondertekend met de sleutel %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "De details van de sleutel zijn niet beschikbaar." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Dit bericht was ondertekend met de sleutel %1 door %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "De sleutel is ingetrokken." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "De sleutel is verlopen." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Deze sleutel wordt door u vertrouwd." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "De ondertekening is ongeldig." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "Dit bericht is versleuteld maar we hebben de sleutel ervoor niet." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Dit bericht is versleuteld met de sleutel: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Dit bericht was ondertekend overeenkomstig VS-NfD met de sleutel <a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Dit bericht was ondertekend met de sleutel <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Dit bericht was ondertekend overeenkomstig VS-NfD door %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Dit bericht was ondertekend door %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "De <a href=\"%1\">sleutel</a> is ingetrokken." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "De <a href=\"%1\">sleutel</a> is verlopen." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"De ondertekening is geldig, maar de geldigheid van de <a href=" +"\"%1\">sleutel</a> is onbekend." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"De ondertekening is geldig en de <a href=\"%1\">sleutel</a> is gedeeltelijk " +"betrouwbaar." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" +"De ondertekening is geldig en de <a href=\"%1\">sleutel</a> is volledig " +"betrouwbaar." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"De ondertekening is geldig en de <a href=\"%1\">sleutel</a> is uiterst " +"betrouwbaar." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" +"De ondertekening is geldig, maar de <a href=\"%1\">sleutel</a> is " +"onbetrouwbaar." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Dit bericht is overeenkomstig VS-NfD versleuteld maar we hebben de sleutel " +"ervoor niet." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Dit bericht is overeenkomstig VS-NfD versleuteld." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Dit bericht is versleuteld." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Het bericht is versleuteld voor de volgende sleutels:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Onbekende sleutel" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "Bijlage &opslaan als..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Openen" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Publieke sleutel importeren" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Uitnodiging" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Samenvatting:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organisator:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Locatie:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "&Begindatum:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "&Einddatum:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Details:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Ingebed e-mailbericht" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Van:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Datum:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Fout: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "&Onderwerp:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&Van:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&Aan:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"Het Certificaatbeheer kon niet worden gestart. Controleer uw installatie." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "KMail - foutmelding" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "" +#~ "Kon de gegevens niet ontcijferen: geen sleutel gevonden voor ontvangers." diff --git a/po/sl/mimetreeparser.po b/po/sl/mimetreeparser.po index 7e21f85..4043b44 100644 --- a/po/sl/mimetreeparser.po +++ b/po/sl/mimetreeparser.po @@ -1,343 +1,466 @@ # Copyright (C) YEAR This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Matjaž Jeran <matjaz.jeran@amis.net>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-13 10:10+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 06:45+0200\n" "Last-Translator: Matjaž Jeran <matjaz.jeran@amis.net>\n" "Language-Team: Slovenian <lugos-slo@lugos.si>\n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" "%100==4 ? 3 : 0);\n" -"X-Generator: Lokalize 23.04.3\n" +"X-Generator: Poedit 3.3.2\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Ime" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Velikost" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Šifrirano" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Podpisano" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Shranjevanje priloge ni uspelo." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "Priponka shranjena na disk: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Priponke ni bilo mogoče odpreti." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Priponke ni bilo mogoče shraniti za odpiranje." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "V tej prilogi ni bilo najdenih nobenih ključev" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "%1 ključ uvožen" msgstr[1] "%1 ključa uvožena" msgstr[2] "%1 ključi uvoženi" msgstr[3] "%1 ključev uvoženo" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "%1 ključ je že uvožen" msgstr[1] "%1 ključa sta že uvožena" msgstr[2] "%1 ključi so že uvoženi" msgstr[3] "%1 ključev je že uvoženih" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "naprej" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "odg" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Neznano" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Napačen kripto vtičnik." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "" -"Podatkov ni bilo mogoče dešifrirati: ključ za prejemnike ni bil najden." +msgid "No appropriate crypto plug-in was found." +msgstr "Ni najdenega ustreznega kripto-vtičnika." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "Podatkov ni bilo mogoče dešifrirati." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "Kripto-vtičnik \"%1\" ne more dešifrirati sporočil." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "Kripto-vtičnik \"%1\" ni mogel dešifrirati podatkov." + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Napaka: %1" -#: core/partmodel.cpp:483 +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Ključ ni na voljo." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Napačno geslo." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Pregledovalnik sporočil" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Zadeva:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Od:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Pošiljatelj:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Za:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Shrani prilogo" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Uvozi ključ" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Zapri" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Prišlo je do napake: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Ta pošta vsebuje povabilo" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "poslal %1 dne %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "To sporočilo je bilo podpisano s ključem %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Podrobnosti ključa niso na voljo." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "To sporočilo je s ključem %1 podpisal %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "Ključ je bil preklican." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "Ključ je potekel." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Temu ključu zaupate." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "Podpis je neveljaven." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "To sporočilo je šifrirano, vendar nimamo ključa zanj." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "To sporočilo je šifrirano na ključ: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"To sporočilo je bilo podpisano ustrezno z VS-NfD s ključem <a href=\"%1\">" +"%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "To sporočilo je bilo podpisano s ključem <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "To sporočilo je bilo podpisano ustrezno z VS-NfD z %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "To sporočilo je bilo podpisano s %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">Ključ</a> je bil preklican." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "<a href=\"%1\">Ključ</a> je potekel." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "Podpis je veljaven, a veljavnost <a href=\"%1\">ključa</a> ni znana." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"Podpis je veljaven in <a href=\"%1\">ključ</a> je le malo zaupanja vreden." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" +"Podpis je veljaven in <a href=\"%1\">ključ</a> je polno zaupanja vreden." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"Podpis je veljaven in <a href=\"%1\">ključ</a> je končno vreden zaupanja." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "Podpis je veljaven, a <a href=\"%1\">ključ</a> ni vreden zaupanja." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"To sporočilo je šifrirano v skladu z VS-NfD, vendar nimamo ključa zanj." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "To sporočilo je šifrirano v skladu z VS-NfD." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "To sporočilo je šifrirano." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Sporočilo je šifrirano z naslednjimi ključi:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Neznan ključ" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "&Shrani priloge kot..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Odpri" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Uvozi javni ključ" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Povabilo" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "Povzetek:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organizator:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Lokacija:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "Datum začetka:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "Datum konca:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "Po&drobnosti:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Enkapsulirano e-poštno sporočilo" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Od:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Datum:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Napaka: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "Predmet:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "Od:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "Za:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"Ni bilo mogoče zagnati upravljalnika potrdil. Preverite vašo namestitev." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "Napaka KMaila" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "" +#~ "Podatkov ni bilo mogoče dešifrirati: ključ za prejemnike ni bil najden." diff --git a/po/sv/mimetreeparser.po b/po/sv/mimetreeparser.po index 2295826..71f874c 100644 --- a/po/sv/mimetreeparser.po +++ b/po/sv/mimetreeparser.po @@ -1,337 +1,459 @@ # Copyright (C) YEAR This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Stefan Asserhäll <stefan.asserhall@bredband.net>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 07:56+0200\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 07:18+0200\n" "Last-Translator: Stefan Asserhäll <stefan.asserhall@bredband.net>\n" "Language-Team: Swedish <kde-i18n-doc@kde.org>\n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Lokalize 20.08.1\n" +"X-Generator: Lokalize 23.04.3\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Namn" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Storlek" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Krypterad" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Signerad" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Misslyckades spara bilaga." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "Sparade bilaga på disk: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Misslyckades öppna bilaga." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Misslyckades spara bilaga för att öppna den." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "Inga nycklar hittades i den här bilagan" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "en nyckel importerad" msgstr[1] "%1 nycklar importerade" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "en nyckel har redan importerats" msgstr[1] "%1 nycklar har redan importerats" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "fwd" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "sv" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Okänd" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Felaktigt kryptoinsticksprogram." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "Kunde inte dekryptera data. Ingen nyckel hittades för mottagarna." +msgid "No appropriate crypto plug-in was found." +msgstr "Inget lämpligt kryptoinsticksprogram hittades." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "Kunde inte dekryptera data." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "Kryptoinsticksprogrammet \"%1\" kan inte dekryptera några brev." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "Kryptoinsticksprogrammet \"%1\" kunde inte dekryptera data." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Fel: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Ingen nyckel tillgänglig." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Felaktig lösenordsfras." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Brevvisning" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Rubrik:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Från:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Avsändare:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Till:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Spara bilaga" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Importera nyckel" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Stäng" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Ett fel uppstod: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Brevet innehåller en inbjudan" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "skickat av %1 %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Brevet har signerats med användning av nyckel %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Nyckelinformationen är inte tillgänglig." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Brevet har signerats med användning av nyckel %1 av %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "Nyckeln har återkallats." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "Nyckeln har gått ut." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Du litar på den här nyckeln." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "Signaturen är ogiltig." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "Brevet är krypterat men vi har inte nyckeln för det." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Brevet är krypterat med nyckeln: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Brevet har signerats VS-NfD kompatibelt med användning av nyckel <a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Brevet har signerats med användning av nyckel <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Brevet har signerats VS-NfD kompatibelt av %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Brevet har signerats av %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">Nyckeln</a> har återkallats." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "<a href=\"%1\">Nyckeln</a> har gått ut." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"Signaturen är giltig, men <a href=\"%1\">nyckelns</a> giltighet är okänd." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"Signaturen är giltig och <a href=\"%1\">nyckeln</a> är möjligen pålitlig." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "Signaturen är giltig och <a href=\"%1\">nyckeln</a> är pålitlig." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" +"Signaturen är giltig och <a href=\"%1\">nyckeln</a> är fullständigt pålitlig." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "Signaturen är giltig, men <a href=\"%1\">nyckeln</a> är opålitlig." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Brevet är krypterat VS-NfD kompatibelt men vi har inte nyckeln för det." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Brevet är krypterat VS-NfD kompatibelt." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Brevet är krypterat." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Brevet är krypterat för följande nycklar:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Okänd nyckel" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "&Spara bilaga som..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Öppna" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Importera öppen nyckel" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Inbjudan" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Sammanfattning:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organisatör:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Plats:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "&Startdatum:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "Slut&datum:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Detaljinformation:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Inbäddat brev" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Från:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Datum:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Fel: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "&Rubrik:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&Från:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&Till:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "Kunde inte starta certifikathanteraren. Kontrollera installationen." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "Kmail-fel" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "Kunde inte dekryptera data. Ingen nyckel hittades för mottagarna." diff --git a/po/tr/mimetreeparser.po b/po/tr/mimetreeparser.po index 2b81d6a..5fb99a8 100644 --- a/po/tr/mimetreeparser.po +++ b/po/tr/mimetreeparser.po @@ -1,337 +1,458 @@ # Copyright (C) YEAR This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Emir SARI <emir_sari@icloud.com>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 12:08+0300\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 21:49+0300\n" "Last-Translator: Emir SARI <emir_sari@icloud.com>\n" "Language-Team: Turkish <kde-l10n-tr@kde.org>\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Lokalize 23.11.70\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Ad" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Boyut" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Şifrelenmiş" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "İmzalanmış" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Ek kaydedilemedi." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "Ek diske kaydedildi: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Ek açılamadı." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Ek, açmak üzere kaydedilemedi." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "Bu ekte hiçbir anahtar bulunamadı." -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "1 anahtar içe aktarıldı" msgstr[1] "%1 anahtar içe aktarıldı" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "1 anahtar halihazırda içe aktarılmıştı" msgstr[1] "%1 anahtar halihazırda içe aktarılmıştı" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "ilt" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "ynt" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Bilinmeyen" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Yanlış kripto eklentisi." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "Verinin şifresi çözülemedi: Alıcılar için hiçbir anahtar bulunamadı." +msgid "No appropriate crypto plug-in was found." +msgstr "Uygun kripto eklentisi bulunamadı." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "Verinin şifresi çözülemedi." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "Kripto eklentisi \"%1\", iletilerin şifresini çözemez." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "Kripto eklentisi \"%1\", verinin şifresini çözemedi." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Hata: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Kullanılabilir anahtar yok." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Yanlış anahtar parolası." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "İleti Görüntüleyicisi" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Konu:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Kimden:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Gönderen:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Kime:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Eki Kaydet" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Anahtar İçe Aktar" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Kapat" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Bir hata oluştu: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "Bu ileti, bir davet içeriyor" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "%1 tarafından %2 tarihinde gönderildi" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." -msgstr "Bu ileti, %1 anahtarı kullanılarak imzalandı." +msgstr "Bu ileti, %1 anahtarı kullanılarak imzalanmış." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Anahtar ayrıntıları kullanılabilir değil." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." -msgstr "Bu ileti, %2 tarafından %1 anahtarı kullanılarak imzalandı." +msgstr "Bu ileti, %2 tarafından %1 anahtarı kullanılarak imzalanmış." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "Anahtar yürürlükten kaldırılmış." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "Anahtarın süresi geçmiş." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Bu anahtara güveniyorsunuz." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "İmza geçersiz." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." -msgstr "İleti şifrelenmiş; ancak şifresini çözmek için anahtarımız yok." +msgstr "İleti şifrelenmiş; ancak bunun anahtarı bizde yok." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Bu ileti, şu anahtarla şifrelenmiş: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Bu ileti, <a href=\"%1\">%2</a> anahtarı kullanılarak VS-NfD uyumlu olarak " +"imzalandı." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Bu ileti, <a href=\"%1\">%2</a> anahtarı kullanılarak imzalandı." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Bu ileti, %1 tarafından VS-NfD uyumlu olarak imzalandı." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Bu ileti, %1 tarafından imzalandı." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">Anahtar</a> yürürlükten kaldırılmış." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "<a href=\"%1\">Anahtarın</a> süresi geçmiş." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"İmza geçerli; ancak <a href=\"%1\">anahtarın</a> geçerliliği bilinmiyor." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" +"İmza geçerli ve <a href=\"%1\">anahtara</a> marjinal olarak güveniliyor." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "İmza geçerli ve <a href=\"%1\">anahtara</a> tümüyle güveniliyor." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "İmza geçerli ve <a href=\"%1\">anahtara</a> sonuç olarak güveniliyor." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "İmza geçerli; ancak <a href=\"%1\">anahtara</a> güvenilmiyor." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "Bu ileti, VS-NfD uyumlu şifrelenmiş; ancak bunun anahtarı bizde yok." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Bu ileti, VS-NfD uyumlu şifrelenmiş." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Bu ileti şifrelenmiş." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Bu ileti, şu anahtarlar için şifrelenmiş:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Bilinmeyen anahtar" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "Eki Farklı &Kaydet..." -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Aç" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Genel Anahtar İçe Aktar" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Davet" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Özet:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Organizatör:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Konum:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "&Başlangıç tarihi:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "B&itiş tarihi:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Ayrıntılar:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Kapsüllenmiş E-posta" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Kimden:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Tarih:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Hata: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "K&onu:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&Kimden:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "Kim&e:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "Sertifika yöneticisi başlatılamadı. Lütfen kurulumunuzu denetleyin." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "K Posta Hatası" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "" +#~ "Verinin şifresi çözülemedi: Alıcılar için hiçbir anahtar bulunamadı." diff --git a/po/uk/mimetreeparser.po b/po/uk/mimetreeparser.po index 41d7881..778a88f 100644 --- a/po/uk/mimetreeparser.po +++ b/po/uk/mimetreeparser.po @@ -1,343 +1,466 @@ # Copyright (C) YEAR This file is copyright: # This file is distributed under the same license as the mimetreeparser package. # # Yuri Chornoivan <yurchor@ukr.net>, 2023. msgid "" msgstr "" "Project-Id-Version: mimetreeparser\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" -"POT-Creation-Date: 2023-08-15 00:46+0000\n" -"PO-Revision-Date: 2023-08-12 08:58+0300\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-26 08:47+0300\n" "Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n" "Language-Team: Ukrainian <kde-i18n-uk@kde.org>\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Lokalize 20.12.0\n" -#: core/attachmentmodel.cpp:88 +#: core/attachmentmodel.cpp:90 #, kde-format msgctxt "@title:column" msgid "Name" msgstr "Назва" -#: core/attachmentmodel.cpp:90 +#: core/attachmentmodel.cpp:92 #, kde-format msgctxt "@title:column" msgid "Size" msgstr "Розмір" -#: core/attachmentmodel.cpp:92 +#: core/attachmentmodel.cpp:94 #, kde-format msgctxt "@title:column" msgid "Encrypted" msgstr "Зашифровано" -#: core/attachmentmodel.cpp:94 +#: core/attachmentmodel.cpp:96 #, kde-format msgctxt "@title:column" msgid "Signed" msgstr "Підписано" -#: core/attachmentmodel.cpp:196 +#: core/attachmentmodel.cpp:198 #, kde-format msgctxt "@info" msgid "Failed to save attachment." msgstr "Не вдалося зберегти долучення." -#: core/attachmentmodel.cpp:228 +#: core/attachmentmodel.cpp:230 #, kde-format msgctxt "@info" msgid "Saved the attachment to disk: %1" msgstr "Збереження долучення на диску: %1" -#: core/attachmentmodel.cpp:245 +#: core/attachmentmodel.cpp:247 #, kde-format msgctxt "@info" msgid "Failed to open attachment." msgstr "Не вдалося відкрити долучення." -#: core/attachmentmodel.cpp:250 +#: core/attachmentmodel.cpp:252 #, kde-format msgctxt "@info" msgid "Failed to save attachment for opening." msgstr "Не вдалося зберегти долучення для відкриття." -#: core/attachmentmodel.cpp:268 +#: core/attachmentmodel.cpp:273 #, kde-format msgctxt "@info" msgid "No keys were found in this attachment" msgstr "У цьому долученні не було знайдено ключів" -#: core/attachmentmodel.cpp:271 +#: core/attachmentmodel.cpp:276 #, kde-format msgctxt "@info" msgid "one key imported" msgid_plural "%1 keys imported" msgstr[0] "імпортовано %1 ключ" msgstr[1] "імпортовано %1 ключі" msgstr[2] "імпортовано %1 ключів" msgstr[3] "імпортовано %1 ключ" -#: core/attachmentmodel.cpp:274 +#: core/attachmentmodel.cpp:279 #, kde-format msgctxt "@info" msgid "one key was already imported" msgid_plural "%1 keys were already imported" msgstr[0] "%1 ключ вже імпортовано" msgstr[1] "%1 ключі вже імпортовано" msgstr[2] "%1 ключів вже імпортовано" msgstr[3] "%1 ключ вже імпортовано" #: core/mailtemplates.cpp:103 #, kde-format msgid "fwd" msgstr "fwd" #: core/mailtemplates.cpp:132 #, kde-format msgid "re" msgstr "re" #: core/messageparser.cpp:158 #, kde-format msgctxt "displayed when a mail has unknown sender, receiver or date" msgid "Unknown" msgstr "Невідомо" -#: core/messagepart.cpp:618 core/messagepart.cpp:709 core/messagepart.cpp:783 +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 #, kde-format msgctxt "@info:status" msgid "Wrong Crypto Plug-In." msgstr "Неправильний додаток шифрування." -#: core/messagepart.cpp:857 +#: core/messagepart.cpp:986 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data: no key found for recipients." -msgstr "Не вдалося розшифрувати дані. Не знайдено ключа для отримувачів." +msgid "No appropriate crypto plug-in was found." +msgstr "Не знайдено відповідного додатка шифрування." -#: core/messagepart.cpp:862 +#: core/messagepart.cpp:988 #, kde-format -msgctxt "@info" -msgid "Could not decrypt the data." -msgstr "Не вдалося розшифрувати дані." +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "Додатку шифрування «%1» не вдалося розшифрувати повідомлення." + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "Додатку шифрування «%1» не вдалося розшифрувати дані." -#: core/partmodel.cpp:483 +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "Помилка: %1" + +#: core/partmodel.cpp:487 #, kde-format msgctxt "@info:status" msgid "No key available." msgstr "Немає доступних ключів." -#: core/partmodel.cpp:485 +#: core/partmodel.cpp:489 #, kde-format msgctxt "@info:status" msgid "Wrong passphrase." msgstr "Неправильний пароль." -#: quick/qml/MailViewer.qml:39 +#: quick/qml/MailViewer.qml:38 #, kde-format msgctxt "@title:window" msgid "Message viewer" msgstr "Перегляд повідомлень" -#: quick/qml/MailViewer.qml:76 +#: quick/qml/MailViewer.qml:75 #, kde-format msgctxt "@label" msgid "Subject:" msgstr "Тема:" -#: quick/qml/MailViewer.qml:98 +#: quick/qml/MailViewer.qml:96 #, kde-format msgctxt "@label" msgid "From:" msgstr "Від:" -#: quick/qml/MailViewer.qml:115 +#: quick/qml/MailViewer.qml:112 #, kde-format msgctxt "@label" msgid "Sender:" msgstr "Відправник:" -#: quick/qml/MailViewer.qml:132 +#: quick/qml/MailViewer.qml:128 #, kde-format msgctxt "@label" msgid "To:" msgstr "Кому:" -#: quick/qml/MailViewer.qml:190 +#: quick/qml/MailViewer.qml:185 #, kde-format msgctxt "@action:button" msgid "Save attachment" msgstr "Зберегти долучення" #: quick/qml/private/AttachmentDelegate.qml:56 #, kde-format msgctxt "@action:button" msgid "Import key" msgstr "Імпортувати ключ" #: quick/qml/private/Banner.qml:188 #, kde-format msgctxt "@action:button" msgid "Close" msgstr "Закрити" #: quick/qml/private/ErrorPart.qml:27 #, kde-format msgid "An error occurred: %1" msgstr "Сталася помилка: %1" -#: quick/qml/private/ICalPart.qml:28 +#: quick/qml/private/ICalPart.qml:27 #, kde-format msgid "This mail contains an invitation" msgstr "У цьому повідомленні міститься запрошення" -#: quick/qml/private/MailPart.qml:42 +#: quick/qml/private/MailPart.qml:41 #, kde-format msgctxt "@info" msgid "sent by %1 on %2" msgstr "надіслано %1, %2" -#: quick/qml/private/MailPartModel.qml:54 +#: quick/qml/private/MailPartModel.qml:55 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1." msgstr "Це повідомлення підписано за допомогою ключа %1." -#: quick/qml/private/MailPartModel.qml:55 +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 #, kde-format msgctxt "@label" msgid "The key details are not available." msgstr "Подробиці щодо ключа недоступні." -#: quick/qml/private/MailPartModel.qml:57 +#: quick/qml/private/MailPartModel.qml:58 #, kde-format msgctxt "@label" msgid "This message has been signed using the key %1 by %2." msgstr "Це повідомлення підписано за допомогою ключа %1 %2." -#: quick/qml/private/MailPartModel.qml:59 +#: quick/qml/private/MailPartModel.qml:60 #, kde-format msgctxt "@label" msgid "The key was revoked." msgstr "Ключ відкликано." -#: quick/qml/private/MailPartModel.qml:62 +#: quick/qml/private/MailPartModel.qml:63 #, kde-format msgctxt "@label" msgid "The key has expired." msgstr "Строк дії ключа вичерпано." -#: quick/qml/private/MailPartModel.qml:65 +#: quick/qml/private/MailPartModel.qml:66 #, kde-format msgctxt "@label" msgid "You are trusting this key." msgstr "Ви довіряєте цьому ключу." -#: quick/qml/private/MailPartModel.qml:68 +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 #, kde-format msgctxt "@label" msgid "The signature is invalid." msgstr "Підпис є некоректним." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 #, kde-format msgid "This message is encrypted but we don't have the key for it." msgstr "" "Це повідомлення зашифровано, але у нас немає ключа для його розшифровування." -#: quick/qml/private/MailPartModel.qml:97 +#: quick/qml/private/MailPartModel.qml:98 #, kde-format msgid "This message is encrypted to the key: %1" msgstr "Це повідомлення зашифровано таким ключем: %1" -#: widgets/messageviewer.cpp:54 +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" +"Це повідомлення підписано відповідно до VS-NfD за допомогою ключа <a href=" +"\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "Це повідомлення підписано за допомогою ключа <a href=\"%1\">%2</a>." + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "Це повідомлення підписано відповідно до VS-NfD %1." + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "Це повідомлення підписано %1." + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "<a href=\"%1\">Ключ</a> відкликано." + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "Строк дії <a href=\"%1\">ключа</a> вичерпано." + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" +"Підпис коректний, але невідомо коректний <a href=\"%1\">ключ</a> чи ні." + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "Підпис коректний, надійність <a href=\"%1\">ключа</a> є обмеженою." + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "Підпис коректний, надійність <a href=\"%1\">ключа</a> повна." + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "Підпис коректний, надійність <a href=\"%1\">ключа</a> необмежена." + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "Підпис коректний, але <a href=\"%1\">ключ</a> не є надійним." + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" +"Це повідомлення зашифровано відповідно до VS-NfD, але у нас немає ключа для " +"його розшифровування." + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "Це повідомлення зашифровано відповідно до VS-NfD." + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "Це повідомлення зашифровано." + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "Повідомлення зашифровано для таких ключів:" + +#: widgets/messagecontainerwidget.cpp:237 +#, kde-format +msgid "Unknown key" +msgstr "Невідомий ключ" + +#: widgets/messageviewer.cpp:57 #, kde-format msgid "&Save Attachment As..." msgstr "&Зберегти долучення як…" -#: widgets/messageviewer.cpp:59 +#: widgets/messageviewer.cpp:62 #, kde-format msgctxt "to open" msgid "Open" msgstr "Відкрити" -#: widgets/messageviewer.cpp:64 +#: widgets/messageviewer.cpp:67 #, kde-format msgctxt "@action:inmenu" msgid "Import public key" msgstr "Імпортувати відкритий ключ" -#: widgets/messageviewer.cpp:192 +#: widgets/messageviewer.cpp:236 #, kde-format msgid "Invitation" msgstr "Запрошення" -#: widgets/messageviewer.cpp:195 +#: widgets/messageviewer.cpp:239 #, kde-format msgid "&Summary:" msgstr "&Резюме:" -#: widgets/messageviewer.cpp:196 +#: widgets/messageviewer.cpp:240 #, kde-format msgid "&Organizer:" msgstr "&Організатор:" -#: widgets/messageviewer.cpp:198 +#: widgets/messageviewer.cpp:242 #, kde-format msgid "&Location:" msgstr "&Розташування:" -#: widgets/messageviewer.cpp:200 +#: widgets/messageviewer.cpp:244 #, kde-format msgid "&Start date:" msgstr "Дата &початку:" -#: widgets/messageviewer.cpp:202 +#: widgets/messageviewer.cpp:246 #, kde-format msgid "&End date:" msgstr "Дата за&кінчення:" -#: widgets/messageviewer.cpp:205 +#: widgets/messageviewer.cpp:249 #, kde-format msgid "&Details:" msgstr "&Подробиці:" -#: widgets/messageviewer.cpp:215 +#: widgets/messageviewer.cpp:271 #, kde-format msgid "Encapsulated email" msgstr "Вбудовано повідомлення" -#: widgets/messageviewer.cpp:223 +#: widgets/messageviewer.cpp:279 #, kde-format msgid "From:" msgstr "Від:" -#: widgets/messageviewer.cpp:224 +#: widgets/messageviewer.cpp:280 #, kde-format msgid "Date:" msgstr "Дата:" -#: widgets/messageviewer.cpp:237 -#, kde-format -msgid "Error: %1" -msgstr "Помилка: %1" - -#: widgets/messageviewer.cpp:257 +#: widgets/messageviewer.cpp:315 #, kde-format msgid "&Subject:" msgstr "&Тема:" -#: widgets/messageviewer.cpp:260 +#: widgets/messageviewer.cpp:318 #, kde-format msgid "&From:" msgstr "&Від:" -#: widgets/messageviewer.cpp:263 +#: widgets/messageviewer.cpp:321 #, kde-format msgid "&To:" msgstr "&Кому:" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" +"Не вдається запустити програму керування сертифікатами. Будь ласка, " +"перевірте, чи належним чином встановлено програму." + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "Помилка KMail" + +#~ msgctxt "@info" +#~ msgid "Could not decrypt the data: no key found for recipients." +#~ msgstr "Не вдалося розшифрувати дані. Не знайдено ключа для отримувачів." diff --git a/po/zh_CN/mimetreeparser.po b/po/zh_CN/mimetreeparser.po new file mode 100644 index 0000000..0963b98 --- /dev/null +++ b/po/zh_CN/mimetreeparser.po @@ -0,0 +1,449 @@ +msgid "" +msgstr "" +"Project-Id-Version: kdeorg\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2023-08-27 00:47+0000\n" +"PO-Revision-Date: 2023-08-27 03:42\n" +"Last-Translator: \n" +"Language-Team: Chinese Simplified\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Crowdin-Project: kdeorg\n" +"X-Crowdin-Project-ID: 269464\n" +"X-Crowdin-Language: zh-CN\n" +"X-Crowdin-File: /kf5-trunk/messages/mimetreeparser/mimetreeparser.pot\n" +"X-Crowdin-File-ID: 44633\n" + +#: core/attachmentmodel.cpp:90 +#, kde-format +msgctxt "@title:column" +msgid "Name" +msgstr "名称" + +#: core/attachmentmodel.cpp:92 +#, kde-format +msgctxt "@title:column" +msgid "Size" +msgstr "大小" + +#: core/attachmentmodel.cpp:94 +#, kde-format +msgctxt "@title:column" +msgid "Encrypted" +msgstr "已加密" + +#: core/attachmentmodel.cpp:96 +#, kde-format +msgctxt "@title:column" +msgid "Signed" +msgstr "已签名" + +#: core/attachmentmodel.cpp:198 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment." +msgstr "无法保存附件。" + +#: core/attachmentmodel.cpp:230 +#, kde-format +msgctxt "@info" +msgid "Saved the attachment to disk: %1" +msgstr "已将附件保存到磁盘:%1" + +#: core/attachmentmodel.cpp:247 +#, kde-format +msgctxt "@info" +msgid "Failed to open attachment." +msgstr "无法打开附件。" + +#: core/attachmentmodel.cpp:252 +#, kde-format +msgctxt "@info" +msgid "Failed to save attachment for opening." +msgstr "无法保存以打开附件。" + +#: core/attachmentmodel.cpp:273 +#, kde-format +msgctxt "@info" +msgid "No keys were found in this attachment" +msgstr "没有在此附件中找到密钥" + +#: core/attachmentmodel.cpp:276 +#, kde-format +msgctxt "@info" +msgid "one key imported" +msgid_plural "%1 keys imported" +msgstr[0] "已导入 %1 个密钥" + +#: core/attachmentmodel.cpp:279 +#, kde-format +msgctxt "@info" +msgid "one key was already imported" +msgid_plural "%1 keys were already imported" +msgstr[0] "%1 个密钥已经被导入过了" + +#: core/mailtemplates.cpp:103 +#, kde-format +msgid "fwd" +msgstr "转发" + +#: core/mailtemplates.cpp:132 +#, kde-format +msgid "re" +msgstr "回复" + +#: core/messageparser.cpp:158 +#, kde-format +msgctxt "displayed when a mail has unknown sender, receiver or date" +msgid "Unknown" +msgstr "未知" + +#: core/messagepart.cpp:625 core/messagepart.cpp:660 core/messagepart.cpp:835 +#, kde-format +msgctxt "@info:status" +msgid "Wrong Crypto Plug-In." +msgstr "错误的加密插件。" + +#: core/messagepart.cpp:986 +#, kde-format +msgid "No appropriate crypto plug-in was found." +msgstr "没有找到合适的加密算法插件。" + +#: core/messagepart.cpp:988 +#, kde-format +msgid "Crypto plug-in \"%1\" cannot decrypt messages." +msgstr "加密算法插件“%1”无法解密任何信件。" + +#: core/messagepart.cpp:990 +#, kde-format +msgid "Crypto plug-in \"%1\" could not decrypt the data." +msgstr "加密插件“%1”无法解密数据。" + +#: core/messagepart.cpp:991 widgets/messageviewer.cpp:295 +#, kde-format +msgid "Error: %1" +msgstr "错误:%1" + +#: core/partmodel.cpp:487 +#, kde-format +msgctxt "@info:status" +msgid "No key available." +msgstr "没有可用的密钥。" + +#: core/partmodel.cpp:489 +#, kde-format +msgctxt "@info:status" +msgid "Wrong passphrase." +msgstr "密码错误。" + +#: quick/qml/MailViewer.qml:38 +#, kde-format +msgctxt "@title:window" +msgid "Message viewer" +msgstr "消息查看器" + +#: quick/qml/MailViewer.qml:75 +#, kde-format +msgctxt "@label" +msgid "Subject:" +msgstr "主题:" + +#: quick/qml/MailViewer.qml:96 +#, kde-format +msgctxt "@label" +msgid "From:" +msgstr "发件人:" + +#: quick/qml/MailViewer.qml:112 +#, kde-format +msgctxt "@label" +msgid "Sender:" +msgstr "发送者:" + +#: quick/qml/MailViewer.qml:128 +#, kde-format +msgctxt "@label" +msgid "To:" +msgstr "收件人:" + +#: quick/qml/MailViewer.qml:185 +#, kde-format +msgctxt "@action:button" +msgid "Save attachment" +msgstr "保存附件" + +#: quick/qml/private/AttachmentDelegate.qml:56 +#, kde-format +msgctxt "@action:button" +msgid "Import key" +msgstr "导入密钥" + +#: quick/qml/private/Banner.qml:188 +#, kde-format +msgctxt "@action:button" +msgid "Close" +msgstr "关闭" + +#: quick/qml/private/ErrorPart.qml:27 +#, kde-format +msgid "An error occurred: %1" +msgstr "发生错误:%1" + +#: quick/qml/private/ICalPart.qml:27 +#, kde-format +msgid "This mail contains an invitation" +msgstr "此邮件包含一个邀请" + +#: quick/qml/private/MailPart.qml:41 +#, kde-format +msgctxt "@info" +msgid "sent by %1 on %2" +msgstr "由 %1 发送于 %2" + +#: quick/qml/private/MailPartModel.qml:55 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1." +msgstr "此消息已使用密钥 %1 签名。" + +#: quick/qml/private/MailPartModel.qml:56 widgets/messagecontainerwidget.cpp:78 +#, kde-format +msgctxt "@label" +msgid "The key details are not available." +msgstr "无法获取密钥详情。" + +#: quick/qml/private/MailPartModel.qml:58 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key %1 by %2." +msgstr "此消息已由 %2 使用密钥 %1 签名。" + +#: quick/qml/private/MailPartModel.qml:60 +#, kde-format +msgctxt "@label" +msgid "The key was revoked." +msgstr "此密钥已被吊销。" + +#: quick/qml/private/MailPartModel.qml:63 +#, kde-format +msgctxt "@label" +msgid "The key has expired." +msgstr "此密钥已过期。" + +#: quick/qml/private/MailPartModel.qml:66 +#, kde-format +msgctxt "@label" +msgid "You are trusting this key." +msgstr "您信任此密钥。" + +#: quick/qml/private/MailPartModel.qml:69 +#: widgets/messagecontainerwidget.cpp:108 +#, kde-format +msgctxt "@label" +msgid "The signature is invalid." +msgstr "此签名无效。" + +#: quick/qml/private/MailPartModel.qml:98 +#: widgets/messagecontainerwidget.cpp:205 +#, kde-format +msgid "This message is encrypted but we don't have the key for it." +msgstr "此消息已加密,但我们没有它的密钥。" + +#: quick/qml/private/MailPartModel.qml:98 +#, kde-format +msgid "This message is encrypted to the key: %1" +msgstr "此消息已使用密钥 %1 加密" + +#: widgets/messagecontainerwidget.cpp:66 +#, kde-format +msgctxt "@label" +msgid "" +"This message has been signed VS-NfD compliant using the key <a href=\"%1\">" +"%2</a>." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:73 +#, kde-format +msgctxt "@label" +msgid "This message has been signed using the key <a href=\"%1\">%2</a>." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:81 +#, kde-format +msgctxt "@label" +msgid "This message has been signed VS-NfD compliant by %1." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:83 +#, kde-format +msgctxt "@label" +msgid "This message has been signed by %1." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:86 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was revoked." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:89 +#, kde-format +msgctxt "@label" +msgid "The <a href=\"%1\">key</a> was expired." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:94 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:97 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is marginally trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:99 +#, kde-format +msgctxt "@label" +msgid "The signature is valid and the <a href=\"%1\">key</a> is fully trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:102 +#, kde-format +msgctxt "@label" +msgid "" +"The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:104 +#, kde-format +msgctxt "@label" +msgid "The signature is valid, but the <a href=\"%1\">key</a> is untrusted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:203 +#, kde-format +msgid "" +"This message is VS-NfD compliant encrypted but we don't have the key for it." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:209 +#, kde-format +msgid "This message is VS-NfD compliant encrypted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:211 +#, kde-format +msgid "This message is encrypted." +msgstr "" + +#: widgets/messagecontainerwidget.cpp:220 +#, kde-format +msgid "The message is encrypted for the following keys:" +msgstr "" + +#: widgets/messagecontainerwidget.cpp:237 +#, fuzzy, kde-format +#| msgctxt "displayed when a mail has unknown sender, receiver or date" +#| msgid "Unknown" +msgid "Unknown key" +msgstr "未知" + +#: widgets/messageviewer.cpp:57 +#, kde-format +msgid "&Save Attachment As..." +msgstr "附件另存为(&S)..." + +#: widgets/messageviewer.cpp:62 +#, kde-format +msgctxt "to open" +msgid "Open" +msgstr "打开" + +#: widgets/messageviewer.cpp:67 +#, kde-format +msgctxt "@action:inmenu" +msgid "Import public key" +msgstr "导入公钥" + +#: widgets/messageviewer.cpp:236 +#, kde-format +msgid "Invitation" +msgstr "邀请" + +#: widgets/messageviewer.cpp:239 +#, kde-format +msgid "&Summary:" +msgstr "摘要(&S):" + +#: widgets/messageviewer.cpp:240 +#, kde-format +msgid "&Organizer:" +msgstr "组织者(&O):" + +#: widgets/messageviewer.cpp:242 +#, kde-format +msgid "&Location:" +msgstr "地点(&L):" + +#: widgets/messageviewer.cpp:244 +#, kde-format +msgid "&Start date:" +msgstr "开始日期(&S):" + +#: widgets/messageviewer.cpp:246 +#, kde-format +msgid "&End date:" +msgstr "结束日期(&E):" + +#: widgets/messageviewer.cpp:249 +#, kde-format +msgid "&Details:" +msgstr "详细信息(&D):" + +#: widgets/messageviewer.cpp:271 +#, kde-format +msgid "Encapsulated email" +msgstr "封装的信件" + +#: widgets/messageviewer.cpp:279 +#, kde-format +msgid "From:" +msgstr "发件人:" + +#: widgets/messageviewer.cpp:280 +#, kde-format +msgid "Date:" +msgstr "日期:" + +#: widgets/messageviewer.cpp:315 +#, kde-format +msgid "&Subject:" +msgstr "主题(&S):" + +#: widgets/messageviewer.cpp:318 +#, kde-format +msgid "&From:" +msgstr "发件人(&F):" + +#: widgets/messageviewer.cpp:321 +#, kde-format +msgid "&To:" +msgstr "收件人(&T):" + +#: widgets/urlhandler.cpp:37 +#, kde-format +msgid "Could not start certificate manager. Please check your installation." +msgstr "" + +#: widgets/urlhandler.cpp:39 +#, kde-format +msgid "KMail Error" +msgstr "" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 75b59ff..e98b2ec 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,200 +1,194 @@ # SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com> # SPDX-License-Identifier: BSD-3-Clause ecm_setup_version(PROJECT VARIABLE_PREFIX MIMETREEPARSER_CORE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_core_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfigVersion.cmake" SOVERSION 6 ) # private library add_library(mimetreeparserprivate STATIC) set_target_properties(mimetreeparserprivate PROPERTIES POSITION_INDEPENDENT_CODE ON) add_definitions(-DTRANSLATION_DOMAIN=\"mimetreeparser\") target_sources(mimetreeparserprivate PUBLIC messagepart.h messagepart.cpp - crypto.h - crypto.cpp cryptohelper.h cryptohelper.cpp utils.h utils.cpp - mailtemplates.h - mailtemplates.cpp - mailcrypto.h - mailcrypto.cpp + job/qgpgmejobexecutor.h + job/qgpgmejobexecutor.cpp ) target_link_libraries(mimetreeparserprivate PUBLIC KPim6::Mime KF6::I18n Qt6::Gui KF6::Codecs Qt6::Core5Compat Gpgme::Gpgme + KPim${KF_MAJOR_VERSION}::Libkleo ) ecm_qt_declare_logging_category(mimetreeparserprivate HEADER mimetreeparser_core_debug.h IDENTIFIER MIMETREEPARSER_CORE_LOG CATEGORY_NAME org.kde.pim.mimetreeparser.core DESCRIPTION "mimetreeparser (pim lib)" EXPORT MIMETREEPARSERNG ) # public dynamic library add_library(KPim6MimeTreeParserCore) add_library(KPim6::MimeTreeParserCore ALIAS KPim6MimeTreeParserCore ) target_sources(KPim6MimeTreeParserCore PRIVATE errors.h async.h attachmentmodel.h bodypartformatter.h bodypartformatterbasefactory.h bodypartformatterbasefactory_p.h enums.h htmlutils.h messageparser.h objecttreeparser.h partmetadata.h partmodel.h attachmentmodel.cpp bodypartformatter.cpp bodypartformatter_impl.cpp bodypartformatterbasefactory.cpp htmlutils.cpp messageparser.cpp objecttreeparser.cpp partmodel.cpp ) if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KPim6MimeTreeParserCore PROPERTIES UNITY_BUILD ON) endif() generate_export_header(KPim6MimeTreeParserCore BASE_NAME mimetreeparser_core) target_include_directories(KPim6MimeTreeParserCore INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore>") target_link_libraries(KPim6MimeTreeParserCore PUBLIC KPim6::Mime KF6::I18n Qt6::Gui PRIVATE mimetreeparserprivate ) set_target_properties(KPim6MimeTreeParserCore PROPERTIES VERSION ${MIMETREEPARSERNG_VERSION} SOVERSION ${MIMETREEPARSERNG_SOVERSION} EXPORT_NAME MimeTreeParserCore ) ecm_generate_pri_file(BASE_NAME MimeTreeParserCore LIB_NAME KPim6MimeTreeParserCore DEPS "MimeTreeParserCore" FILENAME_VAR PRI_FILENAME ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(TARGETS KPim6MimeTreeParserCore EXPORT KPim6MimeTreeParserCoreTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) ecm_generate_headers(MimeTreeParserCore_CamelCase_HEADERS HEADER_NAMES AttachmentModel ObjectTreeParser MessageParser MessagePart PartModel - MailCrypto - Crypto Errors PartMetaData - MailTemplates REQUIRED_HEADERS MimeTreeParserCore_HEADERS PREFIX MimeTreeParserCore ) install(FILES ${MimeTreeParserCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore/MimeTreeParserCore COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_core_export.h ${MimeTreeParserCore_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore/mimetreeparsercore COMPONENT Devel ) if (BUILD_QCH) ecm_add_qch( KPim6MimeTreeParserCore_QCH NAME MimeTreeParserCore BASE_NAME KPim6MimeTreeParserCore VERSION ${PIM_VERSION} ORG_DOMAIN org.kde # using only public headers, to cover only public API SOURCES ${MimeTreeParserCore_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt6Core_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS MIMETREEPARSERCORE_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6MimeTreeParserCore") if (BUILD_QCH) ecm_install_qch_export( TARGETS KPim6MimeTreeParserCore_QCH FILE KPim6MimeTreeParserCoreQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6MimeTreeParserCoreQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserCoreConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KPim6MimeTreeParserCoreTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim6MimeTreeParserCoreTargets.cmake NAMESPACE KPim6:: ) if (BUILD_TESTING) add_subdirectory(autotests) endif() diff --git a/src/core/attachmentmodel.cpp b/src/core/attachmentmodel.cpp index 6bdaebc..d808ac9 100644 --- a/src/core/attachmentmodel.cpp +++ b/src/core/attachmentmodel.cpp @@ -1,298 +1,304 @@ // SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com> // SPDX-FileCopyCopyright: 2017 Christian Mollekopf <mollekopf@kolabsys.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include "attachmentmodel.h" -#include <QString> +#include "job/qgpgmejobexecutor.h" +#include "objecttreeparser.h" + +#include <QGpgME/ImportJob> +#include <QGpgME/Protocol> #include <KLocalizedString> #include <KMime/Content> + #include <QDebug> #include <QDesktopServices> #include <QDir> #include <QFile> #include <QGuiApplication> #include <QIcon> #include <QMimeDatabase> #include <QStandardPaths> #include <QUrl> -#include <QStringLiteral> - QString sizeHuman(float size) { QStringList list; list << QStringLiteral("KB") << QStringLiteral("MB") << QStringLiteral("GB") << QStringLiteral("TB"); QStringListIterator i(list); QString unit = QStringLiteral("Bytes"); while (size >= 1024.0 && i.hasNext()) { unit = i.next(); size /= 1024.0; } if (unit == QStringLiteral("Bytes")) { return QString().setNum(size) + QStringLiteral(" ") + unit; } else { return QString().setNum(size, 'f', 2) + QStringLiteral(" ") + unit; } } class AttachmentModelPrivate { public: AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser); AttachmentModel *q; std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser; MimeTreeParser::MessagePart::List mAttachments; }; AttachmentModelPrivate::AttachmentModelPrivate(AttachmentModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser) : q(q_ptr) , mParser(parser) { mAttachments = mParser->collectAttachmentParts(); } AttachmentModel::AttachmentModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser) : QAbstractTableModel() , d(std::unique_ptr<AttachmentModelPrivate>(new AttachmentModelPrivate(this, parser))) { } AttachmentModel::~AttachmentModel() { } QHash<int, QByteArray> AttachmentModel::roleNames() const { return { {TypeRole, QByteArrayLiteral("type")}, {NameRole, QByteArrayLiteral("name")}, {SizeRole, QByteArrayLiteral("size")}, {IconRole, QByteArrayLiteral("iconName")}, {IsEncryptedRole, QByteArrayLiteral("encrypted")}, {IsSignedRole, QByteArrayLiteral("signed")}, }; } QVariant AttachmentModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case NameColumn: return i18ndc("mimetreeparser", "@title:column", "Name"); case SizeColumn: return i18ndc("mimetreeparser", "@title:column", "Size"); case IsEncryptedColumn: return i18ndc("mimetreeparser", "@title:column", "Encrypted"); case IsSignedColumn: return i18ndc("mimetreeparser", "@title:column", "Signed"); } } return {}; } QVariant AttachmentModel::data(const QModelIndex &index, int role) const { const auto row = index.row(); const auto column = index.column(); const auto part = d->mAttachments.at(row); Q_ASSERT(part); auto node = part->node(); if (!node) { qWarning() << "no content for attachment"; return {}; } QMimeDatabase mimeDb; const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(part->mimeType())); const auto content = node->encodedContent(); switch (column) { case NameColumn: switch (role) { case TypeRole: return mimetype.name(); case Qt::DisplayRole: case NameRole: return part->filename(); case IconRole: return mimetype.iconName(); case Qt::DecorationRole: return QIcon::fromTheme(mimetype.iconName()); case SizeRole: return sizeHuman(content.size()); case IsEncryptedRole: return part->encryptions().size() > 0; case IsSignedRole: return part->signatures().size() > 0; case AttachmentPartRole: return QVariant::fromValue(part); default: return {}; } case SizeColumn: switch (role) { case Qt::DisplayRole: return sizeHuman(content.size()); default: return {}; } case IsEncryptedColumn: switch (role) { case Qt::CheckStateRole: return part->encryptions().size() > 0 ? Qt::Checked : Qt::Unchecked; default: return {}; } case IsSignedColumn: switch (role) { case Qt::CheckStateRole: return part->signatures().size() > 0 ? Qt::Checked : Qt::Unchecked; default: return {}; } default: return {}; } } static QString internalSaveAttachmentToDisk(AttachmentModel *model, const MimeTreeParser::MessagePart::Ptr &part, const QString &path, bool readonly = false) { Q_ASSERT(part); auto node = part->node(); auto data = node->decodedContent(); // This is necessary to store messages embedded messages (EncapsulatedRfc822MessagePart) if (data.isEmpty()) { data = node->encodedContent(); } if (part->isText()) { // convert CRLF to LF before writing text attachments to disk data = KMime::CRLFtoLF(data); } const auto name = part->filename(); QString fname = path + name; // Fallback name should we end up with an empty name if (name.isEmpty()) { fname = path + QStringLiteral("unnamed"); while (QFileInfo::exists(fname)) { fname = fname + QStringLiteral("_1"); } } // A file with that name already exists, we assume it's the right file if (QFileInfo::exists(fname)) { return fname; } QFile f(fname); if (!f.open(QIODevice::ReadWrite)) { qWarning() << "Failed to write attachment to file:" << fname << " Error: " << f.errorString(); Q_EMIT model->info(i18ndc("mimetreeparser", "@info", "Failed to save attachment.")); return {}; } f.write(data); if (readonly) { // make file read-only so that nobody gets the impression that he migh edit attached files f.setPermissions(QFileDevice::ReadUser); } f.close(); qInfo() << "Wrote attachment to file: " << fname; return fname; } bool AttachmentModel::saveAttachmentToDisk(const int row) { const auto part = d->mAttachments.at(row); return saveAttachmentToDisk(part); } bool AttachmentModel::saveAttachmentToDisk(const MimeTreeParser::MessagePart::Ptr &message) { QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); if (downloadDir.isEmpty()) { downloadDir = QStringLiteral("~"); } downloadDir += QLatin1Char('/') + qGuiApp->applicationName(); QDir{}.mkpath(downloadDir); auto path = internalSaveAttachmentToDisk(this, message, downloadDir); if (path.isEmpty()) { return false; } Q_EMIT info(i18ndc("mimetreeparser", "@info", "Saved the attachment to disk: %1", path)); return true; } bool AttachmentModel::openAttachment(const int row) { const auto part = d->mAttachments.at(row); return openAttachment(part); } bool AttachmentModel::openAttachment(const MimeTreeParser::MessagePart::Ptr &message) { QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/') + qGuiApp->applicationName(); QDir{}.mkpath(downloadDir); const auto filePath = internalSaveAttachmentToDisk(this, message, downloadDir, true); if (!filePath.isEmpty()) { if (!QDesktopServices::openUrl(QUrl(QStringLiteral("file://") + filePath))) { Q_EMIT info(i18ndc("mimetreeparser", "@info", "Failed to open attachment.")); return false; } return true; } Q_EMIT info(i18ndc("mimetreeparser", "@info", "Failed to save attachment for opening.")); return false; } bool AttachmentModel::importPublicKey(const int row) { const auto part = d->mAttachments.at(row); return importPublicKey(part); } bool AttachmentModel::importPublicKey(const MimeTreeParser::MessagePart::Ptr &part) { Q_ASSERT(part); - auto result = Crypto::importKey(Crypto::OpenPGP, part->node()->decodedContent()); + const QByteArray certData = part->node()->decodedContent(); + QGpgME::ImportJob *import = QGpgME::openpgp()->importJob(); + MimeTreeParser::QGpgMEJobExecutor executor; + auto result = executor.exec(import, certData); bool success = true; QString message; - if (result.considered == 0) { + if (result.numConsidered() == 0) { message = i18ndc("mimetreeparser", "@info", "No keys were found in this attachment"); success = false; } else { - message = i18ndcp("mimetreeparser", "@info", "one key imported", "%1 keys imported", result.imported); - if (result.unchanged != 0) { - message += - QStringLiteral("\n") + i18ndcp("mimetreeparser", "@info", "one key was already imported", "%1 keys were already imported", result.unchanged); + message = i18ndcp("mimetreeparser", "@info", "one key imported", "%1 keys imported", result.numImported()); + if (result.numUnchanged() != 0) { + message += QStringLiteral("\n") + + i18ndcp("mimetreeparser", "@info", "one key was already imported", "%1 keys were already imported", result.numUnchanged()); } } Q_EMIT info(message); return success; } int AttachmentModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->mAttachments.size(); } return 0; } int AttachmentModel::columnCount(const QModelIndex &parent) const { if (!parent.isValid()) { return ColumnCount; } return 0; } #include "moc_attachmentmodel.cpp" diff --git a/src/core/autotests/CMakeLists.txt b/src/core/autotests/CMakeLists.txt index 38239d4..ed6c39e 100644 --- a/src/core/autotests/CMakeLists.txt +++ b/src/core/autotests/CMakeLists.txt @@ -1,94 +1,76 @@ # SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> # SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu> # SPDX-License-Identifier: BSD-3-Clause set(AUTOMOC ON) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) add_definitions(-DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../testdata" ) add_definitions(-DMAILTEMPLATE_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../templatetestdata" ) include(ECMAddTests) include(${CMAKE_SOURCE_DIR}/cmake/modules/add_gpg_crypto_test.cmake) function(add_mimetreeparser_class_unittest _name _additionalSource) add_executable(${_name} ${_name}.cpp setupenv.cpp ${_additionalSource}) target_link_libraries(${_name} PRIVATE Qt::Test mimetreeparserprivate KPim6::MimeTreeParserCore ) target_include_directories(${_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) add_test(NAME ${_name} COMMAND $<TARGET_FILE:${_name}>) endfunction() function(add_mimetreeparser_crypto_unittest _name) add_executable(${_name} ${_name}.cpp setupenv.cpp) target_include_directories(${_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.. ${GPGME_INCLUDE_DIRS} ) target_link_libraries(${_name} PRIVATE Qt::Test mimetreeparserprivate KPim6::MimeTreeParserCore ) add_gpg_crypto_test(${_name} mimetreeparser-${_name}) endfunction() add_subdirectory(gnupg_home) add_mimetreeparser_crypto_unittest(attachmenttest) add_mimetreeparser_class_unittest(cryptohelpertest "../cryptohelper.cpp") add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../testdata" ) include(${CMAKE_SOURCE_DIR}/cmake/modules/add_gpg_crypto_test.cmake) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ) include_directories(${GPGME_INCLUDE_DIRS}) include(ECMAddTests) add_executable(mimetreeparsertest mimetreeparsertest.cpp) add_gpg_crypto_test(mimetreeparsertest mimetreeparsertest) target_link_libraries(mimetreeparsertest PUBLIC Qt::Test mimetreeparserprivate KPim6::MimeTreeParserCore ) add_executable(gpgerrortest gpgerrortest.cpp) target_link_libraries(gpgerrortest PUBLIC Qt::Test mimetreeparserprivate KPim6::MimeTreeParserCore ) add_test(NAME gpgerrortest COMMAND $<TARGET_FILE:gpgerrortest>) -## Crypto test -add_executable(cryptotest cryptotest.cpp) -add_gpg_crypto_test(cryptotest cryptotest) -target_link_libraries(cryptotest - Qt::Test - mimetreeparserprivate - KPim6::MimeTreeParserCore -) - -## MailTemplates test -add_executable(mailtemplatetest mailtemplatetest.cpp) -add_gpg_crypto_test(mailtemplatetest mailtemplatetest) -target_link_libraries(mailtemplatetest - Qt::Test - mimetreeparserprivate - KPim6::MimeTreeParserCore -) - ## PartModel test add_executable(partmodeltest partmodeltest.cpp) add_gpg_crypto_test(partmodeltest partmodeltest) target_link_libraries(partmodeltest Qt::Test mimetreeparserprivate KPim6::MimeTreeParserCore ) diff --git a/src/core/autotests/cryptohelpertest.cpp b/src/core/autotests/cryptohelpertest.cpp index c6f5887..588b9d4 100644 --- a/src/core/autotests/cryptohelpertest.cpp +++ b/src/core/autotests/cryptohelpertest.cpp @@ -1,135 +1,135 @@ -// Copyright 2015 Sandro Knauß <knauss@kolabsys.com> +// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #include "cryptohelpertest.h" #include "cryptohelper.h" #include <QTest> using namespace MimeTreeParser; void CryptoHelperTest::testPMFDEmpty() { QCOMPARE(prepareMessageForDecryption("").count(), 0); } void CryptoHelperTest::testPMFDWithNoPGPBlock() { const QByteArray text = "testblabla"; - const QList<Block> blocks = prepareMessageForDecryption(text); + const QVector<Block> blocks = prepareMessageForDecryption(text); QCOMPARE(blocks.count(), 1); QCOMPARE(blocks[0].text(), text); QCOMPARE(blocks[0].type(), NoPgpBlock); } void CryptoHelperTest::testPGPBlockType() { const QString blockText = QStringLiteral("text"); const QString preString = QStringLiteral("before\n"); for (int i = 1; i <= PrivateKeyBlock; ++i) { QString name; switch (i) { case PgpMessageBlock: name = QStringLiteral("MESSAGE"); break; case MultiPgpMessageBlock: name = QStringLiteral("MESSAGE PART"); break; case SignatureBlock: name = QStringLiteral("SIGNATURE"); break; case ClearsignedBlock: name = QStringLiteral("SIGNED MESSAGE"); break; case PublicKeyBlock: name = QStringLiteral("PUBLIC KEY BLOCK"); break; case PrivateKeyBlock: name = QStringLiteral("PRIVATE KEY BLOCK"); break; } - QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1String("\n") + blockText; - QList<Block> blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1()); + QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1Char('\n') + blockText; + QVector<Block> blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1()); QCOMPARE(blocks.count(), 1); QCOMPARE(blocks[0].type(), UnknownBlock); - text += QLatin1String("\n-----END PGP ") + name + QLatin1String("\n"); + text += QLatin1String("\n-----END PGP ") + name + QLatin1Char('\n'); blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1()); QCOMPARE(blocks.count(), 2); QCOMPARE(blocks[1].text(), text.toLatin1()); QCOMPARE(blocks[1].type(), static_cast<PGPBlockType>(i)); } } void CryptoHelperTest::testDeterminePGPBlockType() { const QString blockText = QStringLiteral("text"); for (int i = 1; i <= PrivateKeyBlock; ++i) { QString name; switch (i) { case PgpMessageBlock: name = QStringLiteral("MESSAGE"); break; case MultiPgpMessageBlock: name = QStringLiteral("MESSAGE PART"); break; case SignatureBlock: name = QStringLiteral("SIGNATURE"); break; case ClearsignedBlock: name = QStringLiteral("SIGNED MESSAGE"); break; case PublicKeyBlock: name = QStringLiteral("PUBLIC KEY BLOCK"); break; case PrivateKeyBlock: name = QStringLiteral("PRIVATE KEY BLOCK"); break; } - const QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1String("\n") + blockText + QLatin1String("\n"); + const QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1Char('\n') + blockText + QLatin1Char('\n'); const Block block = Block(text.toLatin1()); QCOMPARE(block.text(), text.toLatin1()); QCOMPARE(block.type(), static_cast<PGPBlockType>(i)); } } void CryptoHelperTest::testEmbededPGPBlock() { const QByteArray text = QByteArray("before\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\nafter"); - const QList<Block> blocks = prepareMessageForDecryption(text); + const QVector<Block> blocks = prepareMessageForDecryption(text); QCOMPARE(blocks.count(), 3); QCOMPARE(blocks[0].text(), QByteArray("before\n")); QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n")); QCOMPARE(blocks[2].text(), QByteArray("after")); } void CryptoHelperTest::testClearSignedMessage() { const QByteArray text = QByteArray( "before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\nafter"); - const QList<Block> blocks = prepareMessageForDecryption(text); + const QVector<Block> blocks = prepareMessageForDecryption(text); QCOMPARE(blocks.count(), 3); QCOMPARE(blocks[0].text(), QByteArray("before\n")); QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n")); QCOMPARE(blocks[2].text(), QByteArray("after")); } void CryptoHelperTest::testMultipleBlockMessage() { const QByteArray text = QByteArray( "before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP " "SIGNATURE-----\nafter\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n"); - const QList<Block> blocks = prepareMessageForDecryption(text); + const QVector<Block> blocks = prepareMessageForDecryption(text); QCOMPARE(blocks.count(), 4); QCOMPARE(blocks[0].text(), QByteArray("before\n")); QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n")); QCOMPARE(blocks[2].text(), QByteArray("after\n")); QCOMPARE(blocks[3].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n")); } QTEST_APPLESS_MAIN(CryptoHelperTest) #include "moc_cryptohelpertest.cpp" diff --git a/src/core/autotests/cryptohelpertest.h b/src/core/autotests/cryptohelpertest.h index 8d45c59..e6faf2e 100644 --- a/src/core/autotests/cryptohelpertest.h +++ b/src/core/autotests/cryptohelpertest.h @@ -1,25 +1,23 @@ -// Copyright 2009 Thomas McGuire <mcguire@kde.org> +// SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org> // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #pragma once #include <QObject> namespace MimeTreeParser { - class CryptoHelperTest : public QObject { Q_OBJECT private Q_SLOTS: void testPMFDEmpty(); void testPMFDWithNoPGPBlock(); void testPGPBlockType(); void testDeterminePGPBlockType(); void testEmbededPGPBlock(); void testClearSignedMessage(); void testMultipleBlockMessage(); }; - } diff --git a/src/core/autotests/cryptotest.cpp b/src/core/autotests/cryptotest.cpp deleted file mode 100644 index 96ca9b4..0000000 --- a/src/core/autotests/cryptotest.cpp +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Christian Mollekopf <christian@mkpf.ch> -// SPDX-License-Identifier: LGPL-2.0-or-later - -#include <crypto.h> - -#include <QDebug> -#include <QTest> - -class CryptoTest : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - - void testDecrypt() - { - QByteArray input = - "-----BEGIN PGP " - "MESSAGE-----\n\nhQEMAwzOQ1qnzNo7AQgA3kD1WyRdQawpduoJ3J9h3SpSC7YiNqU7aiyTMUGAdbGO\nBMhIzPdEkai9P486Wpg5h+ywmQrk3KoH/GioRjwvIaeNZY/cmxetT0/" - "ig5rrnqxM\nj63vFFbCbE6kSeDbvYqF5mL3XH+TqpZRW5ApPSgkr7jMDOK7k1eF5A5ey84LYFny\nKy63LGy5KEQk7E1cMLZOHAZnorcm7Lh3RVWgPj+" - "DRDowMn3yVdFOpT5bQ66zAIkc\nBs9IWuq0lMxGsdfRv5wlzUqZJGge3oT7tkZhI6D56MLIjqg7SurQMiWrn6wh51Sr\nR7W9N6lHyrKrffP2VjFwPPK1/" - "Vjd0Am4gTPkf+GcJ4UBDAPKpRg2CPD7UAEIAJuC\n8s2uGAGF9zgoQdrmL6bInA5JCQiZI+B5Jgg9wQ/dW3idJN9esr1Ff7/" - "d8DVuzf1V\nbFydMBqQk5Zkp5FuDhJsfGWK+NPJBUaOKGlGqRPZX+SjP2k1SuDoxvdxvtWYBVOt\nZhq03zczRWo4dxJ++WYqxu6gMlBCO+z84kfhknWyBNeN7+8mmYGNWDo/" - "ARWhspO0\nCIOfBCFeqwCpCZLIiCTBjGg98Ed+SGIdjQwq97suh3nANlKFpiNp2+w02H+" - "rarMj\nIUkaVrKIGqaKw3X7JxuBcD8gzW2nyw6MKrm4q2iTCXYQb8lpUuvITJmNJFIkTmpt\nDjlDEZdJiNhs0IOIepbS6QETyg97HVDWmL4frclu4QAeF28007HHlg805IAjAZ/x\nwU9P3/" - "VH74zZJinBLmXaIe3XidksWHES3H8ay+UtsUafC+4icSZRwplW9MexBNsl\nmJ4pfrHtAf+t+Tk0/BbuanSbL6OGA1wG7GVEfj39rsc4vYgSS/" - "NLI5njq55AXFVG\nsyeplNt2FNw3Ii6V3NnEkvlKcmj8sVnGCAIIeaG9yiAJ5qOsHP37sk3TAduKRGSE\nd8Ldty4mBftkTPyOG8eMr28XCldnhqnNWbcP9t2maKAyQ4bjv15Erx+" - "1AfXXGtVq\n3PsVsUN2YQIib7VLBjOYzW9jysQWFFuE26sE2oH4of34E0jD6GV3d6Ng8gTtpIhO\nBWePihWtHdBGsNNoYrp19IXX594hayaF+" - "WV8rpaBS0KxVoEHZhFusyvxcDiQnsO7\n3QPVu3btkkte8Hq9KtoFVeFm91M9fii3m3vFBsPu7weMm2zBCxTTRdLd5X+" - "yEYah\nn8tKnUUFHFIcgdZ3FIoQmIJrrYtcjLnvXeDdB2F6HX7z9KMQ5RzKBZcCMnVViKxl\nPIF3bikUzhtg55BGNyiAu2sz1wLzOoERsb38GN3UK4qinFVXLHxXhcdpEXKocb+" - "k\nPyRTykAunux+J5+klASl/k85s8gNvMH1CijdNseQEqLlsISERu+zxyFPPit6/" - "tP6\nwDyvhjHcGV2Jpw5T01n5aJpKklbGb9qUBkzlfayc03ebtqPzBTwG8NEzu9rlZQpr\nldbYUvXYBHSOqk2Z403I0PWR5DlcasFgciHFQw9PODRNK+" - "OVY46btfyBXABlzYTH\nOMfnHd8HoCOKCp29+ugK2G8y91JNd4M8B2xI1zACFB3hlDoc7K4h85Yi0cCYZahX\nOUWZxSZjfl8X793RT5h+2BE+0bRGLOJIGhAxe+JKUC7O5njXBc3O5/" - "ocfLif2t4y\neYJmu/w46hJUPxYk4Poe3Oppcim1hKHI7DVSJeJydBRqJgDzRzQ/1u7dD15z69/0\nrbexuTVmwmXN695s/V0=\n=1o3V\n-----END PGP MESSAGE-----\n"; - - QByteArray output; - const auto decryptResult = Crypto::decrypt(Crypto::OpenPGP, input, output); - - const QByteArray expectedOutput = - "Content-Type: multipart/mixed; boundary=\"HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\";\n protected-headers=\"v1\"\nFrom: test1 <test1@kolab.org>\nTo: " - "test@kolab.org\nMessage-ID: <a85660b4-6fdf-9d74-ad1c-e6899f57e4b0@kolab.org>\nSubject: " - "enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: text/rfc822-headers; protected-headers=\"v1\"\nContent-Disposition: " - "inline\n\nFrom: test1 <test1@kolab.org>\nTo: test@kolab.org\nSubject: enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: " - "text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nContent-Language: en-US\n\ntest\n\n--=20\nThis is a HTML " - "signature.\n\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ--\n"; - QCOMPARE(output, expectedOutput); - QCOMPARE(decryptResult.recipients.size(), 2); - QCOMPARE(decryptResult.recipients[0].keyId, QByteArray{"0CCE435AA7CCDA3B"}); - QCOMPARE(decryptResult.recipients[1].keyId, QByteArray{"CAA5183608F0FB50"}); - } - - void testDecryptAndVerify() - { - QByteArray input = - "-----BEGIN PGP " - "MESSAGE-----\n\nhQEMAwzOQ1qnzNo7AQgA3kD1WyRdQawpduoJ3J9h3SpSC7YiNqU7aiyTMUGAdbGO\nBMhIzPdEkai9P486Wpg5h+ywmQrk3KoH/GioRjwvIaeNZY/cmxetT0/" - "ig5rrnqxM\nj63vFFbCbE6kSeDbvYqF5mL3XH+TqpZRW5ApPSgkr7jMDOK7k1eF5A5ey84LYFny\nKy63LGy5KEQk7E1cMLZOHAZnorcm7Lh3RVWgPj+" - "DRDowMn3yVdFOpT5bQ66zAIkc\nBs9IWuq0lMxGsdfRv5wlzUqZJGge3oT7tkZhI6D56MLIjqg7SurQMiWrn6wh51Sr\nR7W9N6lHyrKrffP2VjFwPPK1/" - "Vjd0Am4gTPkf+GcJ4UBDAPKpRg2CPD7UAEIAJuC\n8s2uGAGF9zgoQdrmL6bInA5JCQiZI+B5Jgg9wQ/dW3idJN9esr1Ff7/" - "d8DVuzf1V\nbFydMBqQk5Zkp5FuDhJsfGWK+NPJBUaOKGlGqRPZX+SjP2k1SuDoxvdxvtWYBVOt\nZhq03zczRWo4dxJ++WYqxu6gMlBCO+z84kfhknWyBNeN7+8mmYGNWDo/" - "ARWhspO0\nCIOfBCFeqwCpCZLIiCTBjGg98Ed+SGIdjQwq97suh3nANlKFpiNp2+w02H+" - "rarMj\nIUkaVrKIGqaKw3X7JxuBcD8gzW2nyw6MKrm4q2iTCXYQb8lpUuvITJmNJFIkTmpt\nDjlDEZdJiNhs0IOIepbS6QETyg97HVDWmL4frclu4QAeF28007HHlg805IAjAZ/x\nwU9P3/" - "VH74zZJinBLmXaIe3XidksWHES3H8ay+UtsUafC+4icSZRwplW9MexBNsl\nmJ4pfrHtAf+t+Tk0/BbuanSbL6OGA1wG7GVEfj39rsc4vYgSS/" - "NLI5njq55AXFVG\nsyeplNt2FNw3Ii6V3NnEkvlKcmj8sVnGCAIIeaG9yiAJ5qOsHP37sk3TAduKRGSE\nd8Ldty4mBftkTPyOG8eMr28XCldnhqnNWbcP9t2maKAyQ4bjv15Erx+" - "1AfXXGtVq\n3PsVsUN2YQIib7VLBjOYzW9jysQWFFuE26sE2oH4of34E0jD6GV3d6Ng8gTtpIhO\nBWePihWtHdBGsNNoYrp19IXX594hayaF+" - "WV8rpaBS0KxVoEHZhFusyvxcDiQnsO7\n3QPVu3btkkte8Hq9KtoFVeFm91M9fii3m3vFBsPu7weMm2zBCxTTRdLd5X+" - "yEYah\nn8tKnUUFHFIcgdZ3FIoQmIJrrYtcjLnvXeDdB2F6HX7z9KMQ5RzKBZcCMnVViKxl\nPIF3bikUzhtg55BGNyiAu2sz1wLzOoERsb38GN3UK4qinFVXLHxXhcdpEXKocb+" - "k\nPyRTykAunux+J5+klASl/k85s8gNvMH1CijdNseQEqLlsISERu+zxyFPPit6/" - "tP6\nwDyvhjHcGV2Jpw5T01n5aJpKklbGb9qUBkzlfayc03ebtqPzBTwG8NEzu9rlZQpr\nldbYUvXYBHSOqk2Z403I0PWR5DlcasFgciHFQw9PODRNK+" - "OVY46btfyBXABlzYTH\nOMfnHd8HoCOKCp29+ugK2G8y91JNd4M8B2xI1zACFB3hlDoc7K4h85Yi0cCYZahX\nOUWZxSZjfl8X793RT5h+2BE+0bRGLOJIGhAxe+JKUC7O5njXBc3O5/" - "ocfLif2t4y\neYJmu/w46hJUPxYk4Poe3Oppcim1hKHI7DVSJeJydBRqJgDzRzQ/1u7dD15z69/0\nrbexuTVmwmXN695s/V0=\n=1o3V\n-----END PGP MESSAGE-----\n"; - - QByteArray output; - - Crypto::DecryptionResult decryptResult; - Crypto::VerificationResult verifyResult; - std::tie(decryptResult, verifyResult) = Crypto::decryptAndVerify(Crypto::OpenPGP, input, output); - - const QByteArray expectedOutput = - "Content-Type: multipart/mixed; boundary=\"HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\";\n protected-headers=\"v1\"\nFrom: test1 <test1@kolab.org>\nTo: " - "test@kolab.org\nMessage-ID: <a85660b4-6fdf-9d74-ad1c-e6899f57e4b0@kolab.org>\nSubject: " - "enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: text/rfc822-headers; protected-headers=\"v1\"\nContent-Disposition: " - "inline\n\nFrom: test1 <test1@kolab.org>\nTo: test@kolab.org\nSubject: enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: " - "text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nContent-Language: en-US\n\ntest\n\n--=20\nThis is a HTML " - "signature.\n\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ--\n"; - - QCOMPARE(output, expectedOutput); - QCOMPARE(verifyResult.signatures.size(), 1); - // QCOMPARE(verifyResult.signatures[0].fingerprint, QByteArray{"CBD116485DB9560CA3CD91E02E3B7787B1B75920"}); - QVERIFY(verifyResult.signatures[0].fingerprint.contains(QByteArray{"2E3B7787B1B75920"})); - QCOMPARE(decryptResult.recipients.size(), 2); - QCOMPARE(decryptResult.recipients[0].keyId, QByteArray{"0CCE435AA7CCDA3B"}); - QCOMPARE(decryptResult.recipients[1].keyId, QByteArray{"CAA5183608F0FB50"}); - } - - void testDecryptAndVerifyKeyMissing() - { - QByteArray input = - "-----BEGIN PGP " - "MESSAGE-----\n\nhQEMAwzOQ1qnzNo7AQgA3kD1WyRdQawpduoJ3J9h3SpSC7YiNqU7aiyTMUGAdbGO\nBMhIzPdEkai9P486Wpg5h+ywmQrk3KoH/GioRjwvIaeNZY/cmxetT0/" - "ig5rrnqxM\nj63vFFbCbE6kSeDbvYqF5mL3XH+TqpZRW5ApPSgkr7jMDOK7k1eF5A5ey84LYFny\nKy63LGy5KEQk7E1cMLZOHAZnorcm7Lh3RVWgPj+" - "DRDowMn3yVdFOpT5bQ66zAIkc\nBs9IWuq0lMxGsdfRv5wlzUqZJGge3oT7tkZhI6D56MLIjqg7SurQMiWrn6wh51Sr\nR7W9N6lHyrKrffP2VjFwPPK1/" - "Vjd0Am4gTPkf+GcJ4UBDAPKpRg2CPD7UAEIAJuC\n8s2uGAGF9zgoQdrmL6bInA5JCQiZI+B5Jgg9wQ/dW3idJN9esr1Ff7/" - "d8DVuzf1V\nbFydMBqQk5Zkp5FuDhJsfGWK+NPJBUaOKGlGqRPZX+SjP2k1SuDoxvdxvtWYBVOt\nZhq03zczRWo4dxJ++WYqxu6gMlBCO+z84kfhknWyBNeN7+8mmYGNWDo/" - "ARWhspO0\nCIOfBCFeqwCpCZLIiCTBjGg98Ed+SGIdjQwq97suh3nANlKFpiNp2+w02H+" - "rarMj\nIUkaVrKIGqaKw3X7JxuBcD8gzW2nyw6MKrm4q2iTCXYQb8lpUuvITJmNJFIkTmpt\nDjlDEZdJiNhs0IOIepbS6QETyg97HVDWmL4frclu4QAeF28007HHlg805IAjAZ/x\nwU9P3/" - "VH74zZJinBLmXaIe3XidksWHES3H8ay+UtsUafC+4icSZRwplW9MexBNsl\nmJ4pfrHtAf+t+Tk0/BbuanSbL6OGA1wG7GVEfj39rsc4vYgSS/" - "NLI5njq55AXFVG\nsyeplNt2FNw3Ii6V3NnEkvlKcmj8sVnGCAIIeaG9yiAJ5qOsHP37sk3TAduKRGSE\nd8Ldty4mBftkTPyOG8eMr28XCldnhqnNWbcP9t2maKAyQ4bjv15Erx+" - "1AfXXGtVq\n3PsVsUN2YQIib7VLBjOYzW9jysQWFFuE26sE2oH4of34E0jD6GV3d6Ng8gTtpIhO\nBWePihWtHdBGsNNoYrp19IXX594hayaF+" - "WV8rpaBS0KxVoEHZhFusyvxcDiQnsO7\n3QPVu3btkkte8Hq9KtoFVeFm91M9fii3m3vFBsPu7weMm2zBCxTTRdLd5X+" - "yEYah\nn8tKnUUFHFIcgdZ3FIoQmIJrrYtcjLnvXeDdB2F6HX7z9KMQ5RzKBZcCMnVViKxl\nPIF3bikUzhtg55BGNyiAu2sz1wLzOoERsb38GN3UK4qinFVXLHxXhcdpEXKocb+" - "k\nPyRTykAunux+J5+klASl/k85s8gNvMH1CijdNseQEqLlsISERu+zxyFPPit6/" - "tP6\nwDyvhjHcGV2Jpw5T01n5aJpKklbGb9qUBkzlfayc03ebtqPzBTwG8NEzu9rlZQpr\nldbYUvXYBHSOqk2Z403I0PWR5DlcasFgciHFQw9PODRNK+" - "OVY46btfyBXABlzYTH\nOMfnHd8HoCOKCp29+ugK2G8y91JNd4M8B2xI1zACFB3hlDoc7K4h85Yi0cCYZahX\nOUWZxSZjfl8X793RT5h+2BE+0bRGLOJIGhAxe+JKUC7O5njXBc3O5/" - "ocfLif2t4y\neYJmu/w46hJUPxYk4Poe3Oppcim1hKHI7DVSJeJydBRqJgDzRzQ/1u7dD15z69/0\nrbexuTVmwmXN695s/V0=\n=1o3V\n-----END PGP MESSAGE-----\n"; - - QByteArray output; - - Crypto::DecryptionResult decryptResult; - Crypto::VerificationResult verifyResult; - std::tie(decryptResult, verifyResult) = Crypto::decryptAndVerify(Crypto::OpenPGP, input, output); - - const QByteArray expectedOutput = - "Content-Type: multipart/mixed; boundary=\"HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\";\n protected-headers=\"v1\"\nFrom: test1 <test1@kolab.org>\nTo: " - "test@kolab.org\nMessage-ID: <a85660b4-6fdf-9d74-ad1c-e6899f57e4b0@kolab.org>\nSubject: " - "enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: text/rfc822-headers; protected-headers=\"v1\"\nContent-Disposition: " - "inline\n\nFrom: test1 <test1@kolab.org>\nTo: test@kolab.org\nSubject: enc+signed\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ\nContent-Type: " - "text/plain; charset=utf-8\nContent-Transfer-Encoding: quoted-printable\nContent-Language: en-US\n\ntest\n\n--=20\nThis is a HTML " - "signature.\n\n\n--HKEnEQX5LVRrGxcPEIzBMsyPh2G25GeZZ--\n"; - - QCOMPARE(output, expectedOutput); - QCOMPARE(verifyResult.signatures.size(), 1); - QCOMPARE(verifyResult.signatures[0].result, Crypto::Signature::KeyNotFound); - } - - void testDecryptAndVerifyCRLFEncryptedWithSignature() - { - // This was the original input that seems to have some padding issues (which could stem from some crlf "normalization" we're doing. - // QByteArray input = "-----BEGIN PGP - // MESSAGE-----\n\nhQEMAwzOQ1qnzNo7AQgAgXLGaohf4ZPJVDkpmpNsyXL/nccd+SyY3/deHQ8d8vp9\nYe9Hr30Yz65+CAI7JCHKjIOaXjw1Nf1qqEDSyghKR0c16dLBGK37GlqOLaqScifZ\n/bC5WQu4V1a+dv1qnNOh3JNp5ynVpg22b5XaBggpAGCxCSrUsWWkRxTR+kBuPdn8\nEPMOlN3xKU1LFQfI+a3HMGpWo3PokVb4nrtuuwi261woSgKUSYjG86MJF1E28y+g\nbMC6rmRV+Jp0wpEmr7aogx4gELe17tglD41oLvNL9yZeEh/V8cMnnEDbO/oG+xba\nUjyM73V+TO2kXk0CTqItVbx6Q7kNWR/DfaJqGnzcy9KMAT1B8EpVCK/nN81mF8ia\n4KFKQ1OGHhcQ2tT5ZXs6m1vJ5/sz/6g0n0CMtSsWUSvPzpM5F+LK+B7dzOeJEQee\n3/S0wUFYpbAh1PyMPYobNLsEQCGtQ1PhsUXM7t1ai6jfM4k/lvbnIzUM11Q6vsnR\nLWS77CELfcW9WlQ755sNcwo3a/WySyU1C1gZdLw=Fijx\n-----END - // PGP MESSAGE-----\n"; - - // This is what I got from reencrypting the output with gpgme. - QByteArray input = - "-----BEGIN PGP " - "MESSAGE-----\n\nhQEMAwzOQ1qnzNo7AQf/e+AvN9SH/nCo8VNWEhw8w9zMFrC+LK0HmXGedmwVDS+p\nX/" - "4dBFC4ftR0jvzKyiBpjtAisD7zfNIcPd1rkfjtjf1kvksuDej+" - "rVBZ8Mupa7Rl\ng9tCGSwxNmKU9v3BzSVXV9z4P2PHYpOn68WOnDZZqUVdvbZ6WuowAzBjiECo59cZ\nZmOp5kO4DGEE7vJ3QSR58VUYhM7kUcUYOf68syZiGOO7gC075ZLXIc3l+" - "rD4fYtJ\nAsGsfVXmbxB58aVuNZllK/CshfjddPbVRladc4xJ1KsnC/M+mOClUM2WJ7Ghw+V6\nkta1xPC3cjc1j7rPm1LH2ESzqvhkhay6ATaBNJ3rd9J/AYU0/" - "WFF+HQryulNxiCg\nzkN0kfC0lJ8GOKtlwgIcY6AsyStPR13fkXT0xRbB+zvms5nHmbtUvtsjeOsw7BJL\ndLBq5P+0xiCZR+/gBhkx/" - "OqBFtel3qktopoDRi3rnVMJGMVcKiAFmf3612HU5tYb\n8iolmHNdKSksNbDKYgzLnQ==\n=YzFH\n-----END PGP MESSAGE-----\n"; - - QByteArray output; - - Crypto::DecryptionResult decryptResult; - Crypto::VerificationResult verifyResult; - std::tie(decryptResult, verifyResult) = Crypto::decryptAndVerify(Crypto::OpenPGP, input, output); - - const QByteArray expectedOutput = "CRLF file\r\n\r\n-- \r\nThis is a signature\r\nWith two lines\r\n\r\nAand another line\r\n"; - - // QEXPECT_FAIL("", "either librnp or gpgme fails to handle CRLF properly", Continue); - QCOMPARE(output, expectedOutput); - QCOMPARE(verifyResult.signatures.size(), 0); - QCOMPARE(decryptResult.recipients.size(), 1); - - // qWarning() << decryptResult.recipients[0].keyId; - // const auto keys = Crypto::findKeys({{decryptResult.recipients[0].keyId}}); - // QCOMPARE(keys.size(), 1); - // auto result = Crypto::signAndEncrypt(expectedOutput, keys, {}); - // qWarning() << result.value(); - } - - void testVerifyDetachedSignature() - { - QByteArray signature = - "-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG v2.0.15 " - "(GNU/Linux)\n\niQEcBAABAgAGBQJMh7F5AAoJEI2YYMWPJG3mOB0IALeHfwg8u7wK0tDKtKqxQSqC\n2Bbk4pTLuLw/VniQNauDG+kc1eUc5RJk/" - "R31aB1ysiQCV5Q8ucI8c9vCDDMbd+s4\nt2bZUEzMpXrw/" - "aFiHgYGXFAY+tpqZqDGNVRNHWsPYJKtx8cci9E5DLnBJcVXVqib\n3iiHlr9AQOok3PUmpPk1a61q2L0kk8wqRenC0yZXNw5qFn5WW/" - "hFeCOfYB+t+s5N\nIuE6ihFCJIlvGborrvl6VgPJTCyUQ3XgI90vS6ABN8TFlCNr3grXOWUePc2a20or\nxFgh38cnSR64WJajU5K1nUL9/RgfIcs+PvyHuJaRf/" - "iUFkj1jiMEuaSi9jVFco0=\n=bArV\n-----END PGP SIGNATURE-----\n"; - QByteArray signedData = - "Content-Type: multipart/mixed; boundary=\"nextPart1512490.WQBKYaOrt8\"\r\nContent-Transfer-Encoding: " - "7Bit\r\n\r\n\r\n--nextPart1512490.WQBKYaOrt8\r\nContent-Transfer-Encoding: 7Bit\r\nContent-Type: text/plain; charset=\"us-ascii\"\r\n\r\nbla bla " - "bla\r\n--nextPart1512490.WQBKYaOrt8\r\nContent-Type: message/rfc822\r\nContent-Disposition: inline; filename=\"forwarded " - "message\"\r\nContent-Description: OpenPGP Test <test@kolab.org>: OpenPGP signed and encrypted\r\n\r\nFrom: OpenPGP Test <test@kolab.org>\r\nTo: " - "test@kolab.org\r\nSubject: OpenPGP signed and encrypted\r\nDate: Tue, 07 Sep 2010 18:08:44 +0200\r\nUser-Agent: KMail/4.6 pre " - "(Linux/2.6.34-rc2-2-default; KDE/4.5.60; x86_64; ; )\r\nMIME-Version: 1.0\r\nContent-Type: multipart/encrypted; " - "boundary=\"nextPart25203163.0xtB501Z4V\"; protocol=\"application/pgp-encrypted\"\r\nContent-Transfer-Encoding: " - "7Bit\r\n\r\n\r\n--nextPart25203163.0xtB501Z4V\r\nContent-Type: application/pgp-encrypted\r\nContent-Disposition: attachment\r\n\r\nVersion: " - "1\r\n--nextPart25203163.0xtB501Z4V\r\nContent-Type: application/octet-stream\r\nContent-Disposition: inline; " - "filename=\"msg.asc\"\r\n\r\n-----BEGIN PGP MESSAGE-----\r\nVersion: GnuPG v2.0.15 " - "(GNU/Linux)\r\n\r\nhQEMAwzOQ1qnzNo7AQf7BFYWaGiCTGtXY59bSh3LCXNnWZejblYALxIUNXOFEXbm\r\ny/YA95FmQsy3U5HRCAJV/" - "DY1PEaJz1RTm9bcdIpDC3Ab2YzSwmOwV5fcoUOB2df4\r\nKjX19Q+2F3JxpPQ0N1gHf4dKfIu19LH+CKeFzUN13aJs5J4A5wlj+" - "NjJikxzmxDS\r\nkDtNYndynPmo9DJQcsUFw3gpvx5HaHvx1cT4mAB2M5cd2l+vN1jYbaWb0x5Zq41z\r\nmRNI89aPieC3rcM2289m68fGloNbYvi8mZJu5RrI4Tbi/" - "D7Rjm1y63lHgVV6AN88\r\nXAzRiedOeF99LoTBulrJdtT8AAgCs8nCetcWpIffdtLpAZiZkzHmYOU7nqGxqpRk\r\nOVeUTrCn9DW2SMmHjaP4IiKnMvzEycu5F4a72+V1LeMIhMSjTRTq+" - "ZE2PTaqH59z\r\nQsMn7Nb6GlOICbTptRKNNtyJKO7xXlpT7YtvNKnCyEOkH2XrYH7GvpYCiuQ0/o+7\r\nSxV436ZejiYIg6DQDXJCoa2DXimGp0C10Jh0HwX0BixpoNtwEjkGRYcX6P/" - "JzkH0\r\noBood4Ly+Tiu6iVDisrK3AVGYpIzCrKkE9qULTw4R/" - "jFKR2tcCqGb7Fxtk2LV7Md\r\n3S+DyOKrvKQ5GNwbp9OE97pwk+Lr1JS3UAvj5f6BR+1PVNcC0i0wWkgwDjPh1eGD\r\nenMQmorE6+N0uHtH2F4fOxo/TbbA3+zhI25kVW3bO03xyUl/" - "cmQZeb52nvfOvtOo\r\ngSb2j6bPkzljDMPEzrtJjbFtGHJbPfUQYJgZv9OE2EQIqpg6goIw279alBq6GLIX\r\npkO+dRmztzjcDyhcLxMuQ4cTizel/0J/" - "bU7U6lvwHSyZVbT4Ev+opG5K70Hbqbwr\r\nNZcgdWXbSeesxGM/oQaMeSurOevxVl+/" - "zrTVAek61aRRd1baAYqgi2pf2V7y4oK3\r\nqkdxzmoFpRdNlfrQW65NZWnHOi9rC9XxANIwnVn3kRcDf+t2K4PrFluI157lXM/" - "o\r\nwX91j88fazysbJlQ6TjsApO9ETiPOFEBqouxCTtCZzlUgyVG8jpIjdHWFnagHeXH\r\n+" - "lXNdYjxnTWTjTxMOZC9ySMpXkjWdFI1ecxVwu6Ik6RX51rvBJAAXWP75yUjPKJ4\r\nrRi5oQl/VLl0QznO7lvgMPtUwgDVNWO/r7Kn9B387h9fAJZ/" - "kWFAEDW2yhAzABqO\r\nrCNKDzBPgfAwCnikCpMoCbOL7SU8BdbzQHD8/Lkv4m0pzliHQ/KkGF710koBzTmF\r\nN7+wk9pwIuvcrEBQj567\r\n=GV0c\r\n-----END PGP " - "MESSAGE-----\r\n\r\n--nextPart25203163.0xtB501Z4V--\r\n\r\n--nextPart1512490.WQBKYaOrt8--\r\n"; - - const auto result = Crypto::verifyDetachedSignature(Crypto::OpenPGP, signature, signedData); - QCOMPARE(result.signatures.size(), 1); - QVERIFY(result.signatures[0].fingerprint.contains(QByteArray{"8D9860C58F246DE6"})); - QCOMPARE(result.signatures[0].result, Crypto::Signature::Ok); - } - - void testVerifyOpaqueSignature() - { - QByteArray signedData = - "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\nohno \xF6\xE4\xFC\n-----BEGIN PGP SIGNATURE-----\nVersion: GnuPG " - "v2\n\niQEcBAEBCAAGBQJV3H/vAAoJEI2YYMWPJG3mEZQH/2mbCDa60risTUsomEecasc7\nkIc8Ch+OjZwlEQWKEiFbpLCMVjMwf0oGFcpc/" - "dqnIyIqeVvF6Em+v7iqKuyAaihu\n7ZxxC816tDDI7UIpmyWu39McqGB/2hoA/" - "q+" - "QAMgBiaIuMwYJK9Aw08hXzoCds6O7\nUor2Y6kMSwEiRnTSYvQHdoaZY3F9SFTLPgjvwfSu7scvp7xvH7bAVIqGGfkLjXpP\nOFkDhEqUI7ORwD5cvvzEu57XmbGB7Nj5LRCGcTq6IlaGeN6Pw" - "5+hOdd6MQ4iISwy\n870msP9NvktURnfXYC3fYnJaK/eUln7LYCBl/k04Z/3Um6dMYyQGh63oGv/2qxQ=\n=4ctb\n-----END PGP SIGNATURE-----"; - QByteArray outdata; - const auto result = Crypto::verifyOpaqueSignature(Crypto::OpenPGP, signedData, outdata); - QCOMPARE(result.signatures.size(), 1); - QVERIFY(result.signatures[0].fingerprint.contains(QByteArray{"8D9860C58F246DE6"})); - QCOMPARE(result.signatures[0].result, Crypto::Signature::Ok); - QCOMPARE(outdata, QByteArray{"ohno \xF6\xE4\xFC\n"}); - } -}; - -QTEST_GUILESS_MAIN(CryptoTest) -#include "cryptotest.moc" diff --git a/src/core/autotests/gpgerrortest.cpp b/src/core/autotests/gpgerrortest.cpp index d55ef3e..6327c8f 100644 --- a/src/core/autotests/gpgerrortest.cpp +++ b/src/core/autotests/gpgerrortest.cpp @@ -1,200 +1,200 @@ // SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsystems.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include <MimeTreeParserCore/ObjectTreeParser> #include <gpgme.h> #include <QProcess> #include <QTest> QByteArray readMailFromFile(const QString &mailFile) { QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); file.open(QIODevice::ReadOnly); Q_ASSERT(file.isOpen()); return file.readAll(); } void killAgent(const QString &dir) { QProcess proc; proc.setProgram(QStringLiteral("gpg-connect-agent")); QStringList arguments; arguments << QStringLiteral("-S ") << dir + QStringLiteral("/S.gpg-agent"); proc.start(); proc.waitForStarted(); proc.write("KILLAGENT\n"); proc.write("BYE\n"); proc.closeWriteChannel(); proc.waitForFinished(); } class GpgErrorTest : public QObject { Q_OBJECT private Q_SLOTS: void testGpgConfiguredCorrectly() { setEnv("GNUPGHOME", GNUPGHOME); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QStringLiteral("openpgp-inline-charset-encrypted.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0]; QVERIFY(bool(part)); QVERIFY(part->text().startsWith(QStringLiteral("asdasd"))); QCOMPARE(part->encryptions().size(), 1); auto enc = part->encryptions()[0]; QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoError); // QCOMPARE((int) enc->recipients().size(), 2); } void testNoGPGInstalled_data() { QTest::addColumn<QString>("mailFileName"); QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; } void testNoGPGInstalled() { QFETCH(QString, mailFileName); setEnv("PATH", "/nonexististing"); setGpgMEfname("/nonexisting/gpg", ""); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(mailFileName)); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 1); QVERIFY(part->text().isEmpty()); auto enc = part->encryptions()[0]; QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoKeyError); } void testGpgIncorrectGPGHOME_data() { QTest::addColumn<QString>("mailFileName"); QTest::newRow("openpgp-inline-charset-encrypted") << "openpgp-inline-charset-encrypted.mbox"; QTest::newRow("openpgp-encrypted-attachment-and-non-encrypted-attachment") << "openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"; QTest::newRow("smime-encrypted") << "smime-encrypted.mbox"; } void testGpgIncorrectGPGHOME() { QFETCH(QString, mailFileName); setEnv("GNUPGHOME", QByteArray(GNUPGHOME) + QByteArray("noexist")); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(mailFileName)); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 0); QVERIFY(part->text().isEmpty()); auto enc = part->encryptions()[0]; - QCOMPARE(enc->error(), MimeTreeParser::MessagePart::NoKeyError); + QVERIFY(enc->isNoSecKey()); // QCOMPARE((int) enc->recipients().size(), 2); } public Q_SLOTS: void init() { mResetGpgmeEngine = false; mModifiedEnv.clear(); { gpgme_check_version(nullptr); gpgme_ctx_t ctx = nullptr; gpgme_new(&ctx); gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); gpgme_engine_info_t info = gpgme_ctx_get_engine_info(ctx); mGpgmeEngine_fname = info->file_name; gpgme_release(ctx); } mEnv = QProcessEnvironment::systemEnvironment(); unsetEnv("GNUPGHOME"); } void cleanup() { QCoreApplication::sendPostedEvents(); const QString &gnupghome = QString::fromUtf8(qgetenv("GNUPGHOME")); if (!gnupghome.isEmpty()) { killAgent(gnupghome); } resetGpgMfname(); resetEnv(); } private: void unsetEnv(const QByteArray &name) { mModifiedEnv << name; qunsetenv(name.data()); } void setEnv(const QByteArray &name, const QByteArray &value) { mModifiedEnv << name; qputenv(name.data(), value); } void resetEnv() { for (const auto &i : std::as_const(mModifiedEnv)) { const auto env = i.data(); if (mEnv.contains(QString::fromUtf8(i))) { qputenv(env, mEnv.value(QString::fromUtf8(i)).toUtf8()); } else { qunsetenv(env); } } } void resetGpgMfname() { if (mResetGpgmeEngine) { gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, mGpgmeEngine_fname.data(), nullptr); } } void setGpgMEfname(const QByteArray &fname, const QByteArray &homedir) { mResetGpgmeEngine = true; gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, fname.data(), homedir.data()); } QSet<QByteArray> mModifiedEnv; QProcessEnvironment mEnv; bool mResetGpgmeEngine; QByteArray mGpgmeEngine_fname; }; QTEST_GUILESS_MAIN(GpgErrorTest) #include "gpgerrortest.moc" diff --git a/src/core/autotests/mailtemplatetest.cpp b/src/core/autotests/mailtemplatetest.cpp deleted file mode 100644 index cef3005..0000000 --- a/src/core/autotests/mailtemplatetest.cpp +++ /dev/null @@ -1,669 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <QDebug> -#include <QStandardPaths> -#include <QTest> -#include <functional> - -#include <MimeTreeParserCore/MailCrypto> -#include <MimeTreeParserCore/MailTemplates> - -static KMime::Content *getSubpart(KMime::Content *msg, const QByteArray &mimeType) -{ - const auto contents = msg->contents(); - for (const auto c : contents) { - if (c->contentType(false)->mimeType() == mimeType) { - return c; - } - } - return nullptr; -} - -static QByteArray readMailFromFile(const QString &mailFile) -{ - Q_ASSERT(!QString::fromLatin1(MAILTEMPLATE_DATA_DIR).isEmpty()); - QFile file(QLatin1String(MAILTEMPLATE_DATA_DIR) + QLatin1Char('/') + mailFile); - file.open(QIODevice::ReadOnly); - Q_ASSERT(file.isOpen()); - return file.readAll(); -} - -static KMime::Message::Ptr readMail(const QString &mailFile) -{ - auto msg = KMime::Message::Ptr::create(); - msg->setContent(readMailFromFile(mailFile)); - msg->parse(); - return msg; -} - -static QString removeFirstLine(const QString &s) -{ - return s.mid(s.indexOf(QLatin1String("\n")) + 1); -} - -static QString normalize(const QString &s) -{ - auto text = s; - text.replace(QLatin1String(">"), QString()); - text.replace(QLatin1String("\n"), QString()); - text.replace(QLatin1String("="), QString()); - text.replace(QLatin1String(" "), QString()); - return text; -} - -static QString unquote(const QString &s) -{ - auto text = s; - text.replace(QLatin1String("> "), QString()); - return text; -} - -class MailTemplateTest : public QObject -{ - Q_OBJECT - - bool validate(KMime::Message::Ptr msg) - { - const auto data = msg->encodedContent(); - // IMAP compat: The ASCII NUL character, %x00, MUST NOT be used at any time. - if (data.contains('\0')) { - return false; - } - return true; - } - -private Q_SLOTS: - - // Ensures we don't crash on garbage - void testEmpty() - { - MailTemplates::reply(KMime::Message::Ptr::create(), [&](const KMime::Message::Ptr &) {}); - } - - void testPlainReply() - { - auto msg = readMail(QLatin1String("plaintext.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(normalize(removeFirstLine(QString::fromUtf8(result->body()))), normalize(QString::fromUtf8(msg->body()))); - QCOMPARE(result->to()->addresses(), QVector<QByteArray>{{"konqi@example.org"}}); - QCOMPARE(result->subject()->asUnicodeString(), QLatin1String{"RE: A random subject with alternative contenttype"}); - } - - void testHtmlReply() - { - auto msg = readMail(QLatin1String("html.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(unquote(removeFirstLine(QString::fromUtf8(result->body()))), QLatin1String("HTML text")); - } - - void testStripSignatureReply() - { - auto msg = readMail(QLatin1String("plaintext-with-signature.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QVERIFY(!result->body().contains("This is a signature")); - } - - void testStripSignatureHtmlReply() - { - auto msg = readMail(QLatin1String("html-with-signature.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QVERIFY(!result->body().contains("This is a signature")); - } - - // We can't test this because we can't commit a CRLF file due to commit hooks. - // void testStripSignatureCrlfReply() - // { - // auto msg = readMail("crlf-with-signature.mbox"); - // KMime::Message::Ptr result; - // MailTemplates::reply(msg, [&] (const KMime::Message::Ptr &r) { - // result = r; - // }); - // QTRY_VERIFY(result); - // QVERIFY(!result->body().contains("This is a signature")); - // } - - void testStripEncryptedCRLFReply() - { - auto msg = readMail(QLatin1String("crlf-encrypted-with-signature.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QVERIFY(!result->body().contains("This is a signature")); - } - - void testHtml8BitEncodedReply() - { - auto msg = readMail(QLatin1String("8bitencoded.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QVERIFY(MailTemplates::plaintextContent(result).contains(QString::fromUtf8("Why Pisa’s Tower"))); - } - - void testMultipartSignedReply() - { - auto msg = readMail(QLatin1String("openpgp-signed-mailinglist.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - auto content = removeFirstLine(QString::fromUtf8(result->body())); - QVERIFY(!content.isEmpty()); - QVERIFY(content.contains(QLatin1String("i noticed a new branch"))); - } - - void testMultipartAlternativeReply() - { - auto msg = readMail(QLatin1String("alternative.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - auto content = removeFirstLine(QString::fromUtf8(result->body())); - QVERIFY(!content.isEmpty()); - QCOMPARE(unquote(content), - QLatin1String("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to " - "view the newsletter on our website: http://www.gog.com/newsletter/\n")); - } - - void testAttachmentReply() - { - auto msg = readMail(QLatin1String("plaintextattachment.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - auto content = removeFirstLine(QString::fromUtf8(result->body())); - QVERIFY(!content.isEmpty()); - QCOMPARE(unquote(content), QLatin1String("sdlkjsdjf")); - } - - void testMultiRecipientReply() - { - auto msg = readMail(QLatin1String("multirecipients.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - auto content = removeFirstLine(QString::fromUtf8(result->body())); - QVERIFY(!content.isEmpty()); - QCOMPARE(unquote(content), QLatin1String("test")); - QCOMPARE(result->to()->addresses(), QVector<QByteArray>{{"from@example.org"}}); - auto l = QVector<QByteArray>{{"to1@example.org"}, {"to2@example.org"}, {"cc1@example.org"}, {"cc2@example.org"}}; - QCOMPARE(result->cc()->addresses(), l); - } - - void testMultiRecipientReplyFilteringMe() - { - KMime::Types::AddrSpecList me; - KMime::Types::Mailbox mb; - mb.setAddress("to1@example.org"); - me << mb.addrSpec(); - - auto msg = readMail(QLatin1String("multirecipients.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply( - msg, - [&](const KMime::Message::Ptr &r) { - result = r; - }, - me); - QTRY_VERIFY(result); - QCOMPARE(result->to()->addresses(), QVector<QByteArray>{{"from@example.org"}}); - auto l = QVector<QByteArray>{{"to2@example.org"}, {"cc1@example.org"}, {"cc2@example.org"}}; - QCOMPARE(result->cc()->addresses(), l); - } - - void testMultiRecipientReplyOwnMessage() - { - KMime::Types::AddrSpecList me; - KMime::Types::Mailbox mb; - mb.setAddress("from@example.org"); - me << mb.addrSpec(); - - auto msg = readMail(QLatin1String("multirecipients.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply( - msg, - [&](const KMime::Message::Ptr &r) { - result = r; - }, - me); - QTRY_VERIFY(result); - - auto to = QVector<QByteArray>{{"to1@example.org"}, {"to2@example.org"}}; - QCOMPARE(result->to()->addresses(), to); - auto cc = QVector<QByteArray>{{"cc1@example.org"}, {"cc2@example.org"}}; - QCOMPARE(result->cc()->addresses(), cc); - } - - void testReplyList() - { - KMime::Types::AddrSpecList me; - KMime::Types::Mailbox mb; - mb.setAddress("me@example.org"); - me << mb.addrSpec(); - - auto msg = readMail(QLatin1String("listmessage.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply( - msg, - [&](const KMime::Message::Ptr &r) { - result = r; - }, - me); - QTRY_VERIFY(result); - - auto to = QVector<QByteArray>{{"list@example.org"}}; - QCOMPARE(result->to()->addresses(), to); - auto cc = QVector<QByteArray>{{"to@example.org"}, {"cc1@example.org"}}; - QCOMPARE(result->cc()->addresses(), cc); - } - - void testForwardAsAttachment() - { - auto msg = readMail(QString::fromUtf8("plaintext.mbox")); - KMime::Message::Ptr result; - MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(result->subject(false)->asUnicodeString(), QLatin1String{"FW: A random subject with alternative contenttype"}); - QCOMPARE(result->to()->addresses(), {}); - QCOMPARE(result->cc()->addresses(), {}); - QCOMPARE(result->references()->identifiers(), {"1505824.VT1nqpAGu0@vkpc5"}); - QCOMPARE(result->inReplyTo()->identifiers(), {}); - - auto attachments = result->attachments(); - QCOMPARE(attachments.size(), 1); - auto attachment = attachments[0]; - QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); - QCOMPARE(attachment->contentDisposition(false)->filename(), QLatin1String{"A random subject with alternative contenttype.eml"}); - QVERIFY(attachment->bodyIsMessage()); - - attachment->parse(); - auto origMsg = attachment->bodyAsMessage(); - QCOMPARE(origMsg->subject(false)->asUnicodeString(), QLatin1String{"A random subject with alternative contenttype"}); - } - - void testEncryptedForwardAsAttachment() - { - auto msg = readMail(QLatin1String("openpgp-encrypted.mbox")); - KMime::Message::Ptr result; - MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(result->subject(false)->asUnicodeString(), QLatin1String{"FW: OpenPGP encrypted"}); - QCOMPARE(result->to()->addresses(), {}); - QCOMPARE(result->cc()->addresses(), {}); - QCOMPARE(result->references()->identifiers(), {"1505824.VT2nqpAGu0@vkpc5"}); - QCOMPARE(result->inReplyTo()->identifiers(), {}); - - auto attachments = result->attachments(); - QCOMPARE(attachments.size(), 1); - auto attachment = attachments[0]; - QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); - QCOMPARE(attachment->contentDisposition(false)->filename(), QLatin1String{"OpenPGP encrypted.eml"}); - QVERIFY(attachment->bodyIsMessage()); - - attachment->parse(); - auto origMsg = attachment->bodyAsMessage(); - QCOMPARE(origMsg->subject(false)->asUnicodeString(), QLatin1String{"OpenPGP encrypted"}); - } - - void testEncryptedWithAttachmentsForwardAsAttachment() - { - auto msg = readMail(QLatin1String("openpgp-encrypted-two-attachments.mbox")); - KMime::Message::Ptr result; - MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(result->subject(false)->asUnicodeString(), QLatin1String{"FW: OpenPGP encrypted with 2 text attachments"}); - QCOMPARE(result->to()->addresses(), {}); - QCOMPARE(result->cc()->addresses(), {}); - QCOMPARE(result->references()->identifiers(), {"1505824.VT0nqpAGu0@vkpc5"}); - QCOMPARE(result->inReplyTo()->identifiers(), {}); - - auto attachments = result->attachments(); - QCOMPARE(attachments.size(), 1); - auto attachment = attachments[0]; - QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); - QCOMPARE(attachment->contentDisposition(false)->filename(), QLatin1String{"OpenPGP encrypted with 2 text attachments.eml"}); - QVERIFY(attachment->bodyIsMessage()); - - attachment->parse(); - auto origMsg = attachment->bodyAsMessage(); - QCOMPARE(origMsg->subject(false)->asUnicodeString(), QLatin1String{"OpenPGP encrypted with 2 text attachments"}); - - auto attattachments = origMsg->attachments(); - QCOMPARE(attattachments.size(), 2); - QCOMPARE(attattachments[0]->contentDisposition(false)->filename(), QLatin1String{"attachment1.txt"}); - QCOMPARE(attattachments[1]->contentDisposition(false)->filename(), QLatin1String{"attachment2.txt"}); - } - - void testForwardAlreadyForwarded() - { - auto msg = readMail(QLatin1String("cid-links-forwarded-inline.mbox")); - KMime::Message::Ptr result; - MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(result->subject(false)->asUnicodeString(), QLatin1String{"FW: Html Hello (inlin)"}); - QCOMPARE(result->to()->addresses(), {}); - QCOMPARE(result->cc()->addresses(), {}); - const QVector<QByteArray> references{QByteArray{"a1777ec781546ccc5dcd4918a5e4e03d@info"}, QByteArray{"46b164308eb6056361c866932a740a3c@info"}}; - QCOMPARE(result->references()->identifiers(), references); - QCOMPARE(result->inReplyTo()->identifiers(), {}); - } - - void testCreatePlainMail() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments; - - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, false, attachments); - - QVERIFY(result); - QVERIFY(validate(result)); - QCOMPARE(result->subject()->asUnicodeString(), subject); - QCOMPARE(result->body(), body.toUtf8()); - QVERIFY(result->date(false)->dateTime().isValid()); - QVERIFY(result->contentType()->isMimeType("text/plain")); - QVERIFY(result->messageID(false) && !result->messageID(false)->isEmpty()); - } - - void testCreateHtmlMail() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments; - - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, true, attachments); - - QVERIFY(result); - QVERIFY(validate(result)); - QCOMPARE(result->subject()->asUnicodeString(), subject); - QVERIFY(result->date(false)->dateTime().isValid()); - QVERIFY(result->contentType()->isMimeType("multipart/alternative")); - const auto contents = result->contents(); - // 1 Plain + 1 Html - QCOMPARE(contents.size(), 2); - } - - void testCreatePlainMailWithAttachments() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments = {{QLatin1String("name"), QLatin1String("filename"), "mimetype", true, "inlineAttachment"}, - {QLatin1String("name"), QLatin1String("filename"), "mimetype", false, "nonInlineAttachment"}}; - - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, false, attachments); - - QVERIFY(result); - QVERIFY(validate(result)); - QCOMPARE(result->subject()->asUnicodeString(), subject); - QVERIFY(result->contentType()->isMimeType("multipart/mixed")); - QVERIFY(result->date(false)->dateTime().isValid()); - const auto contents = result->contents(); - // 1 Plain + 2 Attachments - QCOMPARE(contents.size(), 3); - auto p = getSubpart(result.data(), "text/plain"); - QVERIFY(p); - } - - void testCreateHtmlMailWithAttachments() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments = { - {QLatin1String("name"), QLatin1String("filename"), "mimetype", true, "inlineAttachment"}, - {QLatin1String("name"), QLatin1String("filename"), "mimetype", false, "nonInlineAttachment"}, - }; - - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, true, attachments); - - QVERIFY(result); - QVERIFY(validate(result)); - QCOMPARE(result->subject()->asUnicodeString(), subject); - QVERIFY(result->contentType()->isMimeType("multipart/mixed")); - QVERIFY(result->date(false)->dateTime().isValid()); - const auto contents = result->contents(); - // 1 alternative + 2 Attachments - QCOMPARE(contents.size(), 3); - auto p = getSubpart(result.data(), "multipart/alternative"); - QVERIFY(p); - } - - void testCreatePlainMailSigned() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments; - - auto keys = Crypto::findKeys({}, true, false); - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, false, attachments, keys, {}, keys[0]); - - QVERIFY(result); - QVERIFY(validate(result)); - // qWarning() << "---------------------------------"; - // qWarning().noquote() << result->encodedContent(); - // qWarning() << "---------------------------------"; - QCOMPARE(result->subject()->asUnicodeString(), subject); - QVERIFY(result->date(false)->dateTime().isValid()); - - QCOMPARE(result->contentType()->mimeType(), QByteArray{"multipart/signed"}); - QCOMPARE(result->attachments().size(), 1); - QCOMPARE(result->attachments()[0]->contentDisposition()->filename(), QLatin1String{"0x8F246DE6.asc"}); - QCOMPARE(result->contents().size(), 2); - - auto signedMessage = result->contents()[0]; - QVERIFY(signedMessage->contentType()->isMimeType("multipart/mixed")); - const auto contents = signedMessage->contents(); - QCOMPARE(contents.size(), 2); - QCOMPARE(contents[0]->contentType()->mimeType(), QByteArray{"text/plain"}); - QCOMPARE(contents[1]->contentType()->mimeType(), QByteArray{"application/pgp-keys"}); - QCOMPARE(contents[1]->contentDisposition()->filename(), QLatin1String{"0x8F246DE6.asc"}); - - auto signature = result->contents()[1]; - QCOMPARE(signature->contentDisposition()->filename(), QLatin1String{"signature.asc"}); - QVERIFY(signature->contentType()->isMimeType("application/pgp-signature")); - } - - void testCreatePlainMailWithAttachmentsSigned() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - - KMime::Types::Mailbox from; - from.fromUnicodeString(QLatin1String("from@example.org")); - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - QList<Attachment> attachments = { - {QLatin1String("name"), QLatin1String("filename1"), "mimetype1", true, "inlineAttachment"}, - {QLatin1String("name"), QLatin1String("filename2"), "mimetype2", false, "nonInlineAttachment"}, - }; - - auto signingKeys = Crypto::findKeys({}, true, false); - auto result = MailTemplates::createMessage({}, to, cc, bcc, from, subject, body, false, attachments, signingKeys, {}, signingKeys[0]); - - QVERIFY(result); - QVERIFY(validate(result)); - qWarning() << "---------------------------------"; - qWarning().noquote() << result->encodedContent(); - qWarning() << "---------------------------------"; - QCOMPARE(result->subject()->asUnicodeString(), subject); - QVERIFY(result->date(false)->dateTime().isValid()); - - QCOMPARE(result->contentType()->mimeType(), QByteArray{"multipart/signed"}); - QCOMPARE(result->attachments().size(), 3); - QCOMPARE(result->attachments()[0]->contentDisposition()->filename(), QLatin1String{"filename1"}); - QCOMPARE(result->attachments()[1]->contentDisposition()->filename(), QLatin1String{"filename2"}); - QCOMPARE(result->attachments()[2]->contentDisposition()->filename(), QLatin1String{"0x8F246DE6.asc"}); - - QCOMPARE(result->contents().size(), 2); - - auto signedMessage = result->contents()[0]; - QVERIFY(signedMessage->contentType()->isMimeType("multipart/mixed")); - const auto contents = signedMessage->contents(); - QCOMPARE(contents.size(), 4); - QCOMPARE(contents[0]->contentType()->mimeType(), QByteArray{"text/plain"}); - QCOMPARE(contents[1]->contentDisposition()->filename(), QLatin1String{"filename1"}); - QCOMPARE(contents[2]->contentDisposition()->filename(), QLatin1String{"filename2"}); - QCOMPARE(contents[3]->contentType()->mimeType(), QByteArray{"application/pgp-keys"}); - QCOMPARE(contents[3]->contentDisposition()->filename(), QLatin1String{"0x8F246DE6.asc"}); - } - - void testCreateIMipMessage() - { - QStringList to = {{QLatin1String("to@example.org")}}; - QStringList cc = {{QLatin1String("cc@example.org")}}; - QStringList bcc = {{QLatin1String("bcc@example.org")}}; - QString from = {QLatin1String("from@example.org")}; - QString subject = QLatin1String("subject"); - QString body = QLatin1String("body"); - - QString ical = QLatin1String("ical"); - - auto result = MailTemplates::createIMipMessage(from, {to, cc, bcc}, subject, body, ical); - - QVERIFY(result); - QVERIFY(validate(result)); - qWarning() << "---------------------------------"; - qWarning().noquote() << result->encodedContent(); - qWarning() << "---------------------------------"; - - QCOMPARE(result->contentType()->mimeType(), QByteArray{"multipart/alternative"}); - - QCOMPARE(result->contents().size(), 2); - QVERIFY(result->contents()[0]->contentType()->isMimeType("text/plain")); - QVERIFY(result->contents()[1]->contentType()->isMimeType("text/calendar")); - QCOMPARE(result->contents()[1]->contentType()->name(), QLatin1String{"event.ics"}); - - QVERIFY(KMime::isAttachment(result->contents()[1])); - // KMime should return 1 attachment for this multipart but it doesn't sometimes(?) - // - // It is an attachment as the ical part: - // A) Has a name - // B) Has a content disposition of CDattachment - // - // Check the result of QVERIFY(KMime::isAttachment(result->contents()[1])); if - // it is true then this confirms that result->attachments() is misreporting - QCOMPARE(result->attachments().count(), 1); - } - - void testEncryptedWithProtectedHeadersReply() - { - KMime::Types::AddrSpecList me; - KMime::Types::Mailbox mb; - mb.setAddress("to1@example.org"); - me << mb.addrSpec(); - - auto msg = readMail(QLatin1String("openpgp-encrypted-memoryhole2.mbox")); - KMime::Message::Ptr result; - MailTemplates::reply( - msg, - [&](const KMime::Message::Ptr &r) { - result = r; - }, - me); - QTRY_VERIFY(result); - QCOMPARE(result->subject()->asUnicodeString(), QLatin1String{"RE: This is the subject"}); - QCOMPARE(result->to()->addresses(), QVector<QByteArray>{{"jane@doe.com"}}); - QCOMPARE(result->cc()->addresses(), QVector<QByteArray>{{"john@doe.com"}}); - QCOMPARE(result->inReplyTo()->asUnicodeString(), QLatin1String{"<03db3530-0000-0000-95a2-8a148f00000@example.com>"}); - QCOMPARE(result->references()->asUnicodeString(), - QLatin1String{"<dbe9d22b-0a3f-cb1e-e883-8a148f00000@example.com> <03db3530-0000-0000-95a2-8a148f00000@example.com>"}); - QCOMPARE(normalize(removeFirstLine(QString::fromUtf8(result->body()))), - QLatin1String{"FsdflkjdslfjHappyMonday!Belowyouwillfindaquickoverviewofthecurrenton-goings.Remember"}); - } - - void testEncryptedWithProtectedHeadersForwardAsAttachment() - { - auto msg = readMail(QLatin1String("openpgp-encrypted-memoryhole2.mbox")); - KMime::Message::Ptr result; - MailTemplates::forward(msg, [&](const KMime::Message::Ptr &r) { - result = r; - }); - QTRY_VERIFY(result); - QCOMPARE(result->subject()->asUnicodeString(), QLatin1String{"FW: This is the subject"}); - QCOMPARE(result->to()->addresses(), {}); - QCOMPARE(result->cc()->addresses(), {}); - QCOMPARE(result->references()->asUnicodeString(), - QLatin1String{"<dbe9d22b-0a3f-cb1e-e883-8a148f00000@example.com> <03db3530-0000-0000-95a2-8a148f00000@example.com>"}); - QCOMPARE(result->inReplyTo()->identifiers(), {}); - - auto attachments = result->attachments(); - QCOMPARE(attachments.size(), 1); - auto attachment = attachments[0]; - QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline); - QCOMPARE(attachment->contentDisposition(false)->filename(), QLatin1String{"This is the subject.eml"}); - QVERIFY(attachment->bodyIsMessage()); - - attachment->parse(); - auto origMsg = attachment->bodyAsMessage(); - QCOMPARE(origMsg->subject(false)->asUnicodeString(), QLatin1String{"..."}); - } -}; - -QTEST_MAIN(MailTemplateTest) -#include "mailtemplatetest.moc" diff --git a/src/core/autotests/mimetreeparsertest.cpp b/src/core/autotests/mimetreeparsertest.cpp index bdcf5e1..6362c63 100644 --- a/src/core/autotests/mimetreeparsertest.cpp +++ b/src/core/autotests/mimetreeparsertest.cpp @@ -1,777 +1,777 @@ // SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsystems.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include <MimeTreeParserCore/ObjectTreeParser> #include <QDebug> #include <QTest> #include <QTimeZone> QByteArray readMailFromFile(const QString &mailFile) { QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile); file.open(QIODevice::ReadOnly); Q_ASSERT(file.isOpen()); return file.readAll(); } class MimeTreeParserTest : public QObject { Q_OBJECT private Q_SLOTS: void testTextMail() { const auto expectedText = QStringLiteral( "If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to view the newsletter " "on our website: http://www.gog.com/newsletter/"); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("plaintext.mbox"))); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QCOMPARE(part->text(), expectedText); QCOMPARE(part->charset(), QStringLiteral("utf-8").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 0); QCOMPARE(otp.plainTextContent(), expectedText); QVERIFY(otp.htmlContent().isEmpty()); } void testAlternative() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("alternative.mbox"))); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->plaintextContent(), QStringLiteral("If you can see this text it means that your email client couldn't display our newsletter properly.\nPlease visit this link to " "view the newsletter on our website: http://www.gog.com/newsletter/\n")); QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part->htmlContent(), QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>\n\n")); QCOMPARE(otp.collectAttachmentParts().size(), 0); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); } void testTextHtml() { auto expectedText = QStringLiteral("<html><body><p><span>HTML</span> text</p></body></html>"); MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("html.mbox"))); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::HtmlMessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->htmlContent(), expectedText); QCOMPARE(part->charset(), QStringLiteral("windows-1252").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 0); QCOMPARE(otp.htmlContent(), expectedText); QVERIFY(otp.plainTextContent().isEmpty()); } void testSMimeEncrypted() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-encrypted.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog.")); QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 0); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 0); } void testOpenPGPEncryptedAttachment() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("test text")); QCOMPARE(part->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 2); // QCOMPARE(contentAttachmentList[0]->availableContents(), QVector<QByteArray>() << "text/plain"); // QCOMPARE(contentAttachmentList[0]->content().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1); QCOMPARE(contentAttachmentList[0]->signatures().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(contentAttachmentList[0]->signatureState(), MimeTreeParser::KMMsgFullySigned); // QCOMPARE(contentAttachmentList[1]->availableContents(), QVector<QByteArray>() << "image/png"); // QCOMPARE(contentAttachmentList[1]->content().size(), 1); QCOMPARE(contentAttachmentList[1]->encryptions().size(), 0); QCOMPARE(contentAttachmentList[1]->signatures().size(), 0); QCOMPARE(contentAttachmentList[1]->encryptionState(), MimeTreeParser::KMMsgNotEncrypted); QCOMPARE(contentAttachmentList[1]->signatureState(), MimeTreeParser::KMMsgNotSigned); } void testOpenPGPInline() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-inline-charset-encrypted.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->charset(), QStringLiteral("ISO-8859-15").toLocal8Bit()); QCOMPARE(part->text(), QString::fromUtf8("asdasd asd asd asdf sadf sdaf sadf öäü")); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(otp.collectAttachmentParts().size(), 0); } void testOpenPPGInlineWithNonEncText() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-inline-encrypted+nonenc.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part1 = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part1)); QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\nsome random text")); // TODO test if we get the proper subparts with the appropriate encryptions QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit()); QCOMPARE(part1->encryptionState(), MimeTreeParser::KMMsgPartiallyEncrypted); QCOMPARE(part1->signatureState(), MimeTreeParser::KMMsgNotSigned); // QCOMPARE(part1->text(), QStringLiteral("Not encrypted not signed :(\n\n")); // QCOMPARE(part1->charset(), QStringLiteral("us-ascii").toLocal8Bit()); // QCOMPARE(contentList[1]->content(), QStringLiteral("some random text").toLocal8Bit()); // QCOMPARE(contentList[1]->charset(), QStringLiteral("us-ascii").toLocal8Bit()); // QCOMPARE(contentList[1]->encryptions().size(), 1); // QCOMPARE(contentList[1]->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 0); } void testEncryptionBlock() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part1 = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part1)); QCOMPARE(part1->encryptions().size(), 1); // auto enc = contentList[0]->encryptions()[0]; // QCOMPARE((int) enc->recipients().size(), 2); // auto r = enc->recipients()[0]; // QCOMPARE(r->keyid(),QStringLiteral("14B79E26050467AA")); // QCOMPARE(r->name(),QStringLiteral("kdetest")); // QCOMPARE(r->email(),QStringLiteral("you@you.com")); // QCOMPARE(r->comment(),QStringLiteral("")); // r = enc->recipients()[1]; // QCOMPARE(r->keyid(),QStringLiteral("8D9860C58F246DE6")); // QCOMPARE(r->name(),QStringLiteral("unittest key")); // QCOMPARE(r->email(),QStringLiteral("test@kolab.org")); // QCOMPARE(r->comment(),QStringLiteral("no password")); auto attachmentList = otp.collectAttachmentParts(); QCOMPARE(attachmentList.size(), 2); auto attachment1 = attachmentList[0]; QVERIFY(attachment1->node()); QCOMPARE(attachment1->filename(), QStringLiteral("file.txt")); auto attachment2 = attachmentList[1]; QVERIFY(attachment2->node()); QCOMPARE(attachment2->filename(), QStringLiteral("image.png")); } void testSignatureBlock() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); // QCOMPARE(contentList[0]->signatures().size(), 1); // auto sig = contentList[0]->signatures()[0]; // QCOMPARE(sig->creationDateTime(), QDateTime(QDate(2015,05,01),QTime(15,12,47))); // QCOMPARE(sig->expirationDateTime(), QDateTime()); // QCOMPARE(sig->neverExpires(), true); // auto key = sig->key(); // QCOMPARE(key->keyid(),QStringLiteral("8D9860C58F246DE6")); // QCOMPARE(key->name(),QStringLiteral("unittest key")); // QCOMPARE(key->email(),QStringLiteral("test@kolab.org")); // QCOMPARE(key->comment(),QStringLiteral("no password")); } void testRelatedAlternative() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("cid-links.mbox"))); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QCOMPARE(otp.collectAttachmentParts().size(), 1); } void testAttachmentPart() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("attachment.mbox"))); otp.print(); auto partList = otp.collectAttachmentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->mimeType(), "image/jpeg"); QCOMPARE(part->filename(), QStringLiteral("aqnaozisxya.jpeg")); } void testAttachment2Part() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("attachment2.mbox"))); otp.print(); auto partList = otp.collectAttachmentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->mimeType(), "image/jpeg"); QCOMPARE(part->filename(), QStringLiteral("aqnaozisxya.jpeg")); } void testCidLink() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("cid-links.mbox"))); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(bool(part)); auto resolvedContent = otp.resolveCidLinks(part->htmlContent()); QVERIFY(!resolvedContent.contains(QLatin1String("cid:"))); } void testCidLinkInForwardedInline() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("cid-links-forwarded-inline.mbox"))); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(bool(part)); auto resolvedContent = otp.resolveCidLinks(part->htmlContent()); QVERIFY(!resolvedContent.contains(QLatin1String("cid:"))); } void testOpenPGPInlineError() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("inlinepgpgencrypted-error.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::EncryptedMessagePart>(); QVERIFY(bool(part)); QVERIFY(part->error()); } void testEncapsulated() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("encapsulated-with-attachment.mbox"))); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 2); auto part = partList[1].dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->from(), QLatin1String("Thomas McGuire <dontspamme@gmx.net>")); QCOMPARE(part->date().toString(), QLatin1String("Wed Aug 5 10:57:58 2009 GMT+0200")); auto subPartList = otp.collectContentParts(part); QCOMPARE(subPartList.size(), 1); qWarning() << subPartList[0]->metaObject()->className(); auto subPart = subPartList[0].dynamicCast<MimeTreeParser::TextMessagePart>(); QVERIFY(bool(subPart)); } void test8bitEncodedInPlaintext() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("8bitencoded.mbox"))); QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("Why Pisa’s Tower"))); QVERIFY(otp.htmlContent().contains(QString::fromUtf8("Why Pisa’s Tower"))); } void testInlineSigned() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-inline-signed.mbox"))); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgNotEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); QCOMPARE(part->text(), QString::fromUtf8("ohno öäü\n")); QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("ohno öäü"))); auto signaturePart = part->signatures().first(); QCOMPARE(signaturePart->partMetaData()->isGoodSignature, true); - QCOMPARE(signaturePart->partMetaData()->keyIsTrusted, true); - QCOMPARE(signaturePart->partMetaData()->keyMissing, false); - QCOMPARE(signaturePart->partMetaData()->keyExpired, false); - QCOMPARE(signaturePart->partMetaData()->keyRevoked, false); - QCOMPARE(signaturePart->partMetaData()->sigExpired, false); - QCOMPARE(signaturePart->partMetaData()->crlMissing, false); - QCOMPARE(signaturePart->partMetaData()->crlTooOld, false); + QCOMPARE(signaturePart->partMetaData()->isTrusted(), true); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyMissing, false); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyExpired, false); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyRevoked, false); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::SigExpired, false); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlMissing, false); + QCOMPARE(signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlTooOld, false); QCOMPARE(signaturePart->partMetaData()->keyId, QByteArray{"8D9860C58F246DE6"}); QCOMPARE(signaturePart->partMetaData()->signer, QLatin1String{"unittest key (no password) <test@kolab.org>"}); QCOMPARE(signaturePart->partMetaData()->signerMailAddresses, QStringList{{QStringLiteral("test@kolab.org")}}); } void testEncryptedAndSigned() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted+signed.mbox"))); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); QVERIFY(otp.plainTextContent().contains(QString::fromUtf8("encrypted message text"))); auto signaturePart = part->signatures().first(); QCOMPARE(signaturePart->partMetaData()->keyId, QByteArray{"8D9860C58F246DE6"}); QCOMPARE(signaturePart->partMetaData()->isGoodSignature, true); } void testOpenpgpMultipartEmbedded() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-multipart-embedded.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(otp.plainTextContent(), QString::fromUtf8("sdflskjsdf\n\n-- \nThis is a HTML signature.\n")); } void testOpenpgpMultipartEmbeddedSigned() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-multipart-embedded-signed.mbox"))); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); QCOMPARE(otp.plainTextContent(), QString::fromUtf8("test\n\n-- \nThis is a HTML signature.\n")); auto signaturePart = part->signatures().first(); QVERIFY(signaturePart->partMetaData()->keyId.endsWith(QByteArray{"2E3B7787B1B75920"})); // We lack the public key for this message - QCOMPARE(signaturePart->partMetaData()->isGoodSignature, false); - QCOMPARE(signaturePart->partMetaData()->keyMissing, true); + QCOMPARE(false, signaturePart->partMetaData()->isGoodSignature); + QCOMPARE(GpgME::Signature::KeyMissing, signaturePart->partMetaData()->sigSummary); } void testAppleHtmlWithAttachments() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("applehtmlwithattachments.mbox"))); otp.decryptParts(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(part); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QVERIFY(part->isHtml()); QCOMPARE(otp.plainTextContent(), QString::fromUtf8("Hi,\n\nThis is an HTML message with attachments.\n\nCheers,\nChristian")); QCOMPARE(otp.htmlContent(), QString::fromUtf8( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=us-ascii\"></head><body style=\"word-wrap: break-word; " "-webkit-nbsp-mode: space; line-break: after-white-space;\" class=\"\"><meta http-equiv=\"Content-Type\" content=\"text/html; " "charset=us-ascii\" class=\"\"><div style=\"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;\" " "class=\"\">Hi,<div class=\"\"><br class=\"\"></div><blockquote style=\"margin: 0 0 0 40px; border: none; padding: 0px;\" class=\"\"><div " "class=\"\">This is an <b class=\"\">HTML</b> message with attachments.</div></blockquote><div class=\"\"><br class=\"\"></div><div " "class=\"\">Cheers,</div><div class=\"\">Christian<img apple-inline=\"yes\" id=\"B9EE68A9-83CA-41CD-A3E4-E5BA301F797A\" class=\"\" " "src=\"cid:F5B62D1D-E4EC-4C59-AA5A-708525C2AC3C\"></div></div></body></html>")); auto attachments = otp.collectAttachmentParts(); QCOMPARE(attachments.size(), 1); } void testAppleHtmlWithAttachmentsMixed() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("applehtmlwithattachmentsmixed.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(part); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QVERIFY(part->isHtml()); QCOMPARE(otp.plainTextContent(), QString::fromUtf8("Hello\n\n\n\nRegards\n\nFsdfsdf")); QCOMPARE(otp.htmlContent(), QString::fromUtf8( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=us-ascii\"></head><body style=\"word-wrap: break-word; " "-webkit-nbsp-mode: space; line-break: after-white-space;\" class=\"\"><strike class=\"\">Hello</strike><div class=\"\"><br " "class=\"\"></div><div class=\"\"></div></body></html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; " "charset=us-ascii\"></head><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;\" " "class=\"\"><div class=\"\"></div><div class=\"\"><br class=\"\"></div><div class=\"\"><b class=\"\">Regards</b></div><div class=\"\"><b " "class=\"\"><br class=\"\"></b></div><div class=\"\">Fsdfsdf</div></body></html>")); auto attachments = otp.collectAttachmentParts(); QCOMPARE(attachments.size(), 1); } void testInvitation() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("invitation.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(part); QCOMPARE(part->encryptions().size(), 0); QCOMPARE(part->signatures().size(), 0); QVERIFY(!part->isHtml()); QVERIFY(part->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)); auto attachments = otp.collectAttachmentParts(); QCOMPARE(attachments.size(), 0); } void testGmailInvitation() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("gmail-invitation.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(part); QCOMPARE(part->encryptions().size(), 0); qWarning() << part; QCOMPARE(part->signatures().size(), 0); QVERIFY(part->isHtml()); QVERIFY(part->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)); auto attachments = otp.collectAttachmentParts(); QCOMPARE(attachments.size(), 1); } void testMemoryHole() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-memoryhole.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("very secret mesage\n")); QCOMPARE(part->header("subject")->asUnicodeString(), QStringLiteral("hidden subject")); QCOMPARE(part->header("from")->asUnicodeString(), QStringLiteral("you@example.com")); QCOMPARE(part->header("to")->asUnicodeString(), QStringLiteral("me@example.com")); QCOMPARE(part->header("cc")->asUnicodeString(), QStringLiteral("cc@example.com")); QCOMPARE(part->header("message-id")->asUnicodeString(), QStringLiteral("<myhiddenreference@me>")); QCOMPARE(part->header("references")->asUnicodeString(), QStringLiteral("<hiddenreference@hidden>")); QCOMPARE(part->header("in-reply-to")->asUnicodeString(), QStringLiteral("<hiddenreference@hidden>")); QCOMPARE(static_cast<const KMime::Headers::Date *>(part->header("date"))->dateTime(), QDateTime(QDate(2018, 1, 2), QTime(3, 4, 5), QTimeZone::utc())); } /** * Required special handling because the list replaces the toplevel part. */ void testMemoryHoleWithList() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("cid-links-forwarded-inline.mbox"))); const auto parts = otp.collectContentParts(); auto part = parts[0]; QVERIFY(part->header("references")); QCOMPARE(part->header("references")->asUnicodeString(), QStringLiteral("<a1777ec781546ccc5dcd4918a5e4e03d@info>")); } void testMemoryHoleMultipartMixed() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-memoryhole2.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("\n\n Fsdflkjdslfj\n\n\nHappy Monday!\n\nBelow you will find a quick overview of the current on-goings. Remember\n")); QCOMPARE(part->header("subject")->asUnicodeString(), QStringLiteral("This is the subject")); } void testMIMESignature() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("text+html-maillinglist.mbox"))); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); for (const auto &part : partList) { qWarning() << "found part " << part->metaObject()->className(); } QCOMPARE(partList.size(), 2); // The actual content { auto part = partList[0].dynamicCast<MimeTreeParser::AlternativeMessagePart>(); QVERIFY(bool(part)); } // The signature { auto part = partList[1].dynamicCast<MimeTreeParser::TextMessagePart>(); QVERIFY(bool(part)); QVERIFY(part->text().contains(QStringLiteral("bugzilla mailing list"))); } } void testCRLFEncryptedWithSignature() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("crlf-encrypted-with-signature.mbox"))); otp.decryptParts(); otp.print(); QCOMPARE(otp.plainTextContent(), QStringLiteral("CRLF file\n\n-- \nThis is a signature\nWith two lines\n\nAand another line\n")); } void testCRLFEncryptedWithSignatureMultipart() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("crlf-encrypted-with-signature-multipart.mbox"))); otp.decryptParts(); otp.print(); // QEXPECT_FAIL("", "because MessagePart::parseInternal uses \n\n to detect encapsulated messages (so 'CRLF file' ends up as header)", Continue); // QCOMPARE(otp.plainTextContent(), QStringLiteral("CRLF file\n\n-- \nThis is a signature\nWith two lines\n\nAand another line\n")); // QVERIFY(!otp.htmlContent().contains(QStringLiteral("\r\n"))); } void testCRLFOutlook() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("outlook.mbox"))); otp.decryptParts(); otp.print(); qWarning() << otp.plainTextContent(); QVERIFY(otp.plainTextContent().startsWith(QStringLiteral("Hi Christian,\n\nhabs gerade getestet:\n\n«This is a test"))); QVERIFY(!otp.htmlContent().contains(QLatin1String("\r\n"))); } void testOpenPGPEncryptedSignedThunderbird() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("openpgp-encrypted-signed-thunderbird.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("sdfsdf\n")); QCOMPARE(part->charset(), QStringLiteral("utf-8").toLocal8Bit()); QCOMPARE(part->encryptions().size(), 1); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->signatures()[0]->partMetaData()->isGoodSignature, true); QCOMPARE(part->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(part->signatureState(), MimeTreeParser::KMMsgFullySigned); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 1); // QCOMPARE(contentAttachmentList[0]->content().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptions().size(), 1); QCOMPARE(contentAttachmentList[0]->signatures().size(), 1); QCOMPARE(contentAttachmentList[0]->encryptionState(), MimeTreeParser::KMMsgFullyEncrypted); QCOMPARE(contentAttachmentList[0]->signatureState(), MimeTreeParser::KMMsgFullySigned); } void testSignedForwardOpenpgpSignedEncrypted() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("signed-forward-openpgp-signed-encrypted.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 2); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("bla bla bla")); part = partList[1].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QString()); QCOMPARE(part->charset(), QStringLiteral("ISO-8859-1").toLocal8Bit()); QCOMPARE(part->signatures().size(), 1); QCOMPARE(part->signatures()[0]->partMetaData()->isGoodSignature, true); auto contentAttachmentList = otp.collectAttachmentParts(); QCOMPARE(contentAttachmentList.size(), 1); } void testSmimeOpaqueSign() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-opaque-sign.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("A simple signed only test.")); } void testSmimeEncrypted() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-encrypted.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog.")); } void testSmimeSignedApple() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-signed-apple.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); // QCOMPARE(part->text(), QStringLiteral("A simple signed only test.")); } void testSmimeEncryptedOctetStream() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-encrypted-octet-stream.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("The quick brown fox jumped over the lazy dog.")); } void testSmimeOpaqueSignedEncryptedAttachment() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-opaque-signed-encrypted-attachment.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("This is an Opaque S/MIME encrypted and signed message with attachment")); } void testSmimeOpaqueEncSign() { MimeTreeParser::ObjectTreeParser otp; otp.parseObjectTree(readMailFromFile(QLatin1String("smime-opaque-enc+sign.mbox"))); otp.print(); otp.decryptParts(); otp.print(); auto partList = otp.collectContentParts(); QCOMPARE(partList.size(), 1); auto part = partList[0].dynamicCast<MimeTreeParser::MessagePart>(); QVERIFY(bool(part)); QCOMPARE(part->text(), QStringLiteral("Encrypted and signed mail.")); } }; QTEST_GUILESS_MAIN(MimeTreeParserTest) #include "mimetreeparsertest.moc" diff --git a/src/core/bodypartformatter_impl.cpp b/src/core/bodypartformatter_impl.cpp index 874a2a5..c8945ae 100644 --- a/src/core/bodypartformatter_impl.cpp +++ b/src/core/bodypartformatter_impl.cpp @@ -1,359 +1,360 @@ // SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> // SPDX-License-Identifier: GPL-2.0-only #include "mimetreeparser_core_debug.h" #include "bodypartformatter.h" #include "bodypartformatterbasefactory.h" #include "bodypartformatterbasefactory_p.h" #include "messagepart.h" #include "objecttreeparser.h" #include "utils.h" #include <KMime/Content> +#include <QGpgME/Protocol> using namespace MimeTreeParser; using namespace MimeTreeParser::Interface; namespace MimeTreeParser { class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { }; class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(objectTreeParser, node, node->bodyAsMessage())); } }; class HeadersBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { return MessagePart::Ptr(new HeadersPart(objectTreeParser, node)); } }; class MultiPartRelatedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: QVector<MessagePart::Ptr> processList(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->contents().isEmpty()) { return {}; } // We rely on the order of the parts. // Theoretically there could also be a Start parameter which would break this.. // https://tools.ietf.org/html/rfc2387#section-4 // We want to display attachments even if displayed inline. QVector<MessagePart::Ptr> list; list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0), true))); for (int i = 1; i < node->contents().size(); i++) { auto p = node->contents().at(i); if (KMime::isAttachment(p)) { list.append(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, p, true))); } } return list; } }; class MultiPartMixedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { const auto contents = node->contents(); if (contents.isEmpty()) { return {}; } // we need the intermediate part to preserve the headers (necessary for with protected headers using multipart mixed) auto part = MessagePart::Ptr(new MessagePart(objectTreeParser, {}, node)); part->appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, contents.at(0), false))); return part; } }; class ApplicationPGPEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->decodedContent().trimmed() != "Version: 1") { qCWarning(MIMETREEPARSER_CORE_LOG) << "Unknown PGP Version String:" << node->decodedContent().trimmed(); } if (!node->parent()) { return MessagePart::Ptr(); } KMime::Content *data = findTypeInDirectChildren(node->parent(), "application/octet-stream"); if (!data) { return MessagePart::Ptr(); // new MimeMessagePart(objectTreeParser, node)); } - EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), OpenPGP, node, data)); + EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), QGpgME::openpgp(), node, data)); mp->setIsEncrypted(true); return mp; } }; class ApplicationPkcs7MimeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->head().isEmpty()) { return MessagePart::Ptr(); } const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower(); if (smimeType == QLatin1String("certs-only")) { - return CertMessagePart::Ptr(new CertMessagePart(objectTreeParser, node, CMS)); + return CertMessagePart::Ptr(new CertMessagePart(objectTreeParser, node, QGpgME::smime())); } bool isSigned = (smimeType == QLatin1String("signed-data")); bool isEncrypted = (smimeType == QLatin1String("enveloped-data")); // Analyze "signTestNode" node to find/verify a signature. // If zero part.objectTreeParser verification was successfully done after // decrypting via recursion by insertAndParseNewChildNode(). KMime::Content *signTestNode = isEncrypted ? nullptr : node; // We try decrypting the content // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. MessagePart::Ptr mp; if (!isSigned) { if (isEncrypted) { qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data"; } else { qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?"; } - auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, node->decodedText(), CMS, node)); + auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(objectTreeParser, node->decodedText(), QGpgME::smime(), node)); mp = _mp; _mp->setIsEncrypted(true); // PartMetaData *messagePart(_mp->partMetaData()); // if (!part.source()->decryptMessage()) { // isEncrypted = true; signTestNode = nullptr; // PENDING(marc) to be abs. sure, we'd need to have to look at the content // } else { // _mp->startDecryption(); // if (messagePart->isDecryptable) { // qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !"; // isEncrypted = true; // part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted); // signTestNode = nullptr; // } else { // // decryption failed, which could be because the part was encrypted but // // decryption failed, or because we didn't know if it was encrypted, tried, // // and failed. If the message was not actually encrypted, we continue // // assuming it's signed // if (_mp->passphraseError() || (smimeType.isEmpty() && messagePart->isEncrypted)) { // isEncrypted = true; // signTestNode = nullptr; // } // if (isEncrypted) { // qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !"; // } else { // qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - NO encryption found"; // } // } // } } // We now try signature verification if necessarry. if (signTestNode) { if (isSigned) { qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data"; } else { qCDebug(MIMETREEPARSER_CORE_LOG) << "pkcs7 mime - type unknown - opaque signed data ?"; } - return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, CMS, nullptr, signTestNode)); + return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, QGpgME::smime(), nullptr, signTestNode)); } return mp; } }; class MultiPartAlternativeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->contents().isEmpty()) { return MessagePart::Ptr(); } AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(objectTreeParser, node)); if (mp->mChildParts.isEmpty()) { return MimeMessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0))); } return mp; } }; class MultiPartEncryptedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->contents().isEmpty()) { Q_ASSERT(false); return MessagePart::Ptr(); } - CryptoProtocol useThisCryptProto = UnknownProtocol; + const QGpgME::Protocol *protocol = nullptr; /* ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- */ KMime::Content *data = findTypeInDirectChildren(node, "application/octet-stream"); if (data) { - useThisCryptProto = OpenPGP; + protocol = QGpgME::openpgp(); } else { data = findTypeInDirectChildren(node, "application/pkcs7-mime"); if (data) { - useThisCryptProto = CMS; + protocol = QGpgME::smime(); } } /* --------------------------------------------------------------------------------------------------------------- */ if (!data) { return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0))); } - EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), useThisCryptProto, node, data)); + EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(objectTreeParser, data->decodedText(), protocol, node, data)); mp->setIsEncrypted(true); return mp; } }; class MultiPartSignedBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: - static CryptoProtocol detectProtocol(const QString &protocolContentType_, const QString &signatureContentType) + static const QGpgME::Protocol *detectProtocol(const QString &protocolContentType_, const QString &signatureContentType) { auto protocolContentType = protocolContentType_; if (protocolContentType.isEmpty()) { qCWarning(MIMETREEPARSER_CORE_LOG) << "Message doesn't set the protocol for the multipart/signed content-type, " "using content-type of the signature:" << signatureContentType; protocolContentType = signatureContentType; } - CryptoProtocol protocol = UnknownProtocol; + const QGpgME::Protocol *protocol = nullptr; if (protocolContentType == QLatin1String("application/pkcs7-signature") || protocolContentType == QLatin1String("application/x-pkcs7-signature")) { - protocol = CMS; + protocol = QGpgME::smime(); } else if (protocolContentType == QLatin1String("application/pgp-signature") || protocolContentType == QLatin1String("application/x-pgp-signature")) { - protocol = OpenPGP; + protocol = QGpgME::openpgp(); } return protocol; } MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (node->contents().size() != 2) { qCDebug(MIMETREEPARSER_CORE_LOG) << "mulitpart/signed must have exactly two child parts!" << Qt::endl << "processing as multipart/mixed"; if (!node->contents().isEmpty()) { return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, node->contents().at(0))); } else { return MessagePart::Ptr(); } } KMime::Content *signedData = node->contents().at(0); KMime::Content *signature = node->contents().at(1); Q_ASSERT(signedData); Q_ASSERT(signature); auto protocol = detectProtocol(node->contentType()->parameter(QStringLiteral("protocol")).toLower(), QLatin1String(signature->contentType()->mimeType().toLower())); - if (protocol == UnknownProtocol) { + if (!protocol) { return MessagePart::Ptr(new MimeMessagePart(objectTreeParser, signedData)); } return SignedMessagePart::Ptr(new SignedMessagePart(objectTreeParser, protocol, signature, signedData)); } }; class TextHtmlBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { return HtmlMessagePart::Ptr(new HtmlMessagePart(objectTreeParser, node)); } }; class TextPlainBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter { public: MessagePart::Ptr process(ObjectTreeParser *objectTreeParser, KMime::Content *node) const Q_DECL_OVERRIDE { if (KMime::isAttachment(node)) { return AttachmentMessagePart::Ptr(new AttachmentMessagePart(objectTreeParser, node)); } return TextMessagePart::Ptr(new TextMessagePart(objectTreeParser, node)); } }; } // anon namespace void BodyPartFormatterBaseFactoryPrivate::messageviewer_create_builtin_bodypart_formatters() { auto any = new AnyTypeBodyPartFormatter; auto textPlain = new TextPlainBodyPartFormatter; auto pkcs7 = new ApplicationPkcs7MimeBodyPartFormatter; auto pgp = new ApplicationPGPEncryptedBodyPartFormatter; auto html = new TextHtmlBodyPartFormatter; auto headers = new HeadersBodyPartFormatter; auto multipartAlternative = new MultiPartAlternativeBodyPartFormatter; auto multipartMixed = new MultiPartMixedBodyPartFormatter; auto multipartSigned = new MultiPartSignedBodyPartFormatter; auto multipartEncrypted = new MultiPartEncryptedBodyPartFormatter; auto message = new MessageRfc822BodyPartFormatter; auto multipartRelated = new MultiPartRelatedBodyPartFormatter; insert("application", "octet-stream", any); insert("application", "pgp", textPlain); insert("application", "pkcs7-mime", pkcs7); insert("application", "x-pkcs7-mime", pkcs7); insert("application", "pgp-encrypted", pgp); insert("application", "*", any); insert("text", "html", html); insert("text", "rtf", any); insert("text", "plain", textPlain); insert("text", "rfc822-headers", headers); insert("text", "*", textPlain); insert("image", "*", any); insert("message", "rfc822", message); insert("message", "*", any); insert("multipart", "alternative", multipartAlternative); insert("multipart", "encrypted", multipartEncrypted); insert("multipart", "signed", multipartSigned); insert("multipart", "related", multipartRelated); insert("multipart", "*", multipartMixed); insert("*", "*", any); } diff --git a/src/core/crypto.cpp b/src/core/crypto.cpp deleted file mode 100644 index d5a4dd2..0000000 --- a/src/core/crypto.cpp +++ /dev/null @@ -1,593 +0,0 @@ -// SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com> -// SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com -// SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org> -// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> -// SPDX-License-Identifier: LGPL-2.0-or-later - -#include "crypto.h" - -#include <gpgme.h> - -#include <QDebug> -#include <QFile> - -#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.error; - 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); - - // Trust new keys - if (auto e = gpgme_set_ctx_flag(ctx, "trust-model", "tofu+pgp")) { - gpgme_release(ctx); - return std::make_pair(e, nullptr); - } - - // That's a great way to bring signature verification to a crawl - if (auto e = gpgme_set_ctx_flag(ctx, "auto-key-retrieve", "0")) { - gpgme_release(ctx); - return std::make_pair(e, nullptr); - } - - return std::make_pair(GPG_ERR_NO_ERROR, ctx); -} - -gpgme_error_t gpgme_passphrase(void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd) -{ - Q_UNUSED(hook); - Q_UNUSED(prev_was_bad); - // uid_hint will be something like "CAA5183608F0FB50 Test1 Kolab <test1@kolab.org>" (CAA518... is the key) - // pahhphrase_info will be something like "CAA5183608F0FB50 2E3B7787B1B75920 1 0" - qInfo() << "Requested passphrase for " << (uid_hint ? QByteArray{uid_hint} : QByteArray{}) - << (passphrase_info ? QByteArray{passphrase_info} : QByteArray{}); - - QFile file; - file.open(fd, QIODevice::WriteOnly); - // FIXME hardcoded as a test - QByteArray passphrase = QByteArray{"test1"} + QByteArray{"\n"}; - file.write(passphrase); - file.close(); - - return 0; -} - -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 != GPG_ERR_NO_SECKEY}); - } - 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); - if (is->summary & GPGME_SIGSUM_VALID) { - sig.result = Signature::Ok; - } else { - sig.result = Signature::Invalid; - if (is->summary & GPGME_SIGSUM_KEY_EXPIRED) { - sig.result = Signature::Expired; - } - if (is->summary & GPGME_SIGSUM_KEY_MISSING) { - sig.result = Signature::KeyNotFound; - } - } - sig.status = {is->status}; - sig.isTrusted = is->validity == GPGME_VALIDITY_FULL || is->validity == GPGME_VALIDITY_ULTIMATE; - 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}}; -} - -static DecryptionResult::Result toResult(gpgme_error_t err) -{ - if (err == GPG_ERR_NO_DATA) { - return DecryptionResult::NotEncrypted; - } else if (err == GPG_ERR_NO_SECKEY) { - return DecryptionResult::NoSecretKeyError; - } else if (err == GPG_ERR_CANCELED || err == GPG_ERR_INV_PASSPHRASE) { - return DecryptionResult::PassphraseError; - } else if (err == GPG_ERR_NO_ERROR) { - return DecryptionResult::NoSecretKeyError; - } - - qWarning() << "unknown error" << err << gpgme_strerror(err); - return DecryptionResult::NoSecretKeyError; -} - -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 " << gpgme_strerror(context.error); - qWarning() << "returning early"; - return std::make_pair(DecryptionResult{{}, {context.error}, DecryptionResult::NoSecretKeyError}, 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}, DecryptionResult::DecryptionError}, 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); - } - decryptionResult.result = toResult(err); - - outdata = toBA(out); - return std::make_pair(decryptionResult, verificationResult); -} - -static DecryptionResult decryptGPGME(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata) -{ - Context context{protocol}; - if (!context) { - qWarning() << "Failed to create context " << context.error; - return DecryptionResult{{}, 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(ctx, Data{ciphertext}.data, out); - if (err) { - qWarning() << "Failed to decrypt" << gpgme_strerror(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 DecryptionResult{{}, {err}}; - } - } - - DecryptionResult decryptionResult{{}, {err}}; - if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) { - decryptionResult.recipients = copyRecipients(res); - } - - decryptionResult.result = toResult(err); - - outdata = toBA(out); - return decryptionResult; -} - -DecryptionResult Crypto::decrypt(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata) -{ - return decryptGPGME(protocol, ciphertext, outdata); -} - -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 bool validateKey(const gpgme_key_t key) -{ - if (key->revoked) { - qWarning() << "Key is revoked " << key->fpr; - return false; - } - if (key->expired) { - qWarning() << "Key is expired " << key->fpr; - return false; - } - if (key->disabled) { - qWarning() << "Key is disabled " << key->fpr; - return false; - } - if (key->invalid) { - qWarning() << "Key is invalid " << key->fpr; - return false; - } - return true; -} - -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)) { - if (gpgme_err_code(err) != GPG_ERR_EOF) { - qWarning() << "Error after listing keys" << result.error << gpgme_strerror(err); - } - 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.isUsable = validateKey(key); - 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" << gpgme_strerror(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)) { - delete[] keys; - qWarning() << "Failed to retrieve key " << k.fingerprint << Error{e}; - return makeUnexpected(Error{e}); - } else { - if (!key->can_encrypt || !validateKey(key)) { - qWarning() << "Key cannot be used for encryption " << k.fingerprint; - delete[] keys; - return makeUnexpected(Error{e}); - } - *keys_it++ = key; - } - } - *keys_it++ = nullptr; - - gpgme_data_t out; - if (auto e = gpgme_data_new(&out)) { - qWarning() << "Failed to allocate output buffer"; - delete[] keys; - 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) { - qWarning() << "Encryption failed:" << gpgme_err_code(err); - switch (gpgme_err_code(err)) { - 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{err}); - } - - 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() << "Found " << res.keys.size() << " keys for the patterns: " << patterns; - - std::vector<Key> usableKeys; - for (const auto &key : res.keys) { - if (!key.isUsable) { - qWarning() << "Key is not usable: " << key.fingerprint; - continue; - } - - qDebug() << "Key:" << key.fingerprint; - for (const auto &userId : key.userIds) { - qDebug() << " userID:" << userId.email; - } - usableKeys.push_back(key); - } - return usableKeys; -} diff --git a/src/core/crypto.h b/src/core/crypto.h deleted file mode 100644 index 5f78705..0000000 --- a/src/core/crypto.h +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com> -// SPDX-License-Identifier: LGPL-2.0-or-later - -#pragma once - -#include "errors.h" -#include "mimetreeparser_core_export.h" -#include <QByteArray> -#include <QVariant> - -#include <QDateTime> -#include <functional> -#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 isUsable = false; - std::vector<UserId> userIds; -}; - -struct Error { - unsigned int error; - operator bool() const - { - return error != 0; - } -}; - -struct Signature { - QByteArray fingerprint; - Error status; - QDateTime creationTime; - enum Result { Ok, NotVerified, Expired, KeyNotFound, Invalid }; - Result result{NotVerified}; - bool isTrusted{false}; -}; - -struct VerificationResult { - std::vector<Signature> signatures; - Error error; -}; - -struct Recipient { - QByteArray keyId; - bool secretKeyAvailable{false}; -}; - -struct DecryptionResult { - std::vector<Recipient> recipients; - Error error; - enum Result { NoError, NotEncrypted, PassphraseError, NoSecretKeyError, DecryptionError }; - Result result{NoError}; -}; - -struct KeyListResult { - std::vector<Key> keys; - Error error; -}; - -struct ImportResult { - int considered; - int imported; - int unchanged; -}; - -MIMETREEPARSER_CORE_EXPORT std::vector<Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false); - -MIMETREEPARSER_CORE_EXPORT Expected<Error, QByteArray> exportPublicKey(const Key &key); - -MIMETREEPARSER_CORE_EXPORT ImportResult importKey(CryptoProtocol protocol, const QByteArray &certData); -MIMETREEPARSER_CORE_EXPORT ImportResult importKey(CryptoProtocol protocol, const Key &key); - -/** - * Sign the given content and returns the signing data and the algorithm used - * for integrity check in the "pgp-<algorithm>" format. - */ -MIMETREEPARSER_CORE_EXPORT Expected<Error, std::pair<QByteArray, QString>> sign(const QByteArray &content, const std::vector<Key> &signingKeys); -MIMETREEPARSER_CORE_EXPORT Expected<Error, QByteArray> -signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys); - -MIMETREEPARSER_CORE_EXPORT std::pair<DecryptionResult, VerificationResult> -decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata); -MIMETREEPARSER_CORE_EXPORT DecryptionResult decrypt(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata); -MIMETREEPARSER_CORE_EXPORT VerificationResult verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &outdata); -MIMETREEPARSER_CORE_EXPORT VerificationResult verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata); -}; - -Q_DECLARE_METATYPE(Crypto::Key); - -MIMETREEPARSER_CORE_EXPORT QDebug operator<<(QDebug d, const Crypto::Key &); -MIMETREEPARSER_CORE_EXPORT QDebug operator<<(QDebug d, const Crypto::Error &); diff --git a/src/core/cryptohelper.cpp b/src/core/cryptohelper.cpp index b1ed365..d9bc7b0 100644 --- a/src/core/cryptohelper.cpp +++ b/src/core/cryptohelper.cpp @@ -1,137 +1,145 @@ // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> // SPDX-FileCopyrightText: 2001,2002 the KPGP authors // SPDX-License-Identifier: GPL-2.0-or-later #include "cryptohelper.h" using namespace MimeTreeParser; PGPBlockType Block::determineType() const { const QByteArray data = text(); - if (data.startsWith("-----BEGIN PGP SIGNED")) { + if (data.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { + return NoPgpBlock; + } else if (data.startsWith("-----BEGIN PGP SIGNED")) { return ClearsignedBlock; } else if (data.startsWith("-----BEGIN PGP SIGNATURE")) { return SignatureBlock; } else if (data.startsWith("-----BEGIN PGP PUBLIC")) { return PublicKeyBlock; } else if (data.startsWith("-----BEGIN PGP PRIVATE") || data.startsWith("-----BEGIN PGP SECRET")) { return PrivateKeyBlock; } else if (data.startsWith("-----BEGIN PGP MESSAGE")) { if (data.startsWith("-----BEGIN PGP MESSAGE PART")) { return MultiPgpMessageBlock; } else { return PgpMessageBlock; } } else if (data.startsWith("-----BEGIN PGP ARMORED FILE")) { return PgpMessageBlock; } else if (data.startsWith("-----BEGIN PGP ")) { return UnknownBlock; } else { return NoPgpBlock; } } -QList<Block> MimeTreeParser::prepareMessageForDecryption(const QByteArray &msg) +QVector<Block> MimeTreeParser::prepareMessageForDecryption(const QByteArray &msg) { PGPBlockType pgpBlock = NoPgpBlock; - QList<Block> blocks; + QVector<Block> blocks; int start = -1; // start of the current PGP block int lastEnd = -1; // end of the last PGP block const int length = msg.length(); if (msg.isEmpty()) { return blocks; } + if (msg.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { + return blocks; + } if (msg.startsWith("-----BEGIN PGP ")) { start = 0; } else { start = msg.indexOf("\n-----BEGIN PGP ") + 1; if (start == 0) { blocks.append(Block(msg, NoPgpBlock)); return blocks; } } while (start != -1) { - int nextEnd, nextStart; + int nextEnd; + int nextStart; // is the PGP block a clearsigned block? if (!strncmp(msg.constData() + start + 15, "SIGNED", 6)) { pgpBlock = ClearsignedBlock; } else { pgpBlock = UnknownBlock; } nextEnd = msg.indexOf("\n-----END PGP ", start + 15); nextStart = msg.indexOf("\n-----BEGIN PGP ", start + 15); if (nextEnd == -1) { // Missing END PGP line if (lastEnd != -1) { blocks.append(Block(msg.mid(lastEnd + 1), UnknownBlock)); } else { blocks.append(Block(msg.mid(start), UnknownBlock)); } break; } if ((nextStart == -1) || (nextEnd < nextStart) || (pgpBlock == ClearsignedBlock)) { // most likely we found a PGP block (but we don't check if it's valid) // store the preceding non-PGP block if (start - lastEnd - 1 > 0) { blocks.append(Block(msg.mid(lastEnd + 1, start - lastEnd - 1), NoPgpBlock)); } lastEnd = msg.indexOf("\n", nextEnd + 14); if (lastEnd == -1) { if (start < length) { blocks.append(Block(msg.mid(start))); } break; } else { blocks.append(Block(msg.mid(start, lastEnd + 1 - start))); if ((nextStart != -1) && (nextEnd > nextStart)) { nextStart = msg.indexOf("\n-----BEGIN PGP ", lastEnd + 1); } } } start = nextStart; if (start == -1) { if (lastEnd + 1 < length) { // rest of mail is no PGP Block blocks.append(Block(msg.mid(lastEnd + 1), NoPgpBlock)); } break; } else { start++; // move start behind the '\n' } } return blocks; } +Block::Block() = default; + Block::Block(const QByteArray &m) : msg(m) { mType = determineType(); } Block::Block(const QByteArray &m, PGPBlockType t) : msg(m) , mType(t) { } QByteArray MimeTreeParser::Block::text() const { return msg; } PGPBlockType Block::type() const { return mType; } diff --git a/src/core/cryptohelper.h b/src/core/cryptohelper.h index 88d939f..b3ff4b8 100644 --- a/src/core/cryptohelper.h +++ b/src/core/cryptohelper.h @@ -1,46 +1,45 @@ // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> // SPDX-FileCopyrightText: 2001,2002 the KPGP authors // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include <QByteArray> -#include <QList> +#include <QVector> namespace MimeTreeParser { - enum PGPBlockType { UnknownBlock = -1, // BEGIN PGP ??? NoPgpBlock = 0, PgpMessageBlock = 1, // BEGIN PGP MESSAGE MultiPgpMessageBlock = 2, // BEGIN PGP MESSAGE, PART X[/Y] SignatureBlock = 3, // BEGIN PGP SIGNATURE ClearsignedBlock = 4, // BEGIN PGP SIGNED MESSAGE PublicKeyBlock = 5, // BEGIN PGP PUBLIC KEY BLOCK - PrivateKeyBlock = 6 // BEGIN PGP PRIVATE KEY BLOCK (PGP 2.x: ...SECRET...) + PrivateKeyBlock = 6, // BEGIN PGP PRIVATE KEY BLOCK (PGP 2.x: ...SECRET...) }; class Block { public: - explicit Block(const QByteArray &m); + Block(); + Block(const QByteArray &m); Block(const QByteArray &m, PGPBlockType t); - QByteArray text() const; - PGPBlockType type() const; - PGPBlockType determineType() const; + Q_REQUIRED_RESULT QByteArray text() const; + Q_REQUIRED_RESULT PGPBlockType type() const; + Q_REQUIRED_RESULT PGPBlockType determineType() const; QByteArray msg; - PGPBlockType mType; + PGPBlockType mType = UnknownBlock; }; /** Parses the given message and splits it into OpenPGP blocks and Non-OpenPGP blocks. */ -QList<Block> prepareMessageForDecryption(const QByteArray &msg); - +Q_REQUIRED_RESULT QVector<Block> prepareMessageForDecryption(const QByteArray &msg); } // namespace MimeTreeParser Q_DECLARE_TYPEINFO(MimeTreeParser::Block, Q_MOVABLE_TYPE); diff --git a/src/core/job/qgpgmejobexecutor.cpp b/src/core/job/qgpgmejobexecutor.cpp new file mode 100644 index 0000000..738c8f0 --- /dev/null +++ b/src/core/job/qgpgmejobexecutor.cpp @@ -0,0 +1,140 @@ +/* + SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "qgpgmejobexecutor.h" +#include "mimetreeparser_core_debug.h" + +#include <QGpgME/DecryptVerifyJob> +#include <QGpgME/ImportJob> +#include <QGpgME/VerifyDetachedJob> +#include <QGpgME/VerifyOpaqueJob> + +#include <QEventLoop> + +#include <cassert> + +using namespace GpgME; +using namespace MimeTreeParser; + +QGpgMEJobExecutor::QGpgMEJobExecutor(QObject *parent) + : QObject(parent) + , mEventLoop(new QEventLoop(this)) +{ + setObjectName(QStringLiteral("KleoJobExecutor")); +} + +GpgME::VerificationResult QGpgMEJobExecutor::exec(QGpgME::VerifyDetachedJob *job, const QByteArray &signature, const QByteArray &signedData) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Starting detached verification job"; + connect(job, &QGpgME::VerifyDetachedJob::result, this, qOverload<const GpgME::VerificationResult &>(&QGpgMEJobExecutor::verificationResult)); + GpgME::Error err = job->start(signature, signedData); + if (err) { + return VerificationResult(err); + } + mEventLoop->exec(QEventLoop::ExcludeUserInputEvents); + return mVerificationResult; +} + +GpgME::VerificationResult QGpgMEJobExecutor::exec(QGpgME::VerifyOpaqueJob *job, const QByteArray &signedData, QByteArray &plainText) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Starting opaque verification job"; + connect(job, + &QGpgME::VerifyOpaqueJob::result, + this, + qOverload<const GpgME::VerificationResult &, const QByteArray &>(&QGpgMEJobExecutor::verificationResult)); + GpgME::Error err = job->start(signedData); + if (err) { + plainText.clear(); + return VerificationResult(err); + } + mEventLoop->exec(QEventLoop::ExcludeUserInputEvents); + plainText = mData; + return mVerificationResult; +} + +std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> +QGpgMEJobExecutor::exec(QGpgME::DecryptVerifyJob *job, const QByteArray &cipherText, QByteArray &plainText) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Starting decryption job"; + connect(job, &QGpgME::DecryptVerifyJob::result, this, &QGpgMEJobExecutor::decryptResult); + GpgME::Error err = job->start(cipherText); + if (err) { + plainText.clear(); + return std::make_pair(DecryptionResult(err), VerificationResult(err)); + } + mEventLoop->exec(QEventLoop::ExcludeUserInputEvents); + plainText = mData; + return std::make_pair(mDecryptResult, mVerificationResult); +} + +GpgME::ImportResult QGpgMEJobExecutor::exec(QGpgME::ImportJob *job, const QByteArray &certData) +{ + connect(job, &QGpgME::AbstractImportJob::result, this, &QGpgMEJobExecutor::importResult); + GpgME::Error err = job->start(certData); + if (err) { + return ImportResult(err); + } + mEventLoop->exec(QEventLoop::ExcludeUserInputEvents); + return mImportResult; +} + +Error QGpgMEJobExecutor::auditLogError() const +{ + return mAuditLogError; +} + +void QGpgMEJobExecutor::verificationResult(const GpgME::VerificationResult &result) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Detached verification job finished"; + auto job = qobject_cast<QGpgME::Job *>(sender()); + assert(job); + mVerificationResult = result; + mAuditLogError = job->auditLogError(); + mAuditLog = job->auditLogAsHtml(); + mEventLoop->quit(); +} + +void QGpgMEJobExecutor::verificationResult(const GpgME::VerificationResult &result, const QByteArray &plainText) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Opaque verification job finished"; + auto job = qobject_cast<QGpgME::Job *>(sender()); + assert(job); + mVerificationResult = result; + mData = plainText; + mAuditLogError = job->auditLogError(); + mAuditLog = job->auditLogAsHtml(); + mEventLoop->quit(); +} + +void QGpgMEJobExecutor::decryptResult(const GpgME::DecryptionResult &decryptionresult, + const GpgME::VerificationResult &verificationresult, + const QByteArray &plainText) +{ + qCDebug(MIMETREEPARSER_CORE_LOG) << "Decryption job finished"; + auto job = qobject_cast<QGpgME::Job *>(sender()); + assert(job); + mVerificationResult = verificationresult; + mDecryptResult = decryptionresult; + mData = plainText; + mAuditLogError = job->auditLogError(); + mAuditLog = job->auditLogAsHtml(); + mEventLoop->quit(); +} + +void QGpgMEJobExecutor::importResult(const GpgME::ImportResult &result) +{ + auto job = qobject_cast<QGpgME::Job *>(sender()); + assert(job); + mImportResult = result; + mAuditLogError = job->auditLogError(); + mAuditLog = job->auditLogAsHtml(); + mEventLoop->quit(); +} + +QString QGpgMEJobExecutor::auditLogAsHtml() const +{ + return mAuditLog; +} diff --git a/src/core/job/qgpgmejobexecutor.h b/src/core/job/qgpgmejobexecutor.h new file mode 100644 index 0000000..368c691 --- /dev/null +++ b/src/core/job/qgpgmejobexecutor.h @@ -0,0 +1,61 @@ +/* + SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org> + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include <gpgme++/decryptionresult.h> +#include <gpgme++/importresult.h> +#include <gpgme++/verificationresult.h> + +#include <QObject> + +#include <utility> + +class QEventLoop; + +namespace QGpgME +{ +class DecryptVerifyJob; +class ImportJob; +class VerifyDetachedJob; +class VerifyOpaqueJob; +} + +namespace MimeTreeParser +{ +/** + Helper class for synchronous execution of Kleo crypto jobs. +*/ +class QGpgMEJobExecutor : public QObject +{ + Q_OBJECT +public: + explicit QGpgMEJobExecutor(QObject *parent = nullptr); + + GpgME::VerificationResult exec(QGpgME::VerifyDetachedJob *job, const QByteArray &signature, const QByteArray &signedData); + GpgME::VerificationResult exec(QGpgME::VerifyOpaqueJob *job, const QByteArray &signedData, QByteArray &plainText); + std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> exec(QGpgME::DecryptVerifyJob *job, const QByteArray &cipherText, QByteArray &plainText); + GpgME::ImportResult exec(QGpgME::ImportJob *job, const QByteArray &certData); + + Q_REQUIRED_RESULT GpgME::Error auditLogError() const; + Q_REQUIRED_RESULT QString auditLogAsHtml() const; + +private Q_SLOTS: + void verificationResult(const GpgME::VerificationResult &result); + void verificationResult(const GpgME::VerificationResult &result, const QByteArray &plainText); + void decryptResult(const GpgME::DecryptionResult &decryptionresult, const GpgME::VerificationResult &verificationresult, const QByteArray &plainText); + void importResult(const GpgME::ImportResult &result); + +private: + QEventLoop *const mEventLoop; + GpgME::VerificationResult mVerificationResult; + GpgME::DecryptionResult mDecryptResult; + GpgME::ImportResult mImportResult; + QByteArray mData; + GpgME::Error mAuditLogError; + QString mAuditLog; +}; +} diff --git a/src/core/mailcrypto.cpp b/src/core/mailcrypto.cpp deleted file mode 100644 index 6a3e32b..0000000 --- a/src/core/mailcrypto.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com> -// SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com -// SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org> -// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com> -// SPDX-License-Identifier: LGPL-2.0-or-later -// -#include "mailcrypto.h" - -#include <QDebug> - -#include <future> -#include <utility> - -using namespace MailCrypto; -using namespace Crypto; - -static QByteArray canonicalizeContent(KMime::Content *content) -{ - // if (d->format & Kleo::InlineOpenPGPFormat) { - // return d->content->body(); - // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) { - - // replace "From " and "--" at the beginning of lines - // with encoded versions according to RfC 3156, 3 - // Note: If any line begins with the string "From ", it is strongly - // suggested that either the Quoted-Printable or Base64 MIME encoding - // be applied. - const auto encoding = content->contentTransferEncoding()->encoding(); - if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !content->contentType(false)) { - QByteArray body = content->encodedBody(); - bool changed = false; - QList<QByteArray> search; - QList<QByteArray> replacements; - - search << "From " - << "from " - << "-"; - replacements << "From=20" - << "from=20" - << "=2D"; - - if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) { - for (int i = 0; i < search.size(); ++i) { - const auto pos = body.indexOf(search[i]); - if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) { - changed = true; - break; - } - } - if (changed) { - content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr); - content->assemble(); - body = content->encodedBody(); - } - } - - for (int i = 0; i < search.size(); ++i) { - const auto pos = body.indexOf(search[i]); - if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) { - changed = true; - body.replace(pos, search[i].size(), replacements[i]); - } - } - - if (changed) { - qDebug() << "Content changed"; - content->setBody(body); - content->contentTransferEncoding()->setDecoded(false); - } - } - - return KMime::LFtoCRLF(content->encodedContent()); - // } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged - // return content->encodedContent(); - // } -} - -/** - * Create a message part like this (according to RFC 3156 Section 4): - * - * - multipart/encrypted - * - application/pgp-encrypted (version information) - * - application/octet-stream (given encrypted data) - * - * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions. - */ -std::unique_ptr<KMime::Content> createEncryptedPart(QByteArray encryptedData) -{ - auto result = std::unique_ptr<KMime::Content>(new KMime::Content); - - result->contentType()->setMimeType("multipart/encrypted"); - result->contentType()->setBoundary(KMime::multiPartBoundary()); - result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted")); - - KMime::Content *controlInformation = new KMime::Content; - { - controlInformation->contentType()->setMimeType("application/pgp-encrypted"); - controlInformation->contentDescription()->from7BitString("PGP/MIME version identification"); - controlInformation->setBody("Version: 1"); - - result->addContent(controlInformation); - } - - KMime::Content *encryptedPartPart = new KMime::Content; - { - const QString filename = QStringLiteral("msg.asc"); - - encryptedPartPart->contentType()->setMimeType("application/octet-stream"); - encryptedPartPart->contentType()->setName(filename, "utf-8"); - - encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message"); - - encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline); - encryptedPartPart->contentDisposition()->setFilename(filename); - - encryptedPartPart->setBody(encryptedData); - - result->addContent(encryptedPartPart); - } - - return result; -} - -/** - * Create a message part like this (according to RFC 3156 Section 5): - * - * + `multipart/signed` - * - whatever the given original `message` is (should be canonicalized) - * - `application/octet-stream` (the given `signature`) - * - * The signature can be generated by the `sign` function. - */ -std::unique_ptr<KMime::Content> createSignedPart(std::unique_ptr<KMime::Content> message, const QByteArray &signature, const QString &micAlg) -{ - auto result = std::unique_ptr<KMime::Content>(new KMime::Content); - - result->contentType()->setMimeType("multipart/signed"); - result->contentType()->setBoundary(KMime::multiPartBoundary()); - result->contentType()->setParameter(QStringLiteral("micalg"), micAlg); - result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature")); - - result->addContent(message.release()); - - KMime::Content *signedPartPart = new KMime::Content; - signedPartPart->contentType()->setMimeType("application/pgp-signature"); - signedPartPart->contentType()->setName(QStringLiteral("signature.asc"), "utf-8"); - signedPartPart->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment); - signedPartPart->contentDisposition(true)->setFilename(QStringLiteral("signature.asc")); - signedPartPart->contentDescription()->from7BitString("OpenPGP digital signature"); - signedPartPart->setBody(signature); - result->addContent(signedPartPart); - - return result; -} - -Expected<Error, std::unique_ptr<KMime::Content>> -MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys) -{ - if (!encryptionKeys.empty()) { - auto encryptionResult = signAndEncrypt(canonicalizeContent(content.get()), encryptionKeys, signingKeys); - if (!encryptionResult) { - return makeUnexpected(Error{encryptionResult.error()}); - } - return createEncryptedPart(encryptionResult.value()); - } else if (!signingKeys.empty()) { - auto signingResult = sign(canonicalizeContent(content.get()), signingKeys); - if (!signingResult) { - return makeUnexpected(Error{signingResult.error()}); - } - QByteArray signingData; - QString micAlg; - std::tie(signingData, micAlg) = signingResult.value(); - return createSignedPart(std::move(content), signingData, micAlg); - } else { - qWarning() << "Processing cryptography, but neither signing nor encrypting"; - Q_ASSERT(false); - return content; - } -} diff --git a/src/core/mailcrypto.h b/src/core/mailcrypto.h deleted file mode 100644 index ffb9b32..0000000 --- a/src/core/mailcrypto.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com> -// SPDX-License-Identifier: LGPL-2.0-or-later - -#pragma once - -#include <KMime/Content> - -#include <memory> - -#include "crypto.h" -#include "errors.h" - -namespace MailCrypto -{ - -Expected<Crypto::Error, std::unique_ptr<KMime::Content>> -processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Crypto::Key> &signingKeys, const std::vector<Crypto::Key> &encryptionKeys); - -}; diff --git a/src/core/messagepart.cpp b/src/core/messagepart.cpp index ab36591..6f7b865 100644 --- a/src/core/messagepart.cpp +++ b/src/core/messagepart.cpp @@ -1,963 +1,1095 @@ // 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_core_debug.h" #include "objecttreeparser.h" +#include "job/qgpgmejobexecutor.h" #include "utils.h" #include <KLocalizedString> #include <KMime/Content> +#include <Libkleo/Compliance> +#include <Libkleo/KeyCache> + +#include <QGpgME/DN> +#include <QGpgME/DecryptVerifyJob> +#include <QGpgME/Protocol> +#include <QGpgME/VerifyDetachedJob> +#include <QGpgME/VerifyOpaqueJob> + +#include <gpgme++/key.h> +#include <gpgme++/keylistresult.h> +#include <gpgme.h> + #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_CORE_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; + const auto cryptProto = QGpgME::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_CORE_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_CORE_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) +CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto) : MessagePart(otp, QString(), node) - , mProtocol(cryptoProto) + , mCryptoProto(cryptoProto) { if (!mNode) { qCWarning(MIMETREEPARSER_CORE_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, + const QGpgME::Protocol *cryptoProto, KMime::Content *node, KMime::Content *signedData, bool parseAfterDecryption) : MessagePart(otp, {}, node) , mParseAfterDecryption(parseAfterDecryption) - , mProtocol(cryptoProto) + , mCryptoProto(cryptoProto) , mSignedData(signedData) { mMetaData.isSigned = true; mMetaData.isGoodSignature = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "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) +const QGpgME::Protocol *SignedMessagePart::cryptoProto() const { - 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_CORE_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint; - } - if (keys.empty()) { - // Should not happen at this point - qCWarning(MIMETREEPARSER_CORE_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('>'); - } - } - } + return mCryptoProto; } void SignedMessagePart::startVerification() { if (!mSignedData) { return; } mMetaData.isSigned = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "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); + const auto job = mCryptoProto->verifyDetachedJob(); + setVerificationResult(job->exec(signature, signedData), signedData); setText(codec->toUnicode(KMime::CRLFtoLF(signedData))); } else { QByteArray outdata; - setVerificationResult(Crypto::verifyOpaqueSignature(mProtocol, mSignedData->decodedContent(), outdata), outdata); + const auto job = mCryptoProto->verifyOpaqueJob(); + setVerificationResult(job->exec(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) +static int signatureToStatus(const GpgME::Signature &sig) +{ + switch (sig.status().code()) { + case GPG_ERR_NO_ERROR: + return GPGME_SIG_STAT_GOOD; + case GPG_ERR_BAD_SIGNATURE: + return GPGME_SIG_STAT_BAD; + case GPG_ERR_NO_PUBKEY: + return GPGME_SIG_STAT_NOKEY; + case GPG_ERR_NO_DATA: + return GPGME_SIG_STAT_NOSIG; + case GPG_ERR_SIG_EXPIRED: + return GPGME_SIG_STAT_GOOD_EXP; + case GPG_ERR_KEY_EXPIRED: + return GPGME_SIG_STAT_GOOD_EXPKEY; + default: + return GPGME_SIG_STAT_ERROR; + } +} + +void SignedMessagePart::sigStatusToMetaData() +{ + GpgME::Key key; + if (partMetaData()->isSigned) { + GpgME::Signature signature = mSignatures.front(); + mMetaData.status_code = signatureToStatus(signature); + mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD; + // save extended signature status flags + mMetaData.sigSummary = signature.summary(); + + if (partMetaData()->isGoodSignature && !key.keyID()) { + // Search for the key by its fingerprint so that we can check for + // trust etc. + key = Kleo::KeyCache::instance()->findByFingerprint(signature.fingerprint()); + if (key.isNull() && signature.fingerprint()) { + // try to find a subkey that was used for signing; + // assumes that the key ID is the last 16 characters of the fingerprint + const auto fpr = std::string_view{signature.fingerprint()}; + const auto keyID = std::string{fpr, fpr.size() - 16, 16}; + const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID}); + if (subkeys.size() > 0) { + key = subkeys[0].parent(); + } + } + if (key.isNull()) { + qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint(); + } + } + + if (key.keyID()) { + partMetaData()->keyId = key.keyID(); + } + if (partMetaData()->keyId.isEmpty()) { + partMetaData()->keyId = signature.fingerprint(); + } + partMetaData()->keyTrust = signature.validity(); + if (key.numUserIDs() > 0 && key.userID(0).id()) { + partMetaData()->signer = prettifyDN(key.userID(0).id()); + } + for (const auto &uid : key.userIDs()) { + // The following if /should/ always result in TRUE but we + // won't trust implicitly the plugin that gave us these data. + if (uid.email()) { + QString email = QString::fromUtf8(uid.email()); + if (!email.isEmpty()) { + partMetaData()->signerMailAddresses.append(email); + } + } + } + + if (signature.creationTime()) { + partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime()); + } else { + partMetaData()->creationTime = QDateTime(); + } + if (partMetaData()->signer.isEmpty()) { + if (key.numUserIDs() > 0 && key.userID(0).name()) { + partMetaData()->signer = prettifyDN(key.userID(0).name()); + } + if (!partMetaData()->signerMailAddresses.empty()) { + if (partMetaData()->signer.isEmpty()) { + partMetaData()->signer = partMetaData()->signerMailAddresses.front(); + } else { + partMetaData()->signer += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); + } + } + } + if (Kleo::DeVSCompliance::isCompliant()) { + partMetaData()->isCompliant = signature.isDeVs(); + partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs()); + } else { + partMetaData()->isCompliant = true; + } + } +} + +void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData) { - const auto signatures = result.signatures; + mSignatures = result.signatures(); // FIXME // mMetaData.auditLogError = result.error; - if (!signatures.empty()) { - mMetaData.isSigned = true; - sigStatusToMetaData(mMetaData, signatures.front()); + mMetaData.isSigned = !mSignatures.empty(); + if (mMetaData.isSigned) { + sigStatusToMetaData(); 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, + const QGpgME::Protocol *cryptoProto, KMime::Content *node, KMime::Content *encryptedNode, bool parseAfterDecryption) : MessagePart(otp, text, node) , mParseAfterDecryption(parseAfterDecryption) - , mProtocol(cryptoProto) + , mPassphraseError(false) + , mNoSecKey(false) + , mDecryptMessage(false) + , mCryptoProto(cryptoProto) , mEncryptedNode(encryptedNode) { mMetaData.isSigned = false; mMetaData.isGoodSignature = false; mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In."); } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { mMetaData.isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return mMetaData.isEncrypted; } +const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const +{ + return mCryptoProto; +} + +void EncryptedMessagePart::setDecryptMessage(bool decrypt) +{ + mDecryptMessage = decrypt; +} + +bool EncryptedMessagePart::decryptMessage() const +{ + return mDecryptMessage; +} + bool EncryptedMessagePart::isDecryptable() const { return mMetaData.isDecryptable; } +bool EncryptedMessagePart::isNoSecKey() const +{ + return mNoSecKey; +} + +bool EncryptedMessagePart::passphraseError() const +{ + return mPassphraseError; +} + 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; + auto job = mCryptoProto->decryptVerifyJob(); + const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText); + auto decryptResult = p.first; + auto verifyResult = p.second; + 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) { + partMetaData()->isSigned = verifyResult.signatures().size() > 0; + + if (partMetaData()->isSigned) { // 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)); + auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, 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; - } + mDecryptRecipients.clear(); + bool cannotDecrypt = false; + bool bDecryptionOk = !decryptResult.error(); - if (mMetaData.isEncrypted) { - mMetaData.keyId = [&] { - for (const auto &recipient : std::as_const(decryptResult.recipients)) { - if (recipient.secretKeyAvailable) { - return recipient.keyId; - } + for (const auto &recipient : decryptResult.recipients()) { + if (!recipient.status()) { + bDecryptionOk = true; + } + GpgME::Key key; + key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID()); + if (key.isNull()) { + auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()}); + if (ret.size() == 1) { + key = ret.front().parent(); } - return QByteArray{}; - }(); + if (key.isNull()) { + qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID(); + } + } + mDecryptRecipients.emplace_back(recipient, key); } - if (!decryptResult.error) { + if (!bDecryptionOk && partMetaData()->isSigned) { + // Only a signed part + partMetaData()->isEncrypted = false; + bDecryptionOk = true; mDecryptedData = plainText; - setText(decoded); } else { - mMetaData.isEncrypted = decryptResult.result != Crypto::DecryptionResult::NotEncrypted; - qWarning() << "Failed to decrypt : " << decryptResult.error; + mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; + mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA; - if (decryptResult.result == Crypto::DecryptionResult::NoSecretKeyError) { - mError = NoKeyError; - mMetaData.errorText = i18ndc("mimetreeparser", "@info", "Could not decrypt the data: no key found for recipients."); - } else if (decryptResult.result == Crypto::DecryptionResult::PassphraseError) { - mError = PassphraseError; + if (decryptResult.error().isCanceled()) { + setDecryptMessage(false); + } + + partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); + if (Kleo::DeVSCompliance::isCompliant()) { + partMetaData()->isCompliant = decryptResult.isDeVs(); + partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs()); } else { - mError = UnknownError; - mMetaData.errorText = i18ndc("mimetreeparser", "@info", "Could not decrypt the data."); + partMetaData()->isCompliant = true; + } + if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { + partMetaData()->keyId = decryptResult.recipient(0).keyID(); + } + + if (bDecryptionOk) { + mDecryptedData = plainText; + } else { + mNoSecKey = true; + const auto decryRecipients = decryptResult.recipients(); + for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) { + mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); + } + if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly + mPassphraseError = true; + } } - setText(QString::fromUtf8(mDecryptedData.constData())); - return false; } - return true; + if (!bDecryptionOk) { + QString cryptPlugLibName; + mError = UnknownError; + if (mCryptoProto) { + cryptPlugLibName = mCryptoProto->name(); + } + + if (mNoSecKey) { + mError = NoKeyError; + } + + if (mPassphraseError) { + mError = PassphraseError; + } + + if (!mCryptoProto) { + partMetaData()->errorText = i18n("No appropriate crypto plug-in was found."); + } else if (cannotDecrypt) { + partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); + } else if (!passphraseError()) { + partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("<br />") + + i18n("Error: %1", partMetaData()->errorText); + } + } + return bDecryptionOk; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { mMetaData.isEncrypted = true; mMetaData.isDecryptable = decrypt(*data); if (mParseAfterDecryption && !mMetaData.isSigned) { parseInternal(mDecryptedData); + } else { + setText(QString::fromUtf8(mDecryptedData.constData())); } } void EncryptedMessagePart::startDecryption() { if (mEncryptedNode) { startDecryption(mEncryptedNode); } else { startDecryption(mNode); } } +std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const +{ + return mDecryptRecipients; +} + 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(); } + + 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_CORE_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) { } #include "moc_messagepart.cpp" diff --git a/src/core/messagepart.h b/src/core/messagepart.h index 154bf3d..6deb273 100644 --- a/src/core/messagepart.h +++ b/src/core/messagepart.h @@ -1,342 +1,371 @@ // SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com> // SPDX-License-Identifier: LGPL-2.0-or-later #pragma once -#include "crypto.h" #include "mimetreeparser_core_export.h" #include "partmetadata.h" +#include <gpgme++/decryptionresult.h> +#include <gpgme++/importresult.h> +#include <gpgme++/verificationresult.h> + #include <KMime/Message> +#include <QMap> #include <QSharedPointer> #include <QString> namespace KMime { class Content; } +namespace QGpgME +{ +class Protocol; +} + namespace MimeTreeParser { /** Flags for the encryption state. */ typedef enum { KMMsgEncryptionStateUnknown, KMMsgNotEncrypted, KMMsgPartiallyEncrypted, KMMsgFullyEncrypted, KMMsgEncryptionProblematic } KMMsgEncryptionState; /** Flags for the signature state. */ typedef enum { KMMsgSignatureStateUnknown, KMMsgNotSigned, KMMsgPartiallySigned, KMMsgFullySigned, KMMsgSignatureProblematic } KMMsgSignatureState; class ObjectTreeParser; class MultiPartAlternativeBodyPartFormatter; class SignedMessagePart; class EncryptedMessagePart; -using Crypto::CryptoProtocol; -using Crypto::CryptoProtocol::CMS; -using Crypto::CryptoProtocol::OpenPGP; -using Crypto::CryptoProtocol::UnknownProtocol; - class MIMETREEPARSER_CORE_EXPORT MessagePart : public QObject { Q_OBJECT Q_PROPERTY(bool attachment READ isAttachment CONSTANT) Q_PROPERTY(bool root READ isRoot CONSTANT) Q_PROPERTY(bool isHtml READ isHtml CONSTANT) Q_PROPERTY(QString plaintextContent READ plaintextContent CONSTANT) Q_PROPERTY(QString htmlContent READ htmlContent CONSTANT) public: enum Disposition { Inline, Attachment, Invalid }; using Ptr = QSharedPointer<MessagePart>; using List = QVector<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<MessagePart::Ptr> &subParts() const; bool hasSubParts() const; KMime::Content *node() const; virtual KMMsgSignatureState signatureState() const; virtual KMMsgEncryptionState encryptionState() const; QVector<SignedMessagePart *> signatures() const; QVector<EncryptedMessagePart *> encryptions() const; /** * Retrieve the header @header in this part or any parent parent. * * Useful for MemoryHole support. */ KMime::Headers::Base *header(const char *header) 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<KMime::Content *> mNodesToDelete; Error mError; private: QVector<MessagePart::Ptr> mBlocks; bool mRoot; }; class MimeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer<MimeMessagePart> 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; }; class MessagePartList : public MessagePart { Q_OBJECT public: typedef QSharedPointer<MessagePartList> Ptr; MessagePartList(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~MessagePartList() = default; QString text() const Q_DECL_OVERRIDE; QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; }; class TextMessagePart : public MessagePartList { Q_OBJECT public: typedef QSharedPointer<TextMessagePart> Ptr; TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~TextMessagePart() = default; KMMsgSignatureState signatureState() const Q_DECL_OVERRIDE; KMMsgEncryptionState encryptionState() const Q_DECL_OVERRIDE; private: void parseContent(); KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; friend class ObjectTreeParser; }; class AttachmentMessagePart : public TextMessagePart { Q_OBJECT public: typedef QSharedPointer<AttachmentMessagePart> Ptr; AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~AttachmentMessagePart() = default; virtual bool isAttachment() const Q_DECL_OVERRIDE { return true; } }; class HtmlMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer<HtmlMessagePart> Ptr; HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node); virtual ~HtmlMessagePart() = default; bool isHtml() const Q_DECL_OVERRIDE { return true; }; }; class AlternativeMessagePart : public MessagePart { Q_OBJECT public: enum HtmlMode { Normal, ///< A normal plaintext message, non-multipart Html, ///< A HTML message, non-multipart MultipartPlain, ///< A multipart/alternative message, the plain text part is currently displayed MultipartHtml, ///< A multipart/altervative message, the HTML part is currently displayed MultipartIcal ///< A multipart/altervative message, the ICal part is currently displayed }; typedef QSharedPointer<AlternativeMessagePart> 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<HtmlMode> availableModes(); private: QMap<HtmlMode, MessagePart::Ptr> mChildParts; friend class ObjectTreeParser; friend class MultiPartAlternativeBodyPartFormatter; }; class CertMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer<CertMessagePart> Ptr; - CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const CryptoProtocol cryptoProto); + CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto); virtual ~CertMessagePart(); QString text() const Q_DECL_OVERRIDE; - void import(); private: - const CryptoProtocol mProtocol; + const QGpgME::Protocol *mCryptoProto; + GpgME::ImportResult mInportResult; }; class EncapsulatedRfc822MessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer<EncapsulatedRfc822MessagePart> Ptr; EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); virtual ~EncapsulatedRfc822MessagePart() = default; QString text() const Q_DECL_OVERRIDE; QString from() const; QDateTime date() const; private: const KMime::Message::Ptr mMessage; }; class EncryptedMessagePart : public MessagePart { Q_OBJECT - Q_PROPERTY(bool isEncrypted READ isEncrypted CONSTANT) + Q_PROPERTY(bool decryptMessage READ decryptMessage WRITE setDecryptMessage) + Q_PROPERTY(bool isEncrypted READ isEncrypted) + Q_PROPERTY(bool isNoSecKey READ isNoSecKey) + Q_PROPERTY(bool passphraseError READ passphraseError) public: typedef QSharedPointer<EncryptedMessagePart> Ptr; EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, - const CryptoProtocol protocol, + const QGpgME::Protocol *protocol, KMime::Content *node, KMime::Content *encryptedNode = nullptr, bool parseAfterDecryption = true); virtual ~EncryptedMessagePart() = default; QString text() const Q_DECL_OVERRIDE; + void setDecryptMessage(bool decrypt); + Q_REQUIRED_RESULT bool decryptMessage() const; + void setIsEncrypted(bool encrypted); - bool isEncrypted() const; + Q_REQUIRED_RESULT bool isEncrypted() const; - bool isDecryptable() const; + Q_REQUIRED_RESULT bool isDecryptable() const; + + Q_REQUIRED_RESULT bool isNoSecKey() const; + Q_REQUIRED_RESULT bool passphraseError() const; void startDecryption(KMime::Content *data); void startDecryption(); QByteArray mDecryptedData; - QString plaintextContent() const Q_DECL_OVERRIDE; - QString htmlContent() const Q_DECL_OVERRIDE; + QString plaintextContent() const override; + QString htmlContent() const override; + + const QGpgME::Protocol *cryptoProto() const; + + std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> decryptRecipients() const; private: bool decrypt(KMime::Content &data); bool mParseAfterDecryption{true}; protected: - const CryptoProtocol mProtocol; + bool mPassphraseError; + bool mNoSecKey; + bool mDecryptMessage; + const QGpgME::Protocol *mCryptoProto; QByteArray mVerifiedText; + std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> mDecryptRecipients; KMime::Content *mEncryptedNode; }; class SignedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isSigned READ isSigned CONSTANT) public: typedef QSharedPointer<SignedMessagePart> Ptr; - SignedMessagePart(ObjectTreeParser *otp, const CryptoProtocol protocol, KMime::Content *node, KMime::Content *signedData, bool parseAfterDecryption = true); + SignedMessagePart(ObjectTreeParser *otp, + const QGpgME::Protocol *protocol, + KMime::Content *node, + KMime::Content *signedData, + bool parseAfterDecryption = true); virtual ~SignedMessagePart(); void setIsSigned(bool isSigned); bool isSigned() const; void startVerification(); QString plaintextContent() const Q_DECL_OVERRIDE; QString htmlContent() const Q_DECL_OVERRIDE; + const QGpgME::Protocol *cryptoProto() const; + private: - void setVerificationResult(const Crypto::VerificationResult &result, const QByteArray &signedData); + void sigStatusToMetaData(); + void setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData); bool mParseAfterDecryption{true}; protected: - CryptoProtocol mProtocol; + const QGpgME::Protocol *mCryptoProto; KMime::Content *mSignedData; + std::vector<GpgME::Signature> mSignatures; friend EncryptedMessagePart; }; class HeadersPart : public MessagePart { Q_OBJECT public: typedef QSharedPointer<HeadersPart> Ptr; HeadersPart(ObjectTreeParser *otp, KMime::Content *node); virtual ~HeadersPart() = default; }; } diff --git a/src/core/objecttreeparser.cpp b/src/core/objecttreeparser.cpp index 572652e..fd36c2a 100644 --- a/src/core/objecttreeparser.cpp +++ b/src/core/objecttreeparser.cpp @@ -1,503 +1,488 @@ // This file is part of KMail, the KDE mail client. // SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> // SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net // SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net> // SPDX-FileCopyrightText: 2015 Sandro Knauß <sknauss@kde.org> // SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsystems.com> // SPDX-License-Identifier: GPL-2.0-or-later #include "objecttreeparser.h" #include "bodypartformatterbasefactory.h" #include "bodypartformatter.h" #include <KMime/Message> #include <KCharsets> #include <QByteArray> #include <QDebug> #include <QMimeDatabase> #include <QRegularExpression> #include <QTextCodec> #include <QTextStream> #include <QUrl> using namespace MimeTreeParser; /* * Collect message parts bottom up. * Filter to avoid evaluating a subtree. * Select parts to include it in the result set. Selecting a part in a branch will keep any parent parts from being selected. */ static QVector<MessagePart::Ptr> collect(MessagePart::Ptr start, const std::function<bool(const MessagePart::Ptr &)> &evaluateSubtree, const std::function<bool(const MessagePart::Ptr &)> &select) { auto ptr = start.dynamicCast<MessagePart>(); Q_ASSERT(ptr); MessagePart::List list; if (evaluateSubtree(ptr)) { for (const auto &p : ptr->subParts()) { list << ::collect(p, evaluateSubtree, select); } } // Don't consider this part if we already selected a subpart if (list.isEmpty()) { if (select(ptr)) { list << start; } } return list; } QString ObjectTreeParser::plainTextContent() { QString content; if (mParsedPart) { auto plainParts = ::collect( mParsedPart, [](const MessagePart::Ptr &) { return true; }, [](const MessagePart::Ptr &part) { if (part->isAttachment()) { return false; } if (dynamic_cast<MimeTreeParser::TextMessagePart *>(part.data())) { return true; } if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) { return true; } return false; }); for (const auto &part : plainParts) { content += part->text(); } } return content; } QString ObjectTreeParser::htmlContent() { QString content; if (mParsedPart) { MessagePart::List contentParts = ::collect( mParsedPart, [](const MessagePart::Ptr &) { return true; }, [](const MessagePart::Ptr &part) { if (dynamic_cast<MimeTreeParser::HtmlMessagePart *>(part.data())) { return true; } if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) { return true; } return false; }); for (const auto &part : contentParts) { if (auto p = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) { content += p->htmlContent(); } else { content += part->text(); } } } return content; } static void print(QTextStream &stream, KMime::Content *node, const QString prefix = {}) { QByteArray mediaType("text"); QByteArray subType("plain"); if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) { mediaType = node->contentType()->mediaType(); subType = node->contentType()->subType(); } stream << prefix << "! " << mediaType << subType << " isAttachment: " << KMime::isAttachment(node) << "\n"; const auto contents = node->contents(); for (const auto nodeContent : contents) { print(stream, nodeContent, prefix + QLatin1String(" ")); } } static void print(QTextStream &stream, const MessagePart &messagePart, const QByteArray pre = {}) { stream << pre << "# " << messagePart.metaObject()->className() << " isAttachment: " << messagePart.isAttachment() << "\n"; const auto subParts = messagePart.subParts(); for (const auto &subPart : subParts) { print(stream, *subPart, pre + " "); } } QString ObjectTreeParser::structureAsString() const { QString string; QTextStream stream{&string}; if (mTopLevelContent) { ::print(stream, mTopLevelContent); } if (mParsedPart) { ::print(stream, *mParsedPart); } return string; } void ObjectTreeParser::print() { qInfo().noquote() << structureAsString(); } static KMime::Content *find(KMime::Content *node, const std::function<bool(KMime::Content *)> &select) { QByteArray mediaType("text"); QByteArray subType("plain"); if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) { mediaType = node->contentType()->mediaType(); subType = node->contentType()->subType(); } if (select(node)) { return node; } const auto contents = node->contents(); for (const auto nodeContent : contents) { if (const auto content = find(nodeContent, select)) { return content; } } return nullptr; } KMime::Content *ObjectTreeParser::find(const std::function<bool(KMime::Content *)> &select) { return ::find(mTopLevelContent, select); } MessagePart::List ObjectTreeParser::collectContentParts() { return collectContentParts(mParsedPart); } MessagePart::List ObjectTreeParser::collectContentParts(MessagePart::Ptr start) { return ::collect( start, [start](const MessagePart::Ptr &part) { // Ignore the top-level if (start.data() == part.data()) { return true; } if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) { return false; } return true; }, [start](const MessagePart::Ptr &part) { if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(part.data())) { return attachment->mimeType() == "text/calendar"; } else if (const auto text = dynamic_cast<MimeTreeParser::TextMessagePart *>(part.data())) { auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(text->parentPart()); if (enc && enc->error()) { return false; } return true; } else if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(part.data())) { return true; } else if (dynamic_cast<MimeTreeParser::HtmlMessagePart *>(part.data())) { // Don't if we have an alternative part as parent return true; } else if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part.data())) { if (start.data() == part.data()) { return false; } return true; } else if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(part.data())) { if (enc->error()) { return true; } // If we have a textpart with encrypted and unencrypted subparts we want to return the textpart if (dynamic_cast<MimeTreeParser::TextMessagePart *>(enc->parentPart())) { return false; } } else if (const auto sig = dynamic_cast<MimeTreeParser::SignedMessagePart *>(part.data())) { // Signatures without subparts already contain the text return !sig->hasSubParts(); } return false; }); } MessagePart::List ObjectTreeParser::collectAttachmentParts() { MessagePart::List contentParts = ::collect( mParsedPart, [](const MessagePart::Ptr &) { return true; }, [](const MessagePart::Ptr &part) { return part->isAttachment(); }); return contentParts; } void ObjectTreeParser::decryptParts() { decryptAndVerify(); } /* * This naive implementation assumes that there is an encrypted part wrapping a signature. * For other cases we would have to process both recursively (I think?) */ void ObjectTreeParser::decryptAndVerify() { // We first decrypt ::collect( mParsedPart, [](const MessagePart::Ptr &) { return true; }, [](const MessagePart::Ptr &part) { if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(part.data())) { enc->startDecryption(); } return false; }); // And then verify the available signatures ::collect( mParsedPart, [](const MessagePart::Ptr &) { return true; }, [](const MessagePart::Ptr &part) { if (const auto enc = dynamic_cast<MimeTreeParser::SignedMessagePart *>(part.data())) { enc->startVerification(); } return false; }); } -void ObjectTreeParser::importCertificates() -{ - MessagePart::List contentParts = ::collect( - mParsedPart, - [](const MessagePart::Ptr &) { - return true; - }, - [](const MessagePart::Ptr &part) { - if (const auto cert = dynamic_cast<MimeTreeParser::CertMessagePart *>(part.data())) { - cert->import(); - } - return false; - }); -} - QString ObjectTreeParser::resolveCidLinks(const QString &html) { auto text = html; static const auto regex = QRegularExpression(QLatin1String("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2")); auto it = regex.globalMatch(text); while (it.hasNext()) { const auto match = it.next(); const auto link = QUrl(match.captured(3)); auto cid = link.path(); auto mailMime = const_cast<KMime::Content *>(find([=](KMime::Content *content) { if (!content || !content->contentID(false)) { return false; } return QString::fromLatin1(content->contentID(false)->identifier()) == cid; })); if (mailMime) { const auto contentType = mailMime->contentType(false); if (!contentType) { qWarning() << "No content type, skipping"; continue; } QMimeDatabase mimeDb; const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(contentType->mimeType())).name(); if (mimetype.startsWith(QLatin1String("image/"))) { // We reencode to base64 below. const auto data = mailMime->decodedContent(); if (data.isEmpty()) { qWarning() << "Attachment is empty."; continue; } text.replace(match.captured(0), QString::fromLatin1("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64()))); } } else { qWarning() << "Failed to find referenced attachment: " << cid; } } return text; } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree(const QByteArray &mimeMessage) { const auto mailData = KMime::CRLFtoLF(mimeMessage); mMsg = KMime::Message::Ptr(new KMime::Message); mMsg->setContent(mailData); mMsg->parse(); // We avoid using mMsg->contentType()->charset(), because that will just return kmime's defaultCharset(), ISO-8859-1 const auto charset = mMsg->contentType()->parameter(QStringLiteral("charset")).toLatin1(); if (charset.isEmpty()) { mMsg->contentType()->setCharset("us-ascii"); } parseObjectTree(mMsg.data()); } void ObjectTreeParser::parseObjectTree(KMime::Content *node) { mTopLevelContent = node; mParsedPart = parseObjectTreeInternal(node, false); } MessagePart::Ptr ObjectTreeParser::parsedPart() const { return mParsedPart; } /* * This will lookup suitable formatters based on the type, * and let them generate a list of parts. * If the formatter generated a list of parts, then those are taken, otherwise we move on to the next match. */ MessagePart::List ObjectTreeParser::processType(KMime::Content *node, const QByteArray &mediaType, const QByteArray &subType) { static MimeTreeParser::BodyPartFormatterBaseFactory factory; const auto sub = factory.subtypeRegistry(mediaType.constData()); const auto range = sub.equal_range(subType.constData()); for (auto it = range.first; it != range.second; ++it) { const auto formatter = it->second; if (!formatter) { continue; } const auto list = formatter->processList(this, node); if (!list.isEmpty()) { return list; } } return {}; } MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart) { if (!node) { return MessagePart::Ptr(); } auto parsedPart = MessagePart::Ptr(new MessagePartList(this, node)); parsedPart->setIsRoot(node->isTopLevel()); const auto contents = node->parent() ? node->parent()->contents() : KMime::Content::List{node}; for (int i = contents.indexOf(node); i < contents.size(); ++i) { node = contents.at(i); QByteArray mediaType("text"); QByteArray subType("plain"); if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) { mediaType = node->contentType()->mediaType(); subType = node->contentType()->subType(); } auto messageParts = [&] { // Try the specific type handler { auto list = processType(node, mediaType, subType); if (!list.isEmpty()) { return list; } } // Fallback to the generic handler { auto list = processType(node, mediaType, "*"); if (!list.isEmpty()) { return list; } } // Fallback to the default handler return defaultHandling(node); }(); for (const auto &part : messageParts) { parsedPart->appendSubPart(part); } if (onlyOneMimePart) { break; } } return parsedPart; } QVector<MessagePart::Ptr> ObjectTreeParser::defaultHandling(KMime::Content *node) { if (node->contentType()->mimeType() == QByteArrayLiteral("application/octet-stream") && (node->contentType()->name().endsWith(QLatin1String("p7m")) || node->contentType()->name().endsWith(QLatin1String("p7s")) || node->contentType()->name().endsWith(QLatin1String("p7c")))) { auto list = processType(node, "application", "pkcs7-mime"); if (!list.isEmpty()) { return list; } } return {AttachmentMessagePart::Ptr(new AttachmentMessagePart(this, node))}; } static QTextCodec *getLocalCodec() { auto codec = QTextCodec::codecForLocale(); // In the case of Japan. Japanese locale name is "eucjp" but // The Japanese mail systems normally used "iso-2022-jp" of locale name. // We want to change locale name from eucjp to iso-2022-jp at KMail only. // (Introduction to i18n, 6.6 Limit of Locale technology): // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP // is the standard for Internet, and Shift-JIS is the encoding // for Windows and Macintosh. if (codec) { const QByteArray codecNameLower = codec->name().toLower(); if (codecNameLower == "eucjp" #if defined Q_OS_WIN || defined Q_OS_MACX || codecNameLower == "shift-jis" // OK? #endif ) { codec = QTextCodec::codecForName("jis7"); // QTextCodec *cdc = QTextCodec::codecForName("jis7"); // QTextCodec::setCodecForLocale(cdc); // KLocale::global()->setEncoding(cdc->mibEnum()); } } return codec; } const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const { static auto localCodec = getLocalCodec(); if (!node) { return localCodec; } QByteArray charset = node->contentType()->charset().toLower(); // utf-8 is a superset of us-ascii, so we don't lose anything if we use it instead // utf-8 is used so widely nowadays that it is a good idea to use it to fix issues with broken clients. if (charset == "us-ascii") { charset = "utf-8"; } if (!charset.isEmpty()) { if (auto c = QTextCodec::codecForName(charset)) { return c; } } // no charset means us-ascii (RFC 2045), so using local encoding should // be okay return localCodec; } diff --git a/src/core/objecttreeparser.h b/src/core/objecttreeparser.h index d66a355..03fa67b 100644 --- a/src/core/objecttreeparser.h +++ b/src/core/objecttreeparser.h @@ -1,106 +1,103 @@ // SPDX-FileCopyrightText: 2016 Sandro Knauß <sknauss@kde.org> // SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> // SPDX-FileCopyrightText: 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net // SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net> // SPDX-License-Identifier: LGPL-2.0-or-later #pragma once #include "messagepart.h" #include "mimetreeparser_core_export.h" #include <QSharedPointer> #include <functional> class QString; namespace KMime { class Content; } class QTextCodec; namespace MimeTreeParser { /** Entry point to parse mime messages. Content returned by the ObjectTreeParser (including messageparts), is normalized to not contain any CRLF's but only LF's (just like KMime). */ class MIMETREEPARSER_CORE_EXPORT ObjectTreeParser { // Disable copy ObjectTreeParser(const ObjectTreeParser &other); public: explicit ObjectTreeParser() = default; virtual ~ObjectTreeParser() = default; QString structureAsString() const; void print(); /** * The text of the message, ie. what would appear in the * composer's text editor if this was edited or replied to. * This is usually the content of the first text/plain MIME part. */ QString plainTextContent(); /** * Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part. */ QString htmlContent(); /** Parse beginning at a given node and recursively parsing the children of that node and it's next sibling. */ void parseObjectTree(KMime::Content *node); void parseObjectTree(const QByteArray &mimeMessage); MessagePart::Ptr parsedPart() const; KMime::Content *find(const std::function<bool(KMime::Content *)> &select); MessagePart::List collectContentParts(); MessagePart::List collectContentParts(MessagePart::Ptr start); MessagePart::List collectAttachmentParts(); /** Decrypt parts and verify signatures */ void decryptAndVerify(); // DEPRECATED calls decryptAndVerify void decryptParts(); - /** Import any certificates found in the message */ - void importCertificates(); - /** Embedd content referenced by cid by inlining */ QString resolveCidLinks(const QString &html); private: /** * Does the actual work for parseObjectTree. Unlike parseObjectTree(), this does not change the * top-level content. */ MessagePart::Ptr parseObjectTreeInternal(KMime::Content *node, bool mOnlyOneMimePart); MessagePart::List processType(KMime::Content *node, const QByteArray &mediaType, const QByteArray &subType); MessagePart::List defaultHandling(KMime::Content *node); const QTextCodec *codecFor(KMime::Content *node) const; KMime::Content *mTopLevelContent{nullptr}; MessagePart::Ptr mParsedPart; KMime::Message::Ptr mMsg; friend class MessagePart; friend class EncryptedMessagePart; friend class SignedMessagePart; friend class EncapsulatedRfc822MessagePart; friend class TextMessagePart; friend class HtmlMessagePart; friend class TextPlainBodyPartFormatter; friend class MultiPartSignedBodyPartFormatter; friend class ApplicationPkcs7MimeBodyPartFormatter; }; } diff --git a/src/core/partmetadata.h b/src/core/partmetadata.h index 472d9a2..3f8ff83 100644 --- a/src/core/partmetadata.h +++ b/src/core/partmetadata.h @@ -1,39 +1,61 @@ // SPDX-FileCopyrightText: 2002-2003 Karl-Heinz Zimmer <khz@kde.org> // SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include <QDateTime> #include <QStringList> +#include <gpgme++/context.h> +#include <gpgme++/verificationresult.h> namespace MimeTreeParser { class PartMetaData { public: - bool keyMissing = false; - bool keyExpired = false; - bool keyRevoked = false; - bool sigExpired = false; - bool crlMissing = false; - bool crlTooOld = false; + PartMetaData() + : isSigned(false) + , isGoodSignature(false) + , isEncrypted(false) + , isDecryptable(false) + , inProgress(false) + , technicalProblem(false) + , isEncapsulatedRfc822Message(false) + , isCompliant(false) + , keyRevoked(false) + { + } + + GpgME::Signature::Summary sigSummary = GpgME::Signature::None; + QString signClass; QString signer; QStringList signerMailAddresses; QByteArray keyId; - bool keyIsTrusted = false; + GpgME::Signature::Validity keyTrust = GpgME::Signature::Validity::Unknown; QString status; // to be used for unknown plug-ins + int status_code = 0; // = GPGME_SIG_STAT_NONE; to be used for i18n of OpenPGP and S/MIME CryptPlugs QString errorText; QDateTime creationTime; QString decryptionError; QString auditLog; - bool isSigned = false; - bool isGoodSignature = false; - bool isEncrypted = false; - bool isDecryptable = false; - bool technicalProblem = false; - bool isEncapsulatedRfc822Message = false; + QString compliance; // textual representation of compliance status; empty if compliance isn't enforced + GpgME::Error auditLogError; + bool isSigned : 1; + bool isGoodSignature : 1; + bool isEncrypted : 1; + bool isDecryptable : 1; + bool inProgress : 1; + bool technicalProblem : 1; + bool isEncapsulatedRfc822Message : 1; + bool isCompliant : 1; // corresponds to the isDeVS flag of signature or decryption result + bool keyRevoked : 1; + + inline bool isTrusted() + { + return keyTrust == GpgME::Signature::Full || keyTrust == GpgME::Signature::Ultimate; + } }; } diff --git a/src/core/partmodel.cpp b/src/core/partmodel.cpp index 07858c7..098b5dc 100644 --- a/src/core/partmodel.cpp +++ b/src/core/partmodel.cpp @@ -1,544 +1,548 @@ // SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include "partmodel.h" #include "htmlutils.h" #include "objecttreeparser.h" #include <KLocalizedString> #include <QDebug> #include <QRegularExpression> #include <QStringLiteral> #include <QTextDocument> // We return a pair containing the trimmed string, as well as a boolean indicating whether the string was trimmed or not std::pair<QString, bool> PartModel::trim(const QString &text) { // The delimiters have <p>.? prefixed including the .? because sometimes we get a byte order mark <feff> (seen with user-agent: // Microsoft-MacOutlook/10.1d.0.190908) We match both regulard withspace with \s and non-breaking spaces with \u00A0 const QList<QRegularExpression> delimiters{ // English QRegularExpression{QStringLiteral("<p>.?-+Original(\\s|\u00A0)Message-+"), QRegularExpression::CaseInsensitiveOption}, // The remainder is not quoted QRegularExpression{QStringLiteral("<p>.?On.*wrote:"), QRegularExpression::CaseInsensitiveOption}, // The remainder is quoted QRegularExpression{QStringLiteral("> On.*wrote:"), QRegularExpression::CaseInsensitiveOption}, // German // Forwarded QRegularExpression{QStringLiteral("<p>.?Von:.*</p>"), QRegularExpression::CaseInsensitiveOption}, // Reply QRegularExpression{QStringLiteral("<p>.?Am.*schrieb.*:</p>"), QRegularExpression::CaseInsensitiveOption}, // Signature QRegularExpression{QStringLiteral("<p>.?--(\\s|\u00A0)<br>"), QRegularExpression::CaseInsensitiveOption}, }; for (const auto &expression : delimiters) { auto i = expression.globalMatch(text); while (i.hasNext()) { const auto match = i.next(); const int startOffset = match.capturedStart(0); // This is a very simplistic detection for an inline reply where we would have the patterns before the actual message content. // We simply ignore anything we find within the first few lines. if (startOffset >= 5) { return {text.mid(0, startOffset), true}; } else { // Search for the next delimiter continue; } } } return {text, false}; } static QString addCss(const QString &s) { // Get the default font from QApplication static const QString fontFamily = QFont{}.family(); // overflow:hidden ensures no scrollbars are ever shown. static const QString css = QStringLiteral("<style>\n") + QStringLiteral( "body {\n" " overflow:hidden;\n" " font-family: \"%1\" ! important;\n" " color: #31363b ! important;\n" " background-color: #fcfcfc ! important\n" "}\n") .arg(fontFamily) + QStringLiteral("blockquote { \n" " border-left: 2px solid #bdc3c7 ! important;\n" "}\n") + QStringLiteral("</style>"); const QString header = QLatin1String( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" "<html><head><title></title>") + css + QLatin1String("</head>\n<body>\n"); return header + s + QStringLiteral("</body></html>"); } class PartModelPrivate { public: PartModelPrivate(PartModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser) : q(q_ptr) , mParser(parser) { collectContents(); } ~PartModelPrivate() = default; void checkPart(const MimeTreeParser::MessagePart::Ptr part) { mMimeTypeCache[part.data()] = part->mimeType(); // Extract the content of the part and mContents.insert(part.data(), extractContent(part.data())); } // Recursively find encapsulated messages void findEncapsulated(const MimeTreeParser::EncapsulatedRfc822MessagePart::Ptr &e) { mEncapsulatedParts[e.data()] = mParser->collectContentParts(e); for (const auto &subPart : std::as_const(mEncapsulatedParts[e.data()])) { checkPart(subPart); mParents[subPart.data()] = e.data(); if (auto encapsulatedSub = subPart.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) { findEncapsulated(encapsulatedSub); } } } QVariant extractContent(MimeTreeParser::MessagePart *messagePart) { if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) { if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) { return alternativePart->icalContent(); } } auto preprocessPlaintext = [&](const QString &text) { // We always do rich text (so we get highlighted links and stuff). // NOTE: this inserts non-breaking spaces instead of regular spaces. const auto html = Qt::convertFromPlainText(text); if (trimMail) { const auto result = PartModel::trim(html); isTrimmed = result.second; Q_EMIT q->trimMailChanged(); return MimeTreeParser::linkify(result.first); } return MimeTreeParser::linkify(html); }; if (messagePart->isHtml()) { if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) { containsHtmlAndPlain = true; Q_EMIT q->containsHtmlChanged(); if (!showHtml) { return preprocessPlaintext(messagePart->plaintextContent()); } } return addCss(mParser->resolveCidLinks(messagePart->htmlContent())); } if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) { auto node = attachmentPart->node(); if (node && mMimeTypeCache[attachmentPart] == "text/calendar") { return messagePart->text(); } } return preprocessPlaintext(messagePart->text()); } QVariant contentForPart(MimeTreeParser::MessagePart *messagePart) const { return mContents.value(messagePart); } void collectContents() { mEncapsulatedParts.clear(); mParents.clear(); mContents.clear(); containsHtmlAndPlain = false; isTrimmed = false; const auto parts = mParser->collectContentParts(); for (const auto &part : parts) { checkPart(part); if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) { findEncapsulated(encapsulatedPart); } } for (const auto &part : parts) { if (mMimeTypeCache[part.data()] == "text/calendar") { mParts.prepend(part); } else { mParts.append(part); } } } PartModel *q; MimeTreeParser::MessagePart::List mParts; QHash<MimeTreeParser::MessagePart *, QByteArray> mMimeTypeCache; QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart::List> mEncapsulatedParts; QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart *> mParents; QMap<MimeTreeParser::MessagePart *, QVariant> mContents; std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser; bool showHtml{false}; bool containsHtmlAndPlain{false}; bool trimMail{false}; bool isTrimmed{false}; }; PartModel::PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser) : d(std::unique_ptr<PartModelPrivate>(new PartModelPrivate(this, parser))) { } PartModel::~PartModel() { } void PartModel::setShowHtml(bool html) { if (d->showHtml != html) { beginResetModel(); d->showHtml = html; d->collectContents(); endResetModel(); Q_EMIT showHtmlChanged(); } } bool PartModel::showHtml() const { return d->showHtml; } void PartModel::setTrimMail(bool trim) { if (d->trimMail != trim) { beginResetModel(); d->trimMail = trim; d->collectContents(); endResetModel(); Q_EMIT trimMailChanged(); } } bool PartModel::trimMail() const { return d->trimMail; } bool PartModel::isTrimmed() const { return d->isTrimmed; } bool PartModel::containsHtml() const { return d->containsHtmlAndPlain; } QHash<int, QByteArray> PartModel::roleNames() const { QHash<int, QByteArray> 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()) { const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer()); auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part); if (encapsulatedPart) { const auto parts = d->mEncapsulatedParts[encapsulatedPart]; 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(); } -SignatureInfo *encryptionInfo(MimeTreeParser::MessagePart *messagePart) +SignatureInfo encryptionInfo(MimeTreeParser::MessagePart *messagePart) { - auto signatureInfo = new SignatureInfo; + SignatureInfo signatureInfo; const auto encryptions = messagePart->encryptions(); if (encryptions.size() > 1) { qWarning() << "Can't deal with more than one encryption"; } for (const auto &encryptionPart : encryptions) { - signatureInfo->keyId = encryptionPart->partMetaData()->keyId; + signatureInfo.keyId = encryptionPart->partMetaData()->keyId; + signatureInfo.cryptoProto = encryptionPart->cryptoProto(); + signatureInfo.decryptRecipients = encryptionPart->decryptRecipients(); } return signatureInfo; }; -SignatureInfo *signatureInfo(MimeTreeParser::MessagePart *messagePart) +SignatureInfo signatureInfo(MimeTreeParser::MessagePart *messagePart) { - auto signatureInfo = new SignatureInfo; + SignatureInfo signatureInfo; const auto signatures = messagePart->signatures(); if (signatures.size() > 1) { qWarning() << "Can't deal with more than one signature"; } for (const auto &signaturePart : signatures) { - signatureInfo->keyId = signaturePart->partMetaData()->keyId; - signatureInfo->keyMissing = signaturePart->partMetaData()->keyMissing; - signatureInfo->keyExpired = signaturePart->partMetaData()->keyExpired; - signatureInfo->keyRevoked = signaturePart->partMetaData()->keyRevoked; - signatureInfo->sigExpired = signaturePart->partMetaData()->sigExpired; - signatureInfo->crlMissing = signaturePart->partMetaData()->crlMissing; - signatureInfo->crlTooOld = signaturePart->partMetaData()->crlTooOld; - signatureInfo->signer = signaturePart->partMetaData()->signer; - signatureInfo->signerMailAddresses = signaturePart->partMetaData()->signerMailAddresses; - signatureInfo->signatureIsGood = signaturePart->partMetaData()->isGoodSignature; - signatureInfo->keyIsTrusted = signaturePart->partMetaData()->keyIsTrusted; + signatureInfo.keyId = signaturePart->partMetaData()->keyId; + signatureInfo.cryptoProto = signaturePart->cryptoProto(); + signatureInfo.keyMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyMissing; + signatureInfo.keyExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyExpired; + signatureInfo.keyRevoked = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyRevoked; + signatureInfo.sigExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::SigExpired; + signatureInfo.crlMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlMissing; + signatureInfo.crlTooOld = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlTooOld; + signatureInfo.signer = signaturePart->partMetaData()->signer; + signatureInfo.isCompliant = signaturePart->partMetaData()->isCompliant; + signatureInfo.signerMailAddresses = signaturePart->partMetaData()->signerMailAddresses; + signatureInfo.signatureIsGood = signaturePart->partMetaData()->isGoodSignature; + signatureInfo.keyTrust = signaturePart->partMetaData()->keyTrust; } return signatureInfo; } QVariant PartModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.internalPointer()) { const auto messagePart = static_cast<MimeTreeParser::MessagePart *>(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<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) { return e->from(); } return {}; } case DateRole: { if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) { return e->date(); } return {}; } case TypeRole: { if (messagePart->error()) { return QVariant::fromValue(Types::Error); } if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) { return QVariant::fromValue(Types::Encapsulated); } if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) { if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) { return QVariant::fromValue(Types::Ical); } } if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) { auto node = attachmentPart->node(); if (!node) { qWarning() << "no content for attachment"; return {}; } if (d->mMimeTypeCache[attachmentPart] == "text/calendar") { return QVariant::fromValue(Types::Ical); } } if (!d->showHtml && d->containsHtmlAndPlain) { return QVariant::fromValue(Types::Plain); } // For simple html we don't need a browser auto complexHtml = [&] { if (messagePart->isHtml()) { const auto text = messagePart->htmlContent(); if (text.contains(QStringLiteral("<!DOCTYPE html PUBLIC"))) { // We can probably deal with this if it adheres to the strict dtd //(that's what our composer produces as well) if (!text.contains(QStringLiteral("http://www.w3.org/TR/REC-html40/strict.dtd"))) { return true; } } // Blockquotes don't support any styling which would be necessary so they become readable. if (text.contains(QStringLiteral("blockquote"))) { return true; } // Media queries are too advanced if (text.contains(QStringLiteral("@media"))) { return true; } // auto css properties are not supported e.g margin-left: auto; if (text.contains(QStringLiteral(": auto;"))) { return true; } return false; } else { return false; } }(); if (complexHtml) { return QVariant::fromValue(Types::Html); } return QVariant::fromValue(Types::Plain); } case IsEmbeddedRole: return false; case IsErrorRole: return messagePart->error(); case ContentRole: return d->contentForPart(messagePart); 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>{signatureInfo(messagePart)}; - if (!sigInfo->signatureIsGood) { - if (sigInfo->keyMissing || sigInfo->keyExpired) { - return QStringLiteral("notsogood"); + const auto sigInfo = signatureInfo(messagePart); + if (!sigInfo.signatureIsGood) { + if (sigInfo.keyMissing || sigInfo.keyExpired) { + return SecurityLevel::NotSoGood; } - return QStringLiteral("bad"); + return SecurityLevel::Bad; } } // All good - if (messageIsSigned || messageIsEncrypted) { - return QStringLiteral("good"); + if ((messageIsSigned || messageIsEncrypted) && !messagePart->error()) { + return SecurityLevel::Good; } // No info - return QStringLiteral("unknown"); + return SecurityLevel::Unknow; } case EncryptionSecurityLevelRole: { auto encryption = messagePart->encryptionState(); bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted; if (messagePart->error()) { - return QStringLiteral("bad"); + return SecurityLevel::Bad; } // All good if (messageIsEncrypted) { - return QStringLiteral("good"); + return SecurityLevel::Good; } // No info - return QStringLiteral("unknown"); + return SecurityLevel::Unknow; } case SignatureSecurityLevelRole: { auto signature = messagePart->signatureState(); bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned; if (messageIsSigned) { - auto sigInfo = std::unique_ptr<SignatureInfo>{signatureInfo(messagePart)}; - if (!sigInfo->signatureIsGood) { - if (sigInfo->keyMissing || sigInfo->keyExpired) { - return QStringLiteral("notsogood"); + const auto sigInfo = signatureInfo(messagePart); + if (!sigInfo.signatureIsGood) { + if (sigInfo.keyMissing || sigInfo.keyExpired) { + return SecurityLevel::NotSoGood; } - return QStringLiteral("bad"); + return SecurityLevel::Bad; } - return QStringLiteral("good"); + return SecurityLevel::Good; } // No info - return QStringLiteral("unknown"); + return SecurityLevel::Unknow; } 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 i18ndc("mimetreeparser", "@info:status", "No key available."); case MimeTreeParser::MessagePart::PassphraseError: return i18ndc("mimetreeparser", "@info:status", "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 indexPart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer())) { for (const auto &part : std::as_const(d->mParts)) { if (part.data() == indexPart) { return QModelIndex(); } } const auto parentPart = d->mParents[indexPart]; Q_ASSERT(parentPart); int row = 0; const auto parts = d->mEncapsulatedParts[parentPart]; for (const auto &part : parts) { if (part.data() == indexPart) { break; } row++; } return createIndex(row, 0, parentPart); } return {}; } return {}; } int PartModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer()); auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part); if (encapsulatedPart) { const auto parts = d->mEncapsulatedParts[encapsulatedPart]; return parts.size(); } return 0; } return d->mParts.count(); } int PartModel::columnCount(const QModelIndex &) const { return 1; } #include "moc_partmodel.cpp" diff --git a/src/core/partmodel.h b/src/core/partmodel.h index 939a0bf..744696a 100644 --- a/src/core/partmodel.h +++ b/src/core/partmodel.h @@ -1,115 +1,134 @@ // SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com> // SPDX-License-Identifier: LGPL-2.0-or-later #pragma once #include <QObject> #include <QAbstractItemModel> #include <QModelIndex> +#include <gpgme++/decryptionresult.h> +#include <gpgme++/key.h> #include "mimetreeparser_core_export.h" #include <memory> +namespace QGpgME +{ +class Protocol; +} + namespace MimeTreeParser { class ObjectTreeParser; } class PartModelPrivate; class MIMETREEPARSER_CORE_EXPORT PartModel : public QAbstractItemModel { Q_OBJECT Q_PROPERTY(bool showHtml READ showHtml WRITE setShowHtml NOTIFY showHtmlChanged) Q_PROPERTY(bool containsHtml READ containsHtml NOTIFY containsHtmlChanged) Q_PROPERTY(bool trimMail READ trimMail WRITE setTrimMail NOTIFY trimMailChanged) Q_PROPERTY(bool isTrimmed READ isTrimmed NOTIFY trimMailChanged) public: PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser); ~PartModel(); static std::pair<QString, bool> trim(const QString &text); public: enum class Types : quint8 { Error, Encapsulated, Ical, Plain, None, Html, }; Q_ENUM(Types); enum Roles { TypeRole = Qt::UserRole + 1, ContentRole, IsEmbeddedRole, IsEncryptedRole, IsSignedRole, IsErrorRole, SecurityLevelRole, EncryptionSecurityLevelRole, SignatureSecurityLevelRole, SignatureDetails, EncryptionDetails, ErrorType, ErrorString, SenderRole, DateRole }; + enum SecurityLevel { + Unknow, + Good, + NotSoGood, + Bad, + }; + Q_ENUM(SecurityLevel); + QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; void setShowHtml(bool html); bool showHtml() const; bool containsHtml() const; void setTrimMail(bool trim); bool trimMail() const; bool isTrimmed() const; Q_SIGNALS: void showHtmlChanged(); void trimMailChanged(); void containsHtmlChanged(); private: std::unique_ptr<PartModelPrivate> d; }; -class SignatureInfo : public QObject +class MIMETREEPARSER_CORE_EXPORT SignatureInfo { - Q_OBJECT + Q_GADGET Q_PROPERTY(QByteArray keyId MEMBER keyId CONSTANT) Q_PROPERTY(bool keyMissing MEMBER keyMissing CONSTANT) Q_PROPERTY(bool keyRevoked MEMBER keyRevoked CONSTANT) Q_PROPERTY(bool keyExpired MEMBER keyExpired CONSTANT) Q_PROPERTY(bool sigExpired MEMBER sigExpired CONSTANT) Q_PROPERTY(bool crlMissing MEMBER crlMissing CONSTANT) Q_PROPERTY(bool crlTooOld MEMBER crlTooOld CONSTANT) Q_PROPERTY(QString signer MEMBER signer CONSTANT) Q_PROPERTY(QStringList signerMailAddresses MEMBER signerMailAddresses CONSTANT) Q_PROPERTY(bool signatureIsGood MEMBER signatureIsGood CONSTANT) - Q_PROPERTY(bool keyIsTrusted MEMBER keyIsTrusted CONSTANT) + Q_PROPERTY(bool isCompliant MEMBER isCompliant CONSTANT) + Q_PROPERTY(GpgME::Signature::Validity keyTrust MEMBER keyTrust CONSTANT) public: bool keyRevoked = false; bool keyExpired = false; bool sigExpired = false; bool keyMissing = false; bool crlMissing = false; bool crlTooOld = false; + bool isCompliant = false; + GpgME::Signature::Validity keyTrust; QByteArray keyId; + const QGpgME::Protocol *cryptoProto = nullptr; + std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> decryptRecipients; QString signer; QStringList signerMailAddresses; bool signatureIsGood = false; - bool keyIsTrusted = false; }; diff --git a/src/quick/qml/MailViewer.qml b/src/quick/qml/MailViewer.qml index 366fa41..0f2fb8e 100644 --- a/src/quick/qml/MailViewer.qml +++ b/src/quick/qml/MailViewer.qml @@ -1,205 +1,201 @@ // 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.pim.mimetreeparser 1.0 import org.kde.kirigami 2.20 as Kirigami import './private' Kirigami.ScrollablePage { 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 /** * This property holds the url to a custom ical part QML component. * * This allow apps to overwrite it and provide special handling for email * invitation. */ property url icalCustomComponent Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false - padding: Kirigami.Units.largeSpacing * 2 + padding: Kirigami.Units.largeSpacing title: i18ndc("mimetreeparser", "@title:window", "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 } } contentItem: GridLayout { rowSpacing: Kirigami.Units.smallSpacing columnSpacing: Kirigami.Units.smallSpacing columns: 3 QQC2.Label { text: i18ndc("mimetreeparser", "@label", "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: i18ndc("mimetreeparser", "@label", "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: i18ndc("mimetreeparser", "@label", "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: i18ndc("mimetreeparser", "@label", "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 padding: root.padding icalCustomComponent: root.icalCustomComponent } 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 { id: attachmentDelegate required property int index required property string iconName icon.name: iconName clip: true actionIcon: 'download' actionTooltip: i18ndc("mimetreeparser", "@action:button", "Save attachment") onExecute: mailPartView.attachmentModel.saveAttachmentToDisk(index) onClicked: mailPartView.attachmentModel.openAttachment(index) onPublicKeyImport: mailPartView.attachmentModel.importPublicKey(index) } } } } Connections { target: mailPartView.attachmentModel function onInfo(message) { applicationWindow().showPassiveNotification(message); } } } diff --git a/src/quick/qml/private/MailPartModel.qml b/src/quick/qml/private/MailPartModel.qml index 5dd81c9..3a97a2f 100644 --- a/src/quick/qml/private/MailPartModel.qml +++ b/src/quick/qml/private/MailPartModel.qml @@ -1,169 +1,170 @@ // SPDX-FileCopyrightText: 2016 Michael Bohlender <michael.bohlender@kdemail.net> // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu> // SPDX-License-Identifier: GPL-2.0-or-later import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 1.15 import QtQml.Models 2.2 import org.kde.pim.mimetreeparser 1.0 import org.kde.kirigami 2.19 as Kirigami DelegateModel { id: root property string searchString: "" property bool autoLoadImages: false property int padding: Kirigami.Units.largeSpacing property url icalCustomComponent delegate: RowLayout { id: partColumn - width: ListView.view.width + width: ListView.view.width - Kirigami.Units.largeSpacing + x: Kirigami.Units.smallSpacing function getType(securityLevel) { - if (securityLevel == "good") { + if (securityLevel === PartModel.Good) { return Kirigami.MessageType.Positive } - if (securityLevel == "bad") { + if (securityLevel === PartModel.Bad) { return Kirigami.MessageType.Error } - if (securityLevel == "notsogood") { + if (securityLevel === PartModel.NotSoGood) { return Kirigami.MessageType.Warning } return Kirigami.MessageType.Information } function getColor(securityLevel) { - if (securityLevel == "good") { + if (securityLevel === PartModel.Good) { return Kirigami.Theme.positiveTextColor } - if (securityLevel == "bad") { + if (securityLevel === PartModel.Bad) { return Kirigami.Theme.negativeTextColor } - if (securityLevel == "notsogood") { + if (securityLevel === PartModel.NotSoGood) { return Kirigami.Theme.neutralTextColor } return "transparent" } function getDetails(signatureDetails) { let details = ""; if (signatureDetails.keyMissing) { details += i18ndc("mimetreeparser", "@label", "This message has been signed using the key %1.", signatureDetails.keyId) + "\n"; details += i18ndc("mimetreeparser", "@label", "The key details are not available.") } else { details += i18ndc("mimetreeparser", "@label", "This message has been signed using the key %1 by %2.", signatureDetails.keyId, signatureDetails.signer) + "\n"; if (signatureDetails.keyRevoked) { details += "\n" + i18ndc("mimetreeparser", "@label", "The key was revoked.") } if (signatureDetails.keyExpired) { details += "\n" + i18ndc("mimetreeparser", "@label", "The key has expired.") } if (signatureDetails.keyIsTrusted) { details += "\n" + i18ndc("mimetreeparser", "@label", "You are trusting this key.") } if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired && !signatureDetails.keyIsTrusted) { details += "\n" + i18ndc("mimetreeparser", "@label", "The signature is invalid.") } } return details } QQC2.Control { Layout.preferredWidth: Kirigami.Units.smallSpacing Layout.fillHeight: true visible: model.encrypted background: Rectangle { id: border color: getColor(model.securityLevel) opacity: 0.5 } QQC2.ToolTip.text: getDetails(model.encryptionSecurityLevel) QQC2.ToolTip.visible: hovered QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay } ColumnLayout { Banner { iconName: "mail-encrypted" type: getType(model.encryptionSecurityLevel) visible: model.encrypted - text: model.encryptionDetails.keyId.length === 0 ? i18n("This message is encrypted but we don't have the key for it.") : i18n("This message is encrypted to the key: %1", model.encryptionDetails.keyId); + text: !model.encryptionDetails.keyId ? i18n("This message is encrypted but we don't have the key for it.") : i18n("This message is encrypted to the key: %1", model.encryptionDetails.keyId); Layout.fillWidth: true } Banner { iconName: 'mail-signed' visible: model.signed type: getType(model.signatureSecurityLevel) text: getDetails(model.signatureDetails) Layout.fillWidth: true } Loader { id: partLoader Layout.preferredHeight: item ? item.contentHeight : 0 Layout.fillWidth: true Layout.leftMargin: root.padding Layout.rightMargin: root.padding Component.onCompleted: { switch (model.type + 0) { case PartModel.Plain: partLoader.setSource("TextPart.qml", { content: model.content, embedded: model.embedded, }) break case PartModel.Html: partLoader.setSource("HtmlPart.qml", { content: model.content, }) break; case PartModel.Error: partLoader.setSource("ErrorPart.qml", { errorType: model.errorType, errorString: model.errorString, }) break; case PartModel.Encapsulated: partLoader.setSource("MailPart.qml", { rootIndex: root.modelIndex(index), model: root.model, sender: model.sender, date: model.date, }) break; case PartModel.Ical: partLoader.setSource(root.icalCustomComponent ? root.icalCustomComponent : "ICalPart.qml", { content: model.content, }) break; } } 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 } } } } } diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 67a6143..274feee 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -1,146 +1,151 @@ # SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com> # SPDX-License-Identifier: BSD-3-Clause ecm_setup_version(PROJECT VARIABLE_PREFIX MIMETREEPARSER_WIDGETS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfigVersion.cmake" SOVERSION 6 ) add_library(KPim6MimeTreeParserWidgets) add_library(KPim6::MimeTreeParserWidgets ALIAS KPim6MimeTreeParserWidgets) target_sources(KPim6MimeTreeParserWidgets PRIVATE messageviewer.h messageviewer.cpp attachmentview_p.h attachmentview.cpp + messagecontainerwidget_p.h + messagecontainerwidget.cpp + urlhandler_p.h + urlhandler.cpp ) ecm_qt_declare_logging_category(KPim6MimeTreeParserWidgets HEADER mimetreeparser_widgets_debug.h IDENTIFIER MIMETREEPARSER_WIDGET_LOG CATEGORY_NAME org.kde.pim.mimetreeparser.widgets DESCRIPTION "mimetreeparser (pim lib)" EXPORT MIMETREEPARSER ) if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) set_target_properties(KPim6MimeTreeParserWidgets PROPERTIES UNITY_BUILD ON) endif() generate_export_header(KPim6MimeTreeParserWidgets BASE_NAME mimetreeparser_widgets) target_include_directories(KPim6MimeTreeParserWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets>") target_link_libraries(KPim6MimeTreeParserWidgets PUBLIC Qt6::Widgets KPim6::MimeTreeParserCore PRIVATE KF6::Codecs KF6::I18n KF6::CalendarCore KF6::WidgetsAddons + KF6::Libkleo Gpgme::Gpgme ) set_target_properties(KPim6MimeTreeParserWidgets PROPERTIES VERSION ${MIMETREEPARSER_WIDGETS_VERSION} SOVERSION ${MIMETREEPARSER_WIDGETS_SOVERSION} EXPORT_NAME MimeTreeParserWidgets ) ecm_generate_pri_file(BASE_NAME MimeTreeParserWidgets LIB_NAME KPim6MimeTreeParserWidgets DEPS "MimeTreeParserWidgets" FILENAME_VAR PRI_FILENAME ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) install(TARGETS KPim6MimeTreeParserWidgets EXPORT KPim6MimeTreeParserWidgetsTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) ecm_generate_headers(MimeTreeParserWidgets_CamelCase_HEADERS HEADER_NAMES MessageViewer REQUIRED_HEADERS MimeTreeParserWidgets_HEADERS PREFIX MimeTreeParserWidgets ) install(FILES ${MimeTreeParserWidgets_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets/MimeTreeParserWidgets COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_export.h ${MimeTreeParserWidgets_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets/mimetreeparserwidgets COMPONENT Devel ) if (BUILD_QCH) ecm_add_qch( KPim6MimeTreeParserWidgets_QCH NAME MimeTreeParserWidgets BASE_NAME KPim6MimeTreeParserWidgets VERSION ${PIM_VERSION} ORG_DOMAIN org.kde # using only public headers, to cover only public API SOURCES ${MimeTreeParserWidgets_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt6Core_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS MIMETREEPARSER_WIDGETS_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6MimeTreeParserWidgets") if (BUILD_QCH) ecm_install_qch_export( TARGETS KPim6MimeTreeParserWidgets_QCH FILE KPim6MimeTreeParserWidgetsQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6MimeTreeParserWidgetsQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserWidgetsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KPim6MimeTreeParserWidgetsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim6MimeTreeParserWidgetsTargets.cmake NAMESPACE KPim6:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets COMPONENT Devel ) diff --git a/src/widgets/messagecontainerwidget.cpp b/src/widgets/messagecontainerwidget.cpp new file mode 100644 index 0000000..4038451 --- /dev/null +++ b/src/widgets/messagecontainerwidget.cpp @@ -0,0 +1,268 @@ +// SPDX-FileCopyrightText: 2023 g10 Code GmbH +// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com> +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "messagecontainerwidget_p.h" +#include "urlhandler_p.h" + +#include <KLocalizedString> +#include <KMessageWidget> +#include <Libkleo/Compliance> +#include <QGpgME/Protocol> + +#include <QDebug> +#include <QLabel> +#include <QPaintEvent> +#include <QPainter> +#include <QResizeEvent> +#include <QVBoxLayout> + +namespace +{ + +const int borderWidth = 5; + +QColor getColor(PartModel::SecurityLevel securityLevel) +{ + if (securityLevel == PartModel::Good) { + return QColor(39, 174, 96); // Window: ForegroundPositive + } + if (securityLevel == PartModel::Bad) { + return QColor(218, 68, 83); // Window: ForegroundNegative + } + if (securityLevel == PartModel::NotSoGood) { + return QColor(246, 116, 0); // Window: ForegroundNeutral + } + return QColor(); +} + +KMessageWidget::MessageType getType(PartModel::SecurityLevel securityLevel) +{ + if (securityLevel == PartModel::Good) { + return KMessageWidget::MessageType::Positive; + } + if (securityLevel == PartModel::Bad) { + return KMessageWidget::MessageType::Error; + } + if (securityLevel == PartModel::NotSoGood) { + return KMessageWidget::MessageType::Warning; + } + return KMessageWidget::MessageType::Information; +} + +QString getDetails(const SignatureInfo &signatureDetails) +{ + QString href; + if (signatureDetails.cryptoProto) { + href = QStringLiteral("messageviewer:showCertificate#%1 ### %2 ### %3") + .arg(signatureDetails.cryptoProto->displayName(), signatureDetails.cryptoProto->name(), QString::fromLatin1(signatureDetails.keyId)); + } + + QString details; + if (signatureDetails.keyMissing) { + if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) { + details += i18ndc("mimetreeparser", + "@label", + "This message has been signed VS-NfD compliant using the key <a href=\"%1\">%2</a>.", + href, + QString::fromUtf8(signatureDetails.keyId)) + + QLatin1Char('\n'); + } else { + details += i18ndc("mimetreeparser", + "@label", + "This message has been signed using the key <a href=\"%1\">%2</a>.", + href, + QString::fromUtf8(signatureDetails.keyId)) + + QLatin1Char('\n'); + } + details += i18ndc("mimetreeparser", "@label", "The key details are not available."); + } else { + if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) { + details += i18ndc("mimetreeparser", "@label", "This message has been signed VS-NfD compliant by %1.", signatureDetails.signer.toHtmlEscaped()); + } else { + details += i18ndc("mimetreeparser", "@label", "This message has been signed by %1.", signatureDetails.signer.toHtmlEscaped()); + } + if (signatureDetails.keyRevoked) { + details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@label", "The <a href=\"%1\">key</a> was revoked.", href); + } + if (signatureDetails.keyExpired) { + details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@label", "The <a href=\"%1\">key</a> was expired.", href); + } + + if (signatureDetails.keyTrust == GpgME::Signature::Unknown) { + details += + QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid, but the <a href=\"%1\">key</a>'s validity is unknown.", href); + } else if (signatureDetails.keyTrust == GpgME::Signature::Marginal) { + details += + QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">key</a> is marginally trusted.", href); + } else if (signatureDetails.keyTrust == GpgME::Signature::Full) { + details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">key</a> is fully trusted.", href); + } else if (signatureDetails.keyTrust == GpgME::Signature::Ultimate) { + details += + QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">key</a> is ultimately trusted.", href); + } else { + details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid, but the <a href=\"%1\">key</a> is untrusted.", href); + } + if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired + && signatureDetails.keyTrust != GpgME::Signature::Unknown) { + details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is invalid."); + } + } + return details; +} + +} + +MessageWidgetContainer::MessageWidgetContainer(bool isSigned, + const SignatureInfo &signatureInfo, + PartModel::SecurityLevel signatureSecurityLevel, + bool displaySignatureInfo, + bool isEncrypted, + const SignatureInfo &encryptionInfo, + PartModel::SecurityLevel encryptionSecurityLevel, + bool displayEncryptionInfo, + UrlHandler *urlHandler, + QWidget *parent) + : QFrame(parent) + , m_isSigned(isSigned) + , m_signatureInfo(signatureInfo) + , m_signatureSecurityLevel(signatureSecurityLevel) + , m_displaySignatureInfo(displaySignatureInfo) + , m_isEncrypted(isEncrypted) + , m_encryptionInfo(encryptionInfo) + , m_encryptionSecurityLevel(encryptionSecurityLevel) + , m_displayEncryptionInfo(displayEncryptionInfo) + , m_urlHandler(urlHandler) +{ + createLayout(); +} + +MessageWidgetContainer::~MessageWidgetContainer() = default; + +void MessageWidgetContainer::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + if (!m_isSigned && !m_isEncrypted) { + return; + } + + QPainter painter(this); + if (layoutDirection() == Qt::RightToLeft) { + auto r = rect(); + r.setX(width() - borderWidth); + r.setWidth(borderWidth); + const QColor color = getColor(PartModel::SecurityLevel::Good); + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(QColor(color)); + painter.setPen(QPen(Qt::NoPen)); + painter.drawRect(r); + } else { + auto r = rect(); + r.setWidth(borderWidth); + const QColor color = getColor(PartModel::SecurityLevel::Good); + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(QColor(color)); + painter.setPen(QPen(Qt::NoPen)); + painter.drawRect(r); + } +} + +bool MessageWidgetContainer::event(QEvent *event) +{ + if (event->type() == QEvent::Polish && !layout()) { + createLayout(); + } + + return QFrame::event(event); +} + +void MessageWidgetContainer::createLayout() +{ + delete layout(); + + auto vLayout = new QVBoxLayout(this); + + if (m_isSigned || m_isEncrypted) { + if (layoutDirection() == Qt::RightToLeft) { + layout()->setContentsMargins(0, 0, borderWidth * 2, 0); + } else { + layout()->setContentsMargins(borderWidth * 2, 0, 0, 0); + } + } + + if (m_isEncrypted && m_displayEncryptionInfo) { + auto encryptionMessage = new KMessageWidget(this); + encryptionMessage->setMessageType(getType(m_encryptionSecurityLevel)); + encryptionMessage->setCloseButtonVisible(false); + encryptionMessage->setIcon(QIcon::fromTheme(QStringLiteral("mail-encrypted"))); + + QString text; + if (m_encryptionInfo.keyId.isEmpty()) { + if (Kleo::DeVSCompliance::isCompliant() && m_encryptionInfo.isCompliant) { + text = i18n("This message is VS-NfD compliant encrypted but we don't have the key for it.", QString::fromUtf8(m_encryptionInfo.keyId)); + } else { + text = i18n("This message is encrypted but we don't have the key for it."); + } + } else { + if (Kleo::DeVSCompliance::isCompliant() && m_encryptionInfo.isCompliant) { + text = i18n("This message is VS-NfD compliant encrypted."); + } else { + text = i18n("This message is encrypted."); + } + } + + encryptionMessage->setText(text + QLatin1Char(' ') + QStringLiteral("<a href=\"messageviewer:showDetails\">Details</a>")); + + connect(encryptionMessage, &KMessageWidget::linkActivated, this, [this, encryptionMessage, text](const QString &link) { + QUrl url(link); + if (url.path() == QStringLiteral("showDetails")) { + QString newText = text + QStringLiteral(" ") + i18n("The message is encrypted for the following keys:") + QStringLiteral("<ul>"); + + for (const auto &recipient : m_encryptionInfo.decryptRecipients) { + if (recipient.second.keyID()) { + const auto href = QStringLiteral("messageviewer:showCertificate#%1 ### %2 ### %3") + .arg(m_encryptionInfo.cryptoProto->displayName(), + m_encryptionInfo.cryptoProto->name(), + QString::fromLatin1(recipient.second.keyID())); + + newText += QStringLiteral("<li><a href=\"%1\">0x%2</a></li>").arg(href, QString::fromLatin1(recipient.second.keyID())); + } else { + const auto href = QStringLiteral("messageviewer:showCertificate#%1 ### %2 ### %3") + .arg(m_encryptionInfo.cryptoProto->displayName(), + m_encryptionInfo.cryptoProto->name(), + QString::fromLatin1(recipient.first.keyID())); + + newText += + QStringLiteral("<li><a href=\"%1\">0x%2</a> (%3)</li>").arg(href, QString::fromLatin1(recipient.first.keyID()), i18n("Unknown key")); + } + } + + newText += QStringLiteral("</ul>"); + + encryptionMessage->setText(newText); + return; + } + + if (url.path() == QStringLiteral("showCertificate")) { + m_urlHandler->handleClick(QUrl(link), window()->windowHandle()); + } + }); + + vLayout->addWidget(encryptionMessage); + } + + if (m_isSigned && m_displaySignatureInfo) { + auto signatureMessage = new KMessageWidget(this); + signatureMessage->setIcon(QIcon::fromTheme(QStringLiteral("mail-signed"))); + signatureMessage->setCloseButtonVisible(false); + signatureMessage->setText(getDetails(m_signatureInfo)); + connect(signatureMessage, &KMessageWidget::linkActivated, this, [this](const QString &link) { + m_urlHandler->handleClick(QUrl(link), window()->windowHandle()); + }); + signatureMessage->setMessageType(getType(m_signatureSecurityLevel)); + signatureMessage->setWordWrap(true); + + vLayout->addWidget(signatureMessage); + } +} diff --git a/src/widgets/messagecontainerwidget_p.h b/src/widgets/messagecontainerwidget_p.h new file mode 100644 index 0000000..6bc742c --- /dev/null +++ b/src/widgets/messagecontainerwidget_p.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 g10 Code GmbH +// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com> +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include <MimeTreeParserCore/PartModel> +#include <QFrame> + +class SignatureInfo; +class QPaintEvent; +class QResizeEvent; +class UrlHandler; + +class MessageWidgetContainer : public QFrame +{ +public: + explicit MessageWidgetContainer(bool isSigned, + const SignatureInfo &signatureInfo, + PartModel::SecurityLevel signatureSecurityLevel, + bool displaySignatureInfo, + bool isEncrypted, + const SignatureInfo &encryptionInfo, + PartModel::SecurityLevel encryptionSecurityLevel, + bool displayEncryptionInfo, + UrlHandler *urlHandler, + QWidget *parent = nullptr); + ~MessageWidgetContainer(); + +protected: + void paintEvent(QPaintEvent *event) override; + bool event(QEvent *event) override; + +private: + void createLayout(); + + bool m_isSigned; + SignatureInfo const m_signatureInfo; + PartModel::SecurityLevel m_signatureSecurityLevel; + bool m_displaySignatureInfo; + + bool m_isEncrypted; + SignatureInfo const m_encryptionInfo; + PartModel::SecurityLevel m_encryptionSecurityLevel; + bool m_displayEncryptionInfo; + + UrlHandler *const m_urlHandler; +}; diff --git a/src/widgets/messageviewer.cpp b/src/widgets/messageviewer.cpp index 78430e8..af1e2cd 100644 --- a/src/widgets/messageviewer.cpp +++ b/src/widgets/messageviewer.cpp @@ -1,282 +1,340 @@ // SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com> // SPDX-License-Identifier: LGPL-2.0-or-later #include "messageviewer.h" #include "../core/objecttreeparser.h" #include "attachmentview_p.h" +#include "messagecontainerwidget_p.h" +#include "urlhandler_p.h" #include <KCalendarCore/Event> #include <KCalendarCore/ICalFormat> #include <KCalendarCore/Incidence> +#include <KMessageWidget> #define TRANSLATION_DOMAIN "mimetreeparser" #include <KLocalizedString> -#include <KMessageWidget> #include <MimeTreeParserCore/AttachmentModel> #include <MimeTreeParserCore/MessageParser> #include <MimeTreeParserCore/PartModel> #include <QAction> #include <QDebug> #include <QFormLayout> #include <QGroupBox> #include <QLabel> #include <QMenu> #include <QScrollArea> #include <QVBoxLayout> using namespace MimeTreeParser::Widgets; class MessageViewer::Private { public: Private(MessageViewer *q_ptr) : q{q_ptr} { createActions(); } MessageViewer *q; QVBoxLayout *layout = nullptr; KMime::Message::Ptr message; MessageParser parser; QVector<QWidget *> widgets; QScrollArea *scrollArea = nullptr; QFormLayout *formLayout = nullptr; AttachmentView *attachmentView = nullptr; MimeTreeParser::MessagePart::List selectedParts; + UrlHandler *urlHandler = nullptr; QAction *saveAttachmentAction = nullptr; QAction *openAttachmentAction = nullptr; QAction *importPublicKeyAction = nullptr; void createActions() { saveAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("&Save Attachment As..."), q); connect(saveAttachmentAction, &QAction::triggered, q, [this]() { saveSelectedAttachments(); }); openAttachmentAction = new QAction(i18nc("to open", "Open"), q); connect(openAttachmentAction, &QAction::triggered, q, [this]() { openSelectedAttachments(); }); importPublicKeyAction = new QAction(i18nc("@action:inmenu", "Import public key"), q); connect(importPublicKeyAction, &QAction::triggered, q, [this]() { importPublicKey(); }); } void openSelectedAttachments(); void saveSelectedAttachments(); void selectionChanged(); void showContextMenu(); void importPublicKey(); void recursiveBuildViewer(PartModel *parts, QVBoxLayout *layout, const QModelIndex &parent); }; void MessageViewer::Private::openSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const auto &part : std::as_const(selectedParts)) { parser.attachments()->openAttachment(part); } } void MessageViewer::Private::saveSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const auto &part : std::as_const(selectedParts)) { parser.attachments()->saveAttachmentToDisk(part); } } void MessageViewer::Private::importPublicKey() { Q_ASSERT(selectedParts.count() == 1); parser.attachments()->importPublicKey(selectedParts[0]); } void MessageViewer::Private::showContextMenu() { const int numberOfParts(selectedParts.count()); QMenu menu; if (numberOfParts == 1) { const QString mimetype = QString::fromLatin1(selectedParts.first()->mimeType()); if (mimetype == QLatin1String("application/pgp-keys")) { menu.addAction(importPublicKeyAction); } } menu.addAction(openAttachmentAction); menu.addAction(saveAttachmentAction); menu.exec(QCursor::pos()); } void MessageViewer::Private::selectionChanged() { const QModelIndexList selectedRows = attachmentView->selectionModel()->selectedRows(); MimeTreeParser::MessagePart::List selectedParts; selectedParts.reserve(selectedRows.count()); for (const QModelIndex &index : selectedRows) { auto part = attachmentView->model()->data(index, AttachmentModel::AttachmentPartRole).value<MimeTreeParser::MessagePart::Ptr>(); selectedParts.append(part); } this->selectedParts = selectedParts; } MessageViewer::MessageViewer(QWidget *parent) : QSplitter(Qt::Vertical, parent) , d(std::make_unique<MessageViewer::Private>(this)) { setObjectName(QStringLiteral("MessageViewerSplitter")); setChildrenCollapsible(false); setSizes({0}); auto headersArea = new QWidget(this); headersArea->setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Expanding); addWidget(headersArea); + d->urlHandler = new UrlHandler(this); + d->formLayout = new QFormLayout(headersArea); auto widget = new QWidget(this); d->layout = new QVBoxLayout(widget); d->layout->setSizeConstraint(QLayout::SetMinAndMaxSize); auto scrollArea = new QScrollArea(this); scrollArea->setWidget(widget); scrollArea->setWidgetResizable(true); + scrollArea->setBackgroundRole(QPalette::Base); addWidget(scrollArea); setStretchFactor(1, 2); d->attachmentView = new AttachmentView(this); addWidget(d->attachmentView); connect(d->attachmentView, &AttachmentView::contextMenuRequested, this, [this] { d->selectionChanged(); d->showContextMenu(); }); + + setMinimumSize(400, 400); } MessageViewer::~MessageViewer() { qDeleteAll(d->widgets); d->widgets.clear(); } KMime::Message::Ptr MessageViewer::message() const { return d->parser.message(); } void MessageViewer::Private::recursiveBuildViewer(PartModel *parts, QVBoxLayout *layout, const QModelIndex &parent) { for (int i = 0, count = parts->rowCount(parent); i < count; i++) { const auto type = static_cast<PartModel::Types>(parts->data(parts->index(i, 0, parent), PartModel::TypeRole).toUInt()); const auto content = parts->data(parts->index(i, 0, parent), PartModel::ContentRole).toString(); + const auto signatureInfo = parts->data(parts->index(i, 0, parent), PartModel::SignatureDetails).value<SignatureInfo>(); + const auto isSigned = parts->data(parts->index(i, 0, parent), PartModel::IsSignedRole).toBool(); + const auto signatureSecurityLevel = + static_cast<PartModel::SecurityLevel>(parts->data(parts->index(i, 0, parent), PartModel::SignatureSecurityLevelRole).toInt()); + + const auto encryptionInfo = parts->data(parts->index(i, 0, parent), PartModel::EncryptionDetails).value<SignatureInfo>(); + const auto isEncrypted = parts->data(parts->index(i, 0, parent), PartModel::IsEncryptedRole).toBool(); + const auto encryptionSecurityLevel = + static_cast<PartModel::SecurityLevel>(parts->data(parts->index(i, 0, parent), PartModel::EncryptionSecurityLevelRole).toInt()); + + const auto displayEncryptionInfo = + i == 0 || parts->data(parts->index(i - 1, 0, parent), PartModel::EncryptionDetails).value<SignatureInfo>().keyId != encryptionInfo.keyId; + + const auto displaySignatureInfo = + i == 0 || parts->data(parts->index(i - 1, 0, parent), PartModel::SignatureDetails).value<SignatureInfo>().keyId != signatureInfo.keyId; + switch (type) { case PartModel::Types::Plain: { - auto label = new QLabel(); - label->setText(content); - widgets.append(label); - layout->addWidget(label); + auto container = new MessageWidgetContainer(isSigned, + signatureInfo, + signatureSecurityLevel, + displaySignatureInfo, + isEncrypted, + encryptionInfo, + encryptionSecurityLevel, + displayEncryptionInfo, + urlHandler); + auto label = new QLabel(content); + label->setTextInteractionFlags(Qt::TextSelectableByMouse); + container->layout()->addWidget(label); + widgets.append(container); + layout->addWidget(container); break; } case PartModel::Types::Ical: { + auto container = new MessageWidgetContainer(isSigned, + signatureInfo, + signatureSecurityLevel, + displaySignatureInfo, + isEncrypted, + encryptionInfo, + encryptionSecurityLevel, + displayEncryptionInfo, + urlHandler); + KCalendarCore::ICalFormat format; auto incidence = format.fromString(content); - auto widget = new QGroupBox; + auto widget = new QGroupBox(container); widget->setTitle(i18n("Invitation")); auto incidenceLayout = new QFormLayout(widget); incidenceLayout->addRow(i18n("&Summary:"), new QLabel(incidence->summary())); incidenceLayout->addRow(i18n("&Organizer:"), new QLabel(incidence->organizer().fullName())); if (incidence->location().length() > 0) { incidenceLayout->addRow(i18n("&Location:"), new QLabel(incidence->location())); } incidenceLayout->addRow(i18n("&Start date:"), new QLabel(incidence->dtStart().toLocalTime().toString())); if (const auto event = incidence.dynamicCast<KCalendarCore::Event>()) { incidenceLayout->addRow(i18n("&End date:"), new QLabel(event->dtEnd().toLocalTime().toString())); } if (incidence->description().length() > 0) { incidenceLayout->addRow(i18n("&Details:"), new QLabel(incidence->description())); } - widgets.append(widget); - layout->addWidget(widget); + container->layout()->addWidget(widget); + + widgets.append(container); + layout->addWidget(container); break; } case PartModel::Types::Encapsulated: { - auto groupBox = new QGroupBox; + auto container = new MessageWidgetContainer(isSigned, + signatureInfo, + signatureSecurityLevel, + displaySignatureInfo, + isEncrypted, + encryptionInfo, + encryptionSecurityLevel, + displayEncryptionInfo, + urlHandler); + + auto groupBox = new QGroupBox(container); groupBox->setSizePolicy(QSizePolicy::MinimumExpanding, q->sizePolicy().verticalPolicy()); groupBox->setTitle(i18n("Encapsulated email")); auto encapsulatedLayout = new QVBoxLayout(groupBox); auto header = new QWidget(groupBox); auto headerLayout = new QFormLayout(header); const auto from = parts->data(parts->index(i, 0, parent), PartModel::SenderRole).toString(); const auto date = parts->data(parts->index(i, 0, parent), PartModel::DateRole).toDateTime(); headerLayout->addRow(i18n("From:"), new QLabel(from)); headerLayout->addRow(i18n("Date:"), new QLabel(date.toLocalTime().toString())); encapsulatedLayout->addWidget(header); recursiveBuildViewer(parts, encapsulatedLayout, parts->index(i, 0, parent)); - widgets.append(groupBox); - layout->addWidget(groupBox); + container->layout()->addWidget(groupBox); + + widgets.append(container); + layout->addWidget(container); break; } case PartModel::Types::Error: { const auto errorString = parts->data(parts->index(i, 0, parent), PartModel::ErrorString).toString(); auto errorWidget = new KMessageWidget(i18n("Error: %1", errorString)); errorWidget->setMessageType(KMessageWidget::MessageType::Error); widgets.append(errorWidget); layout->addWidget(errorWidget); break; } default: qWarning() << parts->data(parts->index(i, 0, parent), PartModel::ContentRole) << type; } } } void MessageViewer::setMessage(const KMime::Message::Ptr message) { d->parser.setMessage(message); for (int i = d->formLayout->rowCount() - 1; i >= 0; i--) { d->formLayout->removeRow(i); } if (!d->parser.subject().isEmpty()) { d->formLayout->addRow(i18n("&Subject:"), new QLabel(d->parser.subject())); } if (!d->parser.from().isEmpty()) { d->formLayout->addRow(i18n("&From:"), new QLabel(d->parser.from())); } if (!d->parser.to().isEmpty()) { d->formLayout->addRow(i18n("&To:"), new QLabel(d->parser.to())); } const auto parts = d->parser.parts(); for (auto widget : std::as_const(d->widgets)) { d->layout->removeWidget(widget); delete widget; } d->widgets.clear(); d->recursiveBuildViewer(parts, d->layout, {}); d->layout->addStretch(); d->attachmentView->setModel(d->parser.attachments()); d->attachmentView->setVisible(d->parser.attachments()->rowCount() > 0); connect(d->attachmentView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { d->selectionChanged(); }); } diff --git a/src/widgets/urlhandler.cpp b/src/widgets/urlhandler.cpp new file mode 100644 index 0000000..bf2aacc --- /dev/null +++ b/src/widgets/urlhandler.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2023 g10 Code GmbH +// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com> +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "urlhandler_p.h" + +#include "mimetreeparser_widgets_debug.h" + +#include <KLocalizedString> +#include <KMessageBox> +#include <QProcess> +#include <QStandardPaths> +#include <QUrl> + +UrlHandler::UrlHandler(QObject *parent) + : QObject(parent) +{ +} + +bool UrlHandler::handleClick(const QUrl &url, QWindow *window) +{ + if (!url.hasFragment()) { + return false; + } + QString displayName; + QString libName; + QString keyId; + if (!foundSMIMEData(url.path() + QLatin1Char('#') + QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) { + return false; + } + QStringList lst; + lst << QStringLiteral("--parent-windowid") << QString::number(static_cast<qlonglong>(window->winId())) << QStringLiteral("--query") << keyId; + const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); + if (exec.isEmpty()) { + qCWarning(MIMETREEPARSER_WIDGET_LOG) << "Could not find kleopatra executable in PATH"; + KMessageBox::errorWId(window->winId(), + i18n("Could not start certificate manager. " + "Please check your installation."), + i18n("KMail Error")); + return false; + } + QProcess::startDetached(exec, lst); + return true; +} + +bool UrlHandler::foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId) +{ + static QString showCertMan(QStringLiteral("showCertificate#")); + displayName.clear(); + libName.clear(); + keyId.clear(); + int i1 = aUrl.indexOf(showCertMan); + if (-1 < i1) { + i1 += showCertMan.length(); + int i2 = aUrl.indexOf(QLatin1String(" ### "), i1); + if (i1 < i2) { + displayName = aUrl.mid(i1, i2 - i1); + i1 = i2 + 5; + i2 = aUrl.indexOf(QLatin1String(" ### "), i1); + if (i1 < i2) { + libName = aUrl.mid(i1, i2 - i1); + i2 += 5; + + keyId = aUrl.mid(i2); + } + } + } + return !keyId.isEmpty(); +} diff --git a/src/widgets/urlhandler_p.h b/src/widgets/urlhandler_p.h new file mode 100644 index 0000000..0011666 --- /dev/null +++ b/src/widgets/urlhandler_p.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 g10 Code GmbH +// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com> +// SPDX-License-Identifier: LGPL-2.0-or-later + +#pragma once + +#include <QObject> +#include <QWindow> + +class UrlHandler : public QObject +{ +public: + explicit UrlHandler(QObject *parent = nullptr); + bool handleClick(const QUrl &url, QWindow *window); + +private: + bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId); +};