MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 98.2% 54 / 0 / 55
Functions: 100.0% 7 / 0 / 7
Branches: 93.8% 90 / 0 / 96

util/AddressListScan.h
Line Branch Exec Source
1 #pragma once
2 // S106-209: the (address, display name) pairs of an address header such as
3 // From/To/Cc, for the search summary's sender facets. Header-only and pure:
4 // one linear scan, no regular expression — the former regex's lazy name
5 // part re-scanned the rest of a long '@'-free header from every position.
6 // Mail headers are untrusted; fuzzed by fuzz_address_list.
7 //
8 // Accepted forms, separated by ',' or ';' outside double quotes:
9 // "Quoted, Name" <a@b> Plain Name <a@b> <a@b> a@b
10 // Addresses are lower-cased; a token is an address when it has text on both
11 // sides of an '@' and holds no whitespace, '<', '>' or '"'.
12
13 #include <QList>
14 #include <QPair>
15 #include <QString>
16 #include <QStringView>
17 #include <algorithm>
18
19 namespace AddressListScan {
20
21 // Text on both sides of the first '@' (the address rule of both forms).
22 549147 inline bool hasTextAroundAt(QStringView token) {
23 549147 const qsizetype at = token.indexOf(QLatin1Char('@'));
24
3/4
✓ Branch 5 → 6 taken 27963 times.
✓ Branch 5 → 9 taken 521184 times.
✓ Branch 7 → 8 taken 27963 times.
✗ Branch 7 → 9 not taken.
549147 return at > 0 && at < token.size() - 1;
25 }
26
27 1104625 inline bool endsBareToken(QChar ch) {
28
7/8
✓ Branch 3 → 4 taken 604622 times.
✓ Branch 3 → 16 taken 500003 times.
✓ Branch 7 → 8 taken 604621 times.
✓ Branch 7 → 16 taken 1 time.
✓ Branch 11 → 12 taken 604620 times.
✓ Branch 11 → 16 taken 1 time.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 604620 times.
1104625 return ch.isSpace() || ch == QLatin1Char('<') || ch == QLatin1Char('>') || ch == QLatin1Char('"');
29 }
30
31 // Every maximal run without whitespace, <, > or " that has text on both
32 // sides of an '@' is a bare address.
33 27961 inline void appendBare(QStringView text, QList<QPair<QString, QString>> &result) {
34 27961 qsizetype start = 0;
35
2/2
✓ Branch 29 → 3 taken 1132586 times.
✓ Branch 29 → 30 taken 27961 times.
1160547 for (qsizetype i = 0; i <= text.size(); ++i) {
36
6/6
✓ Branch 4 → 5 taken 1104625 times.
✓ Branch 4 → 9 taken 27961 times.
✓ Branch 7 → 8 taken 604620 times.
✓ Branch 7 → 9 taken 500005 times.
✓ Branch 10 → 11 taken 604620 times.
✓ Branch 10 → 12 taken 527966 times.
1132586 if (i < text.size() && !endsBareToken(text.at(i))) continue;
37 527966 const QStringView token = text.mid(start, i - start);
38
5/5
✓ Branch 14 → 15 taken 6782 times.
✓ Branch 14 → 25 taken 521184 times.
✓ Branch 15 → 16 taken 6782 times.
✓ Branch 16 → 17 taken 6782 times.
✓ Branch 19 → 20 taken 6782 times.
527966 if (hasTextAroundAt(token)) result.append({token.toString().toLower(), QString()});
39 527966 start = i + 1;
40 }
41 27961 }
42
43 // The next '<' outside double quotes at or after `from`, or -1.
44 27961 inline qsizetype nextAngle(QStringView part, qsizetype from) {
45 27961 bool quoted = false;
46
2/2
✓ Branch 21 → 3 taken 1351675 times.
✓ Branch 21 → 22 taken 6780 times.
1358455 for (qsizetype i = from; i < part.size(); ++i) {
47
2/2
✓ Branch 7 → 8 taken 12306 times.
✓ Branch 7 → 9 taken 1339369 times.
1351675 if (part.at(i) == QLatin1Char('"')) quoted = !quoted;
48
6/6
✓ Branch 9 → 10 taken 1283991 times.
✓ Branch 9 → 16 taken 55378 times.
✓ Branch 14 → 15 taken 21181 times.
✓ Branch 14 → 16 taken 1262810 times.
✓ Branch 17 → 18 taken 21181 times.
✓ Branch 17 → 19 taken 1318188 times.
1339369 else if (!quoted && part.at(i) == QLatin1Char('<')) return i;
49 }
50 6780 return -1;
51 }
52
53 // One part between separators: "Name <a@b>" (several may follow each
54 // other) and bare a@b tokens. Every step moves `position` forward.
55 27959 inline void appendPart(QStringView part, QList<QPair<QString, QString>> &result) {
56 27959 qsizetype position = 0;
57
2/2
✓ Branch 86 → 3 taken 27961 times.
✓ Branch 86 → 87 taken 21179 times.
49140 while (position < part.size()) {
58 27961 const qsizetype open = nextAngle(part, position);
59
2/2
✓ Branch 4 → 5 taken 21181 times.
✓ Branch 4 → 8 taken 6780 times.
27961 const qsizetype close = open < 0 ? -1 : part.indexOf(QLatin1Char('>'), open + 1);
60
2/2
✓ Branch 9 → 10 taken 6780 times.
✓ Branch 9 → 11 taken 21181 times.
27961 if (close < 0) break;
61 21181 const QStringView inside = part.mid(open + 1, close - open - 1).trimmed();
62 // "<@@b>" has no local part: not an address (fuzz_address_list).
63
1/2
✓ Branch 14 → 15 taken 21181 times.
✗ Branch 14 → 20 not taken.
42362 const bool valid = hasTextAroundAt(inside) &&
64
3/3
✓ Branch 17 → 18 taken 21181 times.
✓ Branch 18 → 19 taken 21180 times.
✓ Branch 18 → 20 taken 1 time.
21181 std::none_of(inside.begin(), inside.end(),
65
3/4
✓ Branch 3 → 4 taken 352335 times.
✓ Branch 3 → 8 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 352335 times.
352336 [](QChar ch) { return ch.isSpace() || ch == QLatin1Char('<'); });
66 21181 const QStringView name = part.mid(position, open - position).trimmed();
67
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 26 taken 21180 times.
21181 if (!valid) {
68
1/1
✓ Branch 25 → 84 taken 1 time.
1 appendBare(part.mid(position, close + 1 - position), result);
69
7/8
✓ Branch 27 → 28 taken 21178 times.
✓ Branch 27 → 37 taken 2 times.
✓ Branch 31 → 32 taken 6153 times.
✓ Branch 31 → 37 taken 15025 times.
✓ Branch 35 → 36 taken 6153 times.
✗ Branch 35 → 37 not taken.
✓ Branch 38 → 39 taken 6153 times.
✓ Branch 38 → 52 taken 15027 times.
21180 } else if (name.size() >= 2 && name.startsWith(QLatin1Char('"')) && name.endsWith(QLatin1Char('"'))) {
70
4/4
✓ Branch 39 → 40 taken 6153 times.
✓ Branch 40 → 41 taken 6153 times.
✓ Branch 44 → 45 taken 6153 times.
✓ Branch 46 → 47 taken 6153 times.
6153 result.append({inside.toString().toLower(), name.mid(1, name.size() - 2).trimmed().toString()});
71
5/6
✓ Branch 55 → 56 taken 15026 times.
✓ Branch 55 → 60 taken 1 time.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 15026 times.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 74 taken 15026 times.
15027 } else if (name.contains(QLatin1Char('@')) || name.contains(QLatin1Char('"'))) {
72 // An unquoted name cannot hold '@' or '"': it is an address of its own.
73
1/1
✓ Branch 63 → 64 taken 1 time.
1 appendBare(name, result);
74
3/3
✓ Branch 64 → 65 taken 1 time.
✓ Branch 65 → 66 taken 1 time.
✓ Branch 68 → 69 taken 1 time.
1 result.append({inside.toString().toLower(), QString()});
75 } else {
76
4/4
✓ Branch 74 → 75 taken 15026 times.
✓ Branch 75 → 76 taken 15026 times.
✓ Branch 76 → 77 taken 15026 times.
✓ Branch 78 → 79 taken 15026 times.
15026 result.append({inside.toString().toLower(), name.toString()});
77 }
78 21181 position = close + 1;
79 }
80 27959 appendBare(part.mid(position), result);
81 27959 }
82
83 // The caller bounds `header` (the summary reads at most 4096 characters).
84 28220 inline QList<QPair<QString, QString>> parse(QStringView header) {
85 28220 QList<QPair<QString, QString>> result;
86 28220 bool quoted = false;
87 28220 qsizetype start = 0;
88
2/2
✓ Branch 35 → 3 taken 2754568 times.
✓ Branch 35 → 36 taken 28220 times.
2782788 for (qsizetype i = 0; i <= header.size(); ++i) {
89
2/2
✓ Branch 4 → 5 taken 2726348 times.
✓ Branch 4 → 26 taken 28220 times.
2754568 if (i < header.size()) {
90 2726348 const QChar ch = header.at(i);
91
2/2
✓ Branch 9 → 10 taken 12306 times.
✓ Branch 9 → 11 taken 2714042 times.
2726348 if (ch == QLatin1Char('"')) quoted = !quoted;
92
8/8
✓ Branch 11 → 12 taken 2664817 times.
✓ Branch 11 → 20 taken 61531 times.
✓ Branch 15 → 16 taken 2664187 times.
✓ Branch 15 → 21 taken 630 times.
✓ Branch 19 → 20 taken 2664185 times.
✓ Branch 19 → 21 taken 2 times.
✓ Branch 22 → 23 taken 2725716 times.
✓ Branch 22 → 24 taken 632 times.
2726348 if (quoted || (ch != QLatin1Char(',') && ch != QLatin1Char(';'))) continue;
93 }
94 28852 const QStringView part = header.mid(start, i - start);
95
3/3
✓ Branch 30 → 31 taken 27959 times.
✓ Branch 30 → 32 taken 893 times.
✓ Branch 31 → 32 taken 27959 times.
28852 if (part.contains(QLatin1Char('@'))) appendPart(part, result);
96 28852 start = i + 1;
97 }
98 28220 return result;
99 ✗ }
100
101 } // namespace AddressListScan
102