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

IJHack / QtPass / 24311948677

12 Apr 2026 05:10PM UTC coverage: 20.864%. Remained the same
24311948677

Pull #979

github

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

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

1 existing line in 1 file now uncovered.

1111 of 5325 relevant lines covered (20.86%)

7.73 hits per line

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

97.96
/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,
9✔
54
                        UserInfo &current_user) {
55
  current_user.key_id = props[GPG_FIELD_KEY_ID];
9✔
56
  current_user.name = props[GPG_FIELD_USERID];
9✔
57
  if (!props[GPG_FIELD_VALIDITY].isEmpty()) {
9✔
58
    current_user.validity = props[GPG_FIELD_VALIDITY][0].toLatin1();
8✔
59
  }
60

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

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

73
  current_user.have_secret = secret;
9✔
74
}
9✔
75

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

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

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

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

115
  UserInfo current_user;
5✔
116

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

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

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

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

153
  return users;
5✔
154
}
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