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

IJHack / QtPass / 24312390262

12 Apr 2026 05:34PM UTC coverage: 20.879% (+0.02%) from 20.864%
24312390262

Pull #979

github

web-flow
Merge f8c76acfd into 8320c7499
Pull Request #979: test: add gpgkeystate coverage tests

3 of 3 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

1112 of 5326 relevant lines covered (20.88%)

7.73 hits per line

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

98.0
/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 {
34✔
23
  if (record_type == "pub") {
34✔
24
    return GpgRecordType::Pub;
25
  }
26
  if (record_type == "sec") {
26✔
27
    return GpgRecordType::Sec;
28
  }
29
  if (record_type == "uid") {
24✔
30
    return GpgRecordType::Uid;
31
  }
32
  if (record_type == "fpr") {
17✔
33
    return GpgRecordType::Fpr;
34
  }
35
  if (record_type == "sub") {
9✔
36
    return GpgRecordType::Sub;
37
  }
38
  if (record_type == "ssb") {
7✔
39
    return GpgRecordType::Ssb;
40
  }
41
  if (record_type == "grp") {
5✔
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,
10✔
54
                        UserInfo &current_user) {
55
  if (props.size() < GPG_MIN_FIELDS) {
10✔
56
    return;
1✔
57
  }
58
  current_user.key_id = props[GPG_FIELD_KEY_ID];
9✔
59
  current_user.name = props[GPG_FIELD_USERID];
9✔
60
  if (!props[GPG_FIELD_VALIDITY].isEmpty()) {
9✔
61
    current_user.validity = props[GPG_FIELD_VALIDITY][0].toLatin1();
8✔
62
  }
63

64
  bool okCreated = false;
9✔
65
  const qint64 createdSecs = props[GPG_FIELD_CREATED].toLongLong(&okCreated);
66
  if (okCreated) {
9✔
67
    current_user.created.setSecsSinceEpoch(createdSecs);
8✔
68
  }
69

70
  bool okExpiry = false;
9✔
71
  const qint64 expirySecs = props[GPG_FIELD_EXPIRY].toLongLong(&okExpiry);
72
  if (okExpiry) {
9✔
UNCOV
73
    current_user.expiry.setSecsSinceEpoch(expirySecs);
×
74
  }
75

76
  current_user.have_secret = secret;
9✔
77
}
78

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

88
/**
89
 * @brief Handle an fpr (fingerprint) record in GPG colon output
90
 * @param props The colon-split fields of the record
91
 * @param current_user UserInfo to update with fingerprint if it matches key
92
 */
93
void handleFprRecord(const QStringList &props, UserInfo &current_user) {
10✔
94
  if (props.size() < GPG_MIN_FIELDS) {
10✔
95
    return;
96
  }
97
  if (!current_user.key_id.isEmpty() &&
20✔
98
      props[GPG_FIELD_USERID].endsWith(current_user.key_id)) {
10✔
99
    current_user.key_id = props[GPG_FIELD_USERID];
4✔
100
  }
101
}
102

103
/**
104
 * @brief Parse GPG --with-colons output into a list of UserInfo
105
 * @param output Raw output from GPG --with-colons --with-fingerprint
106
 * @param secret True if parsing secret keys (--list-secret-keys)
107
 * @return QList of parsed UserInfo objects
108
 */
109
auto parseGpgColonOutput(const QString &output, bool secret)
5✔
110
    -> QList<UserInfo> {
111
  QList<UserInfo> users;
5✔
112

113
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
114
  const QStringList lines =
115
      output.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
5✔
116
#else
117
  const QStringList lines =
118
      output.split(Util::newLinesRegex(), QString::SkipEmptyParts);
119
#endif
120

121
  UserInfo current_user;
5✔
122

123
  for (const QString &key : lines) {
29✔
124
    QStringList props = key.split(':');
24✔
125
    if (props.size() < GPG_MIN_FIELDS) {
24✔
126
      continue;
127
    }
128

129
    const QString record_type = props[0];
130
    const GpgRecordType type = classifyRecord(record_type);
23✔
131

132
    switch (type) {
23✔
133
    case GpgRecordType::Pub:
134
    case GpgRecordType::Sec:
135
      if (!current_user.key_id.isEmpty()) {
8✔
136
        users.append(current_user);
137
      }
138
      current_user = UserInfo();
8✔
139
      handlePubSecRecord(props, secret && (type == GpgRecordType::Sec),
8✔
140
                         current_user);
141
      break;
142
    case GpgRecordType::Uid:
143
      if (current_user.name.isEmpty()) {
6✔
144
        handleUidRecord(props, current_user);
6✔
145
      }
146
      break;
147
    case GpgRecordType::Fpr:
7✔
148
      handleFprRecord(props, current_user);
7✔
149
      break;
150
    default:
151
      break;
152
    }
153
  }
154

155
  if (!current_user.key_id.isEmpty()) {
5✔
156
    users.append(current_user);
157
  }
158

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

© 2026 Coveralls, Inc