• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24101720834

07 Apr 2026 08:01PM UTC coverage: 21.029%. First build
24101720834

Pull #904

github

web-flow
Merge 0d7b76280 into 44219207b
Pull Request #904: refactor: reduce complexity in Pass::listKeys()

47 of 50 new or added lines in 2 files covered. (94.0%)

1108 of 5269 relevant lines covered (21.03%)

7.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.92
/src/gpgkeystate.cpp
1
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "gpgkeystate.h"
4

5
#include "util.h"
6

7
#include <QRegularExpression>
8

9
constexpr int GPG_MIN_FIELDS = 10;
10
constexpr int GPG_FIELD_VALIDITY = 1;
11
constexpr int GPG_FIELD_KEY_ID = 4;
12
constexpr int GPG_FIELD_CREATED = 5;
13
constexpr int GPG_FIELD_EXPIRY = 6;
14
constexpr int GPG_FIELD_USERID = 9;
15

16
/**
17
 * @brief Classify a GPG colon output record type
18
 * @param record_type The first field of a GPG colon record (e.g., "pub", "sec",
19
 * "uid")
20
 * @return The corresponding GpgRecordType enum value
21
 */
22
auto classifyRecord(const QString &record_type) -> GpgRecordType {
31✔
23
  if (record_type == "pub") {
31✔
24
    return GpgRecordType::Pub;
25
  }
26
  if (record_type == "sec") {
23✔
27
    return GpgRecordType::Sec;
28
  }
29
  if (record_type == "uid") {
21✔
30
    return GpgRecordType::Uid;
31
  }
32
  if (record_type == "fpr") {
14✔
33
    return GpgRecordType::Fpr;
34
  }
35
  if (record_type == "sub") {
6✔
36
    return GpgRecordType::Sub;
37
  }
38
  if (record_type == "ssb") {
4✔
39
    return GpgRecordType::Ssb;
40
  }
41
  if (record_type == "grp") {
2✔
42
    return GpgRecordType::Grp;
1✔
43
  }
44
  return GpgRecordType::Unknown;
45
}
46

47
/**
48
 * @brief Handle a pub or sec record in GPG colon output
49
 * @param props The colon-split fields of the record
50
 * @param secret True if this is a secret key record (sec)
51
 * @param current_user UserInfo to populate with key data
52
 */
53
void handlePubSecRecord(const QStringList &props, bool secret,
8✔
54
                        UserInfo &current_user) {
55
  current_user.key_id = props[GPG_FIELD_KEY_ID];
8✔
56
  current_user.name = props[GPG_FIELD_USERID];
8✔
57
  current_user.validity = props[GPG_FIELD_VALIDITY][0].toLatin1();
8✔
58

59
  bool okCreated = false;
8✔
60
  const qint64 createdSecs = props[GPG_FIELD_CREATED].toLongLong(&okCreated);
61
  if (okCreated) {
8✔
62
    current_user.created.setSecsSinceEpoch(createdSecs);
8✔
63
  }
64

65
  bool okExpiry = false;
8✔
66
  const qint64 expirySecs = props[GPG_FIELD_EXPIRY].toLongLong(&okExpiry);
67
  if (okExpiry) {
8✔
NEW
68
    current_user.expiry.setSecsSinceEpoch(expirySecs);
×
69
  }
70

71
  current_user.have_secret = secret;
8✔
72
}
8✔
73

74
/**
75
 * @brief Handle a uid record in GPG colon output
76
 * @param props The colon-split fields of the record
77
 * @param current_user UserInfo to populate with user name
78
 */
79
void handleUidRecord(const QStringList &props, UserInfo &current_user) {
6✔
80
  current_user.name = props[GPG_FIELD_USERID];
6✔
81
}
6✔
82

83
/**
84
 * @brief Handle an fpr (fingerprint) record in GPG colon output
85
 * @param props The colon-split fields of the record
86
 * @param current_user UserInfo to update with fingerprint if it matches key
87
 */
88
void handleFprRecord(const QStringList &props, UserInfo &current_user) {
7✔
89
  if (!current_user.key_id.isEmpty() &&
14✔
90
      props[GPG_FIELD_USERID].endsWith(current_user.key_id)) {
7✔
91
    current_user.key_id = props[GPG_FIELD_USERID];
3✔
92
  }
93
}
7✔
94

95
/**
96
 * @brief Parse GPG --with-colons output into a list of UserInfo
97
 * @param output Raw output from GPG --with-colons --with-fingerprint
98
 * @param secret True if parsing secret keys (--list-secret-keys)
99
 * @return QList of parsed UserInfo objects
100
 */
101
auto parseGpgColonOutput(const QString &output, bool secret)
5✔
102
    -> QList<UserInfo> {
103
  QList<UserInfo> users;
5✔
104

105
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
106
  const QStringList lines =
107
      output.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
5✔
108
#else
109
  const QStringList lines =
110
      output.split(Util::newLinesRegex(), QString::SkipEmptyParts);
111
#endif
112

113
  UserInfo current_user;
5✔
114

115
  for (const QString &key : lines) {
29✔
116
    QStringList props = key.split(':');
24✔
117
    if (props.size() < GPG_MIN_FIELDS) {
24✔
118
      continue;
119
    }
120

121
    const QString record_type = props[0];
122
    const GpgRecordType type = classifyRecord(record_type);
23✔
123

124
    switch (type) {
23✔
125
    case GpgRecordType::Pub:
126
    case GpgRecordType::Sec:
127
      if (!current_user.key_id.isEmpty()) {
8✔
128
        users.append(current_user);
129
      }
130
      current_user = UserInfo();
8✔
131
      handlePubSecRecord(props, secret && (type == GpgRecordType::Sec),
8✔
132
                         current_user);
133
      break;
134
    case GpgRecordType::Uid:
135
      if (current_user.name.isEmpty()) {
6✔
136
        handleUidRecord(props, current_user);
6✔
137
      }
138
      break;
139
    case GpgRecordType::Fpr:
7✔
140
      handleFprRecord(props, current_user);
7✔
141
      break;
142
    default:
143
      break;
144
    }
145
  }
146

147
  if (!current_user.key_id.isEmpty()) {
5✔
148
    users.append(current_user);
149
  }
150

151
  return users;
5✔
152
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc