diff --git a/autotests/keyresolvercoretest.cpp b/autotests/keyresolvercoretest.cpp index bbc641d2b..dd42f7996 100644 --- a/autotests/keyresolvercoretest.cpp +++ b/autotests/keyresolvercoretest.cpp @@ -1,1412 +1,1412 @@ /* autotests/keyresolvercoretest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace QTest { template<> inline bool qCompare(GpgME::UserID::Validity const &t1, GpgME::UserID::Validity const &t2, const char *actual, const char *expected, const char *file, int line) { return qCompare(int(t1), int(t2), actual, expected, file, line); } template<> inline char *toString(const KeyResolverCore::SolutionFlags &flags) { QStringList v; if (flags & KeyResolverCore::AllResolved) { v.append(QStringLiteral("KeyResolverCore::AllResolved")); } else { v.append(QStringLiteral("KeyResolverCore::SomeUnresolved")); } if ((flags & KeyResolverCore::MixedProtocols) == KeyResolverCore::MixedProtocols) { v.append(QStringLiteral("KeyResolverCore::MixedProtocols")); } else if (flags & KeyResolverCore::OpenPGPOnly) { v.append(QStringLiteral("KeyResolverCore::OpenPGPOnly")); } else if (flags & KeyResolverCore::CMSOnly) { v.append(QStringLiteral("KeyResolverCore::CMSOnly")); } return qstrdup(v.join(QStringLiteral(" | ")).toLocal8Bit().constData()); } template<> inline bool qCompare(int const &t1, KeyResolverCore::SolutionFlags const &t2, const char *actual, const char *expected, const char *file, int line) { return qCompare(static_cast(t1), t2, actual, expected, file, line); } template<> inline char *toString(const GpgME::Protocol &t) { return qstrdup(Formatting::displayName(t).toLocal8Bit().constData()); } template<> inline bool qCompare(GpgME::Protocol const &t1, GpgME::Protocol const &t2, const char *actual, const char *expected, const char *file, int line) { return compare_helper(t1 == t2, "Compared values are not the same", toString(t1), toString(t2), actual, expected, file, line); } } namespace { KeyGroup createGroup(const QString &name, const std::vector &keys = std::vector(), KeyGroup::Source source = KeyGroup::ApplicationConfig, const QString &configName = QString()) { const KeyGroup::Id groupId = ((source == KeyGroup::ApplicationConfig) // ? (configName.isEmpty() ? name : configName) : name); KeyGroup g(groupId, name, keys, source); return g; } } class KeyResolverCoreTest : public QObject { Q_OBJECT private Q_SLOTS: void init() { mGnupgHome = QTest::qExtractTestData(QStringLiteral("/fixtures/keyresolvercoretest")); qputenv("GNUPGHOME", mGnupgHome->path().toLocal8Bit()); // hold a reference to the key cache to avoid rebuilding while the test is running mKeyCache = KeyCache::instance(); // make sure that the key cache has been populated (void)mKeyCache->keys(); } void cleanup() { // verify that nobody else holds a reference to the key cache QVERIFY(mKeyCache.use_count() == 1); mKeyCache.reset(); // kill all running gpg daemons (void)QProcess::execute(QStringLiteral("gpgconf"), {"--kill", "all"}); mGnupgHome.reset(); qunsetenv("GNUPGHOME"); } void test_verify_test_keys() { { const Key openpgp = testKey("sender-mixed@example.net", OpenPGP); QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); const Key smime = testKey("sender-mixed@example.net", CMS); QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("sender-openpgp@example.net", OpenPGP); QVERIFY(openpgp.hasSecret() && openpgp.canEncrypt() && openpgp.canSign()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); } { const Key smime = testKey("sender-smime@example.net", CMS); QVERIFY(smime.hasSecret() && smime.canEncrypt() && smime.canSign()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("prefer-openpgp@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Ultimate); const Key smime = testKey("prefer-openpgp@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("full-validity@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Full); const Key smime = testKey("full-validity@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("prefer-smime@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Marginal); const Key smime = testKey("prefer-smime@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } { const Key openpgp = testKey("openpgp-only@example.net", OpenPGP); QVERIFY(openpgp.canEncrypt()); QCOMPARE(openpgp.userID(0).validity(), UserID::Full); const Key smime = testKey("openpgp-only@example.net", CMS); QVERIFY(smime.isNull()); } { const Key openpgp = testKey("smime-only@example.net", OpenPGP); QVERIFY(openpgp.isNull()); const Key smime = testKey("smime-only@example.net", CMS); QVERIFY(smime.canEncrypt()); QCOMPARE(smime.userID(0).validity(), UserID::Full); } } void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.protocol, CMS); QCOMPARE(result.alternative.signingKeys.size(), 1); QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(result.alternative.encryptionKeys.size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } void test_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_openpgp() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setPreferredProtocol(OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.protocol, CMS); QCOMPARE(result.alternative.signingKeys.size(), 1); QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(result.alternative.encryptionKeys.size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } void test_smime_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_smime() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(result.alternative.protocol, OpenPGP); QCOMPARE(result.alternative.signingKeys.size(), 1); QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.encryptionKeys.size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); } void test_in_mixed_mode_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); // no alternative solution is proposed QCOMPARE(result.alternative.protocol, UnknownProtocol); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_in_mixed_mode_openpgp_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_openpgp() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setPreferredProtocol(OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); // no alternative solution is proposed QCOMPARE(result.alternative.protocol, UnknownProtocol); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_in_mixed_mode_smime_is_used_if_openpgp_only_and_smime_only_are_both_possible_with_preference_for_smime() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); // no alternative solution is proposed QCOMPARE(result.alternative.protocol, UnknownProtocol); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_in_mixed_mode_keys_with_higher_validity_are_preferred_if_both_protocols_are_needed() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net", "prefer-openpgp@example.net", "prefer-smime@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.protocol, UnknownProtocol); QCOMPARE(result.solution.encryptionKeys.size(), 4); QVERIFY(result.solution.encryptionKeys.contains("sender-openpgp@example.net")); QVERIFY(result.solution.encryptionKeys.contains("sender-smime@example.net")); QCOMPARE(result.solution.encryptionKeys.value("prefer-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("prefer-openpgp@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.value("prefer-smime@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("prefer-smime@example.net")[0].primaryFingerprint(), testKey("prefer-smime@example.net", CMS).primaryFingerprint()); // no alternative solution is proposed QCOMPARE(result.alternative.protocol, UnknownProtocol); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_reports_unresolved_addresses_if_both_protocols_are_allowed_but_no_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"unknown@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("unknown@example.net").size(), 0); } void test_reports_unresolved_addresses_if_openpgp_is_requested_and_no_openpgp_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, OpenPGP); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.size(), 2); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 0); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_reports_unresolved_addresses_if_smime_is_requested_and_no_smime_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, CMS); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.encryptionKeys.size(), 2); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 0); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_reports_unresolved_addresses_if_mixed_protocols_are_not_allowed_but_needed() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setAllowMixedProtocols(false); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.size(), 2); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 0); QCOMPARE(result.alternative.encryptionKeys.size(), 2); QCOMPARE(result.alternative.encryptionKeys.value("sender-openpgp@example.net").size(), 0); QCOMPARE(result.alternative.encryptionKeys.value("sender-smime@example.net").size(), 1); } void test_openpgp_overrides_are_used_if_both_protocols_are_allowed() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", CMS).primaryFingerprint()); } void test_openpgp_overrides_are_used_if_openpgp_only_is_requested() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_openpgp_overrides_are_ignored_if_smime_only_is_requested() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", CMS).primaryFingerprint()); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_smime_overrides_are_used_if_both_protocols_are_allowed_and_smime_is_preferred() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.alternative.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", OpenPGP).primaryFingerprint()); } void test_smime_overrides_are_used_if_smime_only_is_requested() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), override); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_smime_overrides_are_ignored_if_openpgp_only_is_requested() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"full-validity@example.net"}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("full-validity@example.net")[0].primaryFingerprint(), testKey("full-validity@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.encryptionKeys.size(), 0); } void test_overrides_for_wrong_protocol_are_ignored() { const QString override1 = testKey("full-validity@example.net", CMS).primaryFingerprint(); const QString override2 = testKey("full-validity@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); resolver.setOverrideKeys({{OpenPGP, {{QStringLiteral("Needs to be normalized "), {override1}}}}}); resolver.setOverrideKeys({{CMS, {{QStringLiteral("Needs to be normalized "), {override2}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_openpgp_only_common_overrides_are_used_for_openpgp() { const QString override = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-openpgp@example.net"}); resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(), override); } void test_smime_only_common_overrides_are_used_for_smime() { const QString override = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-smime@example.net"}); resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("Needs to be normalized "), {override}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(), override); } void test_mixed_protocol_common_overrides_override_protocol_specific_resolution() { const QString override1 = testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(); const QString override2 = testKey("prefer-smime@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setOverrideKeys({{UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), {override1, override2}}}}}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 2); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), override1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[1].primaryFingerprint(), override2); } void test_common_overrides_override_protocol_specific_overrides() { const QString override1 = testKey("full-validity@example.net", OpenPGP).primaryFingerprint(); const QString override2 = testKey("full-validity@example.net", CMS).primaryFingerprint(); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); resolver.setRecipients({"sender-openpgp@example.net", "sender-smime@example.net"}); resolver.setOverrideKeys({ { OpenPGP, { {QStringLiteral("sender-openpgp@example.net"), {testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()}}, }, }, { CMS, { {QStringLiteral("sender-smime@example.net"), {testKey("prefer-smime@example.net", CMS).primaryFingerprint()}}, }, }, { UnknownProtocol, { {QStringLiteral("sender-openpgp@example.net"), {override1}}, {QStringLiteral("sender-smime@example.net"), {override2}}, }, }, }); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-openpgp@example.net")[0].primaryFingerprint(), override1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-smime@example.net")[0].primaryFingerprint(), override2); } void test_reports_failure_if_openpgp_is_requested_but_common_overrides_require_smime() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, OpenPGP); resolver.setRecipients({"sender-mixed@example.net"}); resolver.setOverrideKeys({{ UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), {testKey("prefer-smime@example.net", CMS).primaryFingerprint()}}}, }}); const auto result = resolver.resolve(); QVERIFY(result.flags & KeyResolverCore::Error); } void test_reports_failure_if_smime_is_requested_but_common_overrides_require_openpgp() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, CMS); resolver.setRecipients({"sender-mixed@example.net"}); resolver.setOverrideKeys({{ UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), {testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()}}}, }}); const auto result = resolver.resolve(); QVERIFY(result.flags & KeyResolverCore::Error); } void test_reports_failure_if_mixed_protocols_are_not_allowed_but_required_by_common_overrides() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setAllowMixedProtocols(false); resolver.setRecipients({"sender-mixed@example.net"}); resolver.setOverrideKeys({{ UnknownProtocol, {{QStringLiteral("sender-mixed@example.net"), { testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint(), testKey("prefer-smime@example.net", CMS).primaryFingerprint(), }}}, }}); const auto result = resolver.resolve(); QVERIFY(result.flags & KeyResolverCore::Error); } void test_groups__openpgp_only_mode__ignores_non_openpgp_only_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, OpenPGP); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__smime_only_mode__ignores_non_smime_only_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false, CMS); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-smime@example.net", CMS).primaryFingerprint()); } void test_groups__single_protocol_mode__ignores_mixed_protocol_groups() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setAllowMixedProtocols(false); resolver.setRecipients({"sender-mixed@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net")[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); } void test_groups__mixed_mode__single_protocol_groups_are_preferred_over_mixed_protocol_groups() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("prefer-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__mixed_mode__openpgp_only_group_preferred_over_mixed_protocol_group() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups__mixed_mode__smime_only_group_preferred_over_mixed_protocol_group() { const std::vector groups = { createGroup("group@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), createGroup("group@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net")[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups__mixed_mode__mixed_protocol_groups_are_used() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"sender-mixed@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.protocol, UnknownProtocol); QCOMPARE(result.solution.encryptionKeys.value("sender-mixed@example.net").size(), 2); } void test_reports_unresolved_addresses_if_both_protocols_are_allowed_but_no_signing_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setSender(QStringLiteral("unknown@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 0); } void test_reports_unresolved_addresses_if_openpgp_is_requested_and_no_openpgp_signing_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-smime@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 0); } void test_reports_unresolved_addresses_if_smime_is_requested_and_no_smime_signing_keys_are_found_for_an_address() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-openpgp@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 0); } void test_reports_unresolved_addresses_if_both_protocols_are_needed_but_no_signing_keys_are_found_for_smime() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-openpgp@example.net")); resolver.setRecipients({"smime-only@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.protocol, UnknownProtocol); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_reports_unresolved_addresses_if_both_protocols_are_needed_but_no_signing_keys_are_found_for_openpgp() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/true); resolver.setSender(QStringLiteral("sender-smime@example.net")); resolver.setRecipients({"openpgp-only@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::MixedProtocols); QCOMPARE(result.solution.protocol, UnknownProtocol); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__openpgp_only_mode__prefers_groups_over_keys() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups_for_signing_key__openpgp_only_mode__prefers_single_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), createGroup("sender-alias@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), createGroup("sender-alias@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups_for_signing_key__openpgp_only_mode__takes_key_of_mixed_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); } void test_groups_for_signing_key__smime_only_mode__prefers_groups_over_keys() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__smime_only_mode__prefers_single_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), createGroup("sender-alias@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), createGroup("sender-alias@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__smime_only_mode__takes_key_of_mixed_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__single_protocol_mode__prefers_groups_over_keys() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.signingKeys.size(), 1); QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__single_protocol_mode__prefers_single_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), createGroup("sender-alias@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), createGroup("sender-alias@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setAllowMixedProtocols(false); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); QCOMPARE(result.alternative.signingKeys.size(), 1); QCOMPARE(result.alternative.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__mixed_mode__prefers_groups_over_keys() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_groups_over_keys() { const std::vector groups = { createGroup("sender-mixed@example.net", { testKey("sender-openpgp@example.net", OpenPGP), testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups_for_signing_key__mixed_mode__prefers_single_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), createGroup("sender-alias@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), createGroup("sender-alias@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-openpgp@example.net", OpenPGP).primaryFingerprint()); } void test_groups_for_signing_key__mixed_mode_with_smime_preferred__prefers_single_protocol_groups() { const std::vector groups = { createGroup("sender-alias@example.net", { testKey("sender-mixed@example.net", OpenPGP), testKey("sender-mixed@example.net", CMS), }), createGroup("sender-alias@example.net", { testKey("sender-openpgp@example.net", OpenPGP), }), createGroup("sender-alias@example.net", { testKey("sender-smime@example.net", CMS), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setPreferredProtocol(CMS); resolver.setSender(QStringLiteral("sender-alias@example.net")); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-smime@example.net", CMS).primaryFingerprint()); } void test_groups__group_with_marginally_valid_key_is_accepted_by_default() { const std::vector groups = { createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), testKey("prefer-smime@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setPreferredProtocol(OpenPGP); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 2); } void test_groups__group_with_marginally_valid_key_is_ignored_if_full_validity_required() { const std::vector groups = { createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), testKey("prefer-smime@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setMinimumValidity(UserID::Full); resolver.setPreferredProtocol(OpenPGP); resolver.setRecipients({"group@example.net"}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 0); } void test_groups__group_with_marginally_valid_key_is_ignored_in_de_vs_mode() { const std::vector groups = { createGroup("group@example.net", { testKey("prefer-openpgp@example.net", OpenPGP), testKey("prefer-smime@example.net", OpenPGP), }), }; KeyCache::mutableInstance()->setGroups(groups); KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setPreferredProtocol(OpenPGP); resolver.setRecipients({"group@example.net"}); Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")}; Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1}; const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::SomeUnresolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.encryptionKeys.size(), 1); QCOMPARE(result.solution.encryptionKeys.value("group@example.net").size(), 0); } void test_sender_is_set__encrypt_only_mode() { KeyResolverCore resolver(/*encrypt=*/true, /*sign=*/false); resolver.setRecipients({"prefer-openpgp@example.net", "prefer-smime@example.net"}); resolver.setSender(QStringLiteral("sender-mixed@example.net")); const auto result = resolver.resolve(); - QCOMPARE(resolver.normalizedSender(), QLatin1String{"sender-mixed@example.net"}); + QCOMPARE(resolver.normalizedSender(), QLatin1StringView{"sender-mixed@example.net"}); } void test_setSigningKeys_is_preferred() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setSender(QStringLiteral("sender-openpgp@example.net")); resolver.setSigningKeys({testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); } void test_setSigningKeys_is_preferred_smime() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true); resolver.setSender(QStringLiteral("sender-smime@example.net")); resolver.setSigningKeys({testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()}); resolver.setPreferredProtocol(CMS); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } void test_setSigningKeys_is_preferred_only_openpgp() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, OpenPGP); resolver.setSender(QStringLiteral("sender-openpgp@example.net")); resolver.setSigningKeys({testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()}); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::OpenPGPOnly); QCOMPARE(result.solution.protocol, OpenPGP); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", OpenPGP).primaryFingerprint()); } void test_setSigningKeys_is_preferred_only_smime() { KeyResolverCore resolver(/*encrypt=*/false, /*sign=*/true, CMS); resolver.setSender(QStringLiteral("sender-smime@example.net")); resolver.setSigningKeys({testKey("sender-mixed@example.net", CMS).primaryFingerprint()}); resolver.setPreferredProtocol(CMS); const auto result = resolver.resolve(); QCOMPARE(result.flags & KeyResolverCore::ResolvedMask, KeyResolverCore::AllResolved); QCOMPARE(result.flags & KeyResolverCore::ProtocolsMask, KeyResolverCore::CMSOnly); QCOMPARE(result.solution.protocol, CMS); QCOMPARE(result.solution.signingKeys.size(), 1); QCOMPARE(result.solution.signingKeys[0].primaryFingerprint(), testKey("sender-mixed@example.net", CMS).primaryFingerprint()); } private: Key testKey(const char *email, Protocol protocol = UnknownProtocol) { const std::vector keys = KeyCache::instance()->findByEMailAddress(email); for (const auto &key : keys) { if (protocol == UnknownProtocol || key.protocol() == protocol) { return key; } } qWarning() << "No" << Formatting::displayName(protocol) << "test key found for" << email; return {}; } private: QSharedPointer mGnupgHome; std::shared_ptr mKeyCache; }; QTEST_MAIN(KeyResolverCoreTest) #include "keyresolvercoretest.moc" diff --git a/autotests/keyserverconfigtest.cpp b/autotests/keyserverconfigtest.cpp index cf24098f3..6e564354a 100644 --- a/autotests/keyserverconfigtest.cpp +++ b/autotests/keyserverconfigtest.cpp @@ -1,286 +1,286 @@ /* autotests/keyserverconfigtest.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include using namespace Kleo; namespace QTest { template<> inline char *toString(const KeyserverAuthentication &t) { switch (t) { case KeyserverAuthentication::Anonymous: return qstrdup("Anonymous"); case KeyserverAuthentication::ActiveDirectory: return qstrdup("ActiveDirectory"); case KeyserverAuthentication::Password: return qstrdup("Password"); default: return qstrdup((std::string("invalid value (") + std::to_string(static_cast(t)) + ")").c_str()); } } template<> inline char *toString(const KeyserverConnection &t) { switch (t) { case KeyserverConnection::Default: return qstrdup("Default"); case KeyserverConnection::Plain: return qstrdup("Plain"); case KeyserverConnection::UseSTARTTLS: return qstrdup("UseSTARTTLS"); case KeyserverConnection::TunnelThroughTLS: return qstrdup("TunnelThroughTLS"); default: return qstrdup((std::string("invalid value (") + std::to_string(static_cast(t)) + ")").c_str()); } } } class KeyserverConfigTest : public QObject { Q_OBJECT private Q_SLOTS: void test_ldap_keyserver_on_active_directory() { const QUrl url{QStringLiteral("ldap://#ntds")}; auto config = KeyserverConfig::fromUrl(url); QVERIFY(config.host().isEmpty()); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_authentication_via_active_directory() { const QUrl url{QStringLiteral("ldap://ldap.example.net#ntds")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_anonymous_ldap_keyserver() { const QUrl url{QStringLiteral("ldap://ldap.example.net")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); QVERIFY(config.user().isEmpty()); QVERIFY(config.password().isEmpty()); QCOMPARE(config.authentication(), KeyserverAuthentication::Anonymous); QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_password_authentication() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_starttls() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#starttls")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::UseSTARTTLS); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_tls_secured_tunnel() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#ldaptls")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::TunnelThroughTLS); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_explicit_plain_connection() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#plain")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Plain); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_multiple_connection_flags() { // the last flag wins (as in dirmngr/ldapserver.c) const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net#starttls,plain")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Plain); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); // only one connection flag is added const auto expectedUrl = QUrl{QStringLiteral("ldap://user:password@ldap.example.net#plain")}; QCOMPARE(createdUrl, expectedUrl); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_not_normalized_flags() { const QUrl url{QStringLiteral("ldap://ldap.example.net#startTLS, NTDS")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); QCOMPARE(config.connection(), KeyserverConnection::UseSTARTTLS); const auto createdUrl = config.toUrl(); const auto expectedUrl = QUrl{QStringLiteral("ldap://ldap.example.net#starttls,ntds")}; QCOMPARE(createdUrl, expectedUrl); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } void test_ldap_keyserver_with_explicit_port() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net:4242")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), 4242); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Default); QVERIFY(config.ldapBaseDn().isEmpty()); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_base_dn() { const QUrl url{QStringLiteral("ldap://user:password@ldap.example.net?base_dn")}; auto config = KeyserverConfig::fromUrl(url); - QCOMPARE(config.host(), QLatin1String("ldap.example.net")); + QCOMPARE(config.host(), QLatin1StringView("ldap.example.net")); QCOMPARE(config.port(), -1); - QCOMPARE(config.user(), QLatin1String("user")); - QCOMPARE(config.password(), QLatin1String("password")); + QCOMPARE(config.user(), QLatin1StringView("user")); + QCOMPARE(config.password(), QLatin1StringView("password")); QCOMPARE(config.authentication(), KeyserverAuthentication::Password); QCOMPARE(config.connection(), KeyserverConnection::Default); - QCOMPARE(config.ldapBaseDn(), QLatin1String("base_dn")); + QCOMPARE(config.ldapBaseDn(), QLatin1StringView("base_dn")); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, url); QVERIFY(createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_url_with_empty_string_as_user_and_password() { KeyserverConfig config; config.setHost(QStringLiteral("anonymous.example.net")); config.setUser(QStringLiteral("")); config.setPassword(QStringLiteral("")); const auto createdUrl = config.toUrl(); QCOMPARE(createdUrl, QUrl{QStringLiteral("ldap://anonymous.example.net")}); QVERIFY(!createdUrl.hasQuery()); QVERIFY(!createdUrl.hasFragment()); } void test_ldap_keyserver_with_additional_flags() { const QUrl url{QStringLiteral("ldap://ldap.example.net#flag1,StartTLS, Flag2 ,NTDS,flag 3")}; auto config = KeyserverConfig::fromUrl(url); QCOMPARE(config.authentication(), KeyserverAuthentication::ActiveDirectory); QCOMPARE(config.connection(), KeyserverConnection::UseSTARTTLS); const QStringList expectedFlags{"flag1", "flag2", "flag 3"}; QCOMPARE(config.additionalFlags(), expectedFlags); const auto createdUrl = config.toUrl(); const auto expectedUrl = QUrl{QStringLiteral("ldap://ldap.example.net#starttls,ntds,flag1,flag2,flag 3")}; QCOMPARE(createdUrl, expectedUrl); QVERIFY(!createdUrl.hasQuery()); QVERIFY(createdUrl.hasFragment()); } }; QTEST_MAIN(KeyserverConfigTest) #include "keyserverconfigtest.moc" diff --git a/src/kleo/checksumdefinition.cpp b/src/kleo/checksumdefinition.cpp index 9ad2d5f94..582ae90d4 100644 --- a/src/kleo/checksumdefinition.cpp +++ b/src/kleo/checksumdefinition.cpp @@ -1,436 +1,436 @@ /* -*- mode: c++; c-basic-offset:4 -*- checksumdefinition.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "checksumdefinition.h" #include "kleoexception.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef stdin #undef stdin // pah.. #endif using namespace Kleo; static QMutex installPathMutex; Q_GLOBAL_STATIC(QString, _installPath) QString ChecksumDefinition::installPath() { const QMutexLocker locker(&installPathMutex); QString *const ip = _installPath(); if (ip->isEmpty()) { if (QCoreApplication::instance()) { *ip = QCoreApplication::applicationDirPath(); } else { qCWarning(LIBKLEO_LOG) << "checksumdefinition.cpp: installPath() called before QCoreApplication was constructed"; } } return *ip; } void ChecksumDefinition::setInstallPath(const QString &ip) { const QMutexLocker locker(&installPathMutex); *_installPath() = ip; } // Checksum Definition #N groups -static const QLatin1String ID_ENTRY("id"); -static const QLatin1String NAME_ENTRY("Name"); -static const QLatin1String CREATE_COMMAND_ENTRY("create-command"); -static const QLatin1String VERIFY_COMMAND_ENTRY("verify-command"); -static const QLatin1String FILE_PATTERNS_ENTRY("file-patterns"); -static const QLatin1String OUTPUT_FILE_ENTRY("output-file"); -static const QLatin1String FILE_PLACEHOLDER("%f"); -static const QLatin1String INSTALLPATH_PLACEHOLDER("%I"); -static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|"); +static const QLatin1StringView ID_ENTRY("id"); +static const QLatin1StringView NAME_ENTRY("Name"); +static const QLatin1StringView CREATE_COMMAND_ENTRY("create-command"); +static const QLatin1StringView VERIFY_COMMAND_ENTRY("verify-command"); +static const QLatin1StringView FILE_PATTERNS_ENTRY("file-patterns"); +static const QLatin1StringView OUTPUT_FILE_ENTRY("output-file"); +static const QLatin1StringView FILE_PLACEHOLDER("%f"); +static const QLatin1StringView INSTALLPATH_PLACEHOLDER("%I"); +static const QLatin1StringView NULL_SEPARATED_STDIN_INDICATOR("0|"); static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|'); // ChecksumOperations group -static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id"); +static const QLatin1StringView CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id"); namespace { class ChecksumDefinitionError : public Kleo::Exception { const QString m_id; public: ChecksumDefinitionError(const QString &id, const QString &message) : Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in checksum definition %1: %2", id, message), MessageOnly) , m_id(id) { } ~ChecksumDefinitionError() throw() override { } const QString &checksumDefinitionId() const { return m_id; } }; } static QString try_extensions(const QString &path) { static const char exts[][4] = { "", "exe", "bat", "bin", "cmd", }; static const size_t numExts = sizeof exts / sizeof *exts; for (unsigned int i = 0; i < numExts; ++i) { - const QFileInfo fi(path + QLatin1Char('.') + QLatin1String(exts[i])); + const QFileInfo fi(path + QLatin1Char('.') + QLatin1StringView(exts[i])); if (fi.exists()) { return fi.filePath(); } } return QString(); } static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ChecksumDefinition::ArgumentPassingMethod *method) { Q_ASSERT(prefix); Q_ASSERT(suffix); Q_ASSERT(method); KShell::Errors errors; QStringList l; if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) { *method = ChecksumDefinition::NullSeparatedInputFile; cmdline.remove(0, 2); } else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) { *method = ChecksumDefinition::NewlineSeparatedInputFile; cmdline.remove(0, 1); } else { *method = ChecksumDefinition::CommandLine; } if (*method != ChecksumDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) { throw ChecksumDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand)); } cmdline - .replace(FILE_PLACEHOLDER, QLatin1String("__files_go_here__")) // + .replace(FILE_PLACEHOLDER, QLatin1StringView("__files_go_here__")) // .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__")); l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors); l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER); - static const QRegularExpression regExpression(QLatin1String(".*__path_goes_here__.*")); + static const QRegularExpression regExpression(QLatin1StringView(".*__path_goes_here__.*")); if (l.indexOf(regExpression) >= 0) { l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ChecksumDefinition::installPath()); } if (errors == KShell::BadQuoting) { throw ChecksumDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand)); } if (errors == KShell::FoundMeta) { throw ChecksumDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand)); } qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << l; if (l.empty()) { throw ChecksumDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand)); } const QFileInfo fi1(l.front()); if (fi1.isAbsolute()) { *command = try_extensions(l.front()); } else { *command = QStandardPaths::findExecutable(fi1.fileName()); } if (command->isEmpty()) { throw ChecksumDefinitionError(id, i18n("'%1' empty or not found", whichCommand)); } const int idx1 = l.indexOf(FILE_PLACEHOLDER); if (idx1 < 0) { // none -> append *prefix = l.mid(1); } else { *prefix = l.mid(1, idx1 - 1); *suffix = l.mid(idx1 + 1); } switch (*method) { case ChecksumDefinition::CommandLine: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix; break; case ChecksumDefinition::NewlineSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find | " << *command << *prefix; break; case ChecksumDefinition::NullSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find -print0 | " << *command << *prefix; break; case ChecksumDefinition::NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); break; } } namespace { class KConfigBasedChecksumDefinition : public ChecksumDefinition { public: explicit KConfigBasedChecksumDefinition(const KConfigGroup &group) : ChecksumDefinition(group.readEntryUntranslated(ID_ENTRY), group.readEntry(NAME_ENTRY), group.readEntry(OUTPUT_FILE_ENTRY), group.readEntry(FILE_PATTERNS_ENTRY, QStringList())) { if (id().isEmpty()) { throw ChecksumDefinitionError(group.name(), i18n("'id' entry is empty/missing")); } if (outputFileName().isEmpty()) { throw ChecksumDefinitionError(id(), i18n("'output-file' entry is empty/missing")); } if (patterns().empty()) { throw ChecksumDefinitionError(id(), i18n("'file-patterns' entry is empty/missing")); } // create-command ArgumentPassingMethod method; parse_command(group.readEntry(CREATE_COMMAND_ENTRY), id(), CREATE_COMMAND_ENTRY, &m_createCommand, &m_createPrefixArguments, &m_createPostfixArguments, &method); setCreateCommandArgumentPassingMethod(method); // verify-command parse_command(group.readEntry(VERIFY_COMMAND_ENTRY), id(), VERIFY_COMMAND_ENTRY, &m_verifyCommand, &m_verifyPrefixArguments, &m_verifyPostfixArguments, &method); setVerifyCommandArgumentPassingMethod(method); } private: QString doGetCreateCommand() const override { return m_createCommand; } QStringList doGetCreateArguments(const QStringList &files) const override { return m_createPrefixArguments + files + m_createPostfixArguments; } QString doGetVerifyCommand() const override { return m_verifyCommand; } QStringList doGetVerifyArguments(const QStringList &files) const override { return m_verifyPrefixArguments + files + m_verifyPostfixArguments; } private: QString m_createCommand, m_verifyCommand; QStringList m_createPrefixArguments, m_createPostfixArguments; QStringList m_verifyPrefixArguments, m_verifyPostfixArguments; }; } ChecksumDefinition::ChecksumDefinition(const QString &id, const QString &label, const QString &outputFileName, const QStringList &patterns) : m_id(id) , m_label(label.isEmpty() ? id : label) , m_outputFileName(outputFileName) , m_patterns(patterns) , m_createMethod(CommandLine) , m_verifyMethod(CommandLine) { } ChecksumDefinition::~ChecksumDefinition() { } QString ChecksumDefinition::createCommand() const { return doGetCreateCommand(); } QString ChecksumDefinition::verifyCommand() const { return doGetVerifyCommand(); } #if 0 QStringList ChecksumDefinition::createCommandArguments(const QStringList &files) const { return doGetCreateArguments(files); } QStringList ChecksumDefinition::verifyCommandArguments(const QStringList &files) const { return doGetVerifyArguments(files); } #endif static QByteArray make_input(const QStringList &files, char sep) { QByteArray result; for (const QString &file : files) { #ifdef Q_OS_WIN result += file.toUtf8(); #else result += QFile::encodeName(file); #endif result += sep; } return result; } static bool start_command(QProcess *p, const char *functionName, const QString &cmd, const QStringList &args, const QStringList &files, ChecksumDefinition::ArgumentPassingMethod method) { if (!p) { qCWarning(LIBKLEO_LOG) << functionName << ": process == NULL"; return false; } switch (method) { case ChecksumDefinition::NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); case ChecksumDefinition::CommandLine: qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' ')); p->start(cmd, args, QIODevice::ReadOnly); return true; case ChecksumDefinition::NewlineSeparatedInputFile: case ChecksumDefinition::NullSeparatedInputFile: qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' ')); p->start(cmd, args, QIODevice::ReadWrite); if (!p->waitForStarted()) { return false; } const char sep = method == ChecksumDefinition::NewlineSeparatedInputFile ? '\n' : '\0'; const QByteArray stdin = make_input(files, sep); if (p->write(stdin) != stdin.size()) { return false; } p->closeWriteChannel(); return true; } return false; // make compiler happy } bool ChecksumDefinition::startCreateCommand(QProcess *p, const QStringList &files) const { return start_command(p, Q_FUNC_INFO, doGetCreateCommand(), m_createMethod == CommandLine ? doGetCreateArguments(files) : doGetCreateArguments(QStringList()), files, m_createMethod); } bool ChecksumDefinition::startVerifyCommand(QProcess *p, const QStringList &files) const { return start_command(p, Q_FUNC_INFO, doGetVerifyCommand(), m_verifyMethod == CommandLine ? doGetVerifyArguments(files) : doGetVerifyArguments(QStringList()), files, m_verifyMethod); } // static std::vector> ChecksumDefinition::getChecksumDefinitions() { QStringList errors; return getChecksumDefinitions(errors); } // static std::vector> ChecksumDefinition::getChecksumDefinitions(QStringList &errors) { std::vector> result; KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc")); const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Checksum Definition #"))); result.reserve(groups.size()); for (const QString &group : groups) { try { const std::shared_ptr ad(new KConfigBasedChecksumDefinition(KConfigGroup(config, group))); result.push_back(ad); } catch (const std::exception &e) { qDebug() << e.what(); errors.push_back(QString::fromLocal8Bit(e.what())); } catch (...) { errors.push_back(i18n("Caught unknown exception in group %1", group)); } } return result; } // static std::shared_ptr ChecksumDefinition::getDefaultChecksumDefinition(const std::vector> &checksumDefinitions) { const KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("ChecksumOperations")); const QString checksumDefinitionId = group.readEntry(CHECKSUM_DEFINITION_ID_ENTRY, QStringLiteral("sha256sum")); if (!checksumDefinitionId.isEmpty()) { for (const std::shared_ptr &cd : checksumDefinitions) { if (cd && cd->id() == checksumDefinitionId) { return cd; } } } if (!checksumDefinitions.empty()) { return checksumDefinitions.front(); } else { return std::shared_ptr(); } } // static void ChecksumDefinition::setDefaultChecksumDefinition(const std::shared_ptr &checksumDefinition) { if (!checksumDefinition) { return; } KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("ChecksumOperations")); group.writeEntry(CHECKSUM_DEFINITION_ID_ENTRY, checksumDefinition->id()); group.sync(); } diff --git a/src/kleo/defaultkeygenerationjob.cpp b/src/kleo/defaultkeygenerationjob.cpp index 4f9c33a39..1530a18f4 100644 --- a/src/kleo/defaultkeygenerationjob.cpp +++ b/src/kleo/defaultkeygenerationjob.cpp @@ -1,125 +1,125 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "defaultkeygenerationjob.h" #include #include #include #include using namespace Kleo; namespace Kleo { class DefaultKeyGenerationJob::DefaultKeyGenerationJobPrivate { public: DefaultKeyGenerationJobPrivate() { } ~DefaultKeyGenerationJobPrivate() { if (job) { job->deleteLater(); } } QString passphrase; QPointer job; }; } DefaultKeyGenerationJob::DefaultKeyGenerationJob(QObject *parent) : Job(parent) , d(new DefaultKeyGenerationJob::DefaultKeyGenerationJobPrivate()) { } DefaultKeyGenerationJob::~DefaultKeyGenerationJob() = default; QString DefaultKeyGenerationJob::auditLogAsHtml() const { return d->job ? d->job->auditLogAsHtml() : QString(); } GpgME::Error DefaultKeyGenerationJob::auditLogError() const { return d->job ? d->job->auditLogError() : GpgME::Error(); } void DefaultKeyGenerationJob::slotCancel() { if (d->job) { d->job->slotCancel(); } } void DefaultKeyGenerationJob::setPassphrase(const QString &passphrase) { // null QString = ask for passphrase // empty QString = empty passphrase // non-empty QString = passphrase - d->passphrase = passphrase.isNull() ? QLatin1String("") : passphrase; + d->passphrase = passphrase.isNull() ? QLatin1StringView("") : passphrase; } namespace { QString passphraseParameter(const QString &passphrase) { if (passphrase.isNull()) { return QStringLiteral("%ask-passphrase"); } else if (passphrase.isEmpty()) { return QStringLiteral("%no-protection"); } else { return QStringLiteral("passphrase: %1").arg(passphrase); }; } } GpgME::Error DefaultKeyGenerationJob::start(const QString &email, const QString &name) { const QString passphrase = passphraseParameter(d->passphrase); const QString args = QStringLiteral( "\n" "key-type: RSA\n" "key-length: 2048\n" "key-usage: sign\n" "subkey-type: RSA\n" "subkey-length: 2048\n" "subkey-usage: encrypt\n" "%1\n" "name-email: %2\n" "name-real: %3\n" "") .arg(passphrase, email, name); d->job = QGpgME::openpgp()->keyGenerationJob(); d->job->installEventFilter(this); connect(d->job.data(), &QGpgME::KeyGenerationJob::result, this, &DefaultKeyGenerationJob::result); connect(d->job.data(), &QGpgME::KeyGenerationJob::done, this, &DefaultKeyGenerationJob::done); connect(d->job.data(), &QGpgME::KeyGenerationJob::done, this, &QObject::deleteLater); return d->job->start(args); } bool DefaultKeyGenerationJob::eventFilter(QObject *watched, QEvent *event) { // Intercept the KeyGenerationJob's deferred delete event. We want the job // to live at least as long as we do so we can delegate calls to it. We will // delete the job manually afterwards. if (watched == d->job && event->type() == QEvent::DeferredDelete) { return true; } return Job::eventFilter(watched, event); } #include "moc_defaultkeygenerationjob.cpp" diff --git a/src/kleo/dn.cpp b/src/kleo/dn.cpp index 52286f5cd..dac90dc6a 100644 --- a/src/kleo/dn.cpp +++ b/src/kleo/dn.cpp @@ -1,556 +1,556 @@ /* dn.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker DN parsing: SPDX-FileCopyrightText: 2002 g10 Code GmbH SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "dn.h" #include "libkleo_debug.h" #include "oidmap.h" #include #include #ifdef _MSC_VER #include #define strcasecmp _stricmp #endif namespace { static const QStringList defaultOrder = { QStringLiteral("CN"), QStringLiteral("L"), QStringLiteral("_X_"), QStringLiteral("OU"), QStringLiteral("O"), QStringLiteral("C"), }; class DNAttributeOrderStore { DNAttributeOrderStore() : mAttributeOrder{defaultOrder} { } public: static DNAttributeOrderStore *instance() { static DNAttributeOrderStore *self = new DNAttributeOrderStore(); return self; } const QStringList &attributeOrder() const { return mAttributeOrder.empty() ? defaultOrder : mAttributeOrder; } void setAttributeOrder(const QStringList &order) { mAttributeOrder = order; } private: QStringList mAttributeOrder; }; } class Kleo::DN::Private { public: Private() : mRefCount(0) { } Private(const Private &other) : attributes(other.attributes) , reorderedAttributes(other.reorderedAttributes) , mRefCount(0) { } int ref() { return ++mRefCount; } int unref() { if (--mRefCount <= 0) { delete this; return 0; } else { return mRefCount; } } int refCount() const { return mRefCount; } DN::Attribute::List attributes; DN::Attribute::List reorderedAttributes; private: int mRefCount; }; namespace { struct DnPair { char *key; char *value; }; } // copied from CryptPlug and adapted to work on DN::Attribute::List: #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp(a) || (*(a) >= 'A' && *(a) <= 'F') || (*(a) >= 'a' && *(a) <= 'f')) #define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1)) static char *trim_trailing_spaces(char *string) { char *p; char *mark; for (mark = nullptr, p = string; *p; p++) { if (isspace(*p)) { if (!mark) { mark = p; } } else { mark = nullptr; } } if (mark) { *mark = '\0'; } return string; } /* Parse a DN and return an array-ized one. This is not a validating parser and it does not support any old-stylish syntax; gpgme is expected to return only rfc2253 compatible strings. */ static const unsigned char *parse_dn_part(DnPair *array, const unsigned char *string) { const unsigned char *s; const unsigned char *s1; size_t n; char *p; /* parse attributeType */ for (s = string + 1; *s && *s != '='; s++) { ; } if (!*s) { return nullptr; /* error */ } n = s - string; if (!n) { return nullptr; /* empty key */ } p = (char *)malloc(n + 1); memcpy(p, string, n); p[n] = 0; trim_trailing_spaces((char *)p); // map OIDs to their names: if (const char *name = Kleo::attributeNameForOID(p)) { free(p); p = strdup(name); } array->key = p; string = s + 1; if (*string == '#') { /* hexstring */ string++; for (s = string; hexdigitp(s); s++) ; n = s - string; if (!n || (n & 1)) { return nullptr; /* empty or odd number of digits */ } n /= 2; array->value = p = (char *)malloc(n + 1); for (s1 = string; n; s1 += 2, n--) { *p++ = xtoi_2(s1); } *p = 0; } else { /* regular v3 quoted string */ for (n = 0, s = string; *s; s++) { if (*s == '\\') { /* pair */ s++; if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') { n++; } else if (hexdigitp(s) && hexdigitp(s + 1)) { s++; n++; } else { return nullptr; /* invalid escape sequence */ } } else if (*s == '\"') { return nullptr; /* invalid encoding */ } else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';') { break; } else { n++; } } array->value = p = (char *)malloc(n + 1); for (s = string; n; s++, n--) { if (*s == '\\') { s++; if (hexdigitp(s)) { *p++ = xtoi_2(s); s++; } else { *p++ = *s; } } else { *p++ = *s; } } *p = 0; } return s; } /* Parse a DN and return an array-ized one. This is not a validating parser and it does not support any old-stylish syntax; gpgme is expected to return only rfc2253 compatible strings. */ static Kleo::DN::Attribute::List parse_dn(const unsigned char *string) { if (!string) { return QList(); } QList result; while (*string) { while (*string == ' ') { string++; } if (!*string) { break; /* ready */ } DnPair pair = {nullptr, nullptr}; string = parse_dn_part(&pair, string); if (!string) { goto failure; } if (pair.key && pair.value) { result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key), QString::fromUtf8(pair.value))); } free(pair.key); free(pair.value); while (*string == ' ') { string++; } if (*string && *string != ',' && *string != ';' && *string != '+') { goto failure; /* invalid delimiter */ } if (*string) { string++; } } return result; failure: return QList(); } static QList parse_dn(const QString &dn) { return parse_dn((const unsigned char *)dn.toUtf8().data()); } static QString dn_escape(const QString &s) { QString result; for (int i = 0, end = s.length(); i != end; ++i) { const QChar ch = s[i]; switch (ch.unicode()) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': result += QLatin1Char('\\'); // fall through [[fallthrough]]; default: result += ch; } } return result; } static QString serialise(const QList &dn, const QString &sep) { QStringList result; for (QList::const_iterator it = dn.begin(); it != dn.end(); ++it) { if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) { result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed())); } } return result.join(sep); } static Kleo::DN::Attribute::List reorder_dn(const Kleo::DN::Attribute::List &dn) { const QStringList &attrOrder = Kleo::DN::attributeOrder(); Kleo::DN::Attribute::List unknownEntries; Kleo::DN::Attribute::List result; unknownEntries.reserve(dn.size()); result.reserve(dn.size()); // find all unknown entries in their order of appearance for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) { if (!attrOrder.contains((*it).name())) { unknownEntries.push_back(*it); } } // process the known attrs in the desired order for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) { - if (*oit == QLatin1String("_X_")) { + if (*oit == QLatin1StringView("_X_")) { // insert the unknown attrs std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result)); unknownEntries.clear(); // don't produce dup's } else { for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) { if ((*dnit).name() == *oit) { result.push_back(*dnit); } } } } return result; } // // // class DN // // Kleo::DN::DN() { d = new Private(); d->ref(); } Kleo::DN::DN(const QString &dn) { d = new Private(); d->ref(); d->attributes = parse_dn(dn); } Kleo::DN::DN(const char *utf8DN) { d = new Private(); d->ref(); if (utf8DN) { d->attributes = parse_dn((const unsigned char *)utf8DN); } } Kleo::DN::DN(const DN &other) : d(other.d) { if (d) { d->ref(); } } Kleo::DN::~DN() { if (d) { d->unref(); } } const Kleo::DN &Kleo::DN::operator=(const DN &that) { if (this->d == that.d) { return *this; } if (that.d) { that.d->ref(); } if (this->d) { this->d->unref(); } this->d = that.d; return *this; } // static QStringList Kleo::DN::attributeOrder() { return DNAttributeOrderStore::instance()->attributeOrder(); } // static void Kleo::DN::setAttributeOrder(const QStringList &order) { DNAttributeOrderStore::instance()->setAttributeOrder(order); } // static QStringList Kleo::DN::defaultAttributeOrder() { return defaultOrder; } QString Kleo::DN::prettyDN() const { if (!d) { return QString(); } if (d->reorderedAttributes.empty()) { d->reorderedAttributes = reorder_dn(d->attributes); } return serialise(d->reorderedAttributes, QStringLiteral(",")); } QString Kleo::DN::dn() const { return d ? serialise(d->attributes, QStringLiteral(",")) : QString(); } QString Kleo::DN::dn(const QString &sep) const { return d ? serialise(d->attributes, sep) : QString(); } // static QString Kleo::DN::escape(const QString &value) { return dn_escape(value); } void Kleo::DN::detach() { if (!d) { d = new Kleo::DN::Private(); d->ref(); } else if (d->refCount() > 1) { Kleo::DN::Private *d_save = d; d = new Kleo::DN::Private(*d); d->ref(); d_save->unref(); } } void Kleo::DN::append(const Attribute &attr) { detach(); d->attributes.push_back(attr); d->reorderedAttributes.clear(); } QString Kleo::DN::operator[](const QString &attr) const { if (!d) { return QString(); } const QString attrUpper = attr.toUpper(); for (QList::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) { if ((*it).name() == attrUpper) { return (*it).value(); } } return QString(); } static QList empty; Kleo::DN::const_iterator Kleo::DN::begin() const { return d ? d->attributes.constBegin() : empty.constBegin(); } Kleo::DN::const_iterator Kleo::DN::end() const { return d ? d->attributes.constEnd() : empty.constEnd(); } ///////////////////// namespace { static const QMap attributeNamesAndLabels = { // clang-format off {QStringLiteral("CN"), kli18n("Common name") }, {QStringLiteral("SN"), kli18n("Surname") }, {QStringLiteral("GN"), kli18n("Given name") }, {QStringLiteral("L"), kli18n("Location") }, {QStringLiteral("T"), kli18n("Title") }, {QStringLiteral("OU"), kli18n("Organizational unit")}, {QStringLiteral("O"), kli18n("Organization") }, {QStringLiteral("PC"), kli18n("Postal code") }, {QStringLiteral("C"), kli18n("Country code") }, {QStringLiteral("SP"), kli18n("State or province") }, {QStringLiteral("DC"), kli18n("Domain component") }, {QStringLiteral("BC"), kli18n("Business category") }, {QStringLiteral("EMAIL"), kli18n("Email address") }, {QStringLiteral("MAIL"), kli18n("Mail address") }, {QStringLiteral("MOBILE"), kli18n("Mobile phone number")}, {QStringLiteral("TEL"), kli18n("Telephone number") }, {QStringLiteral("FAX"), kli18n("Fax number") }, {QStringLiteral("STREET"), kli18n("Street address") }, {QStringLiteral("UID"), kli18n("Unique ID") }, // clang-format on }; } // static QStringList Kleo::DN::attributeNames() { return attributeNamesAndLabels.keys(); } // static QString Kleo::DN::attributeNameToLabel(const QString &name) { const QString key{name.trimmed().toUpper()}; if (attributeNames().contains(key)) { return attributeNamesAndLabels.value(key).toString(); } qCWarning(LIBKLEO_LOG) << "Attribute " << key << " doesn't exit. Bug ?"; return {}; } diff --git a/src/kleo/enum.cpp b/src/kleo/enum.cpp index 0ca586dbe..c4db826ce 100644 --- a/src/kleo/enum.cpp +++ b/src/kleo/enum.cpp @@ -1,305 +1,305 @@ /* kleo/enum.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "enum.h" #include #include #include #include #include #include #include #include #include static const struct { Kleo::CryptoMessageFormat format; const KLazyLocalizedString displayName; const char *configName; } cryptoMessageFormats[] = { // clang-format off {Kleo::InlineOpenPGPFormat, kli18n("Inline OpenPGP (deprecated)"), "inline openpgp"}, {Kleo::OpenPGPMIMEFormat, kli18n("OpenPGP/MIME"), "openpgp/mime" }, {Kleo::SMIMEFormat, kli18n("S/MIME"), "s/mime" }, {Kleo::SMIMEOpaqueFormat, kli18n("S/MIME Opaque"), "s/mime opaque" }, {Kleo::AnySMIME, kli18n("Any S/MIME"), "any s/mime" }, {Kleo::AnyOpenPGP, kli18n("Any OpenPGP"), "any openpgp" }, // clang-format on }; static const unsigned int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats; const char *Kleo::cryptoMessageFormatToString(Kleo::CryptoMessageFormat f) { if (f == AutoFormat) { return "auto"; } for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f == cryptoMessageFormats[i].format) { return cryptoMessageFormats[i].configName; } } return nullptr; } QStringList Kleo::cryptoMessageFormatsToStringList(unsigned int f) { QStringList result; for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f & cryptoMessageFormats[i].format) { - result.push_back(QLatin1String(cryptoMessageFormats[i].configName)); + result.push_back(QLatin1StringView(cryptoMessageFormats[i].configName)); } } return result; } QString Kleo::cryptoMessageFormatToLabel(Kleo::CryptoMessageFormat f) { if (f == AutoFormat) { return i18n("Any"); } for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { if (f == cryptoMessageFormats[i].format) { return KLocalizedString(cryptoMessageFormats[i].displayName).toString(); } } return QString(); } Kleo::CryptoMessageFormat Kleo::stringToCryptoMessageFormat(const QString &s) { const QString t = s.toLower(); for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) { - if (t == QLatin1String(cryptoMessageFormats[i].configName)) { + if (t == QLatin1StringView(cryptoMessageFormats[i].configName)) { return cryptoMessageFormats[i].format; } } return AutoFormat; } unsigned int Kleo::stringListToCryptoMessageFormats(const QStringList &sl) { unsigned int result = 0; for (QStringList::const_iterator it = sl.begin(); it != sl.end(); ++it) { result |= stringToCryptoMessageFormat(*it); } return result; } // For the config values used below, see also kaddressbook/editors/cryptowidget.cpp const char *Kleo::encryptionPreferenceToString(EncryptionPreference pref) { switch (pref) { case UnknownPreference: return nullptr; case NeverEncrypt: return "never"; case AlwaysEncrypt: return "always"; case AlwaysEncryptIfPossible: return "alwaysIfPossible"; case AlwaysAskForEncryption: return "askAlways"; case AskWheneverPossible: return "askWhenPossible"; } return nullptr; // keep the compiler happy } Kleo::EncryptionPreference Kleo::stringToEncryptionPreference(const QString &str) { - if (str == QLatin1String("never")) { + if (str == QLatin1StringView("never")) { return NeverEncrypt; } - if (str == QLatin1String("always")) { + if (str == QLatin1StringView("always")) { return AlwaysEncrypt; } - if (str == QLatin1String("alwaysIfPossible")) { + if (str == QLatin1StringView("alwaysIfPossible")) { return AlwaysEncryptIfPossible; } - if (str == QLatin1String("askAlways")) { + if (str == QLatin1StringView("askAlways")) { return AlwaysAskForEncryption; } - if (str == QLatin1String("askWhenPossible")) { + if (str == QLatin1StringView("askWhenPossible")) { return AskWheneverPossible; } return UnknownPreference; } QString Kleo::encryptionPreferenceToLabel(EncryptionPreference pref) { switch (pref) { case NeverEncrypt: return i18n("Never Encrypt"); case AlwaysEncrypt: return i18n("Always Encrypt"); case AlwaysEncryptIfPossible: return i18n("Always Encrypt If Possible"); case AlwaysAskForEncryption: return i18n("Ask"); case AskWheneverPossible: return i18n("Ask Whenever Possible"); default: return xi18nc("no specific preference", "none"); } } const char *Kleo::signingPreferenceToString(SigningPreference pref) { switch (pref) { case UnknownSigningPreference: return nullptr; case NeverSign: return "never"; case AlwaysSign: return "always"; case AlwaysSignIfPossible: return "alwaysIfPossible"; case AlwaysAskForSigning: return "askAlways"; case AskSigningWheneverPossible: return "askWhenPossible"; } return nullptr; // keep the compiler happy } Kleo::SigningPreference Kleo::stringToSigningPreference(const QString &str) { - if (str == QLatin1String("never")) { + if (str == QLatin1StringView("never")) { return NeverSign; } - if (str == QLatin1String("always")) { + if (str == QLatin1StringView("always")) { return AlwaysSign; } - if (str == QLatin1String("alwaysIfPossible")) { + if (str == QLatin1StringView("alwaysIfPossible")) { return AlwaysSignIfPossible; } - if (str == QLatin1String("askAlways")) { + if (str == QLatin1StringView("askAlways")) { return AlwaysAskForSigning; } - if (str == QLatin1String("askWhenPossible")) { + if (str == QLatin1StringView("askWhenPossible")) { return AskSigningWheneverPossible; } return UnknownSigningPreference; } QString Kleo::signingPreferenceToLabel(SigningPreference pref) { switch (pref) { case NeverSign: return i18n("Never Sign"); case AlwaysSign: return i18n("Always Sign"); case AlwaysSignIfPossible: return i18n("Always Sign If Possible"); case AlwaysAskForSigning: return i18n("Ask"); case AskSigningWheneverPossible: return i18n("Ask Whenever Possible"); default: return i18nc("no specific preference", ""); } } Kleo::TrustLevel Kleo::trustLevel(const GpgME::Key &key) { TrustLevel maxTl = Level0; for (int i = 0, c = key.numUserIDs(); i < c; ++i) { const auto tl = trustLevel(key.userID(i)); maxTl = qMax(maxTl, tl); if (maxTl == Level4) { break; } } return maxTl; } namespace { bool hasTrustedSignature(const GpgME::UserID &uid) { // lazily initialized cache static std::shared_ptr keyCache; if (!keyCache) { keyCache = Kleo::KeyCache::instance(); } const auto signatures = uid.signatures(); std::vector sigKeyIDs; std::transform(signatures.cbegin(), signatures.cend(), std::back_inserter(sigKeyIDs), std::bind(&GpgME::UserID::Signature::signerKeyID, std::placeholders::_1)); const auto keys = keyCache->findByKeyIDOrFingerprint(sigKeyIDs); return std::any_of(keys.cbegin(), keys.cend(), [](const GpgME::Key &key) { return key.ownerTrust() == GpgME::Key::Ultimate; }); } } Kleo::TrustLevel Kleo::trustLevel(const GpgME::UserID &uid) { // Modelled after https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption, // but modified to cover all cases, unlike the pseudocode in the document. // // TODO: Check whether the key comes from a trusted source (Cert/PKA/DANE/WKD) switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: case GpgME::UserID::Never: // Not enough trust -> level 0 return Level0; case GpgME::UserID::Marginal: // Marginal trust without TOFU data means the key is still trusted // through the Web of Trust -> level 2 if (uid.tofuInfo().isNull()) { return Level2; } // Marginal trust with TOFU, level will depend on TOFU history switch (uid.tofuInfo().validity()) { case GpgME::TofuInfo::ValidityUnknown: case GpgME::TofuInfo::Conflict: case GpgME::TofuInfo::NoHistory: // Marginal trust, but not enough history -> level 0 return Level0; case GpgME::TofuInfo::LittleHistory: // Marginal trust, but too little history -> level 1 return Level1; case GpgME::TofuInfo::BasicHistory: case GpgME::TofuInfo::LargeHistory: // Marginal trust and enough history -> level 2 return Level2; } return Level2; // Not reached, but avoids fallthrough warnings case GpgME::UserID::Full: // Full trust, trust level depends whether the UserID is signed with // at least one key with Ultimate ownertrust. return hasTrustedSignature(uid) ? Level4 : Level3; case GpgME::UserID::Ultimate: // Ultimate trust -> leve 4 return Level4; } Q_UNREACHABLE(); } diff --git a/src/kleo/kconfigbasedkeyfilter.cpp b/src/kleo/kconfigbasedkeyfilter.cpp index e81ce7531..10b1c5eda 100644 --- a/src/kleo/kconfigbasedkeyfilter.cpp +++ b/src/kleo/kconfigbasedkeyfilter.cpp @@ -1,244 +1,244 @@ /* kconfigbasedkeyfilter.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kconfigbasedkeyfilter.h" #include #include #include #include #include using namespace Kleo; using namespace GpgME; // // // FontDescription - intuitive font property resolving // (QFont::resolve doesn't work for us) // // struct KeyFilter::FontDescription::Private { bool bold, italic, strikeOut, fullFont; QFont font; }; KeyFilter::FontDescription::FontDescription() : d(new Private) { d->bold = d->italic = d->strikeOut = d->fullFont = false; } KeyFilter::FontDescription::FontDescription(const FontDescription &other) : d(new Private(*other.d)) { } KeyFilter::FontDescription::~FontDescription() = default; KeyFilter::FontDescription KeyFilter::FontDescription::create(bool b, bool i, bool s) { FontDescription fd; fd.d->bold = b; fd.d->italic = i; fd.d->strikeOut = s; return fd; } KeyFilter::FontDescription KeyFilter::FontDescription::create(const QFont &f, bool b, bool i, bool s) { FontDescription fd; fd.d->fullFont = true; fd.d->font = f; fd.d->bold = b; fd.d->italic = i; fd.d->strikeOut = s; return fd; } QFont KeyFilter::FontDescription::font(const QFont &base) const { QFont font; if (d->fullFont) { font = d->font; font.setPointSize(base.pointSize()); } else { font = base; } if (d->bold) { font.setBold(true); } if (d->italic) { font.setItalic(true); } if (d->strikeOut) { font.setStrikeOut(true); } return font; } KeyFilter::FontDescription KeyFilter::FontDescription::resolve(const FontDescription &other) const { FontDescription fd; fd.d->fullFont = this->d->fullFont || other.d->fullFont; if (fd.d->fullFont) { fd.d->font = this->d->fullFont ? this->d->font : other.d->font; } fd.d->bold = this->d->bold || other.d->bold; fd.d->italic = this->d->italic || other.d->italic; fd.d->strikeOut = this->d->strikeOut || other.d->strikeOut; return fd; } static const struct { const char *name; Key::OwnerTrust trust; UserID::Validity validity; } ownerTrustAndValidityMap[] = { // clang-format off {"unknown", Key::Unknown, UserID::Unknown }, {"undefined", Key::Undefined, UserID::Undefined}, {"never", Key::Never, UserID::Never }, {"marginal", Key::Marginal, UserID::Marginal }, {"full", Key::Full, UserID::Full }, {"ultimate", Key::Ultimate, UserID::Ultimate }, // clang-format on }; static Key::OwnerTrust map2OwnerTrust(const QString &s) { for (unsigned int i = 0; i < sizeof ownerTrustAndValidityMap / sizeof *ownerTrustAndValidityMap; ++i) { - if (s.toLower() == QLatin1String(ownerTrustAndValidityMap[i].name)) { + if (s.toLower() == QLatin1StringView(ownerTrustAndValidityMap[i].name)) { return ownerTrustAndValidityMap[i].trust; } } return ownerTrustAndValidityMap[0].trust; } static UserID::Validity map2Validity(const QString &s) { for (unsigned int i = 0; i < sizeof ownerTrustAndValidityMap / sizeof *ownerTrustAndValidityMap; ++i) { - if (s.toLower() == QLatin1String(ownerTrustAndValidityMap[i].name)) { + if (s.toLower() == QLatin1StringView(ownerTrustAndValidityMap[i].name)) { return ownerTrustAndValidityMap[i].validity; } } return ownerTrustAndValidityMap[0].validity; } KConfigBasedKeyFilter::KConfigBasedKeyFilter(const KConfigGroup &config) : DefaultKeyFilter() { setFgColor(config.readEntry("foreground-color", QColor())); setBgColor(config.readEntry("background-color", QColor())); setName(config.readEntry("Name", config.name())); setIcon(config.readEntry("icon")); setId(config.readEntry("id", config.name())); if (config.hasKey("font")) { setUseFullFont(true); setFont(config.readEntry("font")); } else { setUseFullFont(false); setItalic(config.readEntry("font-italic", false)); setBold(config.readEntry("font-bold", false)); } setStrikeOut(config.readEntry("font-strikeout", false)); #ifdef SET #undef SET #endif #define SET(member, key) \ if (config.hasKey(key)) { \ set##member(config.readEntry(key, false) ? Set : NotSet); \ setSpecificity(specificity() + 1); \ } SET(Revoked, "is-revoked"); SET(Expired, "is-expired"); SET(Disabled, "is-disabled"); SET(Root, "is-root-certificate"); SET(CanEncrypt, "can-encrypt"); SET(CanSign, "can-sign"); SET(CanCertify, "can-certify"); SET(CanAuthenticate, "can-authenticate"); SET(HasEncrypt, "has-encrypt"); SET(HasSign, "has-sign"); SET(HasCertify, "has-certify"); SET(HasAuthenticate, "has-authenticate"); SET(Qualified, "is-qualified"); SET(CardKey, "is-cardkey"); SET(HasSecret, "has-secret-key"); SET(IsOpenPGP, "is-openpgp-key"); SET(WasValidated, "was-validated"); SET(IsDeVs, "is-de-vs"); #undef SET static const struct { const char *prefix; LevelState state; } prefixMap[] = { {"is-", Is}, {"is-not-", IsNot}, {"is-at-least-", IsAtLeast}, {"is-at-most-", IsAtMost}, }; for (unsigned int i = 0; i < sizeof prefixMap / sizeof *prefixMap; ++i) { - const QString key = QLatin1String(prefixMap[i].prefix) + QLatin1String("ownertrust"); + const QString key = QLatin1StringView(prefixMap[i].prefix) + QLatin1String("ownertrust"); if (config.hasKey(key)) { setOwnerTrust(prefixMap[i].state); setOwnerTrustReferenceLevel(map2OwnerTrust(config.readEntry(key, QString()))); setSpecificity(specificity() + 1); break; } } for (unsigned int i = 0; i < sizeof prefixMap / sizeof *prefixMap; ++i) { - const QString key = QLatin1String(prefixMap[i].prefix) + QLatin1String("validity"); + const QString key = QLatin1StringView(prefixMap[i].prefix) + QLatin1String("validity"); if (config.hasKey(key)) { setValidity(prefixMap[i].state); setValidityReferenceLevel(map2Validity(config.readEntry(key, QString()))); setSpecificity(specificity() + 1); break; } } static const struct { const char *key; MatchContext context; } matchMap[] = { {"any", AnyMatchContext}, {"appearance", Appearance}, {"filtering", Filtering}, }; - static const QRegularExpression reg(QRegularExpression(QLatin1String("[^a-z!]+"))); + static const QRegularExpression reg(QRegularExpression(QLatin1StringView("[^a-z!]+"))); const QStringList contexts = config.readEntry("match-contexts", "any").toLower().split(reg, Qt::SkipEmptyParts); setMatchContexts(NoMatchContext); for (const QString &ctx : contexts) { bool found = false; for (unsigned int i = 0; i < sizeof matchMap / sizeof *matchMap; ++i) { - if (ctx == QLatin1String(matchMap[i].key)) { + if (ctx == QLatin1StringView(matchMap[i].key)) { setMatchContexts(availableMatchContexts() |= matchMap[i].context); found = true; break; - } else if (ctx.startsWith(QLatin1Char('!')) && ctx.mid(1) == QLatin1String(matchMap[i].key)) { + } else if (ctx.startsWith(QLatin1Char('!')) && ctx.mid(1) == QLatin1StringView(matchMap[i].key)) { setMatchContexts(availableMatchContexts() &= matchMap[i].context); found = true; break; } } if (!found) { qWarning() << QStringLiteral("KConfigBasedKeyFilter: found unknown match context '%1' in group '%2'").arg(ctx, config.name()); } } if (availableMatchContexts() == NoMatchContext) { qWarning() << QStringLiteral( "KConfigBasedKeyFilter: match context in group '%1' evaluates to NoMatchContext, " "replaced by AnyMatchContext") .arg(config.name()); setMatchContexts(AnyMatchContext); } } diff --git a/src/kleo/keygroupconfig.cpp b/src/kleo/keygroupconfig.cpp index 674d188fc..9b1c8191b 100644 --- a/src/kleo/keygroupconfig.cpp +++ b/src/kleo/keygroupconfig.cpp @@ -1,177 +1,177 @@ /* kleo/keygroupconfig.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keygroupconfig.h" #include "debug.h" #include "keygroup.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; static const QString groupNamePrefix = QStringLiteral("Group-"); class KeyGroupConfig::Private { public: explicit Private(const QString &filename); std::vector readGroups() const; KeyGroup writeGroup(const KeyGroup &group); bool removeGroup(const KeyGroup &group); private: KeyGroup readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const; private: QString filename; }; KeyGroupConfig::Private::Private(const QString &filename) : filename{filename} { if (filename.isEmpty()) { qCWarning(LIBKLEO_LOG) << __func__ << "Warning: name of configuration file is empty"; } } KeyGroup KeyGroupConfig::Private::readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const { const KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + groupId); const QString groupName = configGroup.readEntry("Name", QString()); const auto fingerprints = toStdStrings(configGroup.readEntry("Keys", QStringList())); const std::vector groupKeys = KeyCache::instance()->findByFingerprint(fingerprints); // treat group as immutable if any of its entries is immutable const QStringList entries = configGroup.keyList(); const bool isImmutable = (configGroup.isImmutable() // || std::any_of(entries.begin(), entries.end(), [configGroup](const QString &entry) { return configGroup.isEntryImmutable(entry); })); KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig); g.setIsImmutable(isImmutable); qCDebug(LIBKLEO_LOG) << "Read group" << g; return g; } std::vector KeyGroupConfig::Private::readGroups() const { std::vector groups; if (filename.isEmpty()) { return groups; } const KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); const QStringList configGroups = groupsConfig->groupList(); for (const QString &configGroupName : configGroups) { qCDebug(LIBKLEO_LOG) << "Reading config group" << configGroupName; if (configGroupName.startsWith(groupNamePrefix)) { const QString keyGroupId = configGroupName.mid(groupNamePrefix.size()); if (keyGroupId.isEmpty()) { qCWarning(LIBKLEO_LOG) << "Config group" << configGroupName << "has empty group id"; continue; } KeyGroup group = readGroup(groupsConfig, keyGroupId); groups.push_back(group); } } return groups; } KeyGroup KeyGroupConfig::Private::writeGroup(const KeyGroup &group) { if (filename.isEmpty()) { return {}; } if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return group; } KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id()); qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroup.name(); configGroup.writeEntry("Name", group.name()); configGroup.writeEntry("Keys", Kleo::getFingerprints(group.keys())); // reread group to ensure that it reflects the saved group in case of immutable entries return readGroup(groupsConfig, group.id()); } bool KeyGroupConfig::Private::removeGroup(const KeyGroup &group) { if (filename.isEmpty()) { return false; } if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return false; } KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename); KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id()); qCDebug(LIBKLEO_LOG) << __func__ << "Removing config group" << configGroup.name(); - configGroup.deleteGroup(QLatin1String()); + configGroup.deleteGroup(QLatin1StringView()); return true; } KeyGroupConfig::KeyGroupConfig(const QString &filename) : d{std::make_unique(filename)} { } KeyGroupConfig::~KeyGroupConfig() = default; std::vector KeyGroupConfig::readGroups() const { return d->readGroups(); } KeyGroup KeyGroupConfig::writeGroup(const KeyGroup &group) { return d->writeGroup(group); } void KeyGroupConfig::writeGroups(const std::vector &groups) { std::for_each(std::begin(groups), std::end(groups), [this](const auto &group) { d->writeGroup(group); }); } bool KeyGroupConfig::removeGroup(const KeyGroup &group) { return d->removeGroup(group); } diff --git a/src/kleo/keygroupimportexport.cpp b/src/kleo/keygroupimportexport.cpp index 6ef0b2d3a..a592717fe 100644 --- a/src/kleo/keygroupimportexport.cpp +++ b/src/kleo/keygroupimportexport.cpp @@ -1,149 +1,149 @@ /* kleo/keygroupimportexport.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keygroupimportexport.h" #include "debug.h" #include "keygroup.h" #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; // use a different, less generic prefix for the config group names than in // KeyGroupConfig to avoid problems with "Group-*" config groups created by // other applications; this means that the key groups stored in the normal group // configuration file cannot be read with the below functions, but that's a good // thing because the ini files created by KConfig are incompatible with QSettings static const QString keyGroupNamePrefix = QStringLiteral("KeyGroup-"); namespace { QString readString(const QSettings &settings, const QString &key) { return settings.value(key, QString{}).toString(); } QStringList readStringList(const QSettings &settings, const QString &key) { auto variant = settings.value(key); if (!variant.isValid()) { return {}; } if ((variant.userType() == QMetaType::QString) && variant.toString().isEmpty()) { // interpret empty string value as empty list instead of as list with an empty string return {}; } // opportunistically, interpret the value as string list return variant.toStringList(); } void writeString(QSettings &settings, const QString &key, const QString &string) { settings.setValue(key, string); } void writeStringList(QSettings &settings, const QString &key, const QStringList &list) { // write empty list as empty string to avoid Qt's "@Invalid()" if (list.empty()) { writeString(settings, key, {}); } else { settings.setValue(key, list); } } KeyGroup readGroup(const QSettings &groupsConfig, const QString &groupId) { const QString configGroupPath = keyGroupNamePrefix + groupId + QLatin1Char{'/'}; - const auto groupName = readString(groupsConfig, configGroupPath + QLatin1String{"Name"}); - const auto fingerprints = readStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"}); + const auto groupName = readString(groupsConfig, configGroupPath + QLatin1StringView{"Name"}); + const auto fingerprints = readStringList(groupsConfig, configGroupPath + QLatin1StringView{"Keys"}); const std::vector groupKeys = KeyCache::instance()->findByFingerprint(toStdStrings(fingerprints)); KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig); qCDebug(LIBKLEO_LOG) << __func__ << "Read group" << g; return g; } void writeGroup(QSettings &groupsConfig, const KeyGroup &group) { if (group.isNull()) { qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null"; return; } const QString configGroupName = keyGroupNamePrefix + group.id(); qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroupName; const QString configGroupPath = configGroupName + QLatin1Char{'/'}; - writeString(groupsConfig, configGroupPath + QLatin1String{"Name"}, group.name()); - writeStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"}, Kleo::getFingerprints(group.keys())); + writeString(groupsConfig, configGroupPath + QLatin1StringView{"Name"}, group.name()); + writeStringList(groupsConfig, configGroupPath + QLatin1StringView{"Keys"}, Kleo::getFingerprints(group.keys())); } } // namespace std::vector Kleo::readKeyGroups(const QString &filename) { std::vector groups; if (filename.isEmpty()) { return groups; } if (!QFile::exists(filename)) { qCWarning(LIBKLEO_LOG) << __func__ << "File" << filename << "does not exist"; return groups; } const QSettings groupsConfig{filename, QSettings::IniFormat}; const QStringList configGroups = groupsConfig.childGroups(); for (const QString &configGroupName : configGroups) { if (configGroupName.startsWith(keyGroupNamePrefix)) { qCDebug(LIBKLEO_LOG) << __func__ << "Reading config group" << configGroupName; const QString keyGroupId = configGroupName.mid(keyGroupNamePrefix.size()); if (keyGroupId.isEmpty()) { qCWarning(LIBKLEO_LOG) << __func__ << "Config group" << configGroupName << "has empty group id"; continue; } groups.push_back(readGroup(groupsConfig, keyGroupId)); } } return groups; } Kleo::WriteKeyGroups Kleo::writeKeyGroups(const QString &filename, const std::vector &groups) { if (filename.isEmpty()) { return WriteKeyGroups::InvalidFilename; } QSettings groupsConfig{filename, QSettings::IniFormat}; for (const auto &group : groups) { writeGroup(groupsConfig, group); } // ensure that the data is written to disk before calling status() groupsConfig.sync(); qCDebug(LIBKLEO_LOG) << __func__ << "groupsConfig.status():" << groupsConfig.status(); return groupsConfig.status() == QSettings::NoError ? WriteKeyGroups::Success : WriteKeyGroups::Error; } diff --git a/src/kleo/keyserverconfig.cpp b/src/kleo/keyserverconfig.cpp index c1a86ce45..0c0016c48 100644 --- a/src/kleo/keyserverconfig.cpp +++ b/src/kleo/keyserverconfig.cpp @@ -1,221 +1,221 @@ /* kleo/keyserverconfig.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyserverconfig.h" #include #include #include using namespace Kleo; class KeyserverConfig::Private { public: explicit Private(); QString host; int port = -1; // -1 == use default port KeyserverAuthentication authentication = KeyserverAuthentication::Anonymous; QString user; QString password; KeyserverConnection connection = KeyserverConnection::Default; QString baseDn; QStringList additionalFlags; }; KeyserverConfig::Private::Private() { } KeyserverConfig::KeyserverConfig() : d{std::make_unique()} { } KeyserverConfig::~KeyserverConfig() = default; KeyserverConfig::KeyserverConfig(const KeyserverConfig &other) : d{std::make_unique(*other.d)} { } KeyserverConfig &KeyserverConfig::operator=(const KeyserverConfig &other) { *d = *other.d; return *this; } KeyserverConfig::KeyserverConfig(KeyserverConfig &&other) = default; KeyserverConfig &KeyserverConfig::operator=(KeyserverConfig &&other) = default; KeyserverConfig KeyserverConfig::fromUrl(const QUrl &url) { KeyserverConfig config; config.d->host = url.host(); config.d->port = url.port(); config.d->user = url.userName(); config.d->password = url.password(); if (!config.d->user.isEmpty()) { config.d->authentication = KeyserverAuthentication::Password; } if (url.hasFragment()) { const auto flags = transformInPlace(url.fragment().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) { return flag.trimmed().toLower(); }); for (const auto &flag : flags) { - if (flag == QLatin1String{"starttls"}) { + if (flag == QLatin1StringView{"starttls"}) { config.d->connection = KeyserverConnection::UseSTARTTLS; - } else if (flag == QLatin1String{"ldaptls"}) { + } else if (flag == QLatin1StringView{"ldaptls"}) { config.d->connection = KeyserverConnection::TunnelThroughTLS; - } else if (flag == QLatin1String{"plain"}) { + } else if (flag == QLatin1StringView{"plain"}) { config.d->connection = KeyserverConnection::Plain; - } else if (flag == QLatin1String{"ntds"}) { + } else if (flag == QLatin1StringView{"ntds"}) { config.d->authentication = KeyserverAuthentication::ActiveDirectory; } else { config.d->additionalFlags.push_back(flag); } } } if (url.hasQuery()) { config.d->baseDn = url.query(); } return config; } QUrl KeyserverConfig::toUrl() const { QUrl url; url.setScheme(QStringLiteral("ldap")); // set host to empty string if it's a null string; this ensures that the URL has an authority and always gets a "//" after the scheme url.setHost(d->host.isNull() ? QStringLiteral("") : d->host); if (d->port != -1) { url.setPort(d->port); } if (!d->user.isEmpty()) { url.setUserName(d->user); } if (!d->password.isEmpty()) { url.setPassword(d->password); } if (!d->baseDn.isEmpty()) { url.setQuery(d->baseDn); } QStringList flags; switch (d->connection) { case KeyserverConnection::UseSTARTTLS: flags.push_back(QStringLiteral("starttls")); break; case KeyserverConnection::TunnelThroughTLS: flags.push_back(QStringLiteral("ldaptls")); break; case KeyserverConnection::Plain: flags.push_back(QStringLiteral("plain")); break; case KeyserverConnection::Default:; // omit connection flag to use default } if (d->authentication == KeyserverAuthentication::ActiveDirectory) { flags.push_back(QStringLiteral("ntds")); } std::copy(std::cbegin(d->additionalFlags), std::cend(d->additionalFlags), std::back_inserter(flags)); if (!flags.isEmpty()) { url.setFragment(flags.join(QLatin1Char{','})); } return url; } QString KeyserverConfig::host() const { return d->host; } void KeyserverConfig::setHost(const QString &host) { d->host = host; } int KeyserverConfig::port() const { return d->port; } void KeyserverConfig::setPort(int port) { d->port = port; } KeyserverAuthentication KeyserverConfig::authentication() const { return d->authentication; } void KeyserverConfig::setAuthentication(KeyserverAuthentication authentication) { d->authentication = authentication; } QString KeyserverConfig::user() const { return d->user; } void KeyserverConfig::setUser(const QString &user) { d->user = user; } QString KeyserverConfig::password() const { return d->password; } void KeyserverConfig::setPassword(const QString &password) { d->password = password; } KeyserverConnection KeyserverConfig::connection() const { return d->connection; } void KeyserverConfig::setConnection(KeyserverConnection connection) { d->connection = connection; } QString KeyserverConfig::ldapBaseDn() const { return d->baseDn; } void KeyserverConfig::setLdapBaseDn(const QString &baseDn) { d->baseDn = baseDn; } QStringList KeyserverConfig::additionalFlags() const { return d->additionalFlags; } void KeyserverConfig::setAdditionalFlags(const QStringList &flags) { d->additionalFlags = flags; } diff --git a/src/ui/adjustingscrollarea.cpp b/src/ui/adjustingscrollarea.cpp index cb14e260e..9c5cdf3f6 100644 --- a/src/ui/adjustingscrollarea.cpp +++ b/src/ui/adjustingscrollarea.cpp @@ -1,99 +1,99 @@ /* -*- mode: c++; c-basic-offset:4 -*- ui/adjustingscrollarea.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "adjustingscrollarea.h" #include #include #include #include #include using namespace Kleo; AdjustingScrollArea::AdjustingScrollArea(QWidget *parent) : QScrollArea{parent} { auto w = new QWidget; - w->setObjectName(QLatin1String("scrollarea_widget")); + w->setObjectName(QLatin1StringView("scrollarea_widget")); new QVBoxLayout{w}; setWidget(w); setWidgetResizable(true); w->installEventFilter(this); connect(qApp, &QApplication::focusChanged, this, [this](QWidget *old, QWidget *now) { Q_UNUSED(old); ensureWidgetVisible(now); }); } AdjustingScrollArea::~AdjustingScrollArea() { widget()->removeEventFilter(this); } QSize AdjustingScrollArea::minimumSizeHint() const { const int fw = frameWidth(); QSize sz{2 * fw, 2 * fw}; sz += {widget()->minimumSizeHint().width(), 0}; if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setWidth(sz.width() + verticalScrollBar()->sizeHint().width()); } if (horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setHeight(sz.height() + horizontalScrollBar()->sizeHint().height()); } return QScrollArea::minimumSizeHint().expandedTo(sz); } QSize AdjustingScrollArea::sizeHint() const { const int fw = frameWidth(); QSize sz{2 * fw, 2 * fw}; sz += viewportSizeHint(); if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setWidth(sz.width() + verticalScrollBar()->sizeHint().width()); } if (horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setHeight(sz.height() + horizontalScrollBar()->sizeHint().height()); } sz = QScrollArea::sizeHint().expandedTo(sz); return sz; } void AdjustingScrollArea::adjustSizeOfWindowBy(const QSize &extent) { if (auto w = window()) { const auto currentSize = w->size(); // we limit the automatic size adjustment to 2/3 of the screen's size const auto maxWindowSize = screen()->geometry().size() * 2 / 3; const auto newWindowSize = currentSize.expandedTo((currentSize + extent).boundedTo(maxWindowSize)); if (newWindowSize != currentSize) { w->resize(newWindowSize); } } } bool AdjustingScrollArea::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() == QEvent::Resize && obj == widget() && sizeAdjustPolicy() == AdjustToContents) { const auto *const event = static_cast(ev); if (event->size().height() > event->oldSize().height()) { const auto currentViewportHeight = viewport()->height(); const auto wantedViewportHeight = event->size().height(); const auto wantedAdditionalHeight = wantedViewportHeight - currentViewportHeight; if (wantedAdditionalHeight > 0) { adjustSizeOfWindowBy(QSize{0, wantedAdditionalHeight}); } } } return QScrollArea::eventFilter(obj, ev); } diff --git a/src/ui/auditlogviewer.cpp b/src/ui/auditlogviewer.cpp index 45128e594..bfd4be1ee 100644 --- a/src/ui/auditlogviewer.cpp +++ b/src/ui/auditlogviewer.cpp @@ -1,187 +1,187 @@ /* SPDX-FileCopyrightText: 2015-2021 Laurent Montel SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "auditlogviewer.h" #include #include #include #include #include #include #include #include #ifdef HAVE_PIMTEXTEDIT #include #else #include #endif #include #include #include #include #include #include #include #include #include using namespace Kleo; AuditLogViewer::AuditLogViewer(const QString &log, QWidget *parent) : QDialog(parent) , m_log(/* sic */) , #ifdef HAVE_PIMTEXTEDIT m_textEdit(new TextCustomEditor::RichTextEditorWidget(this)) #else m_textEdit(new QTextEdit(this)) #endif { setWindowTitle(i18nc("@title:window", "View GnuPG Audit Log")); QDialogButtonBox *buttonBox = new QDialogButtonBox{}; auto copyClipBtn = buttonBox->addButton(i18n("&Copy to Clipboard"), QDialogButtonBox::ActionRole); copyClipBtn->setObjectName(QLatin1StringView("copyClipBtn")); copyClipBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); connect(copyClipBtn, &QPushButton::clicked, this, &AuditLogViewer::slotCopyClip); auto saveAsBtn = buttonBox->addButton(i18n("&Save to Disk..."), QDialogButtonBox::ActionRole); saveAsBtn->setObjectName(QLatin1StringView("saveAsBtn")); saveAsBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); connect(saveAsBtn, &QPushButton::clicked, this, &AuditLogViewer::slotSaveAs); auto closeBtn = buttonBox->addButton(QString{}, QDialogButtonBox::AcceptRole); closeBtn->setObjectName(QLatin1StringView("Close")); KGuiItem::assign(closeBtn, KStandardGuiItem::close()); m_textEdit->setObjectName(QLatin1StringView("m_textEdit")); m_textEdit->setReadOnly(true); auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(m_textEdit); mainLayout->addWidget(buttonBox); #if 0 qDebug() << "buttonBox->style()->styleHint(QStyle::SH_DialogButtonLayout, ...):" << buttonBox->style()->styleHint(QStyle::SH_DialogButtonLayout, nullptr, buttonBox); qDebug() << __func__ << "buttonBox->focusProxy():" << buttonBox->focusProxy(); qDebug() << __func__ << "copyClipBtn->nextInFocusChain():" << copyClipBtn->nextInFocusChain(); qDebug() << __func__ << "saveAsBtn->nextInFocusChain():" << saveAsBtn->nextInFocusChain(); qDebug() << __func__ << "closeBtn->nextInFocusChain():" << closeBtn->nextInFocusChain(); #endif connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); setAuditLog(log); readConfig(); } AuditLogViewer::~AuditLogViewer() { writeConfig(); } // static void AuditLogViewer::showAuditLog(QWidget *parent, const AuditLogEntry &auditLog, const QString &title) { const GpgME::Error err = auditLog.error(); if (err.code() == GPG_ERR_NOT_IMPLEMENTED) { KMessageBox::information(parent, i18n("Your system does not have support for GnuPG Audit Logs"), i18n("System Error")); return; } if (err && err.code() != GPG_ERR_NO_DATA) { KMessageBox::information(parent, i18n("An error occurred while trying to retrieve the GnuPG Audit Log:\n%1", Formatting::errorAsString(err)), i18n("GnuPG Audit Log Error")); return; } if (auditLog.text().isEmpty()) { KMessageBox::information(parent, i18n("No GnuPG Audit Log available for this operation."), i18n("No GnuPG Audit Log")); return; } const auto alv = new AuditLogViewer{auditLog.text(), parent}; alv->setAttribute(Qt::WA_DeleteOnClose); alv->setWindowTitle(title.isEmpty() ? i18n("GnuPG Audit Log Viewer") : title); alv->show(); } void AuditLogViewer::setAuditLog(const QString &log) { if (log == m_log) { return; } m_log = log; - m_textEdit->setHtml(QLatin1String("") + log + QLatin1String("")); + m_textEdit->setHtml(QLatin1StringView("") + log + QLatin1String("")); } void AuditLogViewer::slotSaveAs() { const QString fileName = QFileDialog::getSaveFileName(this, i18n("Choose File to Save GnuPG Audit Log to")); if (fileName.isEmpty()) { return; } QSaveFile file(fileName); if (file.open(QIODevice::WriteOnly)) { QTextStream s(&file); s << ""; if (!windowTitle().isEmpty()) { s << "\n" << windowTitle().toHtmlEscaped() << "\n"; } s << "\n" << m_log << "\n\n"; s.flush(); file.commit(); } if (const int err = file.error()) { KMessageBox::error(this, i18n("Could not save to file \"%1\": %2", file.fileName(), QString::fromLocal8Bit(strerror(err))), i18n("File Save Error")); } } void AuditLogViewer::slotCopyClip() { #ifdef HAVE_PIMTEXTEDIT m_textEdit->editor()->selectAll(); m_textEdit->editor()->copy(); m_textEdit->editor()->textCursor().clearSelection(); #else m_textEdit->selectAll(); m_textEdit->copy(); m_textEdit->textCursor().clearSelection(); #endif } void AuditLogViewer::readConfig() { KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("AuditLogViewer")); const QSize size = group.readEntry("Size", QSize()); if (size.isValid()) { resize(size); } else { resize(600, 400); } } void AuditLogViewer::writeConfig() { KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("AuditLogViewer")); group.writeEntry("Size", size()); group.sync(); } #include "moc_auditlogviewer.cpp" diff --git a/src/ui/cryptoconfigmodule.cpp b/src/ui/cryptoconfigmodule.cpp index fcb488f8d..fb8a2a7c4 100644 --- a/src/ui/cryptoconfigmodule.cpp +++ b/src/ui/cryptoconfigmodule.cpp @@ -1,1042 +1,1042 @@ /* cryptoconfigmodule.cpp This file is part of kgpgcertmanager SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "cryptoconfigmodule.h" #include "cryptoconfigentryreaderport_p.h" #include "cryptoconfigmodule_p.h" #include "directoryserviceswidget.h" #include "filenamerequester.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class ScrollArea : public QScrollArea { public: explicit ScrollArea(QWidget *p) : QScrollArea(p) { } QSize sizeHint() const override { const QSize wsz = widget() ? widget()->sizeHint() : QSize(); return {wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height()}; } }; } inline QIcon loadIcon(const QString &s) { QString ss = s; - const static QRegularExpression reg(QRegularExpression(QLatin1String("[^a-zA-Z0-9_]"))); + const static QRegularExpression reg(QRegularExpression(QLatin1StringView("[^a-zA-Z0-9_]"))); return QIcon::fromTheme(ss.replace(reg, QStringLiteral("-"))); } static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config) { if (!config) { return 0; } const QStringList components = config->componentList(); unsigned int result = 0; for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it)) { if (!comp->groupList().empty()) { ++result; } } } return result; } static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok) { ok = true; if (num_components_with_options(config) < 2) { ok = false; return KPageView::Plain; } switch (layout) { case CryptoConfigModule::LinearizedLayout: return KPageView::Plain; case CryptoConfigModule::TabbedLayout: return KPageView::Tabbed; case CryptoConfigModule::IconListLayout: return KPageView::List; } Q_ASSERT(!"we should never get here"); return KPageView::List; } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(IconListLayout); } Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent) : KPageWidget(parent) , mConfig(config) { init(layout); } void Kleo::CryptoConfigModule::init(Layout layout) { if (QLayout *l = this->layout()) { l->setContentsMargins(0, 0, 0, 0); } QGpgME::CryptoConfig *const config = mConfig; bool configOK = false; const KPageView::FaceType type = determineJanusFace(config, layout, configOK); setFaceType(type); QVBoxLayout *vlay = nullptr; QWidget *vbox = nullptr; if (type == Plain) { QWidget *w = new QWidget(this); auto l = new QVBoxLayout(w); l->setContentsMargins(0, 0, 0, 0); auto s = new QScrollArea(w); s->setFrameStyle(QFrame::NoFrame); s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); s->setWidgetResizable(true); l->addWidget(s); vbox = new QWidget(s->viewport()); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); s->setWidget(vbox); addPage(w, configOK ? QString() : i18n("GpgConf Error")); } const QStringList components = sortComponentList(config->componentList()); for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) { // qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":"; QGpgME::CryptoConfigComponent *comp = config->component(*it); Q_ASSERT(comp); if (comp->groupList().empty()) { continue; } std::unique_ptr compGUI(new CryptoConfigComponentGUI(this, comp)); compGUI->setObjectName(*it); // KJanusWidget doesn't seem to have iterators, so we store a copy... mComponentGUIs.append(compGUI.get()); if (type == Plain) { QGroupBox *gb = new QGroupBox(comp->description(), vbox); (new QVBoxLayout(gb))->addWidget(compGUI.release()); vlay->addWidget(gb); } else { vbox = new QWidget(this); vlay = new QVBoxLayout(vbox); vlay->setContentsMargins(0, 0, 0, 0); KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description()); if (type != Tabbed) { pageItem->setIcon(loadIcon(comp->iconName())); } addPage(pageItem); QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox); scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); scrollArea->setWidgetResizable(true); vlay->addWidget(scrollArea); const QSize compGUISize = compGUI->sizeHint(); scrollArea->setWidget(compGUI.release()); // Set a nice startup size const int deskHeight = screen()->size().height(); int dialogHeight; if (deskHeight > 1000) { // very big desktop ? dialogHeight = 800; } else if (deskHeight > 650) { // big desktop ? dialogHeight = 500; } else { // small (800x600, 640x480) desktop dialogHeight = 400; } Q_ASSERT(scrollArea->widget()); if (type != Tabbed) { scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight)); } } } if (mComponentGUIs.empty()) { const QString msg = i18n( "The gpgconf tool used to provide the information " "for this dialog does not seem to be installed " "properly. It did not return any components. " "Try running \"%1\" on the command line for more " "information.", - components.empty() ? QLatin1String("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg")); + components.empty() ? QLatin1StringView("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg")); QLabel *label = new QLabel(msg, vbox); label->setWordWrap(true); label->setMinimumHeight(fontMetrics().lineSpacing() * 5); vlay->addWidget(label); } } namespace { template QStringList sortConfigEntries(const Iterator orderBegin, const Iterator orderEnd, const QStringList &entries) { // components sorting algorithm: // 1. components with predefined order (provided via orderBegin / orderEnd) // 2. other components sorted alphabetically QStringList result; QStringList others; for (auto it = orderBegin; it != orderEnd; ++it) { if (entries.contains(*it)) { result.append(*it); } } for (const auto &item : entries) { if (!result.contains(item)) { others.append(item); } } others.sort(); result.append(others); return result; } } // namespace QStringList Kleo::CryptoConfigModule::sortComponentList(const QStringList &components) { static const std::array order = { QStringLiteral("gpg"), QStringLiteral("gpgsm"), QStringLiteral("gpg-agent"), QStringLiteral("dirmngr"), QStringLiteral("pinentry"), QStringLiteral("scdaemon"), }; return sortConfigEntries(order.begin(), order.end(), components); } QStringList Kleo::CryptoConfigModule::sortGroupList(const QString &moduleName, const QStringList &groups) { if (moduleName == QStringLiteral("gpg")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpgsm")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("gpg-agent")) { static const std::array order = { QStringLiteral("Security"), QStringLiteral("Passphrase policy"), QStringLiteral("Configuration"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("dirmngr")) { static const std::array order = { QStringLiteral("Keyserver"), QStringLiteral("HTTP"), QStringLiteral("LDAP"), QStringLiteral("OCSP"), QStringLiteral("Tor"), QStringLiteral("Enforcement"), QStringLiteral("Configuration"), QStringLiteral("Format"), QStringLiteral("Monitor"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else if (moduleName == QStringLiteral("scdaemon")) { static const std::array order = { QStringLiteral("Monitor"), QStringLiteral("Configuration"), QStringLiteral("Security"), QStringLiteral("Debug"), }; return sortConfigEntries(order.begin(), order.end(), groups); } else { qCDebug(KLEO_UI_LOG) << "Configuration groups order is not defined for " << moduleName; QStringList result(groups); result.sort(); return result; } } bool Kleo::CryptoConfigModule::hasError() const { return mComponentGUIs.empty(); } void Kleo::CryptoConfigModule::save() { bool changed = false; QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } if (changed) { mConfig->sync(true /*runtime*/); } } void Kleo::CryptoConfigModule::reset() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigModule::defaults() { QList::Iterator it = mComponentGUIs.begin(); for (; it != mComponentGUIs.end(); ++it) { (*it)->defaults(); } } void Kleo::CryptoConfigModule::cancel() { mConfig->clear(); } //// namespace { bool offerEntryForConfiguration(QGpgME::CryptoConfigEntry *entry) { static const QRegularExpression entryPathGroupSegmentRegexp{QStringLiteral("/.*/")}; static std::set entriesToExclude; if (entriesToExclude.empty()) { entriesToExclude.insert(QStringLiteral("gpg/keyserver")); if (engineIsVersion(2, 3, 5, GpgME::GpgConfEngine) || (engineIsVersion(2, 2, 34, GpgME::GpgConfEngine) && !engineIsVersion(2, 3, 0, GpgME::GpgConfEngine))) { // exclude for 2.2.{34,...} and 2.3.5+ entriesToExclude.insert(QStringLiteral("gpgsm/keyserver")); } } const bool de_vs = DeVSCompliance::isActive(); // Skip "dangerous" expert options if we are running in CO_DE_VS. // Otherwise, skip any options beyond "invisible" (== expert + 1) level. const auto maxEntryLevel = de_vs ? QGpgME::CryptoConfigEntry::Level_Advanced // : QGpgME::CryptoConfigEntry::Level_Expert + 1; // we ignore the group when looking up entries to exclude because entries // are uniquely identified by their name and their component - const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1String{"/"}).toLower(); + const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1StringView{"/"}).toLower(); return (entry->level() <= maxEntryLevel) && (entriesToExclude.find(entryId) == entriesToExclude.end()); } auto getGroupEntriesToOfferForConfiguration(QGpgME::CryptoConfigGroup *group) { std::vector result; const auto entryNames = group->entryList(); for (const auto &entryName : entryNames) { auto *const entry = group->entry(entryName); Q_ASSERT(entry); if (offerEntryForConfiguration(entry)) { result.push_back(entry); } else { qCDebug(KLEO_UI_LOG) << "entry" << entry->path() << "too advanced or excluded explicitly, skipping"; } } return result; } } Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent) : QWidget(parent) , mComponent(component) { auto glay = new QGridLayout(this); const QStringList groups = module->sortGroupList(mComponent->name(), mComponent->groupList()); if (groups.size() > 1) { glay->setColumnMinimumWidth(0, 30); for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) { QGpgME::CryptoConfigGroup *group = mComponent->group(*it); Q_ASSERT(group); if (!group) { continue; } auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() == 0) { // skip groups without entries to be offered in the UI continue; } const QString title = group->description(); auto hbox = new QHBoxLayout; hbox->addWidget(new QLabel{title.isEmpty() ? *it : title, this}); hbox->addWidget(new KSeparator{Qt::Horizontal, this}, 1); const int row = glay->rowCount(); glay->addLayout(hbox, row, 0, 1, 3); mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } else if (!groups.empty()) { auto *const group = mComponent->group(groups.front()); auto groupEntries = getGroupEntriesToOfferForConfiguration(group); if (groupEntries.size() > 0) { mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this)); } } glay->setRowStretch(glay->rowCount(), 1); } bool Kleo::CryptoConfigComponentGUI::save() { bool changed = false; QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { if ((*it)->save()) { changed = true; } } return changed; } void Kleo::CryptoConfigComponentGUI::load() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigComponentGUI::defaults() { QList::Iterator it = mGroupGUIs.begin(); for (; it != mGroupGUIs.end(); ++it) { (*it)->defaults(); } } //// Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(CryptoConfigModule *module, QGpgME::CryptoConfigGroup *group, const std::vector &entries, QGridLayout *glay, QWidget *widget) : QObject(module) { const int startRow = glay->rowCount(); for (auto entry : entries) { CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, entry->name(), glay, widget); if (entryGUI) { mEntryGUIs.append(entryGUI); entryGUI->load(); } } const int endRow = glay->rowCount() - 1; if (endRow < startRow) { return; } const QString iconName = group->iconName(); if (iconName.isEmpty()) { return; } QLabel *l = new QLabel(widget); l->setPixmap(loadIcon(iconName).pixmap(32, 32)); glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop); } bool Kleo::CryptoConfigGroupGUI::save() { bool changed = false; QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { if ((*it)->isChanged()) { (*it)->save(); changed = true; } } return changed; } void Kleo::CryptoConfigGroupGUI::load() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->load(); } } void Kleo::CryptoConfigGroupGUI::defaults() { QList::Iterator it = mEntryGUIs.begin(); for (; it != mEntryGUIs.end(); ++it) { (*it)->resetToDefault(); } } //// using constructor = CryptoConfigEntryGUI *(*)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *); namespace { template CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p) { return new T_Widget(m, e, n, l, p); } } static const struct WidgetsByEntryName { const char *entryGlob; constructor create; } widgetsByEntryName[] = { {"*/*/debug-level", &_create}, {"scdaemon/*/reader-port", &_create}, }; static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof *widgetsByEntryName; static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox &_create, nullptr, // String // Int/UInt: Let people type list of numbers (1,2,3....). Untested. &_create, &_create, nullptr, // Path nullptr, // Formerly URL &_create, nullptr, // DirPath }; static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = { // clang-format off &_create, // None &_create, // String &_create, // Int &_create, // UInt &_create, // Path nullptr, // Formerly URL nullptr, // LDAPURL &_create, // DirPath // clang-format on }; CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) { Q_ASSERT(entry); // try to lookup by path: const QString path = entry->path(); for (unsigned int i = 0; i < numWidgetsByEntryName; ++i) { if (QRegularExpression::fromWildcard(QString::fromLatin1(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive).match(path).hasMatch()) { return widgetsByEntryName[i].create(module, entry, entryName, glay, widget); } } // none found, so look up by type: const unsigned int argType = entry->argType(); Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType); if (entry->isList()) { if (const constructor create = listWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType(); } } else if (const constructor create = scalarWidgets[argType]) { return create(module, entry, entryName, glay, widget); } else { qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType(); } return nullptr; } //// Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName) : QObject(module) , mEntry(entry) , mName(entryName) , mChanged(false) { connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed); } QString Kleo::CryptoConfigEntryGUI::description() const { QString descr = mEntry->description(); if (descr.isEmpty()) { // happens for expert options // String does not need to be translated because the options itself // are also not translated return QStringLiteral("\"%1\"").arg(mName); } if (i18nc("Translate this to 'yes' or 'no' (use the English words!) " "depending on whether your language uses " "Sentence style capitalization in GUI labels (yes) or not (no). " "Context: We get some backend strings in that have the wrong " "capitalization (in English, at least) so we need to force the " "first character to upper-case. It is this behaviour you can " "control for your language with this translation.", "yes") - == QLatin1String("yes")) { + == QLatin1StringView("yes")) { descr[0] = descr[0].toUpper(); } return descr; } void Kleo::CryptoConfigEntryGUI::resetToDefault() { mEntry->resetToDefault(); load(); } //// Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mLineEdit = new KLineEdit(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mLineEdit); glay->addWidget(label, row, 1); glay->addWidget(mLineEdit, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mLineEdit->setEnabled(false); } else { connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged); } } void Kleo::CryptoConfigEntryLineEdit::doSave() { mEntry->setStringValue(mLineEdit->text()); } void Kleo::CryptoConfigEntryLineEdit::doLoad() { mLineEdit->setText(mEntry->stringValue()); } //// /* Note: Do not use "guru" as debug level but use the value 10. The former also enables the creation of hash dump files and thus leaves traces of plaintext on the disk. */ static const struct { const KLazyLocalizedString label; const char *name; } debugLevels[] = { {kli18n("0 - None"), "none"}, {kli18n("1 - Basic"), "basic"}, {kli18n("2 - Verbose"), "advanced"}, {kli18n("3 - More Verbose"), "expert"}, {kli18n("4 - All"), "10"}, }; static const unsigned int numDebugLevels = sizeof debugLevels / sizeof *debugLevels; Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mComboBox(new QComboBox(widget)) { QLabel *label = new QLabel(i18n("Set the debugging level to"), widget); label->setBuddy(mComboBox); for (unsigned int i = 0; i < numDebugLevels; ++i) { mComboBox->addItem(KLocalizedString(debugLevels[i].label).toString()); } if (entry->isReadOnly()) { label->setEnabled(false); mComboBox->setEnabled(false); } else { connect(mComboBox, &QComboBox::currentIndexChanged, this, &CryptoConfigEntryDebugLevel::slotChanged); } const int row = glay->rowCount(); glay->addWidget(label, row, 1); glay->addWidget(mComboBox, row, 2); } void Kleo::CryptoConfigEntryDebugLevel::doSave() { const unsigned int idx = mComboBox->currentIndex(); if (idx < numDebugLevels) { - mEntry->setStringValue(QLatin1String(debugLevels[idx].name)); + mEntry->setStringValue(QLatin1StringView(debugLevels[idx].name)); } else { mEntry->setStringValue(QString()); } } void Kleo::CryptoConfigEntryDebugLevel::doLoad() { const QString str = mEntry->stringValue(); for (unsigned int i = 0; i < numDebugLevels; ++i) { - if (str == QLatin1String(debugLevels[i].name)) { + if (str == QLatin1StringView(debugLevels[i].name)) { mComboBox->setCurrentIndex(i); return; } } mComboBox->setCurrentIndex(0); } //// Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Files); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged); } } void Kleo::CryptoConfigEntryPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryPath::doLoad() { if (mEntry->urlValue().isLocalFile()) { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } else { mFileNameRequester->setFileName(mEntry->urlValue().toString()); } } //// Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) , mFileNameRequester(nullptr) { const int row = glay->rowCount(); mFileNameRequester = new FileNameRequester(widget); mFileNameRequester->setExistingOnly(false); mFileNameRequester->setFilter(QDir::Dirs); QLabel *label = new QLabel(description(), widget); label->setBuddy(mFileNameRequester); glay->addWidget(label, row, 1); glay->addWidget(mFileNameRequester, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mFileNameRequester->setEnabled(false); } else { connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged); } } void Kleo::CryptoConfigEntryDirPath::doSave() { mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName())); } void Kleo::CryptoConfigEntryDirPath::doLoad() { mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile()); } //// Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) { mKind = ListOfNone; } else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) { mKind = UInt; } else { Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int); mKind = Int; } const int row = glay->rowCount(); mNumInput = new QSpinBox(widget); QLabel *label = new QLabel(description(), widget); label->setBuddy(mNumInput); glay->addWidget(label, row, 1); glay->addWidget(mNumInput, row, 2); if (entry->isReadOnly()) { label->setEnabled(false); mNumInput->setEnabled(false); } else { mNumInput->setMinimum(mKind == Int ? std::numeric_limits::min() : 0); mNumInput->setMaximum(std::numeric_limits::max()); connect(mNumInput, &QSpinBox::valueChanged, this, &CryptoConfigEntrySpinBox::slotChanged); } } void Kleo::CryptoConfigEntrySpinBox::doSave() { int value = mNumInput->value(); switch (mKind) { case ListOfNone: mEntry->setNumberOfTimesSet(value); break; case UInt: mEntry->setUIntValue(value); break; case Int: mEntry->setIntValue(value); break; } } void Kleo::CryptoConfigEntrySpinBox::doLoad() { int value = 0; switch (mKind) { case ListOfNone: value = mEntry->numberOfTimesSet(); break; case UInt: value = mEntry->uintValue(); break; case Int: value = mEntry->intValue(); break; } mNumInput->setValue(value); } //// Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { const int row = glay->rowCount(); mCheckBox = new QCheckBox(widget); glay->addWidget(mCheckBox, row, 1, 1, 2); mCheckBox->setText(description()); if (entry->isReadOnly()) { mCheckBox->setEnabled(false); } else { connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged); } } void Kleo::CryptoConfigEntryCheckBox::doSave() { mEntry->setBoolValue(mCheckBox->isChecked()); } void Kleo::CryptoConfigEntryCheckBox::doLoad() { mCheckBox->setChecked(mEntry->boolValue()); } Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget) : CryptoConfigEntryGUI(module, entry, entryName) { mLabel = new QLabel(widget); mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget); const int row = glay->rowCount(); QLabel *label = new QLabel(description(), widget); label->setBuddy(mPushButton); glay->addWidget(label, row, 1); auto hlay = new QHBoxLayout; glay->addLayout(hlay, row, 2); hlay->addWidget(mLabel, 1); hlay->addWidget(mPushButton); if (entry->isReadOnly()) { mLabel->setEnabled(false); } connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog); } void Kleo::CryptoConfigEntryLDAPURL::doLoad() { setURLList(mEntry->urlValueList()); } void Kleo::CryptoConfigEntryLDAPURL::doSave() { mEntry->setURLValueList(mURLList); } void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly) { QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog); if (!readOnly) { buttonBox->addButton(QDialogButtonBox::Cancel); buttonBox->addButton(QDialogButtonBox::RestoreDefaults); QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults); QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear); QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); } QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); auto layout = new QVBoxLayout; layout->addWidget(dirserv); layout->addWidget(buttonBox); dialog->setLayout(layout); } void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog() { if (!gpgme_check_version("1.16.0")) { KMessageBox::error(mPushButton->parentWidget(), i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), i18n("Sorry")); return; } // I'm a bad boy and I do it all on the stack. Enough classes already :) // This is just a simple dialog around the directory-services-widget QDialog dialog(mPushButton->parentWidget()); dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services")); auto dirserv = new DirectoryServicesWidget(&dialog); prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly()); dirserv->setReadOnly(mEntry->isReadOnly()); std::vector servers; std::transform(std::cbegin(mURLList), std::cend(mURLList), std::back_inserter(servers), [](const auto &url) { return KeyserverConfig::fromUrl(url); }); dirserv->setKeyservers(servers); if (dialog.exec()) { QList urls; const auto servers = dirserv->keyservers(); std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) { return server.toUrl(); }); setURLList(urls); slotChanged(); } } void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList &urlList) { mURLList = urlList; if (mURLList.isEmpty()) { mLabel->setText(i18n("None configured")); } else { mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count())); } } #include "moc_cryptoconfigmodule_p.cpp" #include "moc_cryptoconfigmodule.cpp" diff --git a/src/ui/dnattributeorderconfigwidget.cpp b/src/ui/dnattributeorderconfigwidget.cpp index 8b7f4ec3d..ed99ebdc2 100644 --- a/src/ui/dnattributeorderconfigwidget.cpp +++ b/src/ui/dnattributeorderconfigwidget.cpp @@ -1,418 +1,418 @@ /* -*- c++ -*- dnattributeorderconfigwidget.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "dnattributeorderconfigwidget.h" #include #include #include #include #include #include #include #include #include #include namespace { class TreeWidget : public QTreeWidget { Q_OBJECT public: using QTreeWidget::QTreeWidget; protected: void focusInEvent(QFocusEvent *event) override { QTreeWidget::focusInEvent(event); // queue the invokation, so that it happens after the widget itself got focus QMetaObject::invokeMethod(this, &TreeWidget::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection); } private: void forceAccessibleFocusEventForCurrentItem() { // force Qt to send a focus event for the current item to accessibility // tools; otherwise, the user has no idea which item is selected when the // list gets keyboard input focus const auto current = currentItem(); setCurrentItem(nullptr); setCurrentItem(current); } }; } class Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidgetPrivate { public: enum { Right = 0, Left = 1, UUp = 2, Up = 3, Down = 4, DDown = 5 }; TreeWidget *availableLV = nullptr; TreeWidget *currentLV = nullptr; std::vector navTB; QTreeWidgetItem *placeHolderItem = nullptr; }; static void prepare(QTreeWidget *lv) { lv->setAllColumnsShowFocus(true); lv->header()->setStretchLastSection(true); lv->setHeaderLabels(QStringList() << QString() << i18n("Description")); } Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , d(new DNAttributeOrderConfigWidgetPrivate) { auto glay = new QGridLayout(this); glay->setContentsMargins(0, 0, 0, 0); glay->setColumnStretch(0, 1); glay->setColumnStretch(2, 1); int row = -1; ++row; auto availableAttributesLabel = new QLabel(i18n("Available attributes:"), this); glay->addWidget(availableAttributesLabel, row, 0); auto currentAttributesLabel = new QLabel(i18n("Current attribute order:"), this); glay->addWidget(currentAttributesLabel, row, 2); ++row; glay->setRowStretch(row, 1); d->availableLV = new TreeWidget(this); availableAttributesLabel->setBuddy(d->availableLV); d->availableLV->setAccessibleName(i18n("available attributes")); prepare(d->availableLV); d->availableLV->sortItems(0, Qt::AscendingOrder); glay->addWidget(d->availableLV, row, 0); d->placeHolderItem = new QTreeWidgetItem(d->availableLV); d->placeHolderItem->setText(0, QStringLiteral("_X_")); d->placeHolderItem->setText(1, i18n("All others")); d->placeHolderItem->setData(0, Qt::AccessibleTextRole, i18n("All others")); struct NavButtonInfo { const char *icon; const KLazyLocalizedString accessibleName; const KLazyLocalizedString tooltip; void (DNAttributeOrderConfigWidget::*slot)(); bool autorepeat; }; static const std::vector navButtons = { { "go-next", kli18nc("@action:button", "Add"), kli18n("Add to current attribute order"), &DNAttributeOrderConfigWidget::slotRightButtonClicked, false, }, { "go-previous", kli18nc("@action:button", "Remove"), kli18n("Remove from current attribute order"), &DNAttributeOrderConfigWidget::slotLeftButtonClicked, false, }, { "go-top", kli18nc("@action:button", "Move to Top"), kli18n("Move to top"), &DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked, false, }, { "go-up", kli18nc("@action:button", "Move Up"), kli18n("Move one up"), &DNAttributeOrderConfigWidget::slotUpButtonClicked, true, }, { "go-down", kli18nc("@action:button", "Move Down"), kli18n("Move one down"), &DNAttributeOrderConfigWidget::slotDownButtonClicked, true, }, { "go-bottom", kli18nc("@action:button", "Move to Bottom"), kli18n("Move to bottom"), &DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked, false, }, }; const auto createToolButton = [this](const NavButtonInfo &navButton) { auto tb = new QToolButton{this}; - tb->setIcon(QIcon::fromTheme(QLatin1String(navButton.icon))); + tb->setIcon(QIcon::fromTheme(QLatin1StringView(navButton.icon))); tb->setEnabled(false); tb->setAccessibleName(KLocalizedString{navButton.accessibleName}.toString()); tb->setToolTip(KLocalizedString(navButton.tooltip).toString()); tb->setAutoRepeat(navButton.autorepeat); connect(tb, &QToolButton::clicked, this, navButton.slot); d->navTB.push_back(tb); return tb; }; { auto buttonCol = new QVBoxLayout; buttonCol->addStretch(); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::Right])); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::Left])); buttonCol->addStretch(); glay->addLayout(buttonCol, row, 1); } d->currentLV = new TreeWidget(this); currentAttributesLabel->setBuddy(d->currentLV); d->currentLV->setAccessibleName(i18n("current attribute order")); prepare(d->currentLV); glay->addWidget(d->currentLV, row, 2); { auto buttonCol = new QVBoxLayout; buttonCol->addStretch(); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::UUp])); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::Up])); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::Down])); buttonCol->addWidget(createToolButton(navButtons[DNAttributeOrderConfigWidgetPrivate::DDown])); buttonCol->addStretch(); glay->addLayout(buttonCol, row, 3); } #ifndef NDEBUG Q_ASSERT(d->navTB.size() == navButtons.size()); for (uint i = 0; i < navButtons.size(); ++i) { Q_ASSERT(d->navTB[i]->accessibleName() == KLocalizedString{navButtons[i].accessibleName}.toString()); } #endif connect(d->availableLV, &QTreeWidget::itemSelectionChanged, this, &DNAttributeOrderConfigWidget::slotAvailableSelectionChanged); connect(d->currentLV, &QTreeWidget::itemSelectionChanged, this, &DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged); } Kleo::DNAttributeOrderConfigWidget::~DNAttributeOrderConfigWidget() = default; void Kleo::DNAttributeOrderConfigWidget::setAttributeOrder(const QStringList &order) { // save the _X_ item: takePlaceHolderItem(); // clear the rest: d->availableLV->clear(); d->currentLV->clear(); // fill the RHS listview: QTreeWidgetItem *last = nullptr; for (const auto &entry : order) { const QString attr = entry.toUpper(); - if (attr == QLatin1String("_X_")) { + if (attr == QLatin1StringView("_X_")) { takePlaceHolderItem(); d->currentLV->insertTopLevelItem(d->currentLV->topLevelItemCount(), d->placeHolderItem); last = d->placeHolderItem; } else { last = new QTreeWidgetItem(d->currentLV, last); last->setText(0, attr); const auto label = DN::attributeNameToLabel(attr); last->setText(1, label); - const QString accessibleName = label + QLatin1String(", ") + attr; + const QString accessibleName = label + QLatin1StringView(", ") + attr; last->setData(0, Qt::AccessibleTextRole, accessibleName); } } d->currentLV->setCurrentItem(d->currentLV->topLevelItem(0)); // fill the LHS listview with what's left: const QStringList all = DN::attributeNames(); for (const auto &attr : all) { if (!order.contains(attr, Qt::CaseInsensitive)) { auto item = new QTreeWidgetItem(d->availableLV); item->setText(0, attr); const auto label = DN::attributeNameToLabel(attr); item->setText(1, label); - const QString accessibleName = label + QLatin1String(", ") + attr; + const QString accessibleName = label + QLatin1StringView(", ") + attr; item->setData(0, Qt::AccessibleTextRole, accessibleName); } } if (!d->placeHolderItem->treeWidget()) { d->availableLV->addTopLevelItem(d->placeHolderItem); } d->availableLV->setCurrentItem(d->availableLV->topLevelItem(0)); } void Kleo::DNAttributeOrderConfigWidget::takePlaceHolderItem() { if (QTreeWidget *lv = d->placeHolderItem->treeWidget()) { lv->takeTopLevelItem(lv->indexOfTopLevelItem(d->placeHolderItem)); } } QStringList Kleo::DNAttributeOrderConfigWidget::attributeOrder() const { QStringList order; for (QTreeWidgetItemIterator it(d->currentLV); (*it); ++it) { order.push_back((*it)->text(0)); } return order; } void Kleo::DNAttributeOrderConfigWidget::slotAvailableSelectionChanged() { d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(!d->availableLV->selectedItems().empty()); } void Kleo::DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged() { const auto selectedItems = d->currentLV->selectedItems(); auto selectedItem = selectedItems.empty() ? nullptr : selectedItems.front(); enableDisableButtons(selectedItem); } void Kleo::DNAttributeOrderConfigWidget::enableDisableButtons(QTreeWidgetItem *item) { d->navTB[DNAttributeOrderConfigWidgetPrivate::UUp]->setEnabled(item && d->currentLV->itemAbove(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::Up]->setEnabled(item && d->currentLV->itemAbove(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::Left]->setEnabled(item); d->navTB[DNAttributeOrderConfigWidgetPrivate::Down]->setEnabled(item && d->currentLV->itemBelow(item)); d->navTB[DNAttributeOrderConfigWidgetPrivate::DDown]->setEnabled(item && d->currentLV->itemBelow(item)); } void Kleo::DNAttributeOrderConfigWidget::slotUpButtonClicked() { if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex <= 0) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(itemIndex - 1, item); d->currentLV->setCurrentItem(item); enableDisableButtons(item); Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked() { if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex == 0) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(0, item); d->currentLV->setCurrentItem(item); enableDisableButtons(item); Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::slotDownButtonClicked() { if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->insertTopLevelItem(itemIndex + 1, item); d->currentLV->setCurrentItem(item); enableDisableButtons(item); Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked() { if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *item = d->currentLV->selectedItems().first(); const int itemIndex = d->currentLV->indexOfTopLevelItem(item); if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) { return; } d->currentLV->takeTopLevelItem(itemIndex); d->currentLV->addTopLevelItem(item); d->currentLV->setCurrentItem(item); enableDisableButtons(item); Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::slotLeftButtonClicked() { if (d->currentLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *right = d->currentLV->selectedItems().first(); QTreeWidgetItem *next = d->currentLV->itemBelow(right); if (!next) { next = d->currentLV->itemAbove(right); } d->currentLV->takeTopLevelItem(d->currentLV->indexOfTopLevelItem(right)); d->availableLV->addTopLevelItem(right); d->availableLV->sortItems(0, Qt::AscendingOrder); d->availableLV->setCurrentItem(right); if (next) { d->currentLV->setCurrentItem(next); } enableDisableButtons(next); Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::slotRightButtonClicked() { if (d->availableLV->selectedItems().isEmpty()) { return; } QTreeWidgetItem *left = d->availableLV->selectedItems().first(); QTreeWidgetItem *next = d->availableLV->itemBelow(left); if (!next) { next = d->availableLV->itemAbove(left); } d->availableLV->takeTopLevelItem(d->availableLV->indexOfTopLevelItem(left)); int newRightIndex = d->currentLV->topLevelItemCount(); if (!d->currentLV->selectedItems().isEmpty()) { QTreeWidgetItem *right = d->currentLV->selectedItems().first(); newRightIndex = d->currentLV->indexOfTopLevelItem(right); } d->currentLV->insertTopLevelItem(newRightIndex, left); d->currentLV->setCurrentItem(left); enableDisableButtons(left); d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(next); if (next) { d->availableLV->setCurrentItem(next); } Q_EMIT changed(); } void Kleo::DNAttributeOrderConfigWidget::virtual_hook(int, void *) { } #include "dnattributeorderconfigwidget.moc" #include "moc_dnattributeorderconfigwidget.cpp" diff --git a/src/ui/editdirectoryservicedialog.cpp b/src/ui/editdirectoryservicedialog.cpp index af823c14d..a1c8f0a14 100644 --- a/src/ui/editdirectoryservicedialog.cpp +++ b/src/ui/editdirectoryservicedialog.cpp @@ -1,410 +1,410 @@ /* ui/editdirectoryservicedialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "editdirectoryservicedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { int defaultPort(KeyserverConnection connection) { return connection == KeyserverConnection::TunnelThroughTLS ? 636 : 389; } } class EditDirectoryServiceDialog::Private { EditDirectoryServiceDialog *const q; struct Ui { QLineEdit *hostEdit = nullptr; QSpinBox *portSpinBox = nullptr; QCheckBox *useDefaultPortCheckBox = nullptr; QButtonGroup *authenticationGroup = nullptr; QLineEdit *userEdit = nullptr; KPasswordLineEdit *passwordEdit = nullptr; QButtonGroup *connectionGroup = nullptr; KCollapsibleGroupBox *advancedSettings = nullptr; QLineEdit *baseDnEdit = nullptr; QLineEdit *additionalFlagsEdit = nullptr; QDialogButtonBox *buttonBox = nullptr; Ui(QWidget *parent) : hostEdit{new QLineEdit{parent}} , portSpinBox{new QSpinBox{parent}} , useDefaultPortCheckBox{new QCheckBox{parent}} , authenticationGroup{new QButtonGroup{parent}} , userEdit{new QLineEdit{parent}} , passwordEdit{new KPasswordLineEdit{parent}} , connectionGroup{new QButtonGroup{parent}} , advancedSettings{new KCollapsibleGroupBox{parent}} , baseDnEdit{new QLineEdit{parent}} , additionalFlagsEdit{new QLineEdit{parent}} , buttonBox{new QDialogButtonBox{parent}} { #define SET_OBJECT_NAME(x) x->setObjectName(QStringLiteral(#x)); SET_OBJECT_NAME(hostEdit) SET_OBJECT_NAME(portSpinBox) SET_OBJECT_NAME(useDefaultPortCheckBox) SET_OBJECT_NAME(authenticationGroup) SET_OBJECT_NAME(userEdit) SET_OBJECT_NAME(passwordEdit) SET_OBJECT_NAME(connectionGroup) SET_OBJECT_NAME(advancedSettings) SET_OBJECT_NAME(baseDnEdit) SET_OBJECT_NAME(additionalFlagsEdit) SET_OBJECT_NAME(buttonBox) #undef SET_OBJECT_NAME auto mainLayout = new QVBoxLayout{parent}; auto serverWidget = new QWidget{parent}; { auto layout = new QGridLayout{serverWidget}; layout->setColumnStretch(2, 1); int row = 0; layout->addWidget(new QLabel{i18n("Host:")}, row, 0); hostEdit->setToolTip(i18nc("@info:tooltip", "Enter the name or IP address of the server hosting the directory service.")); hostEdit->setClearButtonEnabled(true); layout->addWidget(hostEdit, row, 1, 1, -1); ++row; layout->addWidget(new QLabel{i18n("Port:")}, row, 0); portSpinBox->setRange(1, USHRT_MAX); portSpinBox->setToolTip(i18nc("@info:tooltip", "(Optional, the default is fine in most cases) " "Pick the port number the directory service is listening on.")); layout->addWidget(portSpinBox, row, 1); useDefaultPortCheckBox->setText(i18n("Use default")); useDefaultPortCheckBox->setChecked(true); layout->addWidget(useDefaultPortCheckBox, row, 2); } mainLayout->addWidget(serverWidget); auto authenticationWidget = new QGroupBox{i18n("Authentication"), parent}; { auto layout = new QVBoxLayout{authenticationWidget}; { auto radioButton = new QRadioButton{i18n("Anonymous")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use an anonymous LDAP server that does not require authentication.")); radioButton->setChecked(true); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Anonymous)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate via Active Directory")}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { radioButton->setText(i18n("Authenticate via Active Directory (requires GnuPG 2.2.28 or later)")); } radioButton->setToolTip( i18nc("@info:tooltip", "On Windows, authenticate to the LDAP server using the Active Directory with the current user.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::ActiveDirectory)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Authenticate with user and password")}; radioButton->setToolTip(i18nc("@info:tooltip", "Authenticate to the LDAP server with your LDAP credentials.")); authenticationGroup->addButton(radioButton, static_cast(KeyserverAuthentication::Password)); layout->addWidget(radioButton); } auto credentialsWidget = new QWidget{parent}; { auto layout = new QGridLayout{credentialsWidget}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("User:")}, row, 0); userEdit->setToolTip(i18nc("@info:tooltip", "Enter your LDAP user resp. Bind DN for authenticating to the LDAP server.")); userEdit->setClearButtonEnabled(true); layout->addWidget(userEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Password:")}, row, 0); passwordEdit->setToolTip(xi18nc("@info:tooltip", "Enter your password for authenticating to the LDAP server." "The password will be saved in the clear " "in a configuration file in your home directory.")); passwordEdit->setClearButtonEnabled(true); layout->addWidget(passwordEdit, row, 1); } layout->addWidget(credentialsWidget); } mainLayout->addWidget(authenticationWidget); auto securityWidget = new QGroupBox{i18n("Connection Security"), parent}; if (!engineIsVersion(2, 2, 28, GpgME::GpgSMEngine)) { securityWidget->setTitle(i18n("Connection Security (requires GnuPG 2.2.28 or later)")); } { auto layout = new QVBoxLayout{securityWidget}; { auto radioButton = new QRadioButton{i18n("Use default connection (probably not TLS secured)")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use GnuPG's default to connect to the LDAP server. " "By default, GnuPG 2.3 and earlier use a plain, not TLS secured connection. " "(Not recommended)")); radioButton->setChecked(true); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Default)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Do not use a TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a plain, not TLS secured connection to connect to the LDAP server. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::Plain)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Use TLS secured connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a standard TLS secured connection (initiated with STARTTLS) " "to connect to the LDAP server. " "(Recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::UseSTARTTLS)); layout->addWidget(radioButton); } { auto radioButton = new QRadioButton{i18n("Tunnel LDAP through a TLS connection")}; radioButton->setToolTip(i18nc("@info:tooltip", "Use a TLS secured connection through which the connection to the " "LDAP server is tunneled. " "(Not recommended)")); connectionGroup->addButton(radioButton, static_cast(KeyserverConnection::TunnelThroughTLS)); layout->addWidget(radioButton); } } mainLayout->addWidget(securityWidget); advancedSettings->setTitle(i18n("Advanced Settings")); { auto layout = new QGridLayout{advancedSettings}; layout->setColumnStretch(1, 1); int row = 0; layout->addWidget(new QLabel{i18n("Base DN:")}, row, 0); baseDnEdit->setToolTip(i18nc("@info:tooltip", "(Optional, can usually be left empty) " "Enter the base DN for this LDAP server to limit searches " "to only that subtree of the directory.")); baseDnEdit->setClearButtonEnabled(true); layout->addWidget(baseDnEdit, row, 1); ++row; layout->addWidget(new QLabel{i18n("Additional flags:")}, row, 0); additionalFlagsEdit->setToolTip(i18nc("@info:tooltip", "Here you can enter additional flags that are not yet (or no longer) " "supported by Kleopatra. For example, older versions of GnuPG use " "ldaps to request a TLS secured connection.")); additionalFlagsEdit->setClearButtonEnabled(true); layout->addWidget(additionalFlagsEdit, row, 1); } mainLayout->addWidget(advancedSettings); mainLayout->addStretch(1); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(okButton, KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); mainLayout->addWidget(buttonBox); }; } ui; QString host() const { return ui.hostEdit->text().trimmed(); } int port() const { return ui.useDefaultPortCheckBox->isChecked() ? -1 : ui.portSpinBox->value(); } KeyserverAuthentication authentication() const { return KeyserverAuthentication{ui.authenticationGroup->checkedId()}; } QString user() const { return ui.userEdit->text().trimmed(); } QString password() const { return ui.passwordEdit->password(); // not trimmed } KeyserverConnection connection() const { return KeyserverConnection{ui.connectionGroup->checkedId()}; } QString baseDn() const { return ui.baseDnEdit->text().trimmed(); } QStringList additionalFlags() const { return transformInPlace(ui.additionalFlagsEdit->text().split(QLatin1Char{','}, Qt::SkipEmptyParts), [](const auto &flag) { return flag.trimmed(); }); } bool inputIsAcceptable() const { const bool hostIsSet = !host().isEmpty(); const bool requiredCredentialsAreSet = authentication() != KeyserverAuthentication::Password || (!user().isEmpty() && !password().isEmpty()); return hostIsSet && requiredCredentialsAreSet; } void updateWidgets() { ui.portSpinBox->setEnabled(!ui.useDefaultPortCheckBox->isChecked()); if (ui.useDefaultPortCheckBox->isChecked()) { ui.portSpinBox->setValue(defaultPort(connection())); } ui.userEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.passwordEdit->setEnabled(authentication() == KeyserverAuthentication::Password); ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(inputIsAcceptable()); } public: Private(EditDirectoryServiceDialog *q) : q{q} , ui{q} { connect(ui.hostEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.useDefaultPortCheckBox, &QCheckBox::toggled, q, [this]() { updateWidgets(); }); connect(ui.authenticationGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.userEdit, &QLineEdit::textEdited, q, [this]() { updateWidgets(); }); connect(ui.passwordEdit, &KPasswordLineEdit::passwordChanged, q, [this]() { updateWidgets(); }); connect(ui.connectionGroup, &QButtonGroup::idToggled, q, [this]() { updateWidgets(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditDirectoryServiceDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditDirectoryServiceDialog::reject); updateWidgets(); restoreLayout(); } ~Private() { saveLayout(); } void setKeyserver(const KeyserverConfig &keyserver) { ui.hostEdit->setText(keyserver.host()); ui.useDefaultPortCheckBox->setChecked(keyserver.port() == -1); ui.portSpinBox->setValue(keyserver.port() == -1 ? defaultPort(keyserver.connection()) : keyserver.port()); ui.authenticationGroup->button(static_cast(keyserver.authentication()))->setChecked(true); ui.userEdit->setText(keyserver.user()); ui.passwordEdit->setPassword(keyserver.password()); ui.connectionGroup->button(static_cast(keyserver.connection()))->setChecked(true); ui.baseDnEdit->setText(keyserver.ldapBaseDn()); ui.additionalFlagsEdit->setText(keyserver.additionalFlags().join(QLatin1Char{','})); ui.advancedSettings->setExpanded(!keyserver.ldapBaseDn().isEmpty() || !keyserver.additionalFlags().empty()); updateWidgets(); } KeyserverConfig keyserver() const { KeyserverConfig keyserver; keyserver.setHost(host()); keyserver.setPort(port()); keyserver.setAuthentication(authentication()); keyserver.setUser(user()); keyserver.setPassword(password()); keyserver.setConnection(connection()); keyserver.setLdapBaseDn(baseDn()); keyserver.setAdditionalFlags(additionalFlags()); return keyserver; } private: void saveLayout() { - KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1String("EditDirectoryServiceDialog")}; + KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")}; configGroup.writeEntry("Size", q->size()); configGroup.sync(); } void restoreLayout() { - const KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1String("EditDirectoryServiceDialog")}; + const KConfigGroup configGroup{KSharedConfig::openStateConfig(), QLatin1StringView("EditDirectoryServiceDialog")}; const auto size = configGroup.readEntry("Size", QSize{}); if (size.isValid()) { q->resize(size); } } }; EditDirectoryServiceDialog::EditDirectoryServiceDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{std::make_unique(this)} { setWindowTitle(i18nc("@title:window", "Edit Directory Service")); } EditDirectoryServiceDialog::~EditDirectoryServiceDialog() = default; void EditDirectoryServiceDialog::setKeyserver(const KeyserverConfig &keyserver) { d->setKeyserver(keyserver); } KeyserverConfig EditDirectoryServiceDialog::keyserver() const { return d->keyserver(); } #include "moc_editdirectoryservicedialog.cpp" diff --git a/src/ui/keyrequester.cpp b/src/ui/keyrequester.cpp index fa7d8b1aa..a20f3af62 100644 --- a/src/ui/keyrequester.cpp +++ b/src/ui/keyrequester.cpp @@ -1,515 +1,515 @@ /* -*- c++ -*- keyrequester.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details This file is part of KPGP, the KDE PGP/GnuPG support library. SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyrequester.h" #include "keyselectiondialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace QGpgME; using namespace Kleo; Kleo::KeyRequester::KeyRequester(unsigned int allowedKeys, bool multipleKeys, QWidget *parent) : QWidget(parent) , mOpenPGPBackend(nullptr) , mSMIMEBackend(nullptr) , mMulti(multipleKeys) , mKeyUsage(allowedKeys) , mJobs(0) , d(nullptr) { init(); } Kleo::KeyRequester::KeyRequester(QWidget *parent) : QWidget(parent) , mOpenPGPBackend(nullptr) , mSMIMEBackend(nullptr) , mMulti(false) , mKeyUsage(0) , mJobs(0) , d(nullptr) { init(); } void Kleo::KeyRequester::init() { auto hlay = new QHBoxLayout(this); hlay->setContentsMargins(0, 0, 0, 0); if (DeVSCompliance::isCompliant()) { mComplianceIcon = new QLabel{this}; mComplianceIcon->setPixmap(Formatting::questionIcon().pixmap(22)); } // the label where the key id is to be displayed: mLabel = new QLabel(this); mLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); // the button to unset any key: mEraseButton = new QPushButton(this); mEraseButton->setAutoDefault(false); mEraseButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); mEraseButton->setIcon( QIcon::fromTheme(QApplication::isRightToLeft() ? QStringLiteral("edit-clear-locationbar-ltr") : QStringLiteral("edit-clear-locationbar-rtl"))); mEraseButton->setToolTip(i18n("Clear")); // the button to call the KeySelectionDialog: mDialogButton = new QPushButton(i18n("Change..."), this); mDialogButton->setAutoDefault(false); if (mComplianceIcon) { hlay->addWidget(mComplianceIcon); } hlay->addWidget(mLabel, 1); hlay->addWidget(mEraseButton); hlay->addWidget(mDialogButton); connect(mEraseButton, &QPushButton::clicked, this, &SigningKeyRequester::slotEraseButtonClicked); connect(mDialogButton, &QPushButton::clicked, this, &SigningKeyRequester::slotDialogButtonClicked); setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); setAllowedKeys(mKeyUsage); } Kleo::KeyRequester::~KeyRequester() { } const std::vector &Kleo::KeyRequester::keys() const { return mKeys; } const GpgME::Key &Kleo::KeyRequester::key() const { static const GpgME::Key null = GpgME::Key::null; if (mKeys.empty()) { return null; } else { return mKeys.front(); } } void Kleo::KeyRequester::setKeys(const std::vector &keys) { mKeys.clear(); for (auto it = keys.begin(); it != keys.end(); ++it) { if (!it->isNull()) { mKeys.push_back(*it); } } updateKeys(); } void Kleo::KeyRequester::setKey(const GpgME::Key &key) { mKeys.clear(); if (!key.isNull()) { mKeys.push_back(key); } updateKeys(); } QString Kleo::KeyRequester::fingerprint() const { if (mKeys.empty()) { return QString(); } else { - return QLatin1String(mKeys.front().primaryFingerprint()); + return QLatin1StringView(mKeys.front().primaryFingerprint()); } } QStringList Kleo::KeyRequester::fingerprints() const { QStringList result; for (auto it = mKeys.begin(); it != mKeys.end(); ++it) { if (!it->isNull()) { if (const char *fpr = it->primaryFingerprint()) { - result.push_back(QLatin1String(fpr)); + result.push_back(QLatin1StringView(fpr)); } } } return result; } void Kleo::KeyRequester::setFingerprint(const QString &fingerprint) { startKeyListJob(QStringList(fingerprint)); } void Kleo::KeyRequester::setFingerprints(const QStringList &fingerprints) { startKeyListJob(fingerprints); } void Kleo::KeyRequester::updateKeys() { if (mKeys.empty()) { if (mComplianceIcon) { mComplianceIcon->setPixmap(Formatting::unavailableIcon().pixmap(22)); mComplianceIcon->setToolTip(QString{}); } mLabel->clear(); return; } if (mKeys.size() > 1) { setMultipleKeysEnabled(true); } QStringList labelTexts; QString toolTipText; for (std::vector::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) { if (it->isNull()) { continue; } - const QString fpr = QLatin1String(it->primaryFingerprint()); + const QString fpr = QLatin1StringView(it->primaryFingerprint()); labelTexts.push_back(fpr.right(8)); - toolTipText += fpr.right(8) + QLatin1String(": "); + toolTipText += fpr.right(8) + QLatin1StringView(": "); if (const char *uid = it->userID(0).id()) { if (it->protocol() == GpgME::OpenPGP) { toolTipText += QString::fromUtf8(uid); } else { toolTipText += Kleo::DN(uid).prettyDN(); } } else { toolTipText += xi18n("unknown"); } toolTipText += QLatin1Char('\n'); } if (mComplianceIcon) { if (Kleo::all_of(mKeys, &Kleo::DeVSCompliance::keyIsCompliant)) { mComplianceIcon->setPixmap(Formatting::successIcon().pixmap(22)); mComplianceIcon->setToolTip(DeVSCompliance::name(true)); } else { mComplianceIcon->setPixmap(Formatting::warningIcon().pixmap(22)); mComplianceIcon->setToolTip(DeVSCompliance::name(false)); } } - mLabel->setText(labelTexts.join(QLatin1String(", "))); + mLabel->setText(labelTexts.join(QLatin1StringView(", "))); mLabel->setToolTip(toolTipText); } #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ static void showKeyListError(QWidget *parent, const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while fetching " "the keys from the backend:

" "

%1

", Formatting::errorAsString(err)); KMessageBox::error(parent, msg, i18n("Key Listing Failed")); } #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ void Kleo::KeyRequester::startKeyListJob(const QStringList &fingerprints) { if (!mSMIMEBackend && !mOpenPGPBackend) { return; } mTmpKeys.clear(); mJobs = 0; unsigned int count = 0; for (QStringList::const_iterator it = fingerprints.begin(); it != fingerprints.end(); ++it) { if (!(*it).trimmed().isEmpty()) { ++count; } } if (!count) { // don't fall into the trap that an empty pattern means // "return all keys" :) setKey(GpgME::Key::null); return; } if (mOpenPGPBackend) { KeyListJob *job = mOpenPGPBackend->keyListJob(false); // local, no sigs if (!job) { KMessageBox::error(this, i18n("The OpenPGP backend does not support listing keys. " "Check your installation."), i18n("Key Listing Failed")); } else { connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult); connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey); const GpgME::Error err = job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys)); if (err) { showKeyListError(this, err); } else { ++mJobs; } } } if (mSMIMEBackend) { KeyListJob *job = mSMIMEBackend->keyListJob(false); // local, no sigs if (!job) { KMessageBox::error(this, i18n("The S/MIME backend does not support listing keys. " "Check your installation."), i18n("Key Listing Failed")); } else { connect(job, &KeyListJob::result, this, &SigningKeyRequester::slotKeyListResult); connect(job, &KeyListJob::nextKey, this, &SigningKeyRequester::slotNextKey); const GpgME::Error err = job->start(fingerprints, mKeyUsage & Kleo::KeySelectionDialog::SecretKeys && !(mKeyUsage & Kleo::KeySelectionDialog::PublicKeys)); if (err) { showKeyListError(this, err); } else { ++mJobs; } } } if (mJobs > 0) { mEraseButton->setEnabled(false); mDialogButton->setEnabled(false); } } void Kleo::KeyRequester::slotNextKey(const GpgME::Key &key) { if (!key.isNull()) { mTmpKeys.push_back(key); } } void Kleo::KeyRequester::slotKeyListResult(const GpgME::KeyListResult &res) { if (res.error()) { showKeyListError(this, res.error()); } if (--mJobs <= 0) { mEraseButton->setEnabled(true); mDialogButton->setEnabled(true); setKeys(mTmpKeys); mTmpKeys.clear(); } } void Kleo::KeyRequester::slotDialogButtonClicked() { KeySelectionDialog *dlg = mKeys.empty() ? new KeySelectionDialog(mDialogCaption, mDialogMessage, mInitialQuery, mKeyUsage, mMulti, false, this) : new KeySelectionDialog(mDialogCaption, mDialogCaption, mKeys, mKeyUsage, mMulti, false, this); if (dlg->exec() == QDialog::Accepted) { if (mMulti) { setKeys(dlg->selectedKeys()); } else { setKey(dlg->selectedKey()); } Q_EMIT changed(); } delete dlg; } void Kleo::KeyRequester::slotEraseButtonClicked() { if (!mKeys.empty()) { Q_EMIT changed(); } mKeys.clear(); updateKeys(); } void Kleo::KeyRequester::setDialogCaption(const QString &caption) { mDialogCaption = caption; } void Kleo::KeyRequester::setDialogMessage(const QString &msg) { mDialogMessage = msg; } bool Kleo::KeyRequester::isMultipleKeysEnabled() const { return mMulti; } void Kleo::KeyRequester::setMultipleKeysEnabled(bool multi) { if (multi == mMulti) { return; } if (!multi && !mKeys.empty()) { mKeys.erase(mKeys.begin() + 1, mKeys.end()); } mMulti = multi; updateKeys(); } unsigned int Kleo::KeyRequester::allowedKeys() const { return mKeyUsage; } void Kleo::KeyRequester::setAllowedKeys(unsigned int keyUsage) { mKeyUsage = keyUsage; mOpenPGPBackend = nullptr; mSMIMEBackend = nullptr; if (mKeyUsage & KeySelectionDialog::OpenPGPKeys) { mOpenPGPBackend = openpgp(); } if (mKeyUsage & KeySelectionDialog::SMIMEKeys) { mSMIMEBackend = smime(); } if (mOpenPGPBackend && !mSMIMEBackend) { mDialogCaption = i18n("OpenPGP Key Selection"); mDialogMessage = i18n("Please select an OpenPGP key to use."); } else if (!mOpenPGPBackend && mSMIMEBackend) { mDialogCaption = i18n("S/MIME Key Selection"); mDialogMessage = i18n("Please select an S/MIME key to use."); } else { mDialogCaption = i18n("Key Selection"); mDialogMessage = i18n("Please select an (OpenPGP or S/MIME) key to use."); } } QPushButton *Kleo::KeyRequester::dialogButton() { return mDialogButton; } QPushButton *Kleo::KeyRequester::eraseButton() { return mEraseButton; } static inline unsigned int foo(bool openpgp, bool smime, bool trusted, bool valid) { unsigned int result = 0; if (openpgp) { result |= Kleo::KeySelectionDialog::OpenPGPKeys; } if (smime) { result |= Kleo::KeySelectionDialog::SMIMEKeys; } if (trusted) { result |= Kleo::KeySelectionDialog::TrustedKeys; } if (valid) { result |= Kleo::KeySelectionDialog::ValidKeys; } return result; } static inline unsigned int encryptionKeyUsage(bool openpgp, bool smime, bool trusted, bool valid) { return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::EncryptionKeys | Kleo::KeySelectionDialog::PublicKeys; } static inline unsigned int signingKeyUsage(bool openpgp, bool smime, bool trusted, bool valid) { return foo(openpgp, smime, trusted, valid) | Kleo::KeySelectionDialog::SigningKeys | Kleo::KeySelectionDialog::SecretKeys; } Kleo::EncryptionKeyRequester::EncryptionKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid) : KeyRequester(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent) , d(nullptr) { } Kleo::EncryptionKeyRequester::EncryptionKeyRequester(QWidget *parent) : KeyRequester(0, false, parent) , d(nullptr) { } Kleo::EncryptionKeyRequester::~EncryptionKeyRequester() { } void Kleo::EncryptionKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid) { KeyRequester::setAllowedKeys(encryptionKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid)); } Kleo::SigningKeyRequester::SigningKeyRequester(bool multi, unsigned int proto, QWidget *parent, bool onlyTrusted, bool onlyValid) : KeyRequester(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid), multi, parent) , d(nullptr) { } Kleo::SigningKeyRequester::SigningKeyRequester(QWidget *parent) : KeyRequester(0, false, parent) , d(nullptr) { } Kleo::SigningKeyRequester::~SigningKeyRequester() { } void Kleo::SigningKeyRequester::setAllowedKeys(unsigned int proto, bool onlyTrusted, bool onlyValid) { KeyRequester::setAllowedKeys(signingKeyUsage(proto & OpenPGP, proto & SMIME, onlyTrusted, onlyValid)); } void Kleo::KeyRequester::virtual_hook(int, void *) { } void Kleo::EncryptionKeyRequester::virtual_hook(int id, void *data) { KeyRequester::virtual_hook(id, data); } void Kleo::SigningKeyRequester::virtual_hook(int id, void *data) { KeyRequester::virtual_hook(id, data); } #include "moc_keyrequester.cpp" diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp index 075012b06..02d87a1af 100644 --- a/src/ui/keyselectioncombo.cpp +++ b/src/ui/keyselectioncombo.cpp @@ -1,725 +1,725 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyselectioncombo.h" #include "progressbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; #if !UNITY_BUILD Q_DECLARE_METATYPE(GpgME::Key) #endif namespace { class SortFilterProxyModel : public KeyListSortFilterProxyModel { Q_OBJECT public: using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel; void setAlwaysAcceptedKey(const QString &fingerprint) { if (fingerprint == mFingerprint) { return; } mFingerprint = fingerprint; invalidate(); } protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (!mFingerprint.isEmpty()) { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString(); if (fingerprint == mFingerprint) { return true; } } return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } private: QString mFingerprint; }; static QString formatUserID(const GpgME::Key &key) { const auto userID = key.userID(0); QString name; QString email; if (key.protocol() == GpgME::OpenPGP) { name = QString::fromUtf8(userID.name()); email = QString::fromUtf8(userID.email()); } else { const Kleo::DN dn(userID.id()); name = dn[QStringLiteral("CN")]; email = dn[QStringLiteral("EMAIL")]; } return email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name ", "%1 <%2>", name, email); } class SortAndFormatCertificatesProxyModel : public QSortFilterProxyModel { Q_OBJECT public: SortAndFormatCertificatesProxyModel(KeyUsage::Flags usageFlags, QObject *parent = nullptr) : QSortFilterProxyModel{parent} , mIconProvider{usageFlags} { } private: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override { const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value(); const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value(); if (leftKey.isNull()) { return false; } if (rightKey.isNull()) { return true; } // As we display UID(0) this is ok. We probably need a get Best UID at some point. const auto lUid = leftKey.userID(0); const auto rUid = rightKey.userID(0); if (lUid.isNull()) { return false; } if (rUid.isNull()) { return true; } const auto leftNameAndEmail = formatUserID(leftKey); const auto rightNameAndEmail = formatUserID(rightKey); const int cmp = QString::localeAwareCompare(leftNameAndEmail, rightNameAndEmail); if (cmp) { return cmp < 0; } if (lUid.validity() != rUid.validity()) { return lUid.validity() > rUid.validity(); } /* Both have the same validity, check which one is newer. */ time_t leftTime = 0; for (const GpgME::Subkey &s : leftKey.subkeys()) { if (s.isBad()) { continue; } if (s.creationTime() > leftTime) { leftTime = s.creationTime(); } } time_t rightTime = 0; for (const GpgME::Subkey &s : rightKey.subkeys()) { if (s.isBad()) { continue; } if (s.creationTime() > rightTime) { rightTime = s.creationTime(); } } if (rightTime != leftTime) { return leftTime > rightTime; } // as final resort we compare the fingerprints return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0; } protected: QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return QVariant(); } const auto key = QSortFilterProxyModel::data(index, KeyList::KeyRole).value(); Q_ASSERT(!key.isNull()); if (key.isNull()) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::AccessibleTextRole: { const auto nameAndEmail = formatUserID(key); if (Kleo::KeyCache::instance()->pgpOnly()) { return i18nc("Name (validity, created: date)", "%1 (%2, created: %3)", nameAndEmail, Kleo::Formatting::complianceStringShort(key), Kleo::Formatting::creationDateString(key)); } else { return i18nc("Name (validity, type, created: date)", "%1 (%2, %3, created: %4)", nameAndEmail, Kleo::Formatting::complianceStringShort(key), Formatting::displayName(key.protocol()), Kleo::Formatting::creationDateString(key)); } } case Qt::ToolTipRole: { using namespace Kleo::Formatting; return Kleo::Formatting::toolTip(key, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs); } case Qt::DecorationRole: { return mIconProvider.icon(key); } default: return QSortFilterProxyModel::data(index, role); } } private: Formatting::IconProvider mIconProvider; }; class CustomItemsProxyModel : public QSortFilterProxyModel { Q_OBJECT private: struct CustomItem { QIcon icon; QString text; QVariant data; QString toolTip; }; public: CustomItemsProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { } ~CustomItemsProxyModel() override { qDeleteAll(mFrontItems); qDeleteAll(mBackItems); } bool isCustomItem(const int row) const { return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount(); } void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { beginInsertRows(QModelIndex(), 0, 0); mFrontItems.push_front(new CustomItem{icon, text, data, toolTip}); endInsertRows(); } void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); mBackItems.push_back(new CustomItem{icon, text, data, toolTip}); endInsertRows(); } void removeCustomItem(const QVariant &data) { for (int i = 0; i < mFrontItems.count(); ++i) { if (mFrontItems[i]->data == data) { beginRemoveRows(QModelIndex(), i, i); delete mFrontItems.takeAt(i); endRemoveRows(); return; } } for (int i = 0; i < mBackItems.count(); ++i) { if (mBackItems[i]->data == data) { const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i; beginRemoveRows(QModelIndex(), index, index); delete mBackItems.takeAt(i); endRemoveRows(); return; } } } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count(); } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) // pretend that there is only one column to workaround a bug in // QAccessibleTable which provides the accessibility interface for the // pop-up of the combo box return 1; } QModelIndex mapToSource(const QModelIndex &index) const override { if (!index.isValid()) { return {}; } if (!isCustomItem(index.row())) { const int sourceRow = index.row() - mFrontItems.count(); return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer())); } return {}; } QModelIndex mapFromSource(const QModelIndex &source_index) const override { const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index); return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer()); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (row < 0 || row >= rowCount()) { return {}; } if (row < mFrontItems.count()) { return createIndex(row, column, mFrontItems[row]); } else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) { return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]); } else { const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent); return createIndex(row, column, mi.internalPointer()); } } Qt::ItemFlags flags(const QModelIndex &index) const override { Q_UNUSED(index) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren; } QModelIndex parent(const QModelIndex &) const override { // Flat list return {}; } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return QVariant(); } if (isCustomItem(index.row())) { Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty()); auto ci = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: return ci->text; case Qt::DecorationRole: return ci->icon; case Qt::UserRole: return ci->data; case Qt::ToolTipRole: return ci->toolTip; default: return QVariant(); } } return QSortFilterProxyModel::data(index, role); } private: QList mFrontItems; QList mBackItems; }; } // anonymous namespace namespace Kleo { class KeySelectionComboPrivate { public: KeySelectionComboPrivate(KeySelectionCombo *parent, bool secretOnly_, KeyUsage::Flags usage) : wasEnabled(true) , secretOnly{secretOnly_} , usageFlags{usage} , q{parent} { } /* Selects the first key with a UID addrSpec that matches * the mPerfectMatchMbox variable. * * The idea here is that if there are keys like: * * tom-store@abc.com * susi-store@abc.com * store@abc.com * * And the user wants to send a mail to "store@abc.com" * the filter should still show tom and susi (because they * both are part of store) but the key for "store" should * be preselected. * * Returns true if one was selected. False otherwise. */ bool selectPerfectIdMatch() const { if (mPerfectMatchMbox.isEmpty()) { return false; } for (int i = 0; i < proxyModel->rowCount(); ++i) { const auto idx = proxyModel->index(i, 0, QModelIndex()); const auto key = proxyModel->data(idx, KeyList::KeyRole).value(); if (key.isNull()) { // WTF? continue; } for (const auto &uid : key.userIDs()) { if (QString::fromStdString(uid.addrSpec()) == mPerfectMatchMbox) { q->setCurrentIndex(i); return true; } } } return false; } /* Updates the current key with the default key if the key matches * the current key filter. */ void updateWithDefaultKey() { GpgME::Protocol filterProto = GpgME::UnknownProtocol; const auto filter = dynamic_cast(sortFilterProxy->keyFilter().get()); if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) { filterProto = GpgME::OpenPGP; } else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) { filterProto = GpgME::CMS; } QString defaultKey = defaultKeys.value(filterProto); if (defaultKey.isEmpty()) { // Fallback to unknown protocol defaultKey = defaultKeys.value(GpgME::UnknownProtocol); } // make sure that the default key is not filtered out unless it has the wrong protocol if (filterProto == GpgME::UnknownProtocol) { sortFilterProxy->setAlwaysAcceptedKey(defaultKey); } else { const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData()); if (!key.isNull() && key.protocol() == filterProto) { sortFilterProxy->setAlwaysAcceptedKey(defaultKey); } else { sortFilterProxy->setAlwaysAcceptedKey({}); } } q->setCurrentKey(defaultKey); } void storeCurrentSelectionBeforeModelChange() { keyBeforeModelChange = q->currentKey(); customItemBeforeModelChange = q->currentData(); } void restoreCurrentSelectionAfterModelChange() { if (!keyBeforeModelChange.isNull()) { q->setCurrentKey(keyBeforeModelChange); } else if (customItemBeforeModelChange.isValid()) { const auto index = q->findData(customItemBeforeModelChange); if (index != -1) { q->setCurrentIndex(index); } else { updateWithDefaultKey(); } } } Kleo::AbstractKeyListModel *model = nullptr; SortFilterProxyModel *sortFilterProxy = nullptr; SortAndFormatCertificatesProxyModel *sortAndFormatProxy = nullptr; CustomItemsProxyModel *proxyModel = nullptr; std::shared_ptr cache; QMap defaultKeys; bool wasEnabled = false; bool useWasEnabled = false; bool secretOnly = false; bool initialKeyListingDone = false; QString mPerfectMatchMbox; GpgME::Key keyBeforeModelChange; QVariant customItemBeforeModelChange; KeyUsage::Flags usageFlags; private: KeySelectionCombo *const q; }; } using namespace Kleo; KeySelectionCombo::KeySelectionCombo(QWidget *parent) : KeySelectionCombo(true, KeyUsage::None, parent) { } KeySelectionCombo::KeySelectionCombo(bool secretOnly, QWidget *parent) : KeySelectionCombo(secretOnly, KeyUsage::None, parent) { } KeySelectionCombo::KeySelectionCombo(KeyUsage::Flags usage, QWidget *parent) : KeySelectionCombo{false, usage, parent} { } KeySelectionCombo::KeySelectionCombo(KeyUsage::Flag usage, QWidget *parent) : KeySelectionCombo{false, usage, parent} { } KeySelectionCombo::KeySelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent) : QComboBox(parent) , d(new KeySelectionComboPrivate(this, secretOnly, usage)) { // set a non-empty string as accessible description to prevent screen readers // from reading the tool tip which isn't meant for screen readers setAccessibleDescription(QStringLiteral(" ")); d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this); d->sortFilterProxy = new SortFilterProxyModel(this); d->sortFilterProxy->setSourceModel(d->model); d->sortAndFormatProxy = new SortAndFormatCertificatesProxyModel{usage, this}; d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy); // initialize dynamic sorting d->sortAndFormatProxy->sort(0); d->proxyModel = new CustomItemsProxyModel{this}; d->proxyModel->setSourceModel(d->sortAndFormatProxy); setModel(d->proxyModel); connect(this, &QComboBox::currentIndexChanged, this, [this](int row) { if (row >= 0 && row < d->proxyModel->rowCount()) { if (d->proxyModel->isCustomItem(row)) { Q_EMIT customItemSelected(currentData(Qt::UserRole)); } else { Q_EMIT currentKeyChanged(currentKey()); } } }); d->cache = Kleo::KeyCache::mutableInstance(); connect(model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::rowsInserted, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() { d->storeCurrentSelectionBeforeModelChange(); }); connect(model(), &QAbstractItemModel::modelReset, this, [this]() { d->restoreCurrentSelectionAfterModelChange(); }); QTimer::singleShot(0, this, &KeySelectionCombo::init); } KeySelectionCombo::~KeySelectionCombo() = default; void KeySelectionCombo::init() { connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() { // Set useKeyCache ensures that the cache is populated // so this can be a blocking call if the cache is not initialized if (!d->initialKeyListingDone) { d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys); } d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys")); // We use the useWasEnabled state variable to decide if we should // change the enable / disable state based on the keylist done signal. // If we triggered the refresh useWasEnabled is true and we want to // enable / disable again after our refresh, as the refresh disabled it. // // But if a keyListingDone signal comes from just a generic refresh // triggered by someone else we don't want to change the enable / disable // state. if (d->useWasEnabled) { setEnabled(d->wasEnabled); d->useWasEnabled = false; } Q_EMIT keyListingFinished(); }); connect(this, &KeySelectionCombo::keyListingFinished, this, [this]() { if (!d->initialKeyListingDone) { d->updateWithDefaultKey(); d->initialKeyListingDone = true; } }); if (!d->cache->initialized()) { refreshKeys(); } else { d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys); Q_EMIT keyListingFinished(); } connect(this, &QComboBox::currentIndexChanged, this, [this]() { setToolTip(currentData(Qt::ToolTipRole).toString()); }); } void KeySelectionCombo::setKeyFilter(const std::shared_ptr &kf) { d->sortFilterProxy->setKeyFilter(kf); d->updateWithDefaultKey(); } std::shared_ptr KeySelectionCombo::keyFilter() const { return d->sortFilterProxy->keyFilter(); } void KeySelectionCombo::setIdFilter(const QString &id) { d->sortFilterProxy->setFilterRegularExpression(id); d->mPerfectMatchMbox = id; d->updateWithDefaultKey(); } QString KeySelectionCombo::idFilter() const { return d->sortFilterProxy->filterRegularExpression().pattern(); } GpgME::Key Kleo::KeySelectionCombo::currentKey() const { return currentData(KeyList::KeyRole).value(); } void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key) { const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly); if (idx > -1) { setCurrentIndex(idx); } else if (!d->selectPerfectIdMatch()) { d->updateWithDefaultKey(); } setToolTip(currentData(Qt::ToolTipRole).toString()); } void Kleo::KeySelectionCombo::setCurrentKey(const QString &fingerprint) { const auto cur = currentKey(); - if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1String(cur.primaryFingerprint())) { + if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1StringView(cur.primaryFingerprint())) { // already set; still emit a changed signal because the current key may // have become the item at the current index by changes in the underlying model Q_EMIT currentKeyChanged(cur); return; } const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly); if (idx > -1) { setCurrentIndex(idx); } else if (!d->selectPerfectIdMatch()) { setCurrentIndex(0); } setToolTip(currentData(Qt::ToolTipRole).toString()); } void KeySelectionCombo::refreshKeys() { d->wasEnabled = isEnabled(); d->useWasEnabled = true; setEnabled(false); const bool wasBlocked = blockSignals(true); prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys")); setCurrentIndex(0); blockSignals(wasBlocked); d->cache->startKeyListing(); } void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { d->proxyModel->appendItem(icon, text, data, toolTip); } void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data) { appendCustomItem(icon, text, data, QString()); } void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip) { d->proxyModel->prependItem(icon, text, data, toolTip); } void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data) { prependCustomItem(icon, text, data, QString()); } void KeySelectionCombo::removeCustomItem(const QVariant &data) { d->proxyModel->removeCustomItem(data); } void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto) { d->defaultKeys.insert(proto, fingerprint); d->updateWithDefaultKey(); } void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint) { setDefaultKey(fingerprint, GpgME::UnknownProtocol); } QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto) const { return d->defaultKeys.value(proto); } QString Kleo::KeySelectionCombo::defaultKey() const { return defaultKey(GpgME::UnknownProtocol); } #include "keyselectioncombo.moc" #include "moc_keyselectioncombo.cpp" diff --git a/src/ui/keyselectiondialog.cpp b/src/ui/keyselectiondialog.cpp index fe466aab3..72943cf39 100644 --- a/src/ui/keyselectiondialog.cpp +++ b/src/ui/keyselectiondialog.cpp @@ -1,1022 +1,1022 @@ /* -*- c++ -*- keyselectiondialog.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB Based on kpgpui.cpp SPDX-FileCopyrightText: 2001, 2002 the KPGP authors See file libkdenetwork/AUTHORS.kpgp for details SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyselectiondialog.h" #include "keylistview.h" #include "progressdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr) { auto setStatusString = [statusString](const QString &status) { if (statusString) { *statusString = status; } }; if (keyUsage & KeySelectionDialog::ValidKeys) { if (key.isInvalid()) { if (key.keyListMode() & GpgME::Validate) { qCDebug(KLEO_UI_LOG) << "key is invalid"; setStatusString(i18n("The key is not valid.")); return false; } else { qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring"; } } if (key.isExpired()) { qCDebug(KLEO_UI_LOG) << "key is expired"; setStatusString(i18n("The key is expired.")); return false; } else if (key.isRevoked()) { qCDebug(KLEO_UI_LOG) << "key is revoked"; setStatusString(i18n("The key is revoked.")); return false; } else if (key.isDisabled()) { qCDebug(KLEO_UI_LOG) << "key is disabled"; setStatusString(i18n("The key is disabled.")); return false; } } if (keyUsage & KeySelectionDialog::EncryptionKeys && !Kleo::keyHasEncrypt(key)) { qCDebug(KLEO_UI_LOG) << "key can't encrypt"; setStatusString(i18n("The key is not designated for encryption.")); return false; } if (keyUsage & KeySelectionDialog::SigningKeys && !Kleo::keyHasSign(key)) { qCDebug(KLEO_UI_LOG) << "key can't sign"; setStatusString(i18n("The key is not designated for signing.")); return false; } if (keyUsage & KeySelectionDialog::CertificationKeys && !Kleo::keyHasCertify(key)) { qCDebug(KLEO_UI_LOG) << "key can't certify"; setStatusString(i18n("The key is not designated for certifying.")); return false; } if (keyUsage & KeySelectionDialog::AuthenticationKeys && !Kleo::keyHasAuthenticate(key)) { qCDebug(KLEO_UI_LOG) << "key can't authenticate"; setStatusString(i18n("The key is not designated for authentication.")); return false; } if (keyUsage & KeySelectionDialog::SecretKeys && !(keyUsage & KeySelectionDialog::PublicKeys) && !key.hasSecret()) { qCDebug(KLEO_UI_LOG) << "key isn't secret"; setStatusString(i18n("The key is not secret.")); return false; } if (keyUsage & KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP && // only check this for secret keys for now. // Seems validity isn't checked for secret keylistings... !key.hasSecret()) { std::vector uids = key.userIDs(); for (std::vector::const_iterator it = uids.begin(); it != uids.end(); ++it) { if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) { setStatusString(i18n("The key can be used.")); return true; } } qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal"; setStatusString(i18n("The key is not trusted enough.")); return false; } // X.509 keys are always trusted, else they won't be the keybox. // PENDING(marc) check that this ^ is correct setStatusString(i18n("The key can be used.")); return true; } static bool checkKeyUsage(const std::vector &keys, unsigned int keyUsage) { for (auto it = keys.begin(); it != keys.end(); ++it) { if (!checkKeyUsage(*it, keyUsage)) { return false; } } return true; } namespace { class ColumnStrategy : public KeyListView::ColumnStrategy { public: ColumnStrategy(unsigned int keyUsage); QString title(int col) const override; int width(int col, const QFontMetrics &fm) const override; QString text(const GpgME::Key &key, int col) const override; QString accessibleText(const GpgME::Key &key, int column) const override; QString toolTip(const GpgME::Key &key, int col) const override; QIcon icon(const GpgME::Key &key, int col) const override; private: const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix; const unsigned int mKeyUsage; }; static QString iconPath(const QString &name) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png")); } ColumnStrategy::ColumnStrategy(unsigned int keyUsage) : KeyListView::ColumnStrategy() , mKeyGoodPix(iconPath(QStringLiteral("key_ok"))) , mKeyBadPix(iconPath(QStringLiteral("key_bad"))) , mKeyUnknownPix(iconPath(QStringLiteral("key_unknown"))) , mKeyValidPix(iconPath(QStringLiteral("key"))) , mKeyUsage(keyUsage) { if (keyUsage == 0) { qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead."; } } QString ColumnStrategy::title(int col) const { switch (col) { case 0: return i18n("Key ID"); case 1: return i18n("User ID"); default: return QString(); } } int ColumnStrategy::width(int col, const QFontMetrics &fm) const { if (col == 0) { static const char hexchars[] = "0123456789ABCDEF"; int maxWidth = 0; for (unsigned int i = 0; i < 16; ++i) { maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth); } return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */; } return KeyListView::ColumnStrategy::width(col, fm); } QString ColumnStrategy::text(const GpgME::Key &key, int col) const { switch (col) { case 0: { if (key.shortKeyID()) { return Formatting::prettyID(key.shortKeyID()); } else { return xi18n("unknown"); } } case 1: { const char *uid = key.userID(0).id(); if (key.protocol() == GpgME::OpenPGP) { return uid && *uid ? QString::fromUtf8(uid) : QString(); } else { // CMS return DN(uid).prettyDN(); } } default: return QString(); } } QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const { switch (col) { case 0: { if (key.shortKeyID()) { return Formatting::accessibleHexID(key.shortKeyID()); } [[fallthrough]]; } default: return {}; } } QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const { const char *uid = key.userID(0).id(); const char *fpr = key.primaryFingerprint(); const char *issuer = key.issuerName(); const GpgME::Subkey subkey = key.subkey(0); const QString expiry = Formatting::expirationDateString(subkey); const QString creation = Formatting::creationDateString(subkey); QString keyStatusString; if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) { // Show the status in bold if there is a problem - keyStatusString = QLatin1String("") % keyStatusString % QLatin1String(""); + keyStatusString = QLatin1StringView("") % keyStatusString % QLatin1String(""); } QString html = QStringLiteral("

"); if (key.protocol() == GpgME::OpenPGP) { html += i18n("OpenPGP key for %1", uid ? QString::fromUtf8(uid) : i18n("unknown")); } else { html += i18n("S/MIME key for %1", uid ? DN(uid).prettyDN() : i18n("unknown")); } html += QStringLiteral("

"); const auto addRow = [&html](const QString &name, const QString &value) { html += QStringLiteral("").arg(name, value); }; addRow(i18n("Valid from"), creation); addRow(i18n("Valid until"), expiry); addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown")); if (key.protocol() != GpgME::OpenPGP) { addRow(i18nc("Key issuer", "Issuer"), issuer ? DN(issuer).prettyDN() : i18n("unknown")); } addRow(i18nc("Key status", "Status"), keyStatusString); if (DeVSCompliance::isActive()) { addRow(i18nc("Compliance of key", "Compliance"), DeVSCompliance::name(key.isDeVs())); } html += QStringLiteral("
%1: %2
"); return html; } QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const { if (col != 0) { return QIcon(); } // this key did not undergo a validating keylisting yet: if (!(key.keyListMode() & GpgME::Validate)) { return mKeyUnknownPix; } if (!checkKeyUsage(key, mKeyUsage)) { return mKeyBadPix; } if (key.protocol() == GpgME::CMS) { return mKeyGoodPix; } switch (key.userID(0).validity()) { default: case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return mKeyUnknownPix; case GpgME::UserID::Never: return mKeyValidPix; case GpgME::UserID::Marginal: case GpgME::UserID::Full: case GpgME::UserID::Ultimate: { if (DeVSCompliance::isActive() && !key.isDeVs()) { return mKeyValidPix; } return mKeyGoodPix; } } } } static const int sCheckSelectionDelay = 250; KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options) : QDialog(parent) , mOpenPGPBackend(QGpgME::openpgp()) , mSMIMEBackend(QGpgME::smime()) , mKeyUsage(AllKeys) { qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY; setUpUI(options, QString()); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, QString()); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, const std::vector &selectedKeys, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mSelectedKeys(selectedKeys) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } KeySelectionDialog::KeySelectionDialog(const QString &title, const QString &text, const QString &initialQuery, unsigned int keyUsage, bool extendedSelection, bool rememberChoice, QWidget *parent, bool modal) : QDialog(parent) , mKeyUsage(keyUsage) , mSearchText(initialQuery) , mInitialQuery(initialQuery) { setWindowTitle(title); setModal(modal); init(rememberChoice, extendedSelection, text, initialQuery); } void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery) { auto mainLayout = new QVBoxLayout(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); mOkButton = buttonBox->button(QDialogButtonBox::Ok); mOkButton->setDefault(true); mOkButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return)); mCheckSelectionTimer = new QTimer(this); mStartSearchTimer = new QTimer(this); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); mTopLayout = new QVBoxLayout(page); mTopLayout->setContentsMargins(0, 0, 0, 0); mTextLabel = new QLabel(page); mTextLabel->setWordWrap(true); // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429 // and http://bugreports.qt.nokia.com/browse/QTBUG-8740 mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager); mTopLayout->addWidget(mTextLabel); mTextLabel->hide(); QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page); mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft); connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates); if (initialQuery.isEmpty()) { searchExternalPB->hide(); } auto hlay = new QHBoxLayout(); mTopLayout->addLayout(hlay); auto le = new QLineEdit(page); le->setClearButtonEnabled(true); le->setText(initialQuery); QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page); lbSearchFor->setBuddy(le); hlay->addWidget(lbSearchFor); hlay->addWidget(le, 1); le->setFocus(); connect(le, &QLineEdit::textChanged, this, [this](const QString &s) { slotSearch(s); }); connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter); mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page); mKeyListView->setObjectName(QLatin1StringView("mKeyListView")); mKeyListView->header()->stretchLastSection(); mKeyListView->setRootIsDecorated(true); mKeyListView->setSortingEnabled(true); mKeyListView->header()->setSortIndicatorShown(true); mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID if (options & ExtendedSelection) { mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection); } mTopLayout->addWidget(mKeyListView, 10); if (options & RememberChoice) { mRememberCB = new QCheckBox(i18n("&Remember choice"), page); mTopLayout->addWidget(mRememberCB); mRememberCB->setWhatsThis( i18n("

If you check this box your choice will " "be stored and you will not be asked again." "

")); } connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() { slotCheckSelection(); }); connectSignals(); connect(mKeyListView, &KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk); connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB); if (options & RereadKeys) { QPushButton *button = new QPushButton(i18n("&Reread Keys")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys); } if (options & ExternalCertificateManager) { QPushButton *button = new QPushButton(i18n("&Start Certificate Manager")); buttonBox->addButton(button, QDialogButtonBox::ActionRole); connect(button, &QPushButton::clicked, this, [this]() { slotStartCertificateManager(); }); } connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel); mTopLayout->activate(); if (qApp) { QSize dialogSize(sizeHint()); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog")); dialogSize = dialogConfig.readEntry("Dialog size", dialogSize); const QByteArray headerState = dialogConfig.readEntry("header", QByteArray()); if (!headerState.isEmpty()) { mKeyListView->header()->restoreState(headerState); } resize(dialogSize); } } void KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery) { Options options = {RereadKeys, ExternalCertificateManager}; options.setFlag(ExtendedSelection, extendedSelection); options.setFlag(RememberChoice, rememberChoice); setUpUI(options, initialQuery); setText(text); if (mKeyUsage & OpenPGPKeys) { mOpenPGPBackend = QGpgME::openpgp(); } if (mKeyUsage & SMIMEKeys) { mSMIMEBackend = QGpgME::smime(); } slotRereadKeys(); } KeySelectionDialog::~KeySelectionDialog() { disconnectSignals(); KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog")); dialogConfig.writeEntry("Dialog size", size()); dialogConfig.writeEntry("header", mKeyListView->header()->saveState()); dialogConfig.sync(); } void KeySelectionDialog::setText(const QString &text) { mTextLabel->setText(text); mTextLabel->setVisible(!text.isEmpty()); } void KeySelectionDialog::setKeys(const std::vector &keys) { for (const GpgME::Key &key : keys) { mKeyListView->slotAddKey(key); } } void KeySelectionDialog::connectSignals() { if (mKeyListView->isMultiSelection()) { connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { connect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } void KeySelectionDialog::disconnectSignals() { if (mKeyListView->isMultiSelection()) { disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged); } else { disconnect(mKeyListView, qOverload(&KeyListView::selectionChanged), this, qOverload(&KeySelectionDialog::slotCheckSelection)); } } const GpgME::Key &KeySelectionDialog::selectedKey() const { static const GpgME::Key null = GpgME::Key::null; if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) { return null; } return mKeyListView->selectedItem()->key(); } QString KeySelectionDialog::fingerprint() const { - return QLatin1String(selectedKey().primaryFingerprint()); + return QLatin1StringView(selectedKey().primaryFingerprint()); } QStringList KeySelectionDialog::fingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (const char *fpr = it->primaryFingerprint()) { - result.push_back(QLatin1String(fpr)); + result.push_back(QLatin1StringView(fpr)); } } return result; } QStringList KeySelectionDialog::pgpKeyFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { if (const char *fpr = it->primaryFingerprint()) { - result.push_back(QLatin1String(fpr)); + result.push_back(QLatin1StringView(fpr)); } } } return result; } QStringList KeySelectionDialog::smimeFingerprints() const { QStringList result; for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) { if (it->protocol() == GpgME::CMS) { if (const char *fpr = it->primaryFingerprint()) { - result.push_back(QLatin1String(fpr)); + result.push_back(QLatin1StringView(fpr)); } } } return result; } void KeySelectionDialog::slotRereadKeys() { mKeyListView->clear(); mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); // FIXME: save current selection if (mOpenPGPBackend) { startKeyListJobForBackend(mOpenPGPBackend, std::vector(), false /*non-validating*/); } if (mSMIMEBackend) { startKeyListJobForBackend(mSMIMEBackend, std::vector(), false /*non-validating*/); } if (mListJobCount == 0) { mKeyListView->setEnabled(true); KMessageBox::information(this, i18n("No backends found for listing keys. " "Check your installation."), i18n("Key Listing Failed")); connectSignals(); } } void KeySelectionDialog::slotStartCertificateManager(const QString &query) { QStringList args; if (!query.isEmpty()) { args << QStringLiteral("--search") << query; } const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); if (exec.isEmpty()) { qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH"; KMessageBox::error(this, i18n("Could not start certificate manager; " "please check your installation."), i18n("Certificate Manager Error")); } else { QProcess::startDetached(QStringLiteral("kleopatra"), args); qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started."; } } #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ static void showKeyListError(QWidget *parent, const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while fetching " "the keys from the backend:

" "

%1

", Formatting::errorAsString(err)); KMessageBox::error(parent, msg, i18n("Key Listing Failed")); } #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__ namespace { struct ExtractFingerprint { QString operator()(const GpgME::Key &key) { - return QLatin1String(key.primaryFingerprint()); + return QLatin1StringView(key.primaryFingerprint()); } }; } void KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector &keys, bool validate) { Q_ASSERT(backend); QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given if (!job) { return; } connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult); if (validate) { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey); } else { connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey); } QStringList fprs; std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint()); const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys)); if (err) { return showKeyListError(this, err); } #ifndef LIBKLEO_NO_PROGRESSDIALOG // FIXME: create a MultiProgressDialog: (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this); #endif ++mListJobCount; } static void selectKeys(KeyListView *klv, const std::vector &selectedKeys) { klv->clearSelection(); if (selectedKeys.empty()) { return; } for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) { if (KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) { item->setSelected(true); } } } void KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res) { if (res.error()) { showKeyListError(this, res.error()); } else if (res.isTruncated()) { ++mTruncated; } if (--mListJobCount > 0) { return; // not yet finished... } if (mTruncated > 0) { KMessageBox::information(this, i18np("One backend returned truncated output.

" "Not all available keys are shown

", "%1 backends returned truncated output.

" "Not all available keys are shown

", mTruncated), i18n("Key List Result")); } mKeyListView->flushKeys(); mKeyListView->setEnabled(true); mListJobCount = mTruncated = 0; mKeysToCheck.clear(); selectKeys(mKeyListView, mSelectedKeys); slotFilter(); connectSignals(); slotSelectionChanged(); // restore the saved position of the contents mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY); mSavedOffsetY = 0; } void KeySelectionDialog::slotSelectionChanged() { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()"; // (re)start the check selection timer. Checking the selection is delayed // because else drag-selection doesn't work very good (checking key trust // is slow). mCheckSelectionTimer->start(sCheckSelectionDelay); } namespace { struct AlreadyChecked { bool operator()(const GpgME::Key &key) const { return key.keyListMode() & GpgME::Validate; } }; } void KeySelectionDialog::slotCheckSelection(KeyListViewItem *item) { qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()"; mCheckSelectionTimer->stop(); mSelectedKeys.clear(); if (!mKeyListView->isMultiSelection()) { if (item) { mSelectedKeys.push_back(item->key()); } } for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) { if (it->isSelected()) { mSelectedKeys.push_back(it->key()); } } mKeysToCheck.clear(); std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked()); if (mKeysToCheck.empty()) { mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)); return; } // performed all fast checks - now for validating key listing: startValidatingKeyListing(); } void KeySelectionDialog::startValidatingKeyListing() { if (mKeysToCheck.empty()) { return; } mListJobCount = 0; mTruncated = 0; mSavedOffsetY = mKeyListView->verticalScrollBar()->value(); disconnectSignals(); mKeyListView->setEnabled(false); std::vector smime; std::vector openpgp; for (std::vector::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) { if (it->protocol() == GpgME::OpenPGP) { openpgp.push_back(*it); } else { smime.push_back(*it); } } if (!openpgp.empty()) { Q_ASSERT(mOpenPGPBackend); startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/); } if (!smime.empty()) { Q_ASSERT(mSMIMEBackend); startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/); } Q_ASSERT(mListJobCount > 0); } bool KeySelectionDialog::rememberSelection() const { return mRememberCB && mRememberCB->isChecked(); } void KeySelectionDialog::slotRMB(KeyListViewItem *item, const QPoint &p) { if (!item) { return; } mCurrentContextMenuItem = item; QMenu menu; menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey); menu.exec(p); } void KeySelectionDialog::slotRecheckKey() { if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) { return; } mKeysToCheck.clear(); mKeysToCheck.push_back(mCurrentContextMenuItem->key()); } void KeySelectionDialog::slotTryOk() { if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { slotOk(); } } void KeySelectionDialog::slotOk() { if (mCheckSelectionTimer->isActive()) { slotCheckSelection(); } #if 0 // Laurent I don't understand why we returns here. // button could be disabled again after checking the selected key1 if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) { return; } #endif mStartSearchTimer->stop(); accept(); } void KeySelectionDialog::slotCancel() { mCheckSelectionTimer->stop(); mStartSearchTimer->stop(); reject(); } void KeySelectionDialog::slotSearch(const QString &text) { mSearchText = text.trimmed().toUpper(); slotSearch(); } void KeySelectionDialog::slotSearch() { mStartSearchTimer->setSingleShot(true); mStartSearchTimer->start(sCheckSelectionDelay); } void KeySelectionDialog::slotFilter() { if (mSearchText.isEmpty()) { showAllItems(); return; } // OK, so we need to filter: - QRegularExpression keyIdRegExp(QRegularExpression::anchoredPattern(QLatin1String("(?:0x)?[A-F0-9]{1,8}")), QRegularExpression::CaseInsensitiveOption); + QRegularExpression keyIdRegExp(QRegularExpression::anchoredPattern(QLatin1StringView("(?:0x)?[A-F0-9]{1,8}")), QRegularExpression::CaseInsensitiveOption); if (keyIdRegExp.match(mSearchText).hasMatch()) { - if (mSearchText.startsWith(QLatin1String("0X"))) + if (mSearchText.startsWith(QLatin1StringView("0X"))) // search for keyID only: { filterByKeyID(mSearchText.mid(2)); } else // search for UID and keyID: { filterByKeyIDOrUID(mSearchText); } } else { // search in UID: filterByUID(mSearchText); } } void KeySelectionDialog::filterByKeyID(const QString &keyID) { Q_ASSERT(keyID.length() <= 8); Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these if (keyID.isEmpty()) { showAllItems(); } else { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(keyID)); } } } static bool anyUIDMatches(const KeyListViewItem *item, const QRegularExpression &rx) { if (!item) { return false; } const std::vector uids = item->key().userIDs(); for (auto it = uids.begin(); it != uids.end(); ++it) { if (it->id() && rx.match(QString::fromUtf8(it->id())).hasMatch()) { return true; } } return false; } void KeySelectionDialog::filterByKeyIDOrUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: - QRegularExpression rx(QLatin1String("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption); + QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx)); } } void KeySelectionDialog::filterByUID(const QString &str) { Q_ASSERT(!str.isEmpty()); // match beginnings of words: - QRegularExpression rx(QLatin1String("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption); + QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption); for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(!anyUIDMatches(item, rx)); } } void KeySelectionDialog::showAllItems() { for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) { item->setHidden(false); } } #include "moc_keyselectiondialog.cpp" diff --git a/src/utils/classify.cpp b/src/utils/classify.cpp index be2f11903..e95904f16 100644 --- a/src/utils/classify.cpp +++ b/src/utils/classify.cpp @@ -1,375 +1,375 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/classify.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "classify.h" #include "algorithm.h" #include "classifyconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo::Class; namespace { const unsigned int ExamineContentHint = 0x8000; static const QMap classifications{ // using QMap to keep ordering by extension which incidentally is also the prioritized order for outputFileExtension() {QStringLiteral("arl"), Kleo::Class::CMS | Binary | CertificateRevocationList}, {QStringLiteral("asc"), Kleo::Class::OpenPGP | Ascii | OpaqueSignature | DetachedSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {QStringLiteral("cer"), Kleo::Class::CMS | Binary | Certificate}, {QStringLiteral("crl"), Kleo::Class::CMS | Binary | CertificateRevocationList}, {QStringLiteral("crt"), Kleo::Class::CMS | Binary | Certificate}, {QStringLiteral("der"), Kleo::Class::CMS | Binary | Certificate | CertificateRevocationList}, {QStringLiteral("eml"), Kleo::Class::MimeFile | Ascii}, {QStringLiteral("gpg"), Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {QStringLiteral("mim"), Kleo::Class::MimeFile | Ascii}, {QStringLiteral("mime"), Kleo::Class::MimeFile | Ascii}, {QStringLiteral("mbox"), Kleo::Class::MimeFile | Ascii}, {QStringLiteral("p10"), Kleo::Class::CMS | Ascii | CertificateRequest}, {QStringLiteral("p12"), Kleo::Class::CMS | Binary | ExportedPSM}, {QStringLiteral("p7c"), Kleo::Class::CMS | Binary | Certificate}, {QStringLiteral("p7m"), Kleo::Class::CMS | AnyFormat | CipherText}, {QStringLiteral("p7s"), Kleo::Class::CMS | AnyFormat | AnySignature}, {QStringLiteral("pem"), Kleo::Class::CMS | Ascii | AnyType | ExamineContentHint}, {QStringLiteral("pfx"), Kleo::Class::CMS | Binary | Certificate}, {QStringLiteral("pgp"), Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint}, {QStringLiteral("sig"), Kleo::Class::OpenPGP | AnyFormat | DetachedSignature}, }; static const QHash gpgmeTypeMap{ // clang-format off {GpgME::Data::PGPSigned, Kleo::Class::OpenPGP | OpaqueSignature }, /* PGPOther might be just an unencrypted unsigned pgp message. Decrypt * would yield the plaintext anyway so for us this is CipherText. */ {GpgME::Data::PGPOther, Kleo::Class::OpenPGP | CipherText }, {GpgME::Data::PGPKey, Kleo::Class::OpenPGP | Certificate }, {GpgME::Data::CMSSigned, Kleo::Class::CMS | AnySignature }, {GpgME::Data::CMSEncrypted, Kleo::Class::CMS | CipherText }, /* See PGPOther */ {GpgME::Data::CMSOther, Kleo::Class::CMS | CipherText }, {GpgME::Data::X509Cert, Kleo::Class::CMS | Certificate }, {GpgME::Data::PKCS12, Kleo::Class::CMS | Binary | ExportedPSM }, {GpgME::Data::PGPEncrypted, Kleo::Class::OpenPGP | CipherText }, {GpgME::Data::PGPSignature, Kleo::Class::OpenPGP | DetachedSignature}, // clang-format on }; static const QSet mimeFileNames{ /* KMail standard name */ QStringLiteral("msg.asc"), QStringLiteral("smime.p7m"), QStringLiteral("openpgp-encrypted-message.asc"), /* Old names of internal GpgOL attachments newer versions * should use .mime file ending as it is connected with * Kleopatra. */ QStringLiteral("GpgOL_MIME_structure.txt"), QStringLiteral("GpgOL_MIME_structure.mime"), /* This is gpgtools take on the filename */ QStringLiteral("OpenPGP encrypted message.asc"), }; static const unsigned int defaultClassification = NoClass; template class asKeyValueRange { public: asKeyValueRange(T &data) : m_data{data} { } auto begin() { return m_data.keyValueBegin(); } auto end() { return m_data.keyValueEnd(); } private: T &m_data; }; } unsigned int Kleo::classify(const QStringList &fileNames) { if (fileNames.empty()) { return 0; } unsigned int result = classify(fileNames.front()); for (const QString &fileName : fileNames) { result &= classify(fileName); } return result; } static bool mimeTypeInherits(const QMimeType &mimeType, const QString &mimeTypeName) { // inherits is expensive on an invalid mimeType return mimeType.isValid() && mimeType.inherits(mimeTypeName); } /// Detect either a complete mail file (e.g. mbox or eml file) or a encrypted attachment /// corresponding to a mail file static bool isMailFile(const QFileInfo &fi) { static const QRegularExpression attachmentNumbering{QStringLiteral(R"(\([0-9]+\))")}; const auto fileName = fi.fileName().remove(attachmentNumbering); if (mimeFileNames.contains(fileName)) { return true; } { Kleo::ClassifyConfig classifyConfig; if (classifyConfig.p7mWithoutExtensionAreEmail() && fileName.endsWith(QStringLiteral(".p7m")) && fi.completeSuffix() == fi.suffix()) { // match "myfile.p7m" but not "myfile.pdf.p7m" return true; } } QMimeDatabase mimeDatabase; const auto mimeType = mimeDatabase.mimeTypeForFile(fi); return mimeTypeInherits(mimeType, QStringLiteral("message/rfc822")) || mimeTypeInherits(mimeType, QStringLiteral("application/mbox")); } static unsigned int classifyExtension(const QFileInfo &fi) { return classifications.value(fi.suffix(), defaultClassification); } unsigned int Kleo::classify(const QString &filename) { const QFileInfo fi(filename); if (!fi.exists()) { return 0; } if (isMailFile(fi)) { return Kleo::Class::MimeFile | Ascii; } QFile file(filename); /* The least reliable but always available classification */ const unsigned int extClass = classifyExtension(fi); if (!file.open(QIODevice::ReadOnly)) { qCDebug(LIBKLEO_LOG) << "Failed to open file: " << filename << " for classification."; return extClass; } /* More reliable */ const unsigned int contentClass = classifyContent(file.read(4096)); if (contentClass != defaultClassification) { qCDebug(LIBKLEO_LOG) << "Classified based on content as:" << contentClass; return contentClass; } /* Probably some X509 Stuff that GpgME in its wisdom does not handle. Again * file extension is probably more reliable as the last resort. */ qCDebug(LIBKLEO_LOG) << "No classification based on content."; return extClass; } unsigned int Kleo::classifyContent(const QByteArray &data) { QGpgME::QByteArrayDataProvider dp(data); GpgME::Data gpgmeData(&dp); GpgME::Data::Type type = gpgmeData.type(); return gpgmeTypeMap.value(type, defaultClassification); } QString Kleo::printableClassification(unsigned int classification) { QStringList parts; if (classification & Kleo::Class::CMS) { parts.push_back(QStringLiteral("CMS")); } if (classification & Kleo::Class::OpenPGP) { parts.push_back(QStringLiteral("OpenPGP")); } if (classification & Kleo::Class::Binary) { parts.push_back(QStringLiteral("Binary")); } if (classification & Kleo::Class::Ascii) { parts.push_back(QStringLiteral("Ascii")); } if (classification & Kleo::Class::DetachedSignature) { parts.push_back(QStringLiteral("DetachedSignature")); } if (classification & Kleo::Class::OpaqueSignature) { parts.push_back(QStringLiteral("OpaqueSignature")); } if (classification & Kleo::Class::ClearsignedMessage) { parts.push_back(QStringLiteral("ClearsignedMessage")); } if (classification & Kleo::Class::CipherText) { parts.push_back(QStringLiteral("CipherText")); } if (classification & Kleo::Class::Certificate) { parts.push_back(QStringLiteral("Certificate")); } if (classification & Kleo::Class::ExportedPSM) { parts.push_back(QStringLiteral("ExportedPSM")); } if (classification & Kleo::Class::CertificateRequest) { parts.push_back(QStringLiteral("CertificateRequest")); } if (classification & Kleo::Class::MimeFile) { parts.push_back(QStringLiteral("MimeFile")); } - return parts.join(QLatin1String(", ")); + return parts.join(QLatin1StringView(", ")); } /*! \return the data file that corresponds to the signature file \a signatureFileName, or QString(), if no such file can be found. */ QString Kleo::findSignedData(const QString &signatureFileName) { if (!mayBeDetachedSignature(signatureFileName)) { return QString(); } const QFileInfo fi{signatureFileName}; const QString baseName = signatureFileName.chopped(fi.suffix().size() + 1); return QFile::exists(baseName) ? baseName : QString(); } /*! \return all (existing) candidate signature files for \a signedDataFileName Note that there can very well be more than one such file, e.g. if the same data file was signed by both CMS and OpenPGP certificates. */ QStringList Kleo::findSignatures(const QString &signedDataFileName) { QStringList result; for (const auto &[extension, classification] : asKeyValueRange(classifications)) { if (classification & DetachedSignature) { const QString candidate = signedDataFileName + QLatin1Char('.') + extension; if (QFile::exists(candidate)) { result.push_back(candidate); } } } return result; } #ifdef Q_OS_WIN static QString stripOutlookAttachmentNumbering(const QString &s) { static const QRegularExpression attachmentNumbering{QStringLiteral(R"(\s\([0-9]+\)$)")}; return QString{s}.remove(attachmentNumbering); } #endif /*! \return the (likely) output filename for \a inputFileName, or "inputFileName.out" if none can be determined. */ QString Kleo::outputFileName(const QString &inputFileName) { const QFileInfo fi(inputFileName); const QString suffix = fi.suffix(); if (classifications.find(suffix) == std::cend(classifications)) { - return inputFileName + QLatin1String(".out"); + return inputFileName + QLatin1StringView(".out"); } else { #ifdef Q_OS_WIN return stripOutlookAttachmentNumbering(inputFileName.chopped(suffix.size() + 1)); #else return inputFileName.chopped(suffix.size() + 1); #endif } } /*! \return the commonly used extension for files of type \a classification, or NULL if none such exists. */ QString Kleo::outputFileExtension(unsigned int classification, bool usePGPFileExt) { if (usePGPFileExt && (classification & Class::OpenPGP) && (classification & Class::Binary)) { return QStringLiteral("pgp"); } for (const auto &[extension, classification_] : asKeyValueRange(classifications)) { if ((classification_ & classification) == classification) { return extension; } } return {}; } bool Kleo::isFingerprint(const QString &fpr) { static QRegularExpression fprRegex(QStringLiteral("[0-9a-fA-F]{40}")); return fprRegex.match(fpr).hasMatch(); } bool Kleo::isChecksumFile(const QString &file) { static bool initialized; static QList patterns; const QFileInfo fi(file); if (!fi.exists()) { return false; } if (!initialized) { const auto getChecksumDefinitions = ChecksumDefinition::getChecksumDefinitions(); for (const std::shared_ptr &cd : getChecksumDefinitions) { if (cd) { const auto patternsList = cd->patterns(); for (const QString &pattern : patternsList) { #ifdef Q_OS_WIN patterns << QRegularExpression(QRegularExpression::anchoredPattern(pattern), QRegularExpression::CaseInsensitiveOption); #else patterns << QRegularExpression(QRegularExpression::anchoredPattern(pattern)); #endif } } } initialized = true; } const QString fileName = fi.fileName(); for (const QRegularExpression &pattern : std::as_const(patterns)) { if (pattern.match(fileName).hasMatch()) { return true; } } return false; } diff --git a/src/utils/compliance.cpp b/src/utils/compliance.cpp index 722d31262..2acdc9dd8 100644 --- a/src/utils/compliance.cpp +++ b/src/utils/compliance.cpp @@ -1,161 +1,161 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/compliance.cpp This file is part of libkleopatra SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "compliance.h" #include "algorithm.h" #include "cryptoconfig.h" #include "gnupg.h" #include "keyhelpers.h" #include "stringutils.h" #include "systeminfo.h" #include #include #include #include #include #include #include bool Kleo::DeVSCompliance::isActive() { - return getCryptoConfigStringValue("gpg", "compliance") == QLatin1String{"de-vs"}; + return getCryptoConfigStringValue("gpg", "compliance") == QLatin1StringView{"de-vs"}; } bool Kleo::DeVSCompliance::isCompliant() { if (!isActive()) { return false; } // The pseudo option compliance_de_vs was fully added in 2.2.34; // For versions between 2.2.28 and 2.2.33 there was a broken config // value with a wrong type. So for them we add an extra check. This // can be removed in future versions because for GnuPG we could assume // non-compliance for older versions as versions of Kleopatra for // which this matters are bundled with new enough versions of GnuPG anyway. if (engineIsVersion(2, 2, 28) && !engineIsVersion(2, 2, 34)) { return true; } return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0; } bool Kleo::DeVSCompliance::algorithmIsCompliant(std::string_view algo) { return !isActive() || Kleo::contains(compliantAlgorithms(), algo); } bool Kleo::DeVSCompliance::allSubkeysAreCompliant(const GpgME::Key &key) { if (!isActive()) { return true; } // there is at least one usable subkey const auto usableSubkeys = Kleo::count_if(key.subkeys(), [](const auto &sub) { return !sub.isExpired() && !sub.isRevoked(); }); if (usableSubkeys == 0) { qCDebug(LIBKLEO_LOG) << __func__ << "No usable subkeys found for key" << key; return false; } // and all usable subkeys are compliant return Kleo::all_of(key.subkeys(), [](const auto &sub) { return sub.isDeVs() || sub.isExpired() || sub.isRevoked(); }); } bool Kleo::DeVSCompliance::userIDIsCompliant(const GpgME::UserID &id) { if (!isActive()) { return true; } return (id.parent().keyListMode() & GpgME::Validate) // && !id.isRevoked() // && id.validity() >= GpgME::UserID::Full // && allSubkeysAreCompliant(id.parent()); } bool Kleo::DeVSCompliance::keyIsCompliant(const GpgME::Key &key) { if (!isActive()) { return true; } return (key.keyListMode() & GpgME::Validate) // && allUserIDsHaveFullValidity(key) // && allSubkeysAreCompliant(key); } const std::vector &Kleo::DeVSCompliance::compliantAlgorithms() { static const std::vector compliantAlgos = { "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "rsa3072", "rsa4096", }; return isActive() ? compliantAlgos : Kleo::availableAlgorithms(); } const std::vector &Kleo::DeVSCompliance::preferredCompliantAlgorithms() { static std::vector result; if (result.empty()) { const auto &preferredAlgos = Kleo::preferredAlgorithms(); result.reserve(preferredAlgos.size()); Kleo::copy_if(preferredAlgos, std::back_inserter(result), Kleo::DeVSCompliance::algorithmIsCompliant); } return result; } void Kleo::DeVSCompliance::decorate(QPushButton *button) { decorate(button, isCompliant()); } void Kleo::DeVSCompliance::decorate(QPushButton *button, bool compliant) { if (!button) { return; } if (compliant) { button->setIcon(QIcon::fromTheme(QStringLiteral("security-high"))); if (!SystemInfo::isHighContrastModeActive()) { const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name(); button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor)); } } else { button->setIcon(QIcon::fromTheme(QStringLiteral("security-medium"))); if (!SystemInfo::isHighContrastModeActive()) { const auto bgColor = KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name(); button->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; };").arg(bgColor)); } } } QString Kleo::DeVSCompliance::name() { return name(isCompliant()); } QString Kleo::DeVSCompliance::name(bool compliant) { const auto filterId = compliant ? QStringLiteral("de-vs-filter") : QStringLiteral("not-de-vs-filter"); if (auto filter = KeyFilterManager::instance()->keyFilterByID(filterId)) { return filter->name(); } return compliant ? i18n("VS-NfD compliant") : i18n("Not VS-NfD compliant"); } diff --git a/src/utils/filesystemwatcher.cpp b/src/utils/filesystemwatcher.cpp index cd98a0d26..49e267ed3 100644 --- a/src/utils/filesystemwatcher.cpp +++ b/src/utils/filesystemwatcher.cpp @@ -1,329 +1,329 @@ /* -*- mode: c++; c-basic-offset:4 -*- filesystemwatcher.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "filesystemwatcher.h" #include #include #include #include #include #include #include #include using namespace Kleo; class FileSystemWatcher::Private { FileSystemWatcher *const q; public: explicit Private(FileSystemWatcher *qq, const QStringList &paths = QStringList()); ~Private() { delete m_watcher; } void onFileChanged(const QString &path); void onDirectoryChanged(const QString &path); void handleTimer(); void onTimeout(); void connectWatcher(); QFileSystemWatcher *m_watcher = nullptr; QTimer m_timer; std::set m_seenPaths; std::set m_cachedDirectories; std::set m_cachedFiles; QStringList m_paths, m_blacklist, m_whitelist; }; FileSystemWatcher::Private::Private(FileSystemWatcher *qq, const QStringList &paths) : q(qq) , m_watcher(nullptr) , m_paths(paths) { m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, q, [this]() { onTimeout(); }); } static bool is_matching(const QString &file, const QStringList &list) { for (const QString &entry : list) { if (QRegularExpression::fromWildcard(entry, Qt::CaseInsensitive).match(file).hasMatch()) { return true; } } return false; } static bool is_blacklisted(const QString &file, const QStringList &blacklist) { return is_matching(file, blacklist); } static bool is_whitelisted(const QString &file, const QStringList &whitelist) { if (whitelist.empty()) { return true; // special case } return is_matching(file, whitelist); } void FileSystemWatcher::Private::onFileChanged(const QString &path) { const QFileInfo fi(path); if (is_blacklisted(fi.fileName(), m_blacklist)) { return; } if (!is_whitelisted(fi.fileName(), m_whitelist)) { return; } qCDebug(LIBKLEO_LOG) << path; if (fi.exists()) { m_seenPaths.insert(path); } else { m_seenPaths.erase(path); } m_cachedFiles.insert(path); handleTimer(); } static QStringList list_dir_absolute(const QString &path, const QStringList &blacklist, const QStringList &whitelist) { QDir dir(path); QStringList entries = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot); QStringList::iterator end = std::remove_if(entries.begin(), entries.end(), [&blacklist](const QString &entry) { return is_blacklisted(entry, blacklist); }); if (!whitelist.empty()) { end = std::remove_if(entries.begin(), end, [&whitelist](const QString &entry) { return !is_whitelisted(entry, whitelist); }); } entries.erase(end, entries.end()); std::sort(entries.begin(), entries.end()); std::transform(entries.begin(), entries.end(), entries.begin(), [&dir](const QString &entry) { return dir.absoluteFilePath(entry); }); return entries; } static QStringList find_new_files(const QStringList ¤t, const std::set &seen) { QStringList result; std::set_difference(current.begin(), current.end(), seen.begin(), seen.end(), std::back_inserter(result)); return result; } void FileSystemWatcher::Private::onDirectoryChanged(const QString &path) { const QStringList newFiles = find_new_files(list_dir_absolute(path, m_blacklist, m_whitelist), m_seenPaths); if (newFiles.empty()) { return; } qCDebug(LIBKLEO_LOG) << "newFiles" << newFiles; m_cachedFiles.insert(newFiles.begin(), newFiles.end()); q->addPaths(newFiles); m_cachedDirectories.insert(path); handleTimer(); } void FileSystemWatcher::Private::onTimeout() { std::set dirs; std::set files; dirs.swap(m_cachedDirectories); files.swap(m_cachedFiles); if (dirs.empty() && files.empty()) { return; } Q_EMIT q->triggered(); for (const QString &i : std::as_const(dirs)) { Q_EMIT q->directoryChanged(i); } for (const QString &i : std::as_const(files)) { Q_EMIT q->fileChanged(i); } } void FileSystemWatcher::Private::handleTimer() { if (m_timer.interval() == 0) { onTimeout(); return; } m_timer.start(); } void FileSystemWatcher::Private::connectWatcher() { if (!m_watcher) { return; } connect(m_watcher, &QFileSystemWatcher::directoryChanged, q, [this](const QString &str) { onDirectoryChanged(str); }); connect(m_watcher, &QFileSystemWatcher::fileChanged, q, [this](const QString &str) { onFileChanged(str); }); } FileSystemWatcher::FileSystemWatcher(QObject *p) : QObject(p) , d(new Private(this)) { setEnabled(true); } FileSystemWatcher::FileSystemWatcher(const QStringList &paths, QObject *p) : QObject(p) , d(new Private(this, paths)) { setEnabled(true); } void FileSystemWatcher::setEnabled(bool enable) { if (isEnabled() == enable) { return; } if (enable) { Q_ASSERT(!d->m_watcher); d->m_watcher = new QFileSystemWatcher; if (!d->m_paths.empty()) { d->m_watcher->addPaths(d->m_paths); } d->connectWatcher(); } else { Q_ASSERT(d->m_watcher); delete d->m_watcher; d->m_watcher = nullptr; } } bool FileSystemWatcher::isEnabled() const { return d->m_watcher != nullptr; } FileSystemWatcher::~FileSystemWatcher() { } void FileSystemWatcher::setDelay(int ms) { Q_ASSERT(ms >= 0); d->m_timer.setInterval(ms); } int FileSystemWatcher::delay() const { return d->m_timer.interval(); } void FileSystemWatcher::blacklistFiles(const QStringList &paths) { d->m_blacklist += paths; QStringList blacklisted; d->m_paths.erase(kdtools::separate_if(d->m_paths.begin(), d->m_paths.end(), std::back_inserter(blacklisted), d->m_paths.begin(), [this](const QString &path) { return is_blacklisted(path, d->m_blacklist); }) .second, d->m_paths.end()); if (d->m_watcher && !blacklisted.empty()) { d->m_watcher->removePaths(blacklisted); } } void FileSystemWatcher::whitelistFiles(const QStringList &patterns) { d->m_whitelist += patterns; // ### would be nice to add newly-matching paths here right away, // ### but it's not as simple as blacklisting above, esp. since we // ### don't want to subject addPath()'ed paths to whitelisting. } static QStringList resolve(const QStringList &paths, const QStringList &blacklist, const QStringList &whitelist) { if (paths.empty()) { return QStringList(); } QStringList result; for (const QString &path : paths) { if (QDir(path).exists()) { result += list_dir_absolute(path, blacklist, whitelist); } } return result + resolve(result, blacklist, whitelist); } void FileSystemWatcher::addPaths(const QStringList &paths) { if (paths.empty()) { return; } const QStringList newPaths = paths + resolve(paths, d->m_blacklist, d->m_whitelist); if (!newPaths.empty()) { - qCDebug(LIBKLEO_LOG) << "adding\n " << newPaths.join(QLatin1String("\n ")) << "\n/end"; + qCDebug(LIBKLEO_LOG) << "adding\n " << newPaths.join(QLatin1StringView("\n ")) << "\n/end"; } d->m_paths += newPaths; d->m_seenPaths.insert(newPaths.begin(), newPaths.end()); if (d->m_watcher && !newPaths.empty()) { d->m_watcher->addPaths(newPaths); } } void FileSystemWatcher::addPath(const QString &path) { addPaths(QStringList(path)); } void FileSystemWatcher::removePaths(const QStringList &paths) { if (paths.empty()) { return; } for (const QString &i : paths) { d->m_paths.removeAll(i); } if (d->m_watcher) { d->m_watcher->removePaths(paths); } } void FileSystemWatcher::removePath(const QString &path) { removePaths(QStringList(path)); } #include "moc_filesystemwatcher.cpp" diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp index e343283a0..6397cbafa 100644 --- a/src/utils/formatting.cpp +++ b/src/utils/formatting.cpp @@ -1,1485 +1,1486 @@ /* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*- utils/formatting.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "formatting.h" #include "algorithm.h" #include "compat.h" #include "compliance.h" #include "cryptoconfig.h" #include "gnupg.h" #include "keyhelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include using namespace GpgME; using namespace Kleo; namespace { QIcon iconForValidityAndCompliance(UserID::Validity validity, bool isCompliant) { switch (validity) { case UserID::Ultimate: case UserID::Full: case UserID::Marginal: return isCompliant ? Formatting::successIcon() : Formatting::infoIcon(); case UserID::Never: return Formatting::errorIcon(); case UserID::Undefined: case UserID::Unknown: default: return Formatting::infoIcon(); } } QIcon iconForValidity(const UserID &userId) { const bool keyIsCompliant = !DeVSCompliance::isActive() || // (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent())); return iconForValidityAndCompliance(userId.validity(), keyIsCompliant); } } QIcon Formatting::IconProvider::icon(const GpgME::Key &key) const { if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(key)) { return Formatting::errorIcon(); } if (usage.canSign() && !Kleo::canBeUsedForSigning(key)) { return Formatting::errorIcon(); } if (key.isBad()) { return Formatting::errorIcon(); } const auto primaryUserId = key.userID(0); if (Kleo::isRevokedOrExpired(primaryUserId)) { return Formatting::errorIcon(); } return iconForValidity(primaryUserId); } QIcon Formatting::IconProvider::icon(const KeyGroup &group) const { if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) { return Formatting::errorIcon(); } if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) { return Formatting::errorIcon(); } return validityIcon(group); } QIcon Formatting::successIcon() { return QIcon::fromTheme(QStringLiteral("emblem-success")); } QIcon Formatting::infoIcon() { return QIcon::fromTheme(QStringLiteral("emblem-information")); } QIcon Formatting::questionIcon() { return QIcon::fromTheme(QStringLiteral("emblem-question")); } QIcon Formatting::unavailableIcon() { return QIcon::fromTheme(QStringLiteral("emblem-unavailable")); } QIcon Formatting::warningIcon() { return QIcon::fromTheme(QStringLiteral("emblem-warning")); } QIcon Formatting::errorIcon() { return QIcon::fromTheme(QStringLiteral("emblem-error")); } // // Name // QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_) { if (proto == GpgME::OpenPGP) { const QString name = QString::fromUtf8(name_); if (name.isEmpty()) { return QString(); } const QString comment = QString::fromUtf8(comment_); if (comment.isEmpty()) { return name; } return QStringLiteral("%1 (%2)").arg(name, comment); } if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_) { return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_)); } QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment) { if (proto == GpgME::OpenPGP) { if (name.isEmpty()) { if (email.isEmpty()) { return QString(); } else if (comment.isEmpty()) { return QStringLiteral("<%1>").arg(email); } else { return QStringLiteral("(%2) <%1>").arg(email, comment); } } if (email.isEmpty()) { if (comment.isEmpty()) { return name; } else { return QStringLiteral("%1 (%2)").arg(name, comment); } } if (comment.isEmpty()) { return QStringLiteral("%1 <%2>").arg(name, email); } else { return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment); } } if (proto == GpgME::CMS) { const DN subject(id); const QString cn = subject[QStringLiteral("CN")].trimmed(); if (cn.isEmpty()) { return subject.prettyDN(); } return cn; } return QString(); } QString Formatting::prettyUserID(const UserID &uid) { if (uid.parent().protocol() == GpgME::OpenPGP) { return prettyNameAndEMail(uid); } const QByteArray id = QByteArray(uid.id()).trimmed(); if (id.startsWith('<')) { return prettyEMail(uid.email(), uid.id()); } if (id.startsWith('(')) { // ### parse uri/dns: return QString::fromUtf8(uid.id()); } else { return DN(uid.id()).prettyDN(); } } QString Formatting::prettyKeyID(const char *id) { if (!id) { return QString(); } - return QLatin1String("0x") + QString::fromLatin1(id).toUpper(); + return QLatin1StringView("0x") + QString::fromLatin1(id).toUpper(); } QString Formatting::prettyNameAndEMail(const UserID &uid) { return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment()); } QString Formatting::prettyNameAndEMail(const Key &key) { return prettyNameAndEMail(key.userID(0)); } QString Formatting::prettyName(const Key &key) { return prettyName(key.userID(0)); } QString Formatting::prettyName(const UserID &uid) { return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment()); } QString Formatting::prettyName(const UserID::Signature &sig) { return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment()); } // // EMail // QString Formatting::prettyEMail(const Key &key) { for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) { const QString email = prettyEMail(key.userID(i)); if (!email.isEmpty()) { return email; } } return QString(); } QString Formatting::prettyEMail(const UserID &uid) { return prettyEMail(uid.email(), uid.id()); } QString Formatting::prettyEMail(const UserID::Signature &sig) { return prettyEMail(sig.signerEmail(), sig.signerUserID()); } QString Formatting::prettyEMail(const char *email_, const char *id) { QString email; QString name; QString comment; if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) { return email; } else { return DN(id)[QStringLiteral("EMAIL")].trimmed(); } } // // Tooltip // namespace { static QString protect_whitespace(QString s) { static const QLatin1Char SP(' '); static const QLatin1Char NBSP('\xA0'); return s.replace(SP, NBSP); } template QString format_row(const QString &field, const T_arg &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg); } QString format_row(const QString &field, const QString &arg) { return QStringLiteral("%1:%2").arg(protect_whitespace(field), arg.toHtmlEscaped()); } QString format_row(const QString &field, const char *arg) { return format_row(field, QString::fromUtf8(arg)); } QString format_keytype(const Key &key) { const Subkey subkey = key.subkey(0); if (key.hasSecret()) { - return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); + return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); } else { - return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); + return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); } } QString format_subkeytype(const Subkey &subkey) { const auto algo = subkey.publicKeyAlgorithm(); if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) { return QString::fromStdString(subkey.algoName()); } - return i18n("%1-bit %2", subkey.length(), QLatin1String(subkey.publicKeyAlgorithmAsString())); + return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString())); } QString format_keyusage(const Key &key) { QStringList capabilities; if (Kleo::keyHasSign(key)) { if (key.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (Kleo::keyHasEncrypt(key)) { capabilities.push_back(i18n("Encryption")); } if (Kleo::keyHasCertify(key)) { capabilities.push_back(i18n("Certifying User-IDs")); } if (Kleo::keyHasAuthenticate(key)) { capabilities.push_back(i18n("SSH Authentication")); } - return capabilities.join(QLatin1String(", ")); + return capabilities.join(QLatin1StringView(", ")); } QString format_subkeyusage(const Subkey &subkey) { QStringList capabilities; if (subkey.canSign()) { if (subkey.isQualified()) { capabilities.push_back(i18n("Signing (Qualified)")); } else { capabilities.push_back(i18n("Signing")); } } if (subkey.canEncrypt()) { capabilities.push_back(i18n("Encryption")); } if (subkey.canCertify()) { capabilities.push_back(i18n("Certifying User-IDs")); } if (subkey.canAuthenticate()) { capabilities.push_back(i18n("SSH Authentication")); } - return capabilities.join(QLatin1String(", ")); + return capabilities.join(QLatin1StringView(", ")); } static QString time_t2string(time_t t) { const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t)); return QLocale().toString(dt, QLocale::ShortFormat); } static QString make_red(const QString &txt) { - return QLatin1String("") + txt.toHtmlEscaped() + QLatin1String(""); + return QLatin1StringView("") + txt.toHtmlEscaped() + QLatin1String(""); } } QString Formatting::toolTip(const Key &key, int flags) { if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) { return QString(); } const Subkey subkey = key.subkey(0); QString result; if (flags & Validity) { if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) { if (key.isRevoked()) { result = make_red(i18n("Revoked")); } else if (key.isExpired()) { result = make_red(i18n("Expired")); } else if (key.isDisabled()) { result = i18n("Disabled"); } else if (key.keyListMode() & GpgME::Validate) { unsigned int fullyTrusted = 0; for (const auto &uid : key.userIDs()) { if (uid.validity() >= UserID::Validity::Full) { fullyTrusted++; } } if (fullyTrusted == key.numUserIDs()) { result = i18n("All User-IDs are certified."); const auto compliance = complianceStringForKey(key); if (!compliance.isEmpty()) { result += QStringLiteral("
") + compliance; } } else { result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted); } } else { result = i18n("The validity cannot be checked at the moment."); } } else { result = i18n("The validity cannot be checked at the moment."); } } if (flags == Validity) { return result; } - result += QLatin1String(""); + result += QLatin1StringView("
"); if (key.protocol() == GpgME::CMS) { if (flags & SerialNumber) { result += format_row(i18n("Serial number"), key.issuerSerial()); } if (flags & Issuer) { result += format_row(i18n("Issuer"), key.issuerName()); } } if (flags & UserIDs) { const std::vector uids = key.userIDs(); if (!uids.empty()) { result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front())); } if (uids.size() > 1) { for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) { if (!it->isRevoked() && !it->isInvalid()) { result += format_row(i18n("a.k.a."), prettyUserID(*it)); } } } } if (flags & ExpiryDates) { result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime())); if (!subkey.neverExpires()) { result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_keytype(key)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_keyusage(key)); } if (flags & KeyID) { result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID())); } if (flags & Fingerprint) { result += format_row(i18n("Fingerprint"), key.primaryFingerprint()); } if (flags & OwnerTrust) { if (key.protocol() == GpgME::OpenPGP) { result += format_row(i18n("Certification trust"), ownerTrustShort(key)); } else if (key.isRoot()) { result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No")); } } if (flags & StorageLocation) { if (const char *card = subkey.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } if (flags & Subkeys) { for (const auto &sub : key.subkeys()) { - result += QLatin1String("
"); + result += QLatin1StringView("
"); result += format_row(i18n("Subkey"), sub.fingerprint()); if (sub.isRevoked()) { result += format_row(i18n("Status"), i18n("Revoked")); } else if (sub.isExpired()) { result += format_row(i18n("Status"), i18n("Expired")); } if (flags & ExpiryDates) { result += format_row(i18n("Valid from"), time_t2string(sub.creationTime())); if (!sub.neverExpires()) { result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime())); } } if (flags & CertificateType) { result += format_row(i18n("Type"), format_subkeytype(sub)); } if (flags & CertificateUsage) { result += format_row(i18n("Usage"), format_subkeyusage(sub)); } if (flags & StorageLocation) { if (const char *card = sub.cardSerialNumber()) { result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card))); } else { result += format_row(i18n("Stored"), i18nc("stored...", "on this computer")); } } } } - result += QLatin1String("
"); + result += QLatin1StringView(""); return result; } namespace { template QString getValidityStatement(const Container &keys) { const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == GpgME::OpenPGP; }); const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.keyListMode() & Validate; }); if (allKeysAreOpenPGP || allKeysAreValidated) { const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad)); if (someKeysAreBad) { return i18n("Some keys are revoked, expired, disabled, or invalid."); } else { const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity); if (allKeysAreFullyValid) { return i18n("All keys are certified."); } else { return i18n("Some keys are not certified."); } } } return i18n("The validity of the keys cannot be checked at the moment."); } } QString Formatting::toolTip(const KeyGroup &group, int flags) { static const unsigned int maxNumKeysForTooltip = 20; if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18nc("@info:tooltip", "This group does not contain any keys."); } const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString(); if (flags == Validity) { return validity; } // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys" const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size(); QStringList result; result.reserve(3 + 2 + numKeysForTooltip + 2); if (!validity.isEmpty()) { result.push_back(QStringLiteral("

")); result.push_back(validity.toHtmlEscaped()); result.push_back(QStringLiteral("

")); } result.push_back(QStringLiteral("

")); result.push_back(i18n("Keys:")); { auto it = keys.cbegin(); for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) { - result.push_back(QLatin1String("
") + Formatting::summaryLine(*it).toHtmlEscaped()); + result.push_back(QLatin1StringView("
") + Formatting::summaryLine(*it).toHtmlEscaped()); } } if (keys.size() > numKeysForTooltip) { - result.push_back(QLatin1String("
") + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); + result.push_back(QLatin1StringView("
") + + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip)); } result.push_back(QStringLiteral("

")); return result.join(QLatin1Char('\n')); } // // Creation and Expiration // namespace { static QDate time_t2date(time_t t) { if (!t) { return {}; } const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t)); return dt.date(); } static QString accessible_date_format() { return i18nc( "date format suitable for screen readers; " "d: day as a number without a leading zero, " "MMMM: localized month name, " "yyyy: year as a four digit number", "MMMM d, yyyy"); } template QString expiration_date_string(const T &tee, const QString &noExpiration) { return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime())); } template QDate creation_date(const T &tee) { return time_t2date(tee.creationTime()); } template QDate expiration_date(const T &tee) { return time_t2date(tee.expirationTime()); } } QString Formatting::dateString(time_t t) { return dateString(time_t2date(t)); } QString Formatting::dateString(const QDate &date) { return QLocale().toString(date, QLocale::ShortFormat); } QString Formatting::accessibleDate(time_t t) { return accessibleDate(time_t2date(t)); } QString Formatting::accessibleDate(const QDate &date) { return QLocale().toString(date, accessible_date_format()); } QString Formatting::expirationDateString(const Key &key, const QString &noExpiration) { // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD), // then we assume that the date is valid; if the date is zero for a remote key, then // we don't know if it's unknown or unlimited return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) // ? i18nc("@info the expiration date of the key is unknown", "unknown") : expiration_date_string(key.subkey(0), noExpiration); } QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration) { return expiration_date_string(subkey, noExpiration); } QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration) { return expiration_date_string(sig, noExpiration); } QDate Formatting::expirationDate(const Key &key) { return expiration_date(key.subkey(0)); } QDate Formatting::expirationDate(const Subkey &subkey) { return expiration_date(subkey); } QDate Formatting::expirationDate(const UserID::Signature &sig) { return expiration_date(sig); } QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration) { // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD), // then we assume that the date is valid; if the date is zero for a remote key, then // we don't know if it's unknown or unlimited return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) // ? i18nc("@info the expiration date of the key is unknown", "unknown") : accessibleExpirationDate(key.subkey(0), noExpiration); } QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration) { if (subkey.neverExpires()) { return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration; } else { return accessibleDate(expirationDate(subkey)); } } QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration) { if (sig.neverExpires()) { return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration; } else { return accessibleDate(expirationDate(sig)); } } QString Formatting::creationDateString(const Key &key) { return dateString(creation_date(key.subkey(0))); } QString Formatting::creationDateString(const Subkey &subkey) { return dateString(creation_date(subkey)); } QString Formatting::creationDateString(const UserID::Signature &sig) { return dateString(creation_date(sig)); } QDate Formatting::creationDate(const Key &key) { return creation_date(key.subkey(0)); } QDate Formatting::creationDate(const Subkey &subkey) { return creation_date(subkey); } QDate Formatting::creationDate(const UserID::Signature &sig) { return creation_date(sig); } QString Formatting::accessibleCreationDate(const Key &key) { return accessibleDate(creationDate(key)); } QString Formatting::accessibleCreationDate(const Subkey &subkey) { return accessibleDate(creationDate(subkey)); } // // Types // QString Formatting::displayName(GpgME::Protocol p) { if (p == GpgME::CMS) { return i18nc("X.509/CMS encryption standard", "S/MIME"); } if (p == GpgME::OpenPGP) { return i18n("OpenPGP"); } return i18nc("Unknown encryption protocol", "Unknown"); } QString Formatting::type(const Key &key) { return displayName(key.protocol()); } QString Formatting::type(const Subkey &subkey) { return QString::fromUtf8(subkey.publicKeyAlgorithmAsString()); } QString Formatting::type(const KeyGroup &group) { Q_UNUSED(group) return i18nc("a group of keys/certificates", "Group"); } // // Status / Validity // QString Formatting::ownerTrustShort(const Key &key) { return ownerTrustShort(key.ownerTrust()); } QString Formatting::ownerTrustShort(Key::OwnerTrust trust) { switch (trust) { case Key::Unknown: return i18nc("unknown trust level", "unknown"); case Key::Never: return i18n("untrusted"); case Key::Marginal: return i18nc("marginal trust", "marginal"); case Key::Full: return i18nc("full trust", "full"); case Key::Ultimate: return i18nc("ultimate trust", "ultimate"); case Key::Undefined: return i18nc("undefined trust", "undefined"); default: Q_ASSERT(!"unexpected owner trust value"); break; } return QString(); } QString Formatting::validityShort(const Subkey &subkey) { if (subkey.isRevoked()) { return i18n("revoked"); } if (subkey.isExpired()) { return i18n("expired"); } if (subkey.isDisabled()) { return i18n("disabled"); } if (subkey.isInvalid()) { return i18n("invalid"); } return i18nc("as in good/valid signature", "good"); } QString Formatting::validityShort(const UserID &uid) { if (uid.isRevoked()) { return i18n("revoked"); } if (uid.isInvalid()) { return i18n("invalid"); } switch (uid.validity()) { case UserID::Unknown: return i18nc("unknown trust level", "unknown"); case UserID::Undefined: return i18nc("undefined trust", "undefined"); case UserID::Never: return i18n("untrusted"); case UserID::Marginal: return i18nc("marginal trust", "marginal"); case UserID::Full: return i18nc("full trust", "full"); case UserID::Ultimate: return i18nc("ultimate trust", "ultimate"); } return QString(); } QString Formatting::validityShort(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return i18n("valid"); case 0x30: return i18n("revoked"); default: return i18n("class %1", sig.certClass()); } } [[fallthrough]]; // fall through: case UserID::Signature::GeneralError: return i18n("invalid"); case UserID::Signature::SigExpired: return i18n("expired"); case UserID::Signature::KeyExpired: return i18n("certificate expired"); case UserID::Signature::BadSignature: return i18nc("fake/invalid signature", "bad"); case UserID::Signature::NoPublicKey: { /* GnuPG returns the same error for no public key as for expired * or revoked certificates. */ const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID()); if (key.isNull()) { return i18n("no public key"); } else if (key.isExpired()) { return i18n("key expired"); } else if (key.isRevoked()) { return i18n("key revoked"); } else if (key.isDisabled()) { return i18n("key disabled"); } /* can't happen */ return QStringLiteral("unknown"); } } return QString(); } QIcon Formatting::validityIcon(const UserID::Signature &sig) { switch (sig.status()) { case UserID::Signature::NoError: if (!sig.isInvalid()) { /* See RFC 4880 Section 5.2.1 */ switch (sig.certClass()) { case 0x10: /* Generic */ case 0x11: /* Persona */ case 0x12: /* Casual */ case 0x13: /* Positive */ return Formatting::successIcon(); case 0x30: return Formatting::errorIcon(); default: return QIcon(); } } [[fallthrough]]; // fall through: case UserID::Signature::BadSignature: case UserID::Signature::GeneralError: return Formatting::errorIcon(); case UserID::Signature::SigExpired: case UserID::Signature::KeyExpired: return Formatting::infoIcon(); case UserID::Signature::NoPublicKey: return Formatting::questionIcon(); } return QIcon(); } QString Formatting::formatKeyLink(const Key &key) { if (key.isNull()) { return QString(); } - return QStringLiteral("%2").arg(QLatin1String(key.primaryFingerprint()), Formatting::prettyName(key)); + return QStringLiteral("%2").arg(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key)); } QString Formatting::formatForComboBox(const GpgME::Key &key) { const QString name = prettyName(key); QString mail = prettyEMail(key); if (!mail.isEmpty()) { mail = QLatin1Char('<') + mail + QLatin1Char('>'); } - return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1String(key.shortKeyID())).simplified(); + return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1StringView(key.shortKeyID())).simplified(); } QString Formatting::nameAndEmailForSummaryLine(const UserID &id) { Q_ASSERT(!id.isNull()); const QString email = Formatting::prettyEMail(id); const QString name = Formatting::prettyName(id); if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } QString Formatting::nameAndEmailForSummaryLine(const Key &key) { Q_ASSERT(!key.isNull()); const QString email = Formatting::prettyEMail(key); const QString name = Formatting::prettyName(key); if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } const char *Formatting::summaryToString(const Signature::Summary summary) { if (summary & Signature::Red) { return "RED"; } if (summary & Signature::Green) { return "GREEN"; } return "YELLOW"; } QString Formatting::signatureToString(const Signature &sig, const Key &key) { if (sig.isNull()) { return QString(); } const bool red = (sig.summary() & Signature::Red); const bool valid = (sig.summary() & Signature::Valid); if (red) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status())); } else { return i18n("Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status())); } } else { return i18n("Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status())); } } else if (valid) { if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr)); } else { return i18n("Good signature by an unknown certificate."); } } else { return i18n("Good signature by %1.", nameAndEmailForSummaryLine(key)); } } else if (key.isNull()) { if (const char *fpr = sig.fingerprint()) { return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status())); } else { return i18n("Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status())); } } else { return i18n("Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status())); } } // // ImportResult // QString Formatting::importMetaData(const Import &import, const QStringList &ids) { const QString result = importMetaData(import); if (result.isEmpty()) { return QString(); } else { return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n')); } } QString Formatting::importMetaData(const Import &import) { if (import.isNull()) { return QString(); } if (import.error().isCanceled()) { return i18n("The import of this certificate was canceled."); } if (import.error()) { return i18n("An error occurred importing this certificate: %1", Formatting::errorAsString(import.error())); } const unsigned int status = import.status(); if (status & Import::NewKey) { return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.") : i18n("This certificate is new to your keystore."); } QStringList results; if (status & Import::NewUserIDs) { results.push_back(i18n("New user-ids were added to this certificate by the import.")); } if (status & Import::NewSignatures) { results.push_back(i18n("New signatures were added to this certificate by the import.")); } if (status & Import::NewSubkeys) { results.push_back(i18n("New subkeys were added to this certificate by the import.")); } return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n')); } // // Overview in CertificateDetailsDialog // QString Formatting::formatOverview(const Key &key) { return toolTip(key, AllOptions); } QString Formatting::usageString(const Subkey &sub) { QStringList usageStrings; if (sub.canCertify()) { usageStrings << i18n("Certify"); } if (sub.canSign()) { usageStrings << i18n("Sign"); } if (sub.canEncrypt()) { usageStrings << i18n("Encrypt"); } if (sub.canAuthenticate()) { usageStrings << i18n("Authenticate"); } if (sub.canRenc()) { usageStrings << i18nc("Means 'Additional Decryption Subkey'; Don't try translating that, though.", "ADSK"); } - return usageStrings.join(QLatin1String(", ")); + return usageStrings.join(QLatin1StringView(", ")); } QString Formatting::summaryLine(const UserID &id) { return i18nc("name (validity, protocol, creation date)", "%1 (%2, %3, created: %4)", nameAndEmailForSummaryLine(id), Formatting::complianceStringShort(id), displayName(id.parent().protocol()), Formatting::creationDateString(id.parent())); } QString Formatting::summaryLine(const Key &key) { return nameAndEmailForSummaryLine(key) + QLatin1Char(' ') + i18nc("(validity, protocol, creation date)", "(%1, %2, created: %3)", Formatting::complianceStringShort(key), displayName(key.protocol()), Formatting::creationDateString(key)); } QString Formatting::summaryLine(const KeyGroup &group) { switch (group.source()) { case KeyGroup::ApplicationConfig: case KeyGroup::GnuPGConfig: return i18ncp("name of group of keys (n key(s), validity)", "%2 (1 key, %3)", "%2 (%1 keys, %3)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); case KeyGroup::Tags: return i18ncp("name of group of keys (n key(s), validity, tag)", "%2 (1 key, %3, tag)", "%2 (%1 keys, %3, tag)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); default: return i18ncp("name of group of keys (n key(s), validity, group ...)", "%2 (1 key, %3, unknown origin)", "%2 (%1 keys, %3, unknown origin)", group.keys().size(), group.name(), Formatting::complianceStringShort(group)); } } // Icon for certificate selection indication QIcon Formatting::iconForUid(const UserID &uid) { if (Kleo::isRevokedOrExpired(uid)) { return Formatting::errorIcon(); } return iconForValidity(uid); } QString Formatting::validity(const UserID &uid) { switch (uid.validity()) { case UserID::Ultimate: return i18n("The certificate is marked as your own."); case UserID::Full: return i18n("The certificate belongs to this recipient."); case UserID::Marginal: return i18n("The trust model indicates marginally that the certificate belongs to this recipient."); case UserID::Never: return i18n("This certificate should not be used."); case UserID::Undefined: case UserID::Unknown: default: return i18n("There is no indication that this certificate belongs to this recipient."); } } QString Formatting::validity(const KeyGroup &group) { if (group.isNull()) { return QString(); } const KeyGroup::Keys &keys = group.keys(); if (keys.size() == 0) { return i18n("This group does not contain any keys."); } return getValidityStatement(keys); } namespace { template UserID::Validity minimalValidity(const Container &keys) { const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) { return std::min(validity, minimalValidityOfNotRevokedUserIDs(key)); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } template bool allKeysAreCompliant(const Container &keys) { if (!DeVSCompliance::isActive()) { return true; } if (!DeVSCompliance::isCompliant()) { return false; } return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant); } } QIcon Formatting::validityIcon(const KeyGroup &group) { if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) { return Formatting::errorIcon(); } return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys())); } bool Formatting::uidsHaveFullValidity(const Key &key) { return allUserIDsHaveFullValidity(key); } QString Formatting::complianceMode() { const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance"); - return complianceValue == QLatin1String("gnupg") ? QString() : complianceValue; + return complianceValue == QLatin1StringView("gnupg") ? QString() : complianceValue; } bool Formatting::isKeyDeVs(const GpgME::Key &key) { return DeVSCompliance::allSubkeysAreCompliant(key); } QString Formatting::complianceStringForKey(const GpgME::Key &key) { // There will likely be more in the future for other institutions // for now we only have DE-VS if (DeVSCompliance::isCompliant()) { return isRemoteKey(key) // ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown") : DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key)); } return QString(); } QString Formatting::complianceStringShort(const GpgME::UserID &id) { if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(id)) { return QStringLiteral("★ ") + DeVSCompliance::name(true); } const bool keyValidityChecked = (id.parent().keyListMode() & GpgME::Validate); if (keyValidityChecked && id.validity() >= UserID::Full) { return i18nc("As in 'this user ID is valid.'", "certified"); } if (id.parent().isExpired() || isExpired(id)) { return i18n("expired"); } if (id.parent().isRevoked() || id.isRevoked()) { return i18n("revoked"); } if (id.parent().isDisabled()) { return i18n("disabled"); } if (id.parent().isInvalid() || id.isInvalid()) { return i18n("invalid"); } if (keyValidityChecked) { return i18nc("As in 'this user ID is not certified'", "not certified"); } return i18nc("The validity of this user ID has not been/could not be checked", "not checked"); } QString Formatting::complianceStringShort(const GpgME::Key &key) { if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) { return QStringLiteral("★ ") + DeVSCompliance::name(true); } const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate); if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) { return i18nc("As in all user IDs are valid.", "certified"); } if (key.isExpired()) { return i18n("expired"); } if (key.isRevoked()) { return i18n("revoked"); } if (key.isDisabled()) { return i18n("disabled"); } if (key.isInvalid()) { return i18n("invalid"); } if (keyValidityChecked) { return i18nc("As in not all user IDs are valid.", "not certified"); } return i18nc("The validity of the user IDs has not been/could not be checked", "not checked"); } QString Formatting::complianceStringShort(const KeyGroup &group) { const KeyGroup::Keys &keys = group.keys(); const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity); if (allKeysFullyValid) { return i18nc("As in all keys are valid.", "all certified"); } return i18nc("As in not all keys are valid.", "not all certified"); } QString Formatting::prettyID(const char *id) { if (!id) { return QString(); } QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed(); // For the standard 10 group fingerprint let us use a double space in the // middle to increase readability if (ret.size() == 49) { ret.insert(24, QLatin1Char(' ')); } return ret; } QString Formatting::accessibleHexID(const char *id) { static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")}; QString ret; ret = QString::fromLatin1(id); if (!ret.isEmpty() && (ret.size() % 4 == 0)) { ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2); } return ret; } QString Formatting::origin(int o) { switch (o) { case Key::OriginKS: return i18n("Keyserver"); case Key::OriginDane: return QStringLiteral("DANE"); case Key::OriginWKD: return QStringLiteral("WKD"); case Key::OriginURL: return QStringLiteral("URL"); case Key::OriginFile: return i18n("File import"); case Key::OriginSelf: return i18n("Generated"); case Key::OriginOther: case Key::OriginUnknown: default: return i18n("Unknown"); } } QString Formatting::deVsString(bool compliant) { return DeVSCompliance::name(compliant); } namespace { QString formatTrustScope(const char *trustScope) { static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")}; const auto scopeRegExp = QString::fromUtf8(trustScope); if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) { // looks like a trust scope regular expression created by gpg auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2); domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)")); return domain; } return scopeRegExp; } } QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig) { return formatTrustScope(sig.trustScope()); } QString Formatting::trustSignature(const GpgME::UserID::Signature &sig) { switch (sig.trustValue()) { case TrustSignatureTrust::Partial: return i18nc("Certifies this key as partially trusted introducer for 'domain name'.", "Certifies this key as partially trusted introducer for '%1'.", trustSignatureDomain(sig)); case TrustSignatureTrust::Complete: return i18nc("Certifies this key as fully trusted introducer for 'domain name'.", "Certifies this key as fully trusted introducer for '%1'.", trustSignatureDomain(sig)); default: return {}; } } QString Formatting::errorAsString(const GpgME::Error &error) { #ifdef Q_OS_WIN // On Windows, we set GpgME resp. libgpg-error to return (translated) error messages as UTF-8 const char *s = error.asString(); qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1); qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s; qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray{s}.toPercentEncoding(); return QString::fromUtf8(s); #else return QString::fromLocal8Bit(error.asString()); #endif } QString Formatting::prettyAlgorithmName(const std::string &algorithm) { static const std::map displayNames = { {"brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)")}, {"brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)")}, {"brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)")}, {"curve25519", i18nc("@info", "ECC (Curve25519)")}, {"curve448", i18nc("@info", "ECC (Curve448)")}, {"ed25519", i18nc("@info", "ECC (Ed25519)")}, {"ed448", i18nc("@info", "ECC (Ed448)")}, {"cv25519", i18nc("@info", "ECC (Cv25519)")}, {"cv448", i18nc("@info", "ECC (Cv448)")}, {"nistp256", i18nc("@info", "ECC (NIST P-256)")}, {"nistp384", i18nc("@info", "ECC (NIST P-384)")}, {"nistp521", i18nc("@info", "ECC (NIST P-521)")}, {"rsa2048", i18nc("@info", "RSA 2048")}, {"rsa3072", i18nc("@info", "RSA 3072")}, {"rsa4096", i18nc("@info", "RSA 4096")}, {"dsa1024", i18nc("@info", "DSA 1024")}, {"dsa2048", i18nc("@info", "DSA 2048")}, {"elg1024", i18nc("@info", "Elgamal 1024")}, {"elg2048", i18nc("@info", "Elgamal 2048")}, {"elg3072", i18nc("@info", "Elgamal 3072")}, {"elg4096", i18nc("@info", "Elgamal 4096")}, }; const auto it = displayNames.find(algorithm); return (it != displayNames.end()) ? it->second : i18nc("@info", "Unknown algorithm"); } diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp index fa2ad6893..09b435343 100644 --- a/src/utils/gnupg.cpp +++ b/src/utils/gnupg.cpp @@ -1,669 +1,669 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/gnupg.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "gnupg.h" #include "assuan.h" #include "compat.h" #include "compliance.h" #include "cryptoconfig.h" #include "hex.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include "gnupg-registry.h" #endif // Q_OS_WIN #include #include using namespace GpgME; QString Kleo::gnupgHomeDirectory() { static const QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir")); return homeDir; } QString Kleo::gnupgPrivateKeysDirectory() { static const QString dir = QDir{gnupgHomeDirectory()}.filePath(QStringLiteral("private-keys-v1.d")); return dir; } int Kleo::makeGnuPGError(int code) { return gpg_error(static_cast(code)); } static QString findGpgExe(GpgME::Engine engine, const QString &exe) { const GpgME::EngineInfo info = GpgME::engineInfo(engine); return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe); } QString Kleo::gpgConfPath() { static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf")); return path; } QString Kleo::gpgSmPath() { static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm")); return path; } QString Kleo::gpgPath() { static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg")); return path; } QStringList Kleo::gnupgFileWhitelist() { return { // The obvious pubring QStringLiteral("pubring.gpg"), // GnuPG 2.1 pubring QStringLiteral("pubring.kbx"), // Trust in X509 Certificates QStringLiteral("trustlist.txt"), // Trustdb controls ownertrust and thus WOT validity QStringLiteral("trustdb.gpg"), // We want to update when smartcard status changes QStringLiteral("reader*.status"), // No longer used in 2.1 but for 2.0 we want this QStringLiteral("secring.gpg"), // Secret keys (living under private-keys-v1.d/) QStringLiteral("*.key"), // Changes to the trustmodel / compliance mode might // affect validity so we check this, too. // Globbing for gpg.conf* here will trigger too often // as gpgconf creates files like gpg.conf.bak or // gpg.conf.tmp12312.gpgconf that should not trigger // a change. QStringLiteral("gpg.conf"), QStringLiteral("gpg.conf-?"), QStringLiteral("gpg.conf-?.?"), }; } QStringList Kleo::gnupgFolderWhitelist() { static const QDir gnupgHome{gnupgHomeDirectory()}; return { gnupgHome.path(), gnupgPrivateKeysDirectory(), }; } QString Kleo::gpg4winInstallPath() { #ifdef Q_OS_WIN // QApplication::applicationDirPath is only used as a fallback // to support the case where Kleopatra is not installed from // Gpg4win but Gpg4win is also installed. char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GPG4Win", "Install Directory"); if (!instDir) { // Fallback to HKCU instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GPG4Win", "Install Directory"); } if (instDir) { QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); free(instDir); return ret; } qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir."; #endif return QCoreApplication::applicationDirPath(); } QString Kleo::gnupgInstallPath() { #ifdef Q_OS_WIN // QApplication::applicationDirPath is only used as a fallback // to support the case where Kleopatra is not installed from // Gpg4win but Gpg4win is also installed. char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", "Software\\GnuPG", "Install Directory"); if (!instDir) { // Fallback to HKCU instDir = read_w32_registry_string("HKEY_CURRENT_USER", "Software\\GnuPG", "Install Directory"); } if (instDir) { QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); free(instDir); return ret; } qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir."; #endif return gpgConfListDir("bindir"); } QString Kleo::gpgConfListDir(const char *which) { if (!which || !*which) { return QString(); } const QString gpgConfPath = Kleo::gpgConfPath(); if (gpgConfPath.isEmpty()) { return QString(); } QProcess gpgConf; qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs"; gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs")); if (!gpgConf.waitForFinished()) { qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString()); qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData(); return QString(); } const QList lines = gpgConf.readAllStandardOutput().split('\n'); for (const QByteArray &line : lines) { if (line.startsWith(which) && line[qstrlen(which)] == ':') { const int begin = qstrlen(which) + 1; int end = line.size(); while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) { --end; } const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin)))); qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result) << " for '" << which << "'entry"; return result; } } qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'" << "entry in output:\n" << gpgConf.readAllStandardError().constData(); return QString(); } static std::array getVersionFromString(const char *actual, bool &ok) { std::array ret; ok = false; if (!actual) { return ret; } QString versionString = QString::fromLatin1(actual); // Try to fix it up - QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1String(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)"))); + QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1StringView(R"((\d+)\.(\d+)\.(\d+)(?:-svn\d+)?.*)"))); QRegularExpressionMatch match; for (int i = 0; i < 3; i++) { match = rx.match(versionString); if (!match.hasMatch()) { versionString += QStringLiteral(".0"); } else { ok = true; break; } } if (!ok) { qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual; return ret; } for (int i = 0; i < 3; ++i) { ret[i] = match.capturedView(i + 1).toUInt(&ok); if (!ok) { return ret; } } ok = true; return ret; } bool Kleo::versionIsAtLeast(const char *minimum, const char *actual) { if (!minimum || !actual) { return false; } bool ok; const auto minimum_version = getVersionFromString(minimum, ok); if (!ok) { return false; } const auto actual_version = getVersionFromString(actual, ok); if (!ok) { return false; } return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(minimum_version), std::end(minimum_version)); } bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine) { static QMap> cachedVersions; const int required_version[] = {major, minor, patch}; // Gpgconf means spawning processes which is expensive on windows. std::array actual_version; if (!cachedVersions.contains(engine)) { const Error err = checkEngine(engine); if (err.code() == GPG_ERR_INV_ENGINE) { qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '"; return false; } const char *actual = GpgME::engineInfo(engine).version(); bool ok; actual_version = getVersionFromString(actual, ok); qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: " << actual_version[0] << '.' << actual_version[1] << '.' << actual_version[2] << '.'; if (!ok) { return false; } cachedVersions.insert(engine, actual_version); } else { actual_version = cachedVersions.value(engine); } // return ! ( actual_version < required_version ) return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), std::begin(required_version), std::end(required_version)); } const QString &Kleo::paperKeyInstallPath() { static const QString pkPath = (QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty() ? QStandardPaths::findExecutable(QStringLiteral("paperkey")) : QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath())); return pkPath; } bool Kleo::haveKeyserverConfigured() { if (engineIsVersion(2, 4, 4) // || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) { - return Kleo::keyserver() != QLatin1String{"none"}; + return Kleo::keyserver() != QLatin1StringView{"none"}; } if (engineIsVersion(2, 1, 19)) { // since 2.1.19 there is a builtin keyserver return true; } return !Kleo::keyserver().isEmpty(); } QString Kleo::keyserver() { QString result = getCryptoConfigStringValue("gpg", "keyserver"); if (result.isEmpty()) { result = getCryptoConfigStringValue("dirmngr", "keyserver"); } - if (result.endsWith(QLatin1String{"://none"})) { + if (result.endsWith(QLatin1StringView{"://none"})) { // map hkps://none, etc., to "none"; see https://dev.gnupg.org/T6708 result = QStringLiteral("none"); } return result; } bool Kleo::haveX509DirectoryServerConfigured() { return !getCryptoConfigUrlList("dirmngr", "ldapserver").empty() // || !getCryptoConfigUrlList("dirmngr", "LDAP Server").empty() // || !getCryptoConfigUrlList("gpgsm", "keyserver").empty(); } bool Kleo::gpgComplianceP(const char *mode) { const auto conf = QGpgME::cryptoConfig(); const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance"); return entry && entry->stringValue() == QString::fromLatin1(mode); } bool Kleo::gnupgUsesDeVsCompliance() { return DeVSCompliance::isActive(); } bool Kleo::gnupgIsDeVsCompliant() { return DeVSCompliance::isCompliant(); } #ifdef Q_OS_WIN static unsigned int guessConsoleOutputCodePage() { /* Qt on Windows uses GetACP while GnuPG prefers * GetConsoleOutputCP. * * As we are not a console application GetConsoleOutputCP * usually returns 0. * From experience the closest thing that let's us guess * what GetConsoleOutputCP returns for a console application * it appears to be the OEMCP. */ unsigned int cpno = GetConsoleOutputCP(); if (!cpno) { cpno = GetOEMCP(); } if (!cpno) { cpno = GetACP(); } if (!cpno) { qCDebug(LIBKLEO_LOG) << __func__ << "Failed to find native codepage"; } qCDebug(LIBKLEO_LOG) << __func__ << "returns" << cpno; return cpno; } static QString fromEncoding(unsigned int src_encoding, const char *data) { if (!data || !*data) { return {}; } // returns necessary buffer size including the terminating null character int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0); if (n <= 0) { qCDebug(LIBKLEO_LOG) << __func__ << "determining necessary buffer size failed with error code" << GetLastError(); return QString(); } wchar_t *result = (wchar_t *)malloc((n + 1) * sizeof *result); n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n); if (n <= 0) { free(result); qCDebug(LIBKLEO_LOG) << __func__ << "conversion failed with error code" << GetLastError(); return QString(); } const auto ret = QString::fromWCharArray(result, n - 1); free(result); return ret; } static QString stringFromGpgOutput_legacy(const QByteArray &ba) { static const unsigned int cpno = guessConsoleOutputCodePage(); if (cpno) { qCDebug(LIBKLEO_LOG) << __func__ << "trying to decode" << ba << "using codepage" << cpno; const auto rawData = QByteArray{ba}.replace("\r\n", "\n"); const auto s = fromEncoding(cpno, rawData.constData()); if (!s.isEmpty() || ba.isEmpty()) { return s; } qCDebug(LIBKLEO_LOG) << __func__ << "decoding output failed; falling back to QString::fromLocal8Bit()"; } qCDebug(LIBKLEO_LOG) << __func__ << "decoding from local encoding:" << ba; return QString::fromLocal8Bit(ba); } #endif QString Kleo::stringFromGpgOutput(const QByteArray &ba) { #ifdef Q_OS_WIN // since 2.2.28, GnuPG always uses UTF-8 for console output (and input) if (Kleo::engineIsVersion(2, 2, 28, GpgME::GpgEngine)) { return QString::fromUtf8(ba); } else { return stringFromGpgOutput_legacy(ba); } #else return QString::fromLocal8Bit(ba); #endif } QStringList Kleo::backendVersionInfo() { QStringList versions; if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) { QProcess p; qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ..."; p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")}); // wait at most 1 second if (!p.waitForFinished(1000)) { qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second."; } else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString(); qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError(); qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput(); } else { const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n"); qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << output; const auto lines = output.split('\n'); for (const auto &line : lines) { if (line.startsWith("* GnuPG") || line.startsWith("* Libgcrypt")) { const auto components = line.split(' '); versions.push_back(QString::fromLatin1(components.at(1) + ' ' + components.value(2))); } } } } return versions; } namespace { template auto startGpgConf(const QStringList &arguments, Function1 onSuccess, Function2 onFailure) { auto process = new QProcess; process->setProgram(Kleo::gpgConfPath()); process->setArguments(arguments); QObject::connect(process, &QProcess::started, [process]() { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") was started successfully"; }); QObject::connect(process, &QProcess::errorOccurred, [process, onFailure](auto error) { qCDebug(LIBKLEO_LOG).nospace() << "Error while running gpgconf (" << process << "): " << error; process->deleteLater(); onFailure(); }); QObject::connect(process, &QProcess::readyReadStandardError, [process]() { for (const auto &line : process->readAllStandardError().trimmed().split('\n')) { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") stderr: " << line; } }); QObject::connect(process, &QProcess::readyReadStandardOutput, [process]() { (void)process->readAllStandardOutput(); /* ignore stdout */ }); QObject::connect(process, qOverload(&QProcess::finished), [process, onSuccess, onFailure](int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::NormalExit) { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") exited (exit code: " << exitCode << ")"; if (exitCode == 0) { onSuccess(); } else { onFailure(); } } else { qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") crashed (exit code: " << exitCode << ")"; onFailure(); } process->deleteLater(); }); qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgconf (" << process << ") with arguments " << process->arguments().join(QLatin1Char(' ')) << " ..."; process->start(); return process; } static auto startGpgConf(const QStringList &arguments) { return startGpgConf( arguments, []() {}, []() {}); } } void Kleo::launchGpgAgent() { static QPointer process; static qint64 mSecsSinceEpochOfLastLaunch = 0; static int numberOfFailedLaunches = 0; if (Kleo::Assuan::agentIsRunning()) { qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already running"; return; } if (process) { qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being launched"; return; } const auto now = QDateTime::currentMSecsSinceEpoch(); if (now - mSecsSinceEpochOfLastLaunch < 1000) { // reduce attempts to launch the agent to 1 attempt per second return; } mSecsSinceEpochOfLastLaunch = now; if (numberOfFailedLaunches > 5) { qCWarning(LIBKLEO_LOG) << __func__ << ": Launching gpg-agent failed" << numberOfFailedLaunches << "times in a row. Giving up."; return; } process = startGpgConf( {QStringLiteral("--launch"), QStringLiteral("gpg-agent")}, []() { numberOfFailedLaunches = 0; }, []() { numberOfFailedLaunches++; }); } void Kleo::killDaemons() { static QPointer process; if (process) { qCDebug(LIBKLEO_LOG) << __func__ << ": The daemons are already being shut down"; return; } process = startGpgConf({QStringLiteral("--kill"), QStringLiteral("all")}); } const std::vector &Kleo::availableAlgorithms() { static const std::vector algos = { "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "curve25519", "curve448", "nistp256", "nistp384", "nistp521", "rsa2048", "rsa3072", "rsa4096", // "secp256k1", // Curve secp256k1 is explicitly ignored }; return algos; } const std::vector &Kleo::preferredAlgorithms() { static const std::vector algos = { "curve25519", "brainpoolP256r1", "rsa3072", "rsa2048", }; return algos; } const std::vector &Kleo::ignoredAlgorithms() { static const std::vector algos = { "secp256k1", // Curve secp256k1 is not useful }; return algos; } bool Kleo::gpgvVerify(const QString &filePath, const QString &sigPath, const QString &keyring, const QStringList &additionalSearchPaths) { const QFileInfo verifyFi(filePath); if (!verifyFi.isReadable()) { return false; } else { qCDebug(LIBKLEO_LOG) << "Verifying" << filePath; } const auto gpgvPath = QStandardPaths::findExecutable(QStringLiteral("gpgv"), additionalSearchPaths); if (gpgvPath.isEmpty()) { qCDebug(LIBKLEO_LOG) << "Could not find gpgv"; return false; } QFileInfo sigFi; if (!sigPath.isEmpty()) { sigFi.setFile(sigPath); } else { sigFi.setFile(filePath + QStringLiteral(".sig")); } if (!sigFi.isReadable()) { qCDebug(LIBKLEO_LOG) << "No signature found at" << sigFi.absoluteFilePath(); return false; } auto process = QProcess(); process.setProgram(gpgvPath); QStringList args; if (!keyring.isEmpty()) { args << QStringLiteral("--keyring") << keyring; } args << QStringLiteral("--") << sigFi.absoluteFilePath() << verifyFi.absoluteFilePath(); process.setArguments(args); qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgv (" << gpgvPath << ") with arguments " << args.join(QLatin1Char(' ')) << " ..."; process.start(); if (!process.waitForFinished(-1)) { qCDebug(LIBKLEO_LOG) << "Failed to execute gpgv" << process.errorString(); } bool ret = (process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0); if (!ret) { qCDebug(LIBKLEO_LOG) << "Failed to verify file"; qCDebug(LIBKLEO_LOG) << "gpgv stdout:" << QString::fromUtf8(process.readAllStandardOutput()); qCDebug(LIBKLEO_LOG) << "gpgv stderr:" << QString::fromUtf8(process.readAllStandardError()); } return ret; } diff --git a/src/utils/keyhelpers.cpp b/src/utils/keyhelpers.cpp index f1bcbe2fe..423d28d07 100644 --- a/src/utils/keyhelpers.cpp +++ b/src/utils/keyhelpers.cpp @@ -1,308 +1,308 @@ /* utils/keyhelpers.cpp This file is part of libkleopatra, the KDE keymanagement library SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keyhelpers.h" #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #include #include using namespace Kleo; using namespace GpgME; namespace { bool havePublicKeyForSignature(const GpgME::UserID::Signature &signature) { // GnuPG returns status "NoPublicKey" for missing signing keys, but also // for expired or revoked signing keys. return (signature.status() != GpgME::UserID::Signature::NoPublicKey) // || !KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()).isNull(); } auto _getMissingSignerKeyIds(const std::vector &signatures) { return std::accumulate(std::begin(signatures), std::end(signatures), std::set{}, [](auto &keyIds, const auto &signature) { if (!havePublicKeyForSignature(signature)) { - keyIds.insert(QLatin1String{signature.signerKeyID()}); + keyIds.insert(QLatin1StringView{signature.signerKeyID()}); } return keyIds; }); } } std::set Kleo::getMissingSignerKeyIds(const std::vector &userIds) { return std::accumulate(std::begin(userIds), std::end(userIds), std::set{}, [](auto &keyIds, const auto &userID) { if (!userID.isBad()) { const auto newKeyIds = _getMissingSignerKeyIds(userID.signatures()); std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds))); } return keyIds; }); } std::set Kleo::getMissingSignerKeyIds(const std::vector &keys) { return std::accumulate(std::begin(keys), std::end(keys), std::set{}, [](auto &keyIds, const auto &key) { if (!key.isBad()) { const auto newKeyIds = getMissingSignerKeyIds(key.userIDs()); std::copy(std::begin(newKeyIds), std::end(newKeyIds), std::inserter(keyIds, std::end(keyIds))); } return keyIds; }); } bool Kleo::isRemoteKey(const GpgME::Key &key) { // a remote key looked up via WKD has key list mode Local; therefore we also look for the key in the local key ring return (key.keyListMode() == GpgME::Extern) || KeyCache::instance()->findByFingerprint(key.primaryFingerprint()).isNull(); } GpgME::UserID::Validity Kleo::minimalValidityOfNotRevokedUserIDs(const Key &key) { const std::vector userIDs = key.userIDs(); const int minValidity = std::accumulate(userIDs.begin(), userIDs.end(), UserID::Ultimate + 1, [](int validity, const UserID &userID) { return userID.isRevoked() ? validity : std::min(validity, static_cast(userID.validity())); }); return minValidity <= UserID::Ultimate ? static_cast(minValidity) : UserID::Unknown; } GpgME::UserID::Validity Kleo::maximalValidityOfUserIDs(const Key &key) { const auto userIDs = key.userIDs(); const int maxValidity = std::accumulate(userIDs.begin(), userIDs.end(), 0, [](int validity, const UserID &userID) { return std::max(validity, static_cast(userID.validity())); }); return static_cast(maxValidity); } bool Kleo::allUserIDsHaveFullValidity(const GpgME::Key &key) { return minimalValidityOfNotRevokedUserIDs(key) >= UserID::Full; } namespace { bool isLastValidUserID(const GpgME::UserID &userId) { if (Kleo::isRevokedOrExpired(userId)) { return false; } const auto userIds = userId.parent().userIDs(); const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds), [](const auto &u) { return !Kleo::isRevokedOrExpired(u); }); return numberOfValidUserIds == 1; } bool hasValidUserID(const GpgME::Key &key) { return Kleo::any_of(key.userIDs(), [](const auto &u) { return !Kleo::isRevokedOrExpired(u); }); } } bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature) { return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID()); } bool Kleo::isRevokedOrExpired(const GpgME::UserID &userId) { if (userId.isRevoked() || userId.parent().isExpired()) { return true; } const auto sigs = userId.signatures(); std::vector selfSigs; std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature); std::sort(std::begin(selfSigs), std::end(selfSigs)); // check the most recent signature const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{}; return !sig.isNull() && (sig.isRevokation() || sig.isExpired()); } bool Kleo::isExpired(const UserID &userID) { if (userID.parent().isExpired()) { return true; } const auto sigs = userID.signatures(); std::vector selfSigs; std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature); std::sort(std::begin(selfSigs), std::end(selfSigs)); // check the most recent signature const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{}; return !sig.isNull() && sig.isExpired(); } bool Kleo::canCreateCertifications(const GpgME::Key &key) { return Kleo::keyHasCertify(key) && canBeUsedForSecretKeyOperations(key); } bool Kleo::canBeCertified(const GpgME::Key &key) { return key.protocol() == GpgME::OpenPGP // && !key.isBad() // && hasValidUserID(key); } namespace { static inline bool subkeyHasSecret(const GpgME::Subkey &subkey) { #if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2 // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available return subkey.isSecret(); #else // older versions of GpgME did not always set the secret flag for card keys return subkey.isSecret() || subkey.isCardKey(); #endif } } bool Kleo::canBeUsedForEncryption(const GpgME::Key &key) { return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) { return subkey.canEncrypt() && !subkey.isBad(); }); } bool Kleo::canBeUsedForSigning(const GpgME::Key &key) { return !key.isBad() && Kleo::any_of(key.subkeys(), [](const auto &subkey) { return subkey.canSign() && !subkey.isBad() && subkeyHasSecret(subkey); }); } bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key) { return subkeyHasSecret(key.subkey(0)); } bool Kleo::canRevokeUserID(const GpgME::UserID &userId) { return (!userId.isNull() // && userId.parent().protocol() == GpgME::OpenPGP // && !isLastValidUserID(userId)); } bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key) { return key.subkey(0).isSecret() && !key.subkey(0).isCardKey(); } bool Kleo::userHasCertificationKey() { const auto secretKeys = KeyCache::instance()->secretKeys(); return Kleo::any_of(secretKeys, [](const auto &k) { return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k); }); } Kleo::CertificationRevocationFeasibility Kleo::userCanRevokeCertification(const GpgME::UserID::Signature &certification) { const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID()); const bool isSelfSignature = qstrcmp(certification.parent().parent().keyID(), certification.signerKeyID()) == 0; if (!certificationKey.hasSecret()) { return CertificationNotMadeWithOwnKey; } else if (isSelfSignature) { return CertificationIsSelfSignature; } else if (certification.isRevokation()) { return CertificationIsRevocation; } else if (certification.isExpired()) { return CertificationIsExpired; } else if (certification.isInvalid()) { return CertificationIsInvalid; } else if (!canCreateCertifications(certificationKey)) { return CertificationKeyNotAvailable; } return CertificationCanBeRevoked; } bool Kleo::userCanRevokeCertifications(const GpgME::UserID &userId) { if (userId.numSignatures() == 0) { qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available"; } return Kleo::any_of(userId.signatures(), [](const auto &certification) { return userCanRevokeCertification(certification) == CertificationCanBeRevoked; }); } bool Kleo::userIDBelongsToKey(const GpgME::UserID &userID, const GpgME::Key &key) { return !qstricmp(userID.parent().primaryFingerprint(), key.primaryFingerprint()); } static time_t creationDate(const GpgME::UserID &uid) { // returns the date of the first self-signature for (unsigned int i = 0, numSignatures = uid.numSignatures(); i < numSignatures; ++i) { const auto sig = uid.signature(i); if (Kleo::isSelfSignature(sig)) { return sig.creationTime(); } } return 0; } bool Kleo::userIDsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs) { return (qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0 // && qstrcmp(lhs.id(), rhs.id()) == 0 // && creationDate(lhs) == creationDate(rhs)); } static inline bool isOpenPGPCertification(const GpgME::UserID::Signature &sig) { // certification class is 0x10, ..., 0x13 return (sig.certClass() & ~0x03) == 0x10; } static bool isOpenPGPCertificationByUser(const GpgME::UserID::Signature &sig) { if (!isOpenPGPCertification(sig)) { return false; } const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID()); return certificationKey.ownerTrust() == Key::Ultimate; } bool Kleo::userIDIsCertifiedByUser(const GpgME::UserID &userId) { if (userId.parent().protocol() != GpgME::OpenPGP) { qCWarning(LIBKLEO_LOG) << __func__ << "not called with OpenPGP key"; return false; } if (userId.numSignatures() == 0) { qCWarning(LIBKLEO_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available"; } for (unsigned int i = 0, numSignatures = userId.numSignatures(); i < numSignatures; ++i) { const auto sig = userId.signature(i); if ((sig.status() == UserID::Signature::NoError) && !sig.isBad() && sig.isExportable() && isOpenPGPCertificationByUser(sig)) { return true; } } return false; } diff --git a/tests/test_keyresolver.cpp b/tests/test_keyresolver.cpp index 040bbbdf6..15d85a9f1 100644 --- a/tests/test_keyresolver.cpp +++ b/tests/test_keyresolver.cpp @@ -1,148 +1,148 @@ /* test_keyresolver.cpp This file is part of libkleopatra's test suite. SPDX-FileCopyrightText: 2018 Intevation GmbH SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; using namespace Kleo; using namespace GpgME; void dumpKeys(const QMap> &keysMap) { for (auto it = std::begin(keysMap); it != std::end(keysMap); ++it) { const auto &address = it.key(); const auto &keys = it.value(); qDebug() << "Address:" << address; qDebug() << "Keys:"; for (const auto &key : keys) { qDebug() << key.primaryFingerprint(); } } } void dumpSigKeys(const std::vector &keys) { for (const auto &key : keys) { qDebug() << key.primaryFingerprint(); } } class SignalRecipient : public QObject { Q_OBJECT public: SignalRecipient(KeyResolver *res) : resolver(res) { } void keysResolved(bool success, bool sendUnencrypted) { if (!success) { qDebug() << "Canceled"; exit(1); } const auto result = resolver->result(); qDebug() << "Resolved Signing keys:"; dumpSigKeys(result.signingKeys); qDebug() << "Resolved Encryption keys:"; dumpKeys(result.encryptionKeys); qDebug() << "Send Unencrypted:" << sendUnencrypted; exit(0); } private: KeyResolver *resolver; }; int main(int argc, char **argv) { QApplication app(argc, argv); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Test KeyResolver class")); parser.addHelpOption(); parser.addPositionalArgument(QStringLiteral("recipients"), QStringLiteral("Recipients to resolve"), QStringLiteral("[mailboxes]")); parser.addOption(QCommandLineOption({QStringLiteral("overrides"), QStringLiteral("o")}, QStringLiteral("Override where format can be:\n" "OpenPGP\n" "SMIME\n" "Auto"), QStringLiteral("mailbox:fpr,fpr,...[:format]"))); parser.addOption(QCommandLineOption({QStringLiteral("sender"), QStringLiteral("s")}, QStringLiteral("Mailbox of the sender"), QStringLiteral("mailbox"))); parser.addOption( QCommandLineOption({QStringLiteral("sigkeys"), QStringLiteral("k")}, QStringLiteral("Explicit signing keys"), QStringLiteral("signing key"))); parser.addOption(QCommandLineOption({QStringLiteral("encrypt"), QStringLiteral("e")}, QStringLiteral("Only select encryption keys"))); parser.addOption(QCommandLineOption({QStringLiteral("approval"), QStringLiteral("a")}, QStringLiteral("Always show approval dlg"))); parser.addOption(QCommandLineOption({QStringLiteral("group-config")}, QStringLiteral("Path of group config"), QStringLiteral("groupsrc"))); parser.process(app); const QStringList recps = parser.positionalArguments(); auto cache = Kleo::KeyCache::mutableInstance(); if (parser.isSet(QStringLiteral("group-config"))) { cache->setGroupConfig(std::make_shared(parser.value(QStringLiteral("group-config")))); cache->setGroupsEnabled(true); } KeyResolver resolver(!recps.empty(), !parser.isSet(QStringLiteral("encrypt"))); resolver.setRecipients(recps); resolver.setSender(parser.value(QStringLiteral("sender"))); QMap> overrides; for (const QString &oride : parser.values(QStringLiteral("overrides"))) { const QStringList split = oride.split(QLatin1Char(':')); Protocol fmt = UnknownProtocol; if (split.size() < 2 || split.size() > 3) { parser.showHelp(1); } if (split.size() == 3) { const QString fmtStr = split[2].toLower(); - if (fmtStr == QLatin1String("openpgp")) { + if (fmtStr == QLatin1StringView("openpgp")) { fmt = OpenPGP; - } else if (fmtStr == QLatin1String("smime")) { + } else if (fmtStr == QLatin1StringView("smime")) { fmt = CMS; - } else if (fmtStr == QLatin1String("auto")) { + } else if (fmtStr == QLatin1StringView("auto")) { fmt = UnknownProtocol; } else { parser.showHelp(1); } } const QStringList fingerprints = split[1].split(QLatin1Char(',')); auto map = overrides.value(fmt); map.insert(split[0], fingerprints); overrides.insert(fmt, map); } resolver.setOverrideKeys(overrides); auto recp = new SignalRecipient(&resolver); QObject::connect(&resolver, &KeyResolver::keysResolved, recp, &SignalRecipient::keysResolved); QTimer::singleShot(1s, [&parser, &resolver]() { resolver.start(parser.isSet(QStringLiteral("approval"))); }); app.exec(); return 0; } #include "test_keyresolver.moc"