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

KDE's Doxygen guidelines are available online.