diff options
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/messagepart.cpp')
-rw-r--r-- | framework/src/domain/mime/mimetreeparser/messagepart.cpp | 283 |
1 files changed, 75 insertions, 208 deletions
diff --git a/framework/src/domain/mime/mimetreeparser/messagepart.cpp b/framework/src/domain/mime/mimetreeparser/messagepart.cpp index 2fd4f496..4bed80da 100644 --- a/framework/src/domain/mime/mimetreeparser/messagepart.cpp +++ b/framework/src/domain/mime/mimetreeparser/messagepart.cpp | |||
@@ -27,102 +27,12 @@ | |||
27 | 27 | ||
28 | #include <KMime/Content> | 28 | #include <KMime/Content> |
29 | 29 | ||
30 | #include <QGpgME/DN> | 30 | #include <crypto.h> |
31 | #include <QGpgME/DataProvider> | ||
32 | |||
33 | #include <gpgme++/context.h> | ||
34 | #include <gpgme++/data.h> | ||
35 | #include <gpgme++/verificationresult.h> | ||
36 | #include <gpgme++/key.h> | ||
37 | #include <gpgme++/keylistresult.h> | ||
38 | #include <gpgme++/decryptionresult.h> | ||
39 | #include <gpgme++/importresult.h> | ||
40 | #include <gpgme.h> | ||
41 | |||
42 | 31 | ||
43 | #include <QTextCodec> | 32 | #include <QTextCodec> |
44 | #include <sstream> | ||
45 | 33 | ||
46 | using namespace MimeTreeParser; | 34 | using namespace MimeTreeParser; |
47 | 35 | using namespace Crypto; | |
48 | static GpgME::Data fromBA(const QByteArray &ba) | ||
49 | { | ||
50 | return {ba.data(), static_cast<size_t>(ba.size()), false}; | ||
51 | } | ||
52 | |||
53 | |||
54 | static GpgME::Protocol toGpgMe(CryptoProtocol p) | ||
55 | { | ||
56 | switch (p) { | ||
57 | case UnknownProtocol: | ||
58 | return GpgME::UnknownProtocol; | ||
59 | case CMS: | ||
60 | return GpgME::CMS; | ||
61 | case OpenPGP: | ||
62 | return GpgME::OpenPGP; | ||
63 | } | ||
64 | return GpgME::UnknownProtocol; | ||
65 | } | ||
66 | |||
67 | static QSharedPointer<GpgME::Context> gpgContext(CryptoProtocol protocol) | ||
68 | { | ||
69 | GpgME::initializeLibrary(); | ||
70 | auto error = GpgME::checkEngine(toGpgMe(protocol)); | ||
71 | if (error) { | ||
72 | qWarning() << "Engine check failed: " << error.asString(); | ||
73 | } | ||
74 | auto ctx = QSharedPointer<GpgME::Context>(GpgME::Context::createForProtocol(toGpgMe(protocol))); | ||
75 | Q_ASSERT(ctx); | ||
76 | return ctx; | ||
77 | } | ||
78 | |||
79 | static GpgME::VerificationResult verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text) | ||
80 | { | ||
81 | return gpgContext(protocol)->verifyDetachedSignature(fromBA(signature), fromBA(text)); | ||
82 | } | ||
83 | |||
84 | static GpgME::VerificationResult verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata) | ||
85 | { | ||
86 | QGpgME::QByteArrayDataProvider out; | ||
87 | GpgME::Data wrapper(&out); | ||
88 | const auto result = gpgContext(protocol)->verifyOpaqueSignature(fromBA(signature), wrapper); | ||
89 | outdata = out.data(); | ||
90 | return result; | ||
91 | } | ||
92 | |||
93 | |||
94 | static std::pair<GpgME::DecryptionResult,GpgME::VerificationResult> decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata) | ||
95 | { | ||
96 | QGpgME::QByteArrayDataProvider out; | ||
97 | GpgME::Data wrapper(&out); | ||
98 | const std::pair<GpgME::DecryptionResult,GpgME::VerificationResult> res = gpgContext(protocol)->decryptAndVerify(fromBA(ciphertext), wrapper); | ||
99 | outdata = out.data(); | ||
100 | return res; | ||
101 | } | ||
102 | |||
103 | static void importKeys(CryptoProtocol protocol, const QByteArray &certData) | ||
104 | { | ||
105 | gpgContext(protocol)->importKeys(fromBA(certData)); | ||
106 | } | ||
107 | |||
108 | static GpgME::KeyListResult listKeys(CryptoProtocol protocol, const char *pattern, bool secretOnly, std::vector<GpgME::Key> &keys) { | ||
109 | auto ctx = gpgContext(protocol); | ||
110 | if (const GpgME::Error err = ctx->startKeyListing(pattern, secretOnly)) { | ||
111 | return GpgME::KeyListResult( 0, err ); | ||
112 | } | ||
113 | |||
114 | GpgME::Error err; | ||
115 | do { | ||
116 | keys.push_back( ctx->nextKey(err)); | ||
117 | } while ( !err ); | ||
118 | |||
119 | keys.pop_back(); | ||
120 | |||
121 | const GpgME::KeyListResult result = ctx->endKeyListing(); | ||
122 | ctx->cancelPendingOperation(); | ||
123 | return result; | ||
124 | } | ||
125 | |||
126 | 36 | ||
127 | //------MessagePart----------------------- | 37 | //------MessagePart----------------------- |
128 | MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node) | 38 | MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node) |
@@ -771,10 +681,7 @@ SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, | |||
771 | { | 681 | { |
772 | mMetaData.isSigned = true; | 682 | mMetaData.isSigned = true; |
773 | mMetaData.isGoodSignature = false; | 683 | mMetaData.isGoodSignature = false; |
774 | //FIXME | ||
775 | // mMetaData.keyTrust = GpgME::Signature::Unknown; | ||
776 | mMetaData.status = tr("Wrong Crypto Plug-In."); | 684 | mMetaData.status = tr("Wrong Crypto Plug-In."); |
777 | mMetaData.status_code = GPGME_SIG_STAT_NONE; | ||
778 | } | 685 | } |
779 | 686 | ||
780 | SignedMessagePart::~SignedMessagePart() | 687 | SignedMessagePart::~SignedMessagePart() |
@@ -792,99 +699,65 @@ bool SignedMessagePart::isSigned() const | |||
792 | return mMetaData.isSigned; | 699 | return mMetaData.isSigned; |
793 | } | 700 | } |
794 | 701 | ||
795 | static int signatureToStatus(const GpgME::Signature &sig) | ||
796 | { | ||
797 | switch (sig.status().code()) { | ||
798 | case GPG_ERR_NO_ERROR: | ||
799 | return GPGME_SIG_STAT_GOOD; | ||
800 | case GPG_ERR_BAD_SIGNATURE: | ||
801 | return GPGME_SIG_STAT_BAD; | ||
802 | case GPG_ERR_NO_PUBKEY: | ||
803 | return GPGME_SIG_STAT_NOKEY; | ||
804 | case GPG_ERR_NO_DATA: | ||
805 | return GPGME_SIG_STAT_NOSIG; | ||
806 | case GPG_ERR_SIG_EXPIRED: | ||
807 | return GPGME_SIG_STAT_GOOD_EXP; | ||
808 | case GPG_ERR_KEY_EXPIRED: | ||
809 | return GPGME_SIG_STAT_GOOD_EXPKEY; | ||
810 | default: | ||
811 | return GPGME_SIG_STAT_ERROR; | ||
812 | } | ||
813 | } | ||
814 | 702 | ||
815 | QString prettifyDN(const char *uid) | 703 | static QString prettifyDN(const char *uid) |
816 | { | 704 | { |
817 | return QGpgME::DN(uid).prettyDN(); | 705 | // We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for. |
706 | return QString::fromUtf8(uid); | ||
818 | } | 707 | } |
819 | 708 | ||
820 | void SignedMessagePart::sigStatusToMetaData(const GpgME::Signature &signature) | 709 | void SignedMessagePart::sigStatusToMetaData(const Signature &signature) |
821 | { | 710 | { |
822 | GpgME::Key key; | 711 | mMetaData.isGoodSignature = signature.status & GPG_ERR_NO_ERROR; |
823 | mMetaData.status_code = signatureToStatus(signature); | ||
824 | mMetaData.isGoodSignature = mMetaData.status_code & GPGME_SIG_STAT_GOOD; | ||
825 | // save extended signature status flags | 712 | // save extended signature status flags |
826 | auto summary = signature.summary(); | 713 | auto summary = signature.summary; |
827 | mMetaData.keyMissing = summary & GpgME::Signature::KeyMissing; | 714 | mMetaData.keyMissing = summary & GPGME_SIGSUM_KEY_MISSING; |
828 | mMetaData.keyExpired = summary & GpgME::Signature::KeyExpired; | 715 | mMetaData.keyExpired = summary & GPGME_SIGSUM_KEY_EXPIRED; |
829 | mMetaData.keyRevoked = summary & GpgME::Signature::KeyRevoked; | 716 | mMetaData.keyRevoked = summary & GPGME_SIGSUM_KEY_REVOKED; |
830 | mMetaData.sigExpired = summary & GpgME::Signature::SigExpired; | 717 | mMetaData.sigExpired = summary & GPGME_SIGSUM_SIG_EXPIRED; |
831 | mMetaData.crlMissing = summary & GpgME::Signature::CrlMissing; | 718 | mMetaData.crlMissing = summary & GPGME_SIGSUM_CRL_MISSING; |
832 | mMetaData.crlTooOld = summary & GpgME::Signature::CrlTooOld; | 719 | mMetaData.crlTooOld = summary & GPGME_SIGSUM_CRL_TOO_OLD; |
833 | 720 | ||
834 | if (mMetaData.isGoodSignature && !key.keyID()) { | 721 | Key key; |
722 | if (mMetaData.isGoodSignature) { | ||
835 | // Search for the key by its fingerprint so that we can check for trust etc. | 723 | // Search for the key by its fingerprint so that we can check for trust etc. |
836 | std::vector<GpgME::Key> found_keys; | 724 | const auto keys = findKeys({signature.fingerprint}); |
837 | auto res = listKeys(mProtocol, signature.fingerprint(), false, found_keys); | 725 | if (keys.size() > 1) { |
838 | if (res.error()) { | ||
839 | qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); | ||
840 | } | ||
841 | if (found_keys.size() > 1) { | ||
842 | // Should not happen | 726 | // Should not happen |
843 | qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); | 727 | qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint; |
844 | } | 728 | } |
845 | if (found_keys.empty()) { | 729 | if (keys.empty()) { |
846 | // Should not happen at this point | 730 | // Should not happen at this point |
847 | qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); | 731 | qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint; |
848 | } else { | 732 | } else { |
849 | key = found_keys[0]; | 733 | key = keys[0]; |
850 | } | 734 | } |
851 | } | 735 | } |
852 | 736 | ||
853 | if (key.keyID()) { | 737 | mMetaData.keyId = key.keyId; |
854 | mMetaData.keyId = key.keyID(); | ||
855 | } | ||
856 | if (mMetaData.keyId.isEmpty()) { | 738 | if (mMetaData.keyId.isEmpty()) { |
857 | mMetaData.keyId = signature.fingerprint(); | 739 | mMetaData.keyId = signature.fingerprint; |
858 | } | 740 | } |
859 | auto keyTrust = signature.validity(); | 741 | mMetaData.keyIsTrusted = signature.validity == GPGME_VALIDITY_FULL || signature.validity == GPGME_VALIDITY_ULTIMATE; |
860 | mMetaData.keyIsTrusted = keyTrust & GpgME::Signature::Full || keyTrust & GpgME::Signature::Ultimate; | 742 | if (!key.userIds.empty()) { |
861 | if (key.numUserIDs() > 0 && key.userID(0).id()) { | 743 | mMetaData.signer = prettifyDN(key.userIds[0].id); |
862 | mMetaData.signer = prettifyDN(key.userID(0).id()); | 744 | } |
863 | } | 745 | for (const auto &userId : key.userIds) { |
864 | for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) { | 746 | QString email = QString::fromUtf8(userId.email); |
865 | // The following if /should/ always result in TRUE but we | 747 | // ### work around gpgme 0.3.QString text() const Q_DECL_OVERRIDE;x / cryptplug bug where the |
866 | // won't trust implicitely the plugin that gave us these data. | 748 | // ### email addresses are specified as angle-addr, not addr-spec: |
867 | if (key.userID(iMail).email()) { | 749 | if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { |
868 | QString email = QString::fromUtf8(key.userID(iMail).email()); | 750 | email = email.mid(1, email.length() - 2); |
869 | // ### work around gpgme 0.3.QString text() const Q_DECL_OVERRIDE;x / cryptplug bug where the | 751 | } |
870 | // ### email addresses are specified as angle-addr, not addr-spec: | 752 | if (!email.isEmpty()) { |
871 | if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { | 753 | mMetaData.signerMailAddresses.append(email); |
872 | email = email.mid(1, email.length() - 2); | ||
873 | } | ||
874 | if (!email.isEmpty()) { | ||
875 | mMetaData.signerMailAddresses.append(email); | ||
876 | } | ||
877 | } | 754 | } |
878 | } | 755 | } |
879 | 756 | ||
880 | if (signature.creationTime()) { | 757 | mMetaData.creationTime = signature.creationTime; |
881 | mMetaData.creationTime.setTime_t(signature.creationTime()); | ||
882 | } else { | ||
883 | mMetaData.creationTime = QDateTime(); | ||
884 | } | ||
885 | if (mMetaData.signer.isEmpty()) { | 758 | if (mMetaData.signer.isEmpty()) { |
886 | if (key.numUserIDs() > 0 && key.userID(0).name()) { | 759 | if (!key.userIds.empty()) { |
887 | mMetaData.signer = prettifyDN(key.userID(0).name()); | 760 | mMetaData.signer = prettifyDN(key.userIds[0].name); |
888 | } | 761 | } |
889 | if (!mMetaData.signerMailAddresses.empty()) { | 762 | if (!mMetaData.signerMailAddresses.empty()) { |
890 | if (mMetaData.signer.isEmpty()) { | 763 | if (mMetaData.signer.isEmpty()) { |
@@ -924,18 +797,13 @@ void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime: | |||
924 | } | 797 | } |
925 | 798 | ||
926 | mMetaData.isSigned = false; | 799 | mMetaData.isSigned = false; |
927 | //FIXME | ||
928 | // mMetaData.keyTrust = GpgME::Signature::Unknown; | ||
929 | mMetaData.status = tr("Wrong Crypto Plug-In."); | 800 | mMetaData.status = tr("Wrong Crypto Plug-In."); |
930 | mMetaData.status_code = GPGME_SIG_STAT_NONE; | ||
931 | 801 | ||
932 | if (!signature.isEmpty()) { | 802 | if (!signature.isEmpty()) { |
933 | auto result = verifyDetachedSignature(mProtocol, signature, text); | 803 | setVerificationResult(verifyDetachedSignature(mProtocol, signature, text), false, text); |
934 | setVerificationResult(result, false, text); | ||
935 | } else { | 804 | } else { |
936 | QByteArray outdata; | 805 | QByteArray outdata; |
937 | auto result = verifyOpaqueSignature(mProtocol, text, outdata); | 806 | setVerificationResult(verifyOpaqueSignature(mProtocol, text, outdata), false, outdata); |
938 | setVerificationResult(result, false, outdata); | ||
939 | } | 807 | } |
940 | 808 | ||
941 | if (!mMetaData.isSigned) { | 809 | if (!mMetaData.isSigned) { |
@@ -943,11 +811,11 @@ void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime: | |||
943 | } | 811 | } |
944 | } | 812 | } |
945 | 813 | ||
946 | void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, bool parseText, const QByteArray &plainText) | 814 | void SignedMessagePart::setVerificationResult(const VerificationResult &result, bool parseText, const QByteArray &plainText) |
947 | { | 815 | { |
948 | auto signatures = result.signatures(); | 816 | auto signatures = result.signatures; |
949 | // FIXME | 817 | // FIXME |
950 | // mMetaData.auditLogError = result.error(); | 818 | // mMetaData.auditLogError = result.error; |
951 | if (!signatures.empty()) { | 819 | if (!signatures.empty()) { |
952 | mMetaData.isSigned = true; | 820 | mMetaData.isSigned = true; |
953 | sigStatusToMetaData(signatures.front()); | 821 | sigStatusToMetaData(signatures.front()); |
@@ -994,10 +862,7 @@ EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, | |||
994 | mMetaData.isGoodSignature = false; | 862 | mMetaData.isGoodSignature = false; |
995 | mMetaData.isEncrypted = false; | 863 | mMetaData.isEncrypted = false; |
996 | mMetaData.isDecryptable = false; | 864 | mMetaData.isDecryptable = false; |
997 | //FIXME | ||
998 | // mMetaData.keyTrust = GpgME::Signature::Unknown; | ||
999 | mMetaData.status = tr("Wrong Crypto Plug-In."); | 865 | mMetaData.status = tr("Wrong Crypto Plug-In."); |
1000 | mMetaData.status_code = GPGME_SIG_STAT_NONE; | ||
1001 | } | 866 | } |
1002 | 867 | ||
1003 | EncryptedMessagePart::~EncryptedMessagePart() | 868 | EncryptedMessagePart::~EncryptedMessagePart() |
@@ -1055,59 +920,61 @@ bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) | |||
1055 | 920 | ||
1056 | const QByteArray ciphertext = data.decodedContent(); | 921 | const QByteArray ciphertext = data.decodedContent(); |
1057 | QByteArray plainText; | 922 | QByteArray plainText; |
1058 | const auto res = decryptAndVerify(mProtocol, ciphertext, plainText); | 923 | DecryptionResult decryptResult; |
1059 | const GpgME::DecryptionResult &decryptResult = res.first; | 924 | VerificationResult verifyResult; |
1060 | const GpgME::VerificationResult &verifyResult = res.second; | 925 | std::tie(decryptResult, verifyResult) = decryptAndVerify(mProtocol, ciphertext, plainText); |
1061 | mMetaData.isSigned = verifyResult.signatures().size() > 0; | 926 | mMetaData.isSigned = verifyResult.signatures.size() > 0; |
1062 | 927 | ||
1063 | if (verifyResult.signatures().size() > 0) { | 928 | if (verifyResult.signatures.size() > 0) { |
1064 | //We simply attach a signed message part to indicate that this content is also signed | 929 | //We simply attach a signed message part to indicate that this content is also signed |
1065 | auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, QString::fromUtf8(plainText), mProtocol, mFromAddress, nullptr, nullptr)); | 930 | auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, QString::fromUtf8(plainText), mProtocol, mFromAddress, nullptr, nullptr)); |
1066 | subPart->setVerificationResult(verifyResult, true, plainText); | 931 | subPart->setVerificationResult(verifyResult, true, plainText); |
1067 | appendSubPart(subPart); | 932 | appendSubPart(subPart); |
1068 | } | 933 | } |
1069 | 934 | ||
1070 | if (decryptResult.error() && mMetaData.isSigned) { | 935 | if (decryptResult.error && mMetaData.isSigned) { |
1071 | //Only a signed part | 936 | //Only a signed part |
1072 | mMetaData.isEncrypted = false; | 937 | mMetaData.isEncrypted = false; |
1073 | mDecryptedData = plainText; | 938 | mDecryptedData = plainText; |
1074 | return true; | 939 | return true; |
1075 | } | 940 | } |
1076 | 941 | ||
1077 | if (mMetaData.isEncrypted && decryptResult.numRecipients() > 0) { | 942 | if (mMetaData.isEncrypted && decryptResult.recipients.size()) { |
1078 | mMetaData.keyId = decryptResult.recipient(0).keyID(); | 943 | mMetaData.keyId = decryptResult.recipients.at(0).keyId; |
1079 | } | 944 | } |
1080 | 945 | ||
1081 | if (!decryptResult.error()) { | 946 | if (!decryptResult.error) { |
1082 | mDecryptedData = plainText; | 947 | mDecryptedData = plainText; |
1083 | setText(QString::fromUtf8(mDecryptedData.constData())); | 948 | setText(QString::fromUtf8(mDecryptedData.constData())); |
1084 | } else { | 949 | } else { |
1085 | mMetaData.isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; | 950 | const auto errorCode = decryptResult.error.errorCode(); |
1086 | mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); | 951 | mMetaData.isEncrypted = errorCode != GPG_ERR_NO_DATA; |
1087 | 952 | qWarning() << "Failed to decrypt : " << decryptResult.error; | |
1088 | std::stringstream ss; | 953 | |
1089 | ss << decryptResult << '\n' << verifyResult; | 954 | const bool noSecretKeyAvilable = [&] { |
1090 | qWarning() << "Decryption failed: " << ss.str().c_str(); | 955 | foreach (const auto &recipient, decryptResult.recipients) { |
1091 | 956 | if (!(recipient.status.errorCode() == GPG_ERR_NO_SECKEY)) { | |
1092 | bool passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; | 957 | return false; |
1093 | 958 | } | |
1094 | auto noSecKey = true; | 959 | } |
1095 | foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { | 960 | return true; |
1096 | noSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); | 961 | }(); |
1097 | } | 962 | bool passphraseError = errorCode == GPG_ERR_CANCELED || errorCode == GPG_ERR_NO_SECKEY; |
1098 | if (!passphraseError && !noSecKey) { // GpgME do not detect passphrase error correctly | 963 | //We only get a decryption failed error when we enter the wrong passphrase.... |
964 | if (!passphraseError && !noSecretKeyAvilable) { | ||
1099 | passphraseError = true; | 965 | passphraseError = true; |
1100 | } | 966 | } |
1101 | 967 | ||
1102 | if(noSecKey) { | 968 | if(noSecretKeyAvilable) { |
1103 | mError = NoKeyError; | 969 | mError = NoKeyError; |
1104 | mMetaData.errorText = tr("Could not decrypt the data. ") + tr("No key found for recepients."); | 970 | mMetaData.errorText = tr("Could not decrypt the data. ") + tr("No key found for recepients."); |
1105 | } else if (passphraseError) { | 971 | } else if (passphraseError) { |
1106 | mError = PassphraseError; | 972 | mError = PassphraseError; |
973 | // mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); | ||
1107 | } else { | 974 | } else { |
1108 | mError = UnknownError; | 975 | mError = UnknownError; |
1109 | mMetaData.errorText = tr("Could not decrypt the data. ") | 976 | mMetaData.errorText = tr("Could not decrypt the data. "); |
1110 | + tr("Error: %1").arg(mMetaData.errorText); | 977 | // + tr("Error: %1").arg(QString::fromLocal8Bit(decryptResult.error().asString())); |
1111 | } | 978 | } |
1112 | return false; | 979 | return false; |
1113 | } | 980 | } |