Libkleo

formatting.cpp
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2 utils/formatting.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
6 SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11
12#include <config-libkleo.h>
13
14#include "formatting.h"
15
16#include "algorithm.h"
17#include "compat.h"
18#include "compliance.h"
19#include "cryptoconfig.h"
20#include "gnupg.h"
21#include "keyhelpers.h"
22
23#include <libkleo/dn.h>
24#include <libkleo/keycache.h>
25#include <libkleo/keygroup.h>
26
27#include <libkleo_debug.h>
28
29#include <KEmailAddress>
30#include <KLocalizedString>
31
32#include <QGpgME/CryptoConfig>
33#include <QGpgME/Protocol>
34
35#include <QDateTime>
36#include <QIcon>
37#include <QLocale>
38#include <QRegularExpression>
39#include <QString>
40
41#include <gpgme++/importresult.h>
42#include <gpgme++/key.h>
43
44#include <gpg-error.h>
45
46using namespace GpgME;
47using namespace Kleo;
48
49namespace
50{
51QIcon iconForValidityAndCompliance(UserID::Validity validity, bool isCompliant)
52{
53 switch (validity) {
54 case UserID::Ultimate:
55 case UserID::Full:
56 case UserID::Marginal:
57 return isCompliant ? Formatting::successIcon() : Formatting::infoIcon();
58 case UserID::Never:
59 return Formatting::errorIcon();
60 case UserID::Undefined:
61 case UserID::Unknown:
62 default:
63 return Formatting::infoIcon();
64 }
65}
66QIcon iconForValidity(const UserID &userId)
67{
68 const bool keyIsCompliant = !DeVSCompliance::isActive() || //
69 (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent()));
70 return iconForValidityAndCompliance(userId.validity(), keyIsCompliant);
71}
72}
73
74QIcon Formatting::IconProvider::icon(const GpgME::Key &key) const
75{
76 return icon(key.userID(0));
77}
78
79QIcon Formatting::IconProvider::icon(const GpgME::UserID &userID) const
80{
81 if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) {
82 return Formatting::errorIcon();
83 }
84 if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) {
85 return Formatting::errorIcon();
86 }
87 if (userID.parent().isBad() || userID.isBad()) {
88 return Formatting::errorIcon();
89 }
90 if (Kleo::isRevokedOrExpired(userID)) {
91 return Formatting::errorIcon();
92 }
93 return iconForValidity(userID);
94}
95
96QIcon Formatting::IconProvider::icon(const KeyGroup &group) const
97{
98 if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) {
99 return Formatting::errorIcon();
100 }
101 if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) {
102 return Formatting::errorIcon();
103 }
104 return validityIcon(group);
105}
106
107QIcon Formatting::successIcon()
108{
109 return QIcon::fromTheme(QStringLiteral("emblem-success"));
110}
111
112QIcon Formatting::infoIcon()
113{
114 return QIcon::fromTheme(QStringLiteral("emblem-information"));
115}
116
117QIcon Formatting::questionIcon()
118{
119 return QIcon::fromTheme(QStringLiteral("emblem-question"));
120}
121
122QIcon Formatting::unavailableIcon()
123{
124 return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
125}
126
127QIcon Formatting::warningIcon()
128{
129 return QIcon::fromTheme(QStringLiteral("emblem-warning"));
130}
131
132QIcon Formatting::errorIcon()
133{
134 return QIcon::fromTheme(QStringLiteral("emblem-error"));
135}
136
137//
138// Name
139//
140
141QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
142{
143 if (proto == GpgME::OpenPGP) {
144 const QString name = QString::fromUtf8(name_);
145 if (name.isEmpty()) {
146 return QString();
147 }
148 const QString comment = QString::fromUtf8(comment_);
149 if (comment.isEmpty()) {
150 return name;
151 }
152 return QStringLiteral("%1 (%2)").arg(name, comment);
153 }
154
155 if (proto == GpgME::CMS) {
156 const DN subject(id);
157 const QString cn = subject[QStringLiteral("CN")].trimmed();
158 if (cn.isEmpty()) {
159 return subject.prettyDN();
160 }
161 return cn;
162 }
163
164 return QString();
165}
166
167QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
168{
169 return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
170}
171
172QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
173{
174 if (proto == GpgME::OpenPGP) {
175 if (name.isEmpty()) {
176 if (email.isEmpty()) {
177 return QString();
178 } else if (comment.isEmpty()) {
179 return QStringLiteral("<%1>").arg(email);
180 } else {
181 return QStringLiteral("(%2) <%1>").arg(email, comment);
182 }
183 }
184 if (email.isEmpty()) {
185 if (comment.isEmpty()) {
186 return name;
187 } else {
188 return QStringLiteral("%1 (%2)").arg(name, comment);
189 }
190 }
191 if (comment.isEmpty()) {
192 return QStringLiteral("%1 <%2>").arg(name, email);
193 } else {
194 return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
195 }
196 }
197
198 if (proto == GpgME::CMS) {
199 const DN subject(id);
200 const QString cn = subject[QStringLiteral("CN")].trimmed();
201 if (cn.isEmpty()) {
202 return subject.prettyDN();
203 }
204 return cn;
205 }
206 return QString();
207}
208
209QString Formatting::prettyUserID(const UserID &uid)
210{
211 if (uid.parent().protocol() == GpgME::OpenPGP) {
212 return prettyNameAndEMail(uid);
213 }
214 const QByteArray id = QByteArray(uid.id()).trimmed();
215 if (id.startsWith('<')) {
216 return prettyEMail(uid.email(), uid.id());
217 }
218 if (id.startsWith('(')) {
219 // ### parse uri/dns:
220 return QString::fromUtf8(uid.id());
221 } else {
222 return DN(uid.id()).prettyDN();
223 }
224}
225
226QString Formatting::prettyKeyID(const char *id)
227{
228 if (!id) {
229 return QString();
230 }
231 return QLatin1StringView("0x") + QString::fromLatin1(id).toUpper();
232}
233
234QString Formatting::prettyNameAndEMail(const UserID &uid)
235{
236 return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
237}
238
239QString Formatting::prettyNameAndEMail(const Key &key)
240{
241 return prettyNameAndEMail(key.userID(0));
242}
243
244QString Formatting::prettyName(const Key &key)
245{
246 return prettyName(key.userID(0));
247}
248
249QString Formatting::prettyName(const UserID &uid)
250{
251 return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
252}
253
254QString Formatting::prettyName(const UserID::Signature &sig)
255{
256 return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
257}
258
259//
260// EMail
261//
262
263QString Formatting::prettyEMail(const Key &key)
264{
265 for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
266 const QString email = prettyEMail(key.userID(i));
267 if (!email.isEmpty()) {
268 return email;
269 }
270 }
271 return QString();
272}
273
274QString Formatting::prettyEMail(const UserID &uid)
275{
276 return prettyEMail(uid.email(), uid.id());
277}
278
279QString Formatting::prettyEMail(const UserID::Signature &sig)
280{
281 return prettyEMail(sig.signerEmail(), sig.signerUserID());
282}
283
284QString Formatting::prettyEMail(const char *email_, const char *id)
285{
286 QString email;
287 QString name;
288 QString comment;
289 if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) {
290 return email;
291 } else {
292 return DN(id)[QStringLiteral("EMAIL")].trimmed();
293 }
294}
295
296//
297// Tooltip
298//
299
300namespace
301{
302
303static QString protect_whitespace(QString s)
304{
305 static const QLatin1Char SP(' ');
306 static const QLatin1Char NBSP('\xA0');
307 return s.replace(SP, NBSP);
308}
309
310template<typename T_arg>
311QString format_row(const QString &field, const T_arg &arg)
312{
313 return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
314}
315QString format_row(const QString &field, const QString &arg)
316{
317 return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped());
318}
319QString format_row(const QString &field, const char *arg)
320{
321 return format_row(field, QString::fromUtf8(arg));
322}
323
324QString format_keytype(const Key &key)
325{
326 const Subkey subkey = key.subkey(0);
327 if (key.hasSecret()) {
328 return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
329 } else {
330 return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
331 }
332}
333
334QString format_subkeytype(const Subkey &subkey)
335{
336 const auto algo = subkey.publicKeyAlgorithm();
337
338 if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) {
339 return QString::fromStdString(subkey.algoName());
340 }
341 return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
342}
343
344QString format_keyusage(const Key &key)
345{
347 if (Kleo::keyHasSign(key)) {
348 if (key.isQualified()) {
349 capabilities.push_back(i18n("Signing (Qualified)"));
350 } else {
351 capabilities.push_back(i18n("Signing"));
352 }
353 }
354 if (Kleo::keyHasEncrypt(key)) {
355 capabilities.push_back(i18n("Encryption"));
356 }
357 if (Kleo::keyHasCertify(key)) {
358 capabilities.push_back(i18n("Certifying User IDs"));
359 }
360 if (Kleo::keyHasAuthenticate(key)) {
361 capabilities.push_back(i18n("SSH Authentication"));
362 }
363 return capabilities.join(QLatin1StringView(", "));
364}
365
366QString format_subkeyusage(const Subkey &subkey)
367{
369 if (subkey.canSign()) {
370 if (subkey.isQualified()) {
371 capabilities.push_back(i18n("Signing (Qualified)"));
372 } else {
373 capabilities.push_back(i18n("Signing"));
374 }
375 }
376 if (subkey.canEncrypt()) {
377 capabilities.push_back(i18n("Encryption"));
378 }
379 if (subkey.canCertify()) {
380 capabilities.push_back(i18n("Certifying User IDs"));
381 }
382 if (subkey.canAuthenticate()) {
383 capabilities.push_back(i18n("SSH Authentication"));
384 }
385 return capabilities.join(QLatin1StringView(", "));
386}
387
388static QString time_t2string(time_t t)
389{
390 const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
392}
393
394static QString make_red(const QString &txt)
395{
396 return QLatin1StringView("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
397}
398
399}
400
401static QString toolTipInternal(const GpgME::Key &key, const GpgME::UserID &userID, int flags)
402{
403 if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
404 return QString();
405 }
406
407 const Subkey subkey = key.subkey(0);
408
409 QString result;
410 if (flags & Formatting::Validity) {
411 if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
412 if (userID.isRevoked() || key.isRevoked()) {
413 result = make_red(i18n("Revoked"));
414 } else if (key.isExpired()) {
415 result = make_red(i18n("Expired"));
416 } else if (key.isDisabled()) {
417 result = i18n("Disabled");
418 } else if (key.keyListMode() & GpgME::Validate) {
419 if (!userID.isNull()) {
420 if (userID.validity() >= UserID::Validity::Full) {
421 result = i18n("User ID is certified.");
422 const auto compliance = Formatting::complianceStringForUserID(userID);
423 if (!compliance.isEmpty()) {
424 result += QStringLiteral("<br>") + compliance;
425 }
426 } else {
427 result = i18n("User ID is not certified.");
428 }
429 } else {
430 unsigned int fullyTrusted = 0;
431 for (const auto &uid : key.userIDs()) {
432 if (uid.validity() >= UserID::Validity::Full) {
433 fullyTrusted++;
434 }
435 }
436 if (fullyTrusted == key.numUserIDs()) {
437 result = i18n("All User IDs are certified.");
438 const auto compliance = Formatting::complianceStringForKey(key);
439 if (!compliance.isEmpty()) {
440 result += QStringLiteral("<br>") + compliance;
441 }
442 } else {
443 result = i18np("One User ID is not certified.", "%1 User IDs are not certified.", key.numUserIDs() - fullyTrusted);
444 }
445 }
446 } else {
447 result = i18n("The validity cannot be checked at the moment.");
448 }
449 } else {
450 result = i18n("The validity cannot be checked at the moment.");
451 }
452 }
453 if (flags == Formatting::Validity) {
454 return result;
455 }
456
457 result += QLatin1StringView("<table border=\"0\">");
458 if (key.protocol() == GpgME::CMS) {
459 if (flags & Formatting::SerialNumber) {
460 result += format_row(i18n("Serial number"), key.issuerSerial());
461 }
462 if (flags & Formatting::Issuer) {
463 result += format_row(i18n("Issuer"), key.issuerName());
464 }
465 }
466 if (flags & Formatting::UserIDs) {
467 if (userID.isNull()) {
468 const std::vector<UserID> uids = key.userIDs();
469 if (!uids.empty()) {
470 result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User ID"), Formatting::prettyUserID(uids.front()));
471 }
472 if (uids.size() > 1) {
473 for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) {
474 if (!it->isRevoked() && !it->isInvalid()) {
475 result += format_row(i18n("a.k.a."), Formatting::prettyUserID(*it));
476 }
477 }
478 }
479 } else {
480 result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User ID"), Formatting::prettyUserID(userID));
481 }
482 }
483 if (flags & Formatting::ExpiryDates) {
484 result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime()));
485
486 if (!subkey.neverExpires()) {
487 result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime()));
488 }
489 }
490
491 if (flags & Formatting::CertificateType) {
492 result += format_row(i18n("Type"), format_keytype(key));
493 }
494 if (flags & Formatting::CertificateUsage) {
495 result += format_row(i18n("Usage"), format_keyusage(key));
496 }
497 if (flags & Formatting::KeyID) {
498 result += format_row(i18n("Key ID"), QString::fromLatin1(key.shortKeyID()));
499 }
500 if (flags & Formatting::Fingerprint) {
501 result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
502 }
503 if (flags & Formatting::OwnerTrust) {
504 if (key.protocol() == GpgME::OpenPGP) {
505 result += format_row(i18n("Certification trust"), Formatting::ownerTrustShort(key));
506 } else if (key.isRoot()) {
507 result += format_row(i18n("Trusted issuer?"), (userID.isNull() ? key.userID(0) : userID).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
508 }
509 }
510 if (flags & Formatting::StorageLocation) {
511 if (const char *card = subkey.cardSerialNumber()) {
512 result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
513 } else {
514 result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
515 }
516 }
517 if (flags & Formatting::Subkeys) {
518 for (const auto &sub : key.subkeys()) {
519 result += QLatin1StringView("<hr/>");
520 result += format_row(i18n("Subkey"), sub.fingerprint());
521 if (sub.isRevoked()) {
522 result += format_row(i18n("Status"), i18n("Revoked"));
523 } else if (sub.isExpired()) {
524 result += format_row(i18n("Status"), i18n("Expired"));
525 }
526 if (flags & Formatting::ExpiryDates) {
527 result += format_row(i18n("Valid from"), time_t2string(sub.creationTime()));
528
529 if (!sub.neverExpires()) {
530 result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime()));
531 }
532 }
533
534 if (flags & Formatting::CertificateType) {
535 result += format_row(i18n("Type"), format_subkeytype(sub));
536 }
537 if (flags & Formatting::CertificateUsage) {
538 result += format_row(i18n("Usage"), format_subkeyusage(sub));
539 }
540 if (flags & Formatting::StorageLocation) {
541 if (const char *card = sub.cardSerialNumber()) {
542 result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
543 } else {
544 result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
545 }
546 }
547 }
548 }
549 result += QLatin1StringView("</table>");
550
551 return result;
552}
553
554QString Formatting::toolTip(const Key &key, int flags)
555{
556 return toolTipInternal(key, UserID(), flags);
557}
558
559namespace
560{
561template<typename Container>
562QString getValidityStatement(const Container &keys)
563{
564 const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
565 return key.protocol() == GpgME::OpenPGP;
566 });
567 const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
568 return key.keyListMode() & Validate;
569 });
570 if (allKeysAreOpenPGP || allKeysAreValidated) {
571 const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
572 if (someKeysAreBad) {
573 return i18n("Some keys are revoked, expired, disabled, or invalid.");
574 } else {
575 const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
576 if (allKeysAreFullyValid) {
577 return i18n("All keys are certified.");
578 } else {
579 return i18n("Some keys are not certified.");
580 }
581 }
582 }
583 return i18n("The validity of the keys cannot be checked at the moment.");
584}
585}
586
587QString Formatting::toolTip(const KeyGroup &group, int flags)
588{
589 static const unsigned int maxNumKeysForTooltip = 20;
590
591 if (group.isNull()) {
592 return QString();
593 }
594
595 const KeyGroup::Keys &keys = group.keys();
596 if (keys.size() == 0) {
597 return i18nc("@info:tooltip", "This group does not contain any keys.");
598 }
599
600 if (Kleo::any_of(keys, [](const auto &key) {
601 return !key.hasEncrypt();
602 })) {
603 return i18nc("@info:tooltip", "Some of the certificates in this group cannot be used for encryption. Using this group can lead to unexpected results.");
604 }
605
606 const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString();
607 if (flags == Validity) {
608 return validity;
609 }
610
611 // list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys"
612 const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
613
614 QStringList result;
615 result.reserve(3 + 2 + numKeysForTooltip + 2);
616 if (!validity.isEmpty()) {
617 result.push_back(QStringLiteral("<p>"));
618 result.push_back(validity.toHtmlEscaped());
619 result.push_back(QStringLiteral("</p>"));
620 }
621
622 result.push_back(QStringLiteral("<p>"));
623 result.push_back(i18n("Keys:"));
624 {
625 auto it = keys.cbegin();
626 for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
627 result.push_back(QLatin1StringView("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
628 }
629 }
630 if (keys.size() > numKeysForTooltip) {
631 result.push_back(QLatin1StringView("<br>")
632 + i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
633 }
634 result.push_back(QStringLiteral("</p>"));
635
636 return result.join(QLatin1Char('\n'));
637}
638
639QString Formatting::toolTip(const UserID &userID, int flags)
640{
641 return toolTipInternal(userID.parent(), userID, flags);
642}
643
644//
645// Creation and Expiration
646//
647
648namespace
649{
650static QDate time_t2date(time_t t)
651{
652 if (!t) {
653 return {};
654 }
655 const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
656 return dt.date();
657}
658static QString accessible_date_format()
659{
660 return i18nc(
661 "date format suitable for screen readers; "
662 "d: day as a number without a leading zero, "
663 "MMMM: localized month name, "
664 "yyyy: year as a four digit number",
665 "MMMM d, yyyy");
666}
667
668template<typename T>
669QString expiration_date_string(const T &tee, const QString &noExpiration)
670{
671 return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime()));
672}
673template<typename T>
674QDate creation_date(const T &tee)
675{
676 return time_t2date(tee.creationTime());
677}
678template<typename T>
679QDate expiration_date(const T &tee)
680{
681 return time_t2date(tee.expirationTime());
682}
683}
684
685QString Formatting::dateString(time_t t)
686{
687 return dateString(time_t2date(t));
688}
689
690QString Formatting::dateString(const QDate &date)
691{
692 return QLocale().toString(date, QLocale::ShortFormat);
693}
694
695QString Formatting::accessibleDate(time_t t)
696{
697 return accessibleDate(time_t2date(t));
698}
699
700QString Formatting::accessibleDate(const QDate &date)
701{
702 return QLocale().toString(date, accessible_date_format());
703}
704
705QString Formatting::expirationDateString(const Key &key, const QString &noExpiration)
706{
707 // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
708 // then we assume that the date is valid; if the date is zero for a remote key, then
709 // we don't know if it's unknown or unlimited
710 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
711 ? i18nc("@info the expiration date of the key is unknown", "unknown")
712 : expiration_date_string(key.subkey(0), noExpiration);
713}
714
715QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration)
716{
717 return expiration_date_string(subkey, noExpiration);
718}
719
720QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration)
721{
722 return expiration_date_string(sig, noExpiration);
723}
724
725QDate Formatting::expirationDate(const Key &key)
726{
727 return expiration_date(key.subkey(0));
728}
729
730QDate Formatting::expirationDate(const Subkey &subkey)
731{
732 return expiration_date(subkey);
733}
734
735QDate Formatting::expirationDate(const UserID::Signature &sig)
736{
737 return expiration_date(sig);
738}
739
740QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration)
741{
742 // if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
743 // then we assume that the date is valid; if the date is zero for a remote key, then
744 // we don't know if it's unknown or unlimited
745 return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
746 ? i18nc("@info the expiration date of the key is unknown", "unknown")
747 : accessibleExpirationDate(key.subkey(0), noExpiration);
748}
749
750QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration)
751{
752 if (subkey.neverExpires()) {
753 return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
754 } else {
755 return accessibleDate(expirationDate(subkey));
756 }
757}
758
759QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration)
760{
761 if (sig.neverExpires()) {
762 return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
763 } else {
764 return accessibleDate(expirationDate(sig));
765 }
766}
767
768QString Formatting::creationDateString(const Key &key)
769{
770 return dateString(creation_date(key.subkey(0)));
771}
772
773QString Formatting::creationDateString(const Subkey &subkey)
774{
775 return dateString(creation_date(subkey));
776}
777
778QString Formatting::creationDateString(const UserID::Signature &sig)
779{
780 return dateString(creation_date(sig));
781}
782
783QDate Formatting::creationDate(const Key &key)
784{
785 return creation_date(key.subkey(0));
786}
787
788QDate Formatting::creationDate(const Subkey &subkey)
789{
790 return creation_date(subkey);
791}
792
793QDate Formatting::creationDate(const UserID::Signature &sig)
794{
795 return creation_date(sig);
796}
797
798QString Formatting::accessibleCreationDate(const Key &key)
799{
800 return accessibleDate(creationDate(key));
801}
802
803QString Formatting::accessibleCreationDate(const Subkey &subkey)
804{
805 return accessibleDate(creationDate(subkey));
806}
807
808//
809// Types
810//
811
812QString Formatting::displayName(GpgME::Protocol p)
813{
814 if (p == GpgME::CMS) {
815 return i18nc("X.509/CMS encryption standard", "S/MIME");
816 }
817 if (p == GpgME::OpenPGP) {
818 return i18n("OpenPGP");
819 }
820 return i18nc("Unknown encryption protocol", "Unknown");
821}
822
823QString Formatting::type(const Key &key)
824{
825 return displayName(key.protocol());
826}
827
828QString Formatting::type(const Subkey &subkey)
829{
830 return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
831}
832
833QString Formatting::type(const KeyGroup &group)
834{
835 Q_UNUSED(group)
836 return i18nc("a group of keys/certificates", "Group");
837}
838
839//
840// Status / Validity
841//
842
843QString Formatting::ownerTrustShort(const Key &key)
844{
845 return ownerTrustShort(key.ownerTrust());
846}
847
848QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
849{
850 switch (trust) {
851 case Key::Unknown:
852 return i18nc("unknown trust level", "unknown");
853 case Key::Never:
854 return i18n("untrusted");
855 case Key::Marginal:
856 return i18nc("marginal trust", "marginal");
857 case Key::Full:
858 return i18nc("full trust", "full");
859 case Key::Ultimate:
860 return i18nc("ultimate trust", "ultimate");
861 case Key::Undefined:
862 return i18nc("undefined trust", "undefined");
863 default:
864 Q_ASSERT(!"unexpected owner trust value");
865 break;
866 }
867 return QString();
868}
869
870QString Formatting::validityShort(const Subkey &subkey)
871{
872 if (subkey.isRevoked()) {
873 return i18n("revoked");
874 }
875 if (subkey.isExpired()) {
876 return i18n("expired");
877 }
878 if (subkey.isDisabled()) {
879 return i18n("disabled");
880 }
881 if (subkey.isInvalid()) {
882 return i18n("invalid");
883 }
884 return i18nc("as in good/valid signature", "good");
885}
886
887QString Formatting::validityShort(const UserID &uid)
888{
889 if (uid.isRevoked()) {
890 return i18n("revoked");
891 }
892 if (uid.isInvalid()) {
893 return i18n("invalid");
894 }
895 switch (uid.validity()) {
896 case UserID::Unknown:
897 return i18nc("unknown trust level", "unknown");
898 case UserID::Undefined:
899 return i18nc("undefined trust", "undefined");
900 case UserID::Never:
901 return i18n("untrusted");
902 case UserID::Marginal:
903 return i18nc("marginal trust", "marginal");
904 case UserID::Full:
905 return i18nc("full trust", "full");
906 case UserID::Ultimate:
907 return i18nc("ultimate trust", "ultimate");
908 }
909 return QString();
910}
911
912QString Formatting::validityShort(const UserID::Signature &sig)
913{
914 switch (sig.status()) {
915 case UserID::Signature::NoError:
916 if (!sig.isInvalid()) {
917 /* See RFC 4880 Section 5.2.1 */
918 switch (sig.certClass()) {
919 case 0x10: /* Generic */
920 case 0x11: /* Persona */
921 case 0x12: /* Casual */
922 case 0x13: /* Positive */
923 return i18n("valid");
924 case 0x30:
925 return i18n("revoked");
926 default:
927 return i18n("class %1", sig.certClass());
928 }
929 }
930 [[fallthrough]];
931 // fall through:
932 case UserID::Signature::GeneralError:
933 return i18n("invalid");
934 case UserID::Signature::SigExpired:
935 return i18n("expired");
936 case UserID::Signature::KeyExpired:
937 return i18n("certificate expired");
938 case UserID::Signature::BadSignature:
939 return i18nc("fake/invalid signature", "bad");
940 case UserID::Signature::NoPublicKey: {
941 /* GnuPG returns the same error for no public key as for expired
942 * or revoked certificates. */
943 const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
944 if (key.isNull()) {
945 return i18n("no public key");
946 } else if (key.isExpired()) {
947 return i18n("key expired");
948 } else if (key.isRevoked()) {
949 return i18n("key revoked");
950 } else if (key.isDisabled()) {
951 return i18n("key disabled");
952 }
953 /* can't happen */
954 return QStringLiteral("unknown");
955 }
956 }
957 return QString();
958}
959
960QIcon Formatting::validityIcon(const UserID::Signature &sig)
961{
962 switch (sig.status()) {
963 case UserID::Signature::NoError:
964 if (!sig.isInvalid()) {
965 /* See RFC 4880 Section 5.2.1 */
966 switch (sig.certClass()) {
967 case 0x10: /* Generic */
968 case 0x11: /* Persona */
969 case 0x12: /* Casual */
970 case 0x13: /* Positive */
971 return Formatting::successIcon();
972 case 0x30:
973 return Formatting::errorIcon();
974 default:
975 return QIcon();
976 }
977 }
978 [[fallthrough]];
979 // fall through:
980 case UserID::Signature::BadSignature:
981 case UserID::Signature::GeneralError:
982 return Formatting::errorIcon();
983 case UserID::Signature::SigExpired:
984 case UserID::Signature::KeyExpired:
985 return Formatting::infoIcon();
986 case UserID::Signature::NoPublicKey:
987 return Formatting::questionIcon();
988 }
989 return QIcon();
990}
991
992QString Formatting::formatKeyLink(const Key &key)
993{
994 if (key.isNull()) {
995 return QString();
996 }
997 return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key));
998}
999
1000QString Formatting::formatForComboBox(const GpgME::Key &key)
1001{
1002 const QString name = prettyName(key);
1003 QString mail = prettyEMail(key);
1004 if (!mail.isEmpty()) {
1005 mail = QLatin1Char('<') + mail + QLatin1Char('>');
1006 }
1007 return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1StringView(key.shortKeyID())).simplified();
1008}
1009
1010QString Formatting::nameAndEmailForSummaryLine(const UserID &id)
1011{
1012 Q_ASSERT(!id.isNull());
1013
1014 const QString email = Formatting::prettyEMail(id);
1015 const QString name = Formatting::prettyName(id);
1016
1017 if (name.isEmpty()) {
1018 return email;
1019 } else if (email.isEmpty()) {
1020 return name;
1021 } else {
1022 return QStringLiteral("%1 <%2>").arg(name, email);
1023 }
1024}
1025
1026QString Formatting::nameAndEmailForSummaryLine(const Key &key)
1027{
1028 Q_ASSERT(!key.isNull());
1029
1030 const QString email = Formatting::prettyEMail(key);
1031 const QString name = Formatting::prettyName(key);
1032
1033 if (name.isEmpty()) {
1034 return email;
1035 } else if (email.isEmpty()) {
1036 return name;
1037 } else {
1038 return QStringLiteral("%1 <%2>").arg(name, email);
1039 }
1040}
1041
1042const char *Formatting::summaryToString(const Signature::Summary summary)
1043{
1044 if (summary & Signature::Red) {
1045 return "RED";
1046 }
1047 if (summary & Signature::Green) {
1048 return "GREEN";
1049 }
1050 return "YELLOW";
1051}
1052
1053QString Formatting::signatureToString(const Signature &sig, const Key &key)
1054{
1055 if (sig.isNull()) {
1056 return QString();
1057 }
1058
1059 const bool red = (sig.summary() & Signature::Red);
1060 const bool valid = (sig.summary() & Signature::Valid);
1061
1062 if (red) {
1063 if (key.isNull()) {
1064 if (const char *fpr = sig.fingerprint()) {
1065 return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
1066 } else {
1067 return i18n("Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
1068 }
1069 } else {
1070 return i18n("Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
1071 }
1072
1073 } else if (valid) {
1074 if (key.isNull()) {
1075 if (const char *fpr = sig.fingerprint()) {
1076 return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
1077 } else {
1078 return i18n("Good signature by an unknown certificate.");
1079 }
1080 } else {
1081 return i18n("Good signature by %1.", nameAndEmailForSummaryLine(key));
1082 }
1083
1084 } else if (key.isNull()) {
1085 if (const char *fpr = sig.fingerprint()) {
1086 return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
1087 } else {
1088 return i18n("Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
1089 }
1090 } else {
1091 return i18n("Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
1092 }
1093}
1094
1095//
1096// ImportResult
1097//
1098
1099QString Formatting::importMetaData(const Import &import, const QStringList &ids)
1100{
1101 const QString result = importMetaData(import);
1102 if (result.isEmpty()) {
1103 return QString();
1104 } else {
1105 return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n'));
1106 }
1107}
1108
1109QString Formatting::importMetaData(const Import &import)
1110{
1111 if (import.isNull()) {
1112 return QString();
1113 }
1114
1115 if (import.error().isCanceled()) {
1116 return i18n("The import of this certificate was canceled.");
1117 }
1118 if (import.error()) {
1119 return i18n("An error occurred importing this certificate: %1", Formatting::errorAsString(import.error()));
1120 }
1121
1122 const unsigned int status = import.status();
1123 if (status & Import::NewKey) {
1124 return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.")
1125 : i18n("This certificate is new to your keystore.");
1126 }
1127
1128 QStringList results;
1129 if (status & Import::NewUserIDs) {
1130 results.push_back(i18n("New user-ids were added to this certificate by the import."));
1131 }
1132 if (status & Import::NewSignatures) {
1133 results.push_back(i18n("New signatures were added to this certificate by the import."));
1134 }
1135 if (status & Import::NewSubkeys) {
1136 results.push_back(i18n("New subkeys were added to this certificate by the import."));
1137 }
1138
1139 return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n'));
1140}
1141
1142//
1143// Overview in CertificateDetailsDialog
1144//
1145
1146QString Formatting::formatOverview(const Key &key)
1147{
1148 return toolTip(key, AllOptions);
1149}
1150
1151QString Formatting::usageString(const Subkey &sub)
1152{
1153 QStringList usageStrings;
1154 if (sub.canCertify()) {
1155 usageStrings << i18n("Certify");
1156 }
1157 if (sub.canSign()) {
1158 usageStrings << i18n("Sign");
1159 }
1160 if (sub.canEncrypt()) {
1161 usageStrings << i18n("Encrypt");
1162 }
1163 if (sub.canAuthenticate()) {
1164 usageStrings << i18n("Authenticate");
1165 }
1166 if (sub.canRenc()) {
1167 usageStrings << i18nc("Means 'Additional Decryption Subkey'; Don't try translating that, though.", "ADSK");
1168 }
1169 return usageStrings.join(QLatin1StringView(", "));
1170}
1171
1172QString Formatting::summaryLine(const UserID &id)
1173{
1174 return i18nc("name <email> (validity, protocol, creation date)",
1175 "%1 (%2, %3, created: %4)",
1176 nameAndEmailForSummaryLine(id),
1177 Formatting::complianceStringShort(id),
1178 displayName(id.parent().protocol()),
1179 Formatting::creationDateString(id.parent()));
1180}
1181
1182QString Formatting::summaryLine(const Key &key)
1183{
1184 return nameAndEmailForSummaryLine(key) + QLatin1Char(' ')
1185 + i18nc("(validity, protocol, creation date)",
1186 "(%1, %2, created: %3)",
1187 Formatting::complianceStringShort(key),
1188 displayName(key.protocol()),
1189 Formatting::creationDateString(key));
1190}
1191
1192QString Formatting::summaryLine(const KeyGroup &group)
1193{
1194 switch (group.source()) {
1195 case KeyGroup::ApplicationConfig:
1196 case KeyGroup::GnuPGConfig:
1197 return i18ncp("name of group of keys (n key(s), validity)",
1198 "%2 (1 key, %3)",
1199 "%2 (%1 keys, %3)",
1200 group.keys().size(),
1201 group.name(),
1202 Formatting::complianceStringShort(group));
1203 case KeyGroup::Tags:
1204 return i18ncp("name of group of keys (n key(s), validity, tag)",
1205 "%2 (1 key, %3, tag)",
1206 "%2 (%1 keys, %3, tag)",
1207 group.keys().size(),
1208 group.name(),
1209 Formatting::complianceStringShort(group));
1210 default:
1211 return i18ncp("name of group of keys (n key(s), validity, group ...)",
1212 "%2 (1 key, %3, unknown origin)",
1213 "%2 (%1 keys, %3, unknown origin)",
1214 group.keys().size(),
1215 group.name(),
1216 Formatting::complianceStringShort(group));
1217 }
1218}
1219
1220// Icon for certificate selection indication
1221QIcon Formatting::iconForUid(const UserID &uid)
1222{
1223 if (Kleo::isRevokedOrExpired(uid)) {
1224 return Formatting::errorIcon();
1225 }
1226 return iconForValidity(uid);
1227}
1228
1229QString Formatting::validity(const UserID &uid)
1230{
1231 switch (uid.validity()) {
1232 case UserID::Ultimate:
1233 return i18n("The certificate is marked as your own.");
1234 case UserID::Full:
1235 return i18n("The certificate belongs to this recipient.");
1236 case UserID::Marginal:
1237 return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
1238 case UserID::Never:
1239 return i18n("This certificate should not be used.");
1240 case UserID::Undefined:
1241 case UserID::Unknown:
1242 default:
1243 return i18n("There is no indication that this certificate belongs to this recipient.");
1244 }
1245}
1246
1247QString Formatting::validity(const KeyGroup &group)
1248{
1249 if (group.isNull()) {
1250 return QString();
1251 }
1252
1253 const KeyGroup::Keys &keys = group.keys();
1254 if (keys.size() == 0) {
1255 return i18n("This group does not contain any keys.");
1256 }
1257
1258 return getValidityStatement(keys);
1259}
1260
1261namespace
1262{
1263template<typename Container>
1264UserID::Validity minimalValidity(const Container &keys)
1265{
1266 const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) {
1267 return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
1268 });
1269 return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
1270}
1271
1272template<typename Container>
1273bool allKeysAreCompliant(const Container &keys)
1274{
1275 if (!DeVSCompliance::isActive()) {
1276 return true;
1277 }
1278 if (!DeVSCompliance::isCompliant()) {
1279 return false;
1280 }
1281 return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant);
1282}
1283}
1284
1285QIcon Formatting::validityIcon(const KeyGroup &group)
1286{
1287 if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) {
1288 return Formatting::errorIcon();
1289 }
1290 return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys()));
1291}
1292
1293bool Formatting::uidsHaveFullValidity(const Key &key)
1294{
1295 return allUserIDsHaveFullValidity(key);
1296}
1297
1298QString Formatting::complianceMode()
1299{
1300 const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance");
1301 return complianceValue == QLatin1StringView("gnupg") ? QString() : complianceValue;
1302}
1303
1304bool Formatting::isKeyDeVs(const GpgME::Key &key)
1305{
1306 return DeVSCompliance::allSubkeysAreCompliant(key);
1307}
1308
1309QString Formatting::complianceStringForKey(const GpgME::Key &key)
1310{
1311 // There will likely be more in the future for other institutions
1312 // for now we only have DE-VS
1313 if (DeVSCompliance::isCompliant()) {
1314 return isRemoteKey(key) //
1315 ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
1316 : DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key));
1317 }
1318 return QString();
1319}
1320
1321QString Formatting::complianceStringForUserID(const GpgME::UserID &userID)
1322{
1323 // There will likely be more in the future for other institutions
1324 // for now we only have DE-VS
1325 if (DeVSCompliance::isCompliant()) {
1326 return isRemoteKey(userID.parent()) //
1327 ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
1328 : DeVSCompliance::name(DeVSCompliance::userIDIsCompliant(userID));
1329 }
1330 return QString();
1331}
1332
1333QString Formatting::complianceStringShort(const GpgME::UserID &id)
1334{
1335 if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(id)) {
1336 return QStringLiteral("★ ") + DeVSCompliance::name(true);
1337 }
1338 const bool keyValidityChecked = (id.parent().keyListMode() & GpgME::Validate);
1339 if (keyValidityChecked && id.validity() >= UserID::Full) {
1340 return i18nc("As in 'this user ID is valid.'", "certified");
1341 }
1342 if (id.parent().isExpired() || isExpired(id)) {
1343 return i18n("expired");
1344 }
1345 if (id.parent().isRevoked() || id.isRevoked()) {
1346 return i18n("revoked");
1347 }
1348 if (id.parent().isDisabled()) {
1349 return i18n("disabled");
1350 }
1351 if (id.parent().isInvalid() || id.isInvalid()) {
1352 return i18n("invalid");
1353 }
1354 if (keyValidityChecked) {
1355 return i18nc("As in 'this user ID is not certified'", "not certified");
1356 }
1357
1358 return i18nc("The validity of this user ID has not been/could not be checked", "not checked");
1359}
1360
1361QString Formatting::complianceStringShort(const GpgME::Key &key)
1362{
1363 if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) {
1364 return QStringLiteral("★ ") + DeVSCompliance::name(true);
1365 }
1366 const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate);
1367 if (key.isExpired()) {
1368 return i18n("expired");
1369 }
1370 if (key.isRevoked()) {
1371 return i18n("revoked");
1372 }
1373 if (key.isDisabled()) {
1374 return i18n("disabled");
1375 }
1376 if (key.isInvalid()) {
1377 return i18n("invalid");
1378 }
1379 if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) {
1380 return i18nc("As in all user IDs are valid.", "certified");
1381 }
1382 if (keyValidityChecked) {
1383 return i18nc("As in not all user IDs are valid.", "not certified");
1384 }
1385
1386 return i18nc("The validity of the user IDs has not been/could not be checked", "not checked");
1387}
1388
1389QString Formatting::complianceStringShort(const KeyGroup &group)
1390{
1391 const KeyGroup::Keys &keys = group.keys();
1392
1393 const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
1394 if (allKeysFullyValid) {
1395 return i18nc("As in all keys are valid.", "all certified");
1396 }
1397
1398 return i18nc("As in not all keys are valid.", "not all certified");
1399}
1400
1401QString Formatting::prettyID(const char *id)
1402{
1403 if (!id) {
1404 return QString();
1405 }
1406 QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed();
1407 // For the standard 10 group fingerprint let us use a double space in the
1408 // middle to increase readability
1409 if (ret.size() == 49) {
1410 ret.insert(24, QLatin1Char(' '));
1411 }
1412 return ret;
1413}
1414
1415QString Formatting::accessibleHexID(const char *id)
1416{
1417 static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")};
1418
1419 QString ret;
1420 ret = QString::fromLatin1(id);
1421 if (!ret.isEmpty() && (ret.size() % 4 == 0)) {
1422 ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2);
1423 }
1424 return ret;
1425}
1426
1427QString Formatting::origin(int o)
1428{
1429 switch (o) {
1430 case Key::OriginKS:
1431 return i18n("Keyserver");
1432 case Key::OriginDane:
1433 return QStringLiteral("DANE");
1434 case Key::OriginWKD:
1435 return QStringLiteral("WKD");
1436 case Key::OriginURL:
1437 return QStringLiteral("URL");
1438 case Key::OriginFile:
1439 return i18n("File import");
1440 case Key::OriginSelf:
1441 return i18n("Generated");
1442 case Key::OriginOther:
1443 case Key::OriginUnknown:
1444 default:
1445 return i18n("Unknown");
1446 }
1447}
1448
1449QString Formatting::deVsString(bool compliant)
1450{
1451 return DeVSCompliance::name(compliant);
1452}
1453
1454namespace
1455{
1456QString formatTrustScope(const char *trustScope)
1457{
1458 static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\‍([^0-9A-Za-z]))")};
1459
1460 const auto scopeRegExp = QString::fromUtf8(trustScope);
1461 if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) {
1462 // looks like a trust scope regular expression created by gpg
1463 auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2);
1464 domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)"));
1465 return domain;
1466 }
1467 return scopeRegExp;
1468}
1469}
1470
1471QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig)
1472{
1473 return formatTrustScope(sig.trustScope());
1474}
1475
1476QString Formatting::trustSignature(const GpgME::UserID::Signature &sig)
1477{
1478 switch (sig.trustValue()) {
1479 case TrustSignatureTrust::Partial:
1480 return i18nc("Certifies this key as partially trusted introducer for 'domain name'.",
1481 "Certifies this key as partially trusted introducer for '%1'.",
1482 trustSignatureDomain(sig));
1483 case TrustSignatureTrust::Complete:
1484 return i18nc("Certifies this key as fully trusted introducer for 'domain name'.",
1485 "Certifies this key as fully trusted introducer for '%1'.",
1486 trustSignatureDomain(sig));
1487 default:
1488 return {};
1489 }
1490}
1491
1492QString Formatting::errorAsString(const GpgME::Error &error)
1493{
1494#ifdef Q_OS_WIN
1495 // On Windows, we set GpgME resp. libgpg-error to return (translated) error messages as UTF-8
1496 const char *s = error.asString();
1497 qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
1498 qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s;
1499 qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray{s}.toPercentEncoding();
1500 return QString::fromUtf8(s);
1501#else
1502 return QString::fromLocal8Bit(error.asString());
1503#endif
1504}
1505
1506QString Formatting::prettyAlgorithmName(const std::string &algorithm)
1507{
1508 static const std::map<std::string, QString> displayNames = {
1509 {"brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)")},
1510 {"brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)")},
1511 {"brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)")},
1512 {"curve25519", i18nc("@info", "ECC (Curve25519)")},
1513 {"curve448", i18nc("@info", "ECC (Curve448)")},
1514 {"ed25519", i18nc("@info", "ECC (Ed25519)")},
1515 {"ed448", i18nc("@info", "ECC (Ed448)")},
1516 {"cv25519", i18nc("@info", "ECC (Cv25519)")},
1517 {"cv448", i18nc("@info", "ECC (Cv448)")},
1518 {"nistp256", i18nc("@info", "ECC (NIST P-256)")},
1519 {"nistp384", i18nc("@info", "ECC (NIST P-384)")},
1520 {"nistp521", i18nc("@info", "ECC (NIST P-521)")},
1521 {"rsa2048", i18nc("@info", "RSA 2048")},
1522 {"rsa3072", i18nc("@info", "RSA 3072")},
1523 {"rsa4096", i18nc("@info", "RSA 4096")},
1524 {"dsa1024", i18nc("@info", "DSA 1024")},
1525 {"dsa2048", i18nc("@info", "DSA 2048")},
1526 {"elg1024", i18nc("@info", "Elgamal 1024")},
1527 {"elg2048", i18nc("@info", "Elgamal 2048")},
1528 {"elg3072", i18nc("@info", "Elgamal 3072")},
1529 {"elg4096", i18nc("@info", "Elgamal 4096")},
1530 };
1531 const auto it = displayNames.find(algorithm);
1532 return (it != displayNames.end()) ? it->second : i18nc("@info", "Unknown algorithm");
1533}
DN parser and reorderer.
Definition dn.h:27
QString prettyDN() const
Definition dn.cpp:439
Q_SCRIPTABLE CaptureState status()
KCODECS_EXPORT EmailParseResult splitAddress(const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
Capabilities capabilities()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QAction * mail(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
QByteArray toPercentEncoding(const QByteArray &exclude, const QByteArray &include, char percent) const const
QByteArray trimmed() const const
QDate date() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QIcon fromTheme(const QString &name)
bool empty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QString toString(QDate date, FormatType format) const const
QString arg(Args &&... args) const const
QString chopped(qsizetype len) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromStdString(const std::string &str)
QString fromUtf8(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
qsizetype size() const const
QString toHtmlEscaped() const const
QString toUpper() const const
QString trimmed() const const
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:51:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.