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: 84.2% 224 / 0 / 266
Functions: 75.0% 36 / 0 / 48
Branches: 63.0% 410 / 0 / 651

service/MailServerDiscovery.cpp
Line Branch Exec Source
1 #include "MailServerDiscovery.h"
2 #include <QDnsLookup>
3 #include <QElapsedTimer>
4 #include <QLoggingCategory>
5 #include <QNetworkAccessManager>
6 #include <QNetworkReply>
7 #include <QPointer>
8 #include <QRegularExpression>
9 #include <QSslSocket>
10 #include <QXmlStreamReader>
11 #include <algorithm>
12 #include <memory>
13
14
4/7
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 8 taken 114 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
116 Q_LOGGING_CATEGORY(lcDiscovery, "mailjd.settings.discovery")
15
16 namespace {
17 812 MailServerDiscovery::SrvLookup defaultSrv() {
18 812 return [](QObject *owner, const QString &name, std::function<void(QList<MailServerDiscovery::SrvRecord>)> done) {
19 ✗ auto *lookup = new QDnsLookup(QDnsLookup::SRV, name, owner);
20 ✗ QObject::connect(lookup, &QDnsLookup::finished, lookup, [lookup, done] {
21 ✗ QList<MailServerDiscovery::SrvRecord> records;
22 ✗ if (lookup->error() == QDnsLookup::NoError)
23 ✗ for (const auto &record : lookup->serviceRecords())
24 ✗ records.append({record.target(), record.port(), record.priority()});
25 ✗ lookup->deleteLater();
26 ✗ done(records);
27 ✗ });
28 ✗ lookup->lookup();
29 812 };
30 }
31
32 812 MailServerDiscovery::HttpGet defaultHttp(QObject *parent) {
33
2/5
✓ Branch 3 → 4 taken 812 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 812 times.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
812 auto *manager = new QNetworkAccessManager(parent);
34 812 return [manager](QObject *owner, const QUrl &url, std::function<void(int, QByteArray)> done) {
35 ✗ QNetworkRequest request(url);
36 // Rule 11: never follow a redirect silently (it may leave the domain).
37 ✗ request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
38 ✗ request.setTransferTimeout(4000);
39 ✗ request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("MailJD"));
40 ✗ QNetworkReply *reply = manager->get(request);
41 ✗ QObject::connect(owner, &QObject::destroyed, reply, &QNetworkReply::abort);
42 ✗ QObject::connect(reply, &QNetworkReply::finished, reply, [reply, done] {
43 ✗ const int status = reply->error() == QNetworkReply::NoError
44 ✗ ? reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : 0;
45 ✗ const QByteArray body = status == 200 ? reply->read(512 * 1024) : QByteArray();
46 ✗ reply->deleteLater();
47 ✗ done(status, body);
48 ✗ });
49 1624 };
50 }
51
52 812 MailServerDiscovery::TlsProbe defaultTls() {
53 812 return [](QObject *owner, const QString &host, quint16 port, std::function<void(bool)> done) {
54 ✗ auto *socket = new QSslSocket(owner);
55 ✗ auto reported = std::make_shared<bool>(false);
56 ✗ const auto report = [socket, reported, done](bool ok) {
57 ✗ if (*reported) return;
58 ✗ *reported = true;
59 ✗ socket->abort();
60 ✗ socket->deleteLater();
61 ✗ done(ok);
62 ✗ };
63 ✗ QObject::connect(socket, &QSslSocket::encrypted, socket, [report] { report(true); });
64 ✗ QObject::connect(socket, &QSslSocket::errorOccurred, socket, [report](QAbstractSocket::SocketError) { report(false); });
65 ✗ QObject::connect(socket, &QSslSocket::sslErrors, socket, [report](const QList<QSslError> &) { report(false); });
66 ✗ QTimer::singleShot(4000, socket, [report] { report(false); });
67 ✗ socket->connectToHostEncrypted(host, port);
68 812 };
69 }
70
71 44 QString security(const QString &socketType) {
72
2/2
✓ Branch 2 → 3 taken 44 times.
✓ Branch 3 → 4 taken 44 times.
44 const QString type = socketType.trimmed().toUpper();
73
2/2
✓ Branch 7 → 8 taken 25 times.
✓ Branch 7 → 13 taken 19 times.
69 if (type == QLatin1String("SSL")) return QStringLiteral("ssl");
74
2/2
✓ Branch 15 → 16 taken 10 times.
✓ Branch 15 → 21 taken 9 times.
29 if (type == QLatin1String("STARTTLS")) return QStringLiteral("starttls");
75 9 return {}; // "plain" and unknown types are never used (rule 11)
76 44 }
77
78 88 QString resolvePlaceholders(QString text, const QString &address) {
79
1/1
✓ Branch 5 → 6 taken 88 times.
176 return text.replace(QStringLiteral("%EMAILADDRESS%"), address)
80
2/2
✓ Branch 8 → 9 taken 88 times.
✓ Branch 12 → 13 taken 88 times.
264 .replace(QStringLiteral("%EMAILLOCALPART%"), address.section('@', 0, 0))
81
3/3
✓ Branch 15 → 16 taken 88 times.
✓ Branch 16 → 17 taken 88 times.
✓ Branch 20 → 21 taken 88 times.
264 .replace(QStringLiteral("%EMAILDOMAIN%"), address.section('@', 1).toLower());
82 }
83 } // namespace
84
85 812 MailServerDiscovery::MailServerDiscovery(QObject *parent)
86
2/2
✓ Branch 8 → 9 taken 812 times.
✓ Branch 10 → 11 taken 812 times.
812 : QObject(parent), m_srv(defaultSrv()), m_http(defaultHttp(this)), m_tls(defaultTls()) {
87
1/1
✓ Branch 12 → 13 taken 812 times.
812 m_overall.setSingleShot(true);
88
1/1
✓ Branch 13 → 14 taken 812 times.
812 connect(&m_overall, &QTimer::timeout, this, [this] {
89 ✗ for (auto it = m_sources.begin(); it != m_sources.end(); ++it)
90 ✗ if (it.value() == QLatin1String("pending")) it.value() = QStringLiteral("none");
91 ✗ emit sourcesChanged();
92 ✗ finishIfDone();
93 ✗ });
94 812 }
95
96 24 void MailServerDiscovery::setSeams(SrvLookup srv, HttpGet http, TlsProbe tls) {
97 24 m_srv = std::move(srv);
98 24 m_http = std::move(http);
99 24 m_tls = std::move(tls);
100 24 }
101
102 44 QString MailServerDiscovery::resolveUsername(const QString &pattern, const QString &address) {
103
5/9
✓ Branch 2 → 3 taken 44 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 44 times.
✓ Branch 6 → 7 taken 44 times.
✓ Branch 7 → 8 taken 44 times.
✓ Branch 9 → 10 taken 44 times.
✗ Branch 9 → 11 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
44 return pattern.trimmed().isEmpty() ? address : resolvePlaceholders(pattern.trimmed(), address);
104 }
105
106 34 bool MailServerDiscovery::hostInDomain(const QString &host, const QString &domain) {
107
3/3
✓ Branch 2 → 3 taken 34 times.
✓ Branch 7 → 8 taken 34 times.
✓ Branch 8 → 9 taken 34 times.
68 const QString h = host.toLower().remove(QRegularExpression(QStringLiteral("\\.$")));
108
1/1
✓ Branch 14 → 15 taken 34 times.
34 const QString d = domain.toLower();
109
9/12
✓ Branch 16 → 17 taken 34 times.
✗ Branch 16 → 25 not taken.
✓ Branch 18 → 19 taken 31 times.
✓ Branch 18 → 24 taken 3 times.
✓ Branch 21 → 22 taken 31 times.
✓ Branch 22 → 23 taken 31 times.
✓ Branch 23 → 24 taken 9 times.
✓ Branch 23 → 25 taken 22 times.
✓ Branch 27 → 28 taken 31 times.
✓ Branch 27 → 29 taken 3 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
68 return !d.isEmpty() && (h == d || h.endsWith(QLatin1Char('.') + d));
110 34 }
111
112 34 int MailServerDiscovery::rank(const Result &result) {
113
5/10
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 16 taken 32 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 16 not taken.
✓ Branch 13 → 14 taken 8 times.
✓ Branch 13 → 15 taken 2 times.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 33 not taken.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
44 static const QHash<QString, int> sources = {{"autoconfig", 40}, {"srv", 30}, {"probe", 20}, {"ispdb", 10}};
114
5/6
✓ Branch 17 → 18 taken 27 times.
✓ Branch 17 → 19 taken 7 times.
✓ Branch 20 → 21 taken 24 times.
✓ Branch 20 → 22 taken 10 times.
✓ Branch 24 → 25 taken 34 times.
✗ Branch 24 → 26 not taken.
34 return (result.complete() ? 1000 : 0) + (result.trusted ? 500 : 0) + (result.imap.valid() ? 100 : 0) +
115 34 sources.value(result.source);
116 2 }
117
118 17 QList<MailServerDiscovery::Result> MailServerDiscovery::parseAutoconfig(const QByteArray &xml, const QString &address,
119 const QString &source, bool trusted) {
120
1/1
✓ Branch 2 → 3 taken 17 times.
17 QXmlStreamReader reader(xml);
121 17 QList<Server> incoming, outgoing;
122
7/8
✓ Branch 141 → 142 taken 1336 times.
✓ Branch 142 → 143 taken 1319 times.
✓ Branch 142 → 146 taken 17 times.
✓ Branch 143 → 144 taken 1319 times.
✓ Branch 144 → 145 taken 1319 times.
✗ Branch 144 → 146 not taken.
✓ Branch 147 → 4 taken 1319 times.
✓ Branch 147 → 148 taken 17 times.
1336 while (!reader.atEnd() && !reader.hasError()) {
123
1/1
✓ Branch 4 → 5 taken 1319 times.
1319 reader.readNext();
124
3/3
✓ Branch 5 → 6 taken 1319 times.
✓ Branch 6 → 7 taken 956 times.
✓ Branch 6 → 8 taken 363 times.
1648 if (!reader.isStartElement()) continue;
125
2/2
✓ Branch 8 → 9 taken 363 times.
✓ Branch 9 → 10 taken 363 times.
363 const QString element = reader.name().toString();
126
2/2
✓ Branch 10 → 11 taken 363 times.
✓ Branch 16 → 17 taken 363 times.
726 const QString type = reader.attributes().value(QStringLiteral("type")).toString();
127
4/4
✓ Branch 22 → 23 taken 32 times.
✓ Branch 22 → 27 taken 331 times.
✓ Branch 25 → 26 taken 23 times.
✓ Branch 25 → 27 taken 9 times.
363 const bool imap = element == QLatin1String("incomingServer") && type == QLatin1String("imap");
128
3/4
✓ Branch 30 → 31 taken 21 times.
✓ Branch 30 → 35 taken 342 times.
✓ Branch 33 → 34 taken 21 times.
✗ Branch 33 → 35 not taken.
363 const bool smtp = element == QLatin1String("outgoingServer") && type == QLatin1String("smtp");
129
4/4
✓ Branch 36 → 37 taken 340 times.
✓ Branch 36 → 39 taken 23 times.
✓ Branch 37 → 38 taken 319 times.
✓ Branch 37 → 39 taken 21 times.
363 if (!imap && !smtp) continue;
130 44 Server server;
131 44 QString socketType, username;
132
13/16
✓ Branch 88 → 89 taken 432 times.
✓ Branch 89 → 90 taken 237 times.
✓ Branch 89 → 94 taken 195 times.
✓ Branch 90 → 91 taken 237 times.
✓ Branch 91 → 92 taken 237 times.
✓ Branch 93 → 94 taken 193 times.
✓ Branch 93 → 97 taken 44 times.
✓ Branch 94 → 95 taken 388 times.
✓ Branch 95 → 96 taken 388 times.
✗ Branch 95 → 97 not taken.
✓ Branch 98 → 99 taken 237 times.
✓ Branch 98 → 100 taken 195 times.
✓ Branch 100 → 40 taken 388 times.
✓ Branch 100 → 101 taken 44 times.
✗ Branch 201 → 202 not taken.
✗ Branch 201 → 203 not taken.
432 while (!(reader.isEndElement() && reader.name().toString() == element) && !reader.atEnd()) {
133
1/1
✓ Branch 40 → 41 taken 388 times.
388 reader.readNext();
134
3/3
✓ Branch 41 → 42 taken 388 times.
✓ Branch 42 → 43 taken 195 times.
✓ Branch 42 → 44 taken 193 times.
388 if (!reader.isStartElement()) continue;
135
2/2
✓ Branch 44 → 45 taken 193 times.
✓ Branch 45 → 46 taken 193 times.
193 const QString field = reader.name().toString();
136
5/5
✓ Branch 48 → 49 taken 44 times.
✓ Branch 48 → 57 taken 149 times.
✓ Branch 49 → 50 taken 44 times.
✓ Branch 50 → 51 taken 44 times.
✓ Branch 51 → 52 taken 44 times.
193 if (field == QLatin1String("hostname")) server.host = resolvePlaceholders(reader.readElementText().trimmed(), address);
137
2/2
✓ Branch 59 → 60 taken 44 times.
✓ Branch 59 → 71 taken 105 times.
149 else if (field == QLatin1String("port")) {
138 // S106-203: out of range is no port (the server is dropped), not a
139 // truncated one (65600 used to become 64).
140 44 bool ok = false;
141
3/3
✓ Branch 60 → 61 taken 44 times.
✓ Branch 61 → 62 taken 44 times.
✓ Branch 62 → 63 taken 44 times.
44 const uint port = reader.readElementText().trimmed().toUInt(&ok);
142
4/6
✓ Branch 65 → 66 taken 44 times.
✗ Branch 65 → 69 not taken.
✓ Branch 66 → 67 taken 44 times.
✗ Branch 66 → 69 not taken.
✓ Branch 67 → 68 taken 43 times.
✓ Branch 67 → 69 taken 1 time.
44 server.port = ok && port >= 1 && port <= 65535 ? quint16(port) : 0;
143 }
144
3/3
✓ Branch 73 → 74 taken 44 times.
✓ Branch 73 → 78 taken 61 times.
✓ Branch 74 → 75 taken 44 times.
105 else if (field == QLatin1String("socketType")) socketType = reader.readElementText();
145
3/3
✓ Branch 80 → 81 taken 44 times.
✓ Branch 80 → 85 taken 17 times.
✓ Branch 81 → 82 taken 44 times.
61 else if (field == QLatin1String("username")) username = reader.readElementText();
146 193 }
147
1/1
✓ Branch 101 → 102 taken 44 times.
44 server.security = security(socketType);
148
1/1
✓ Branch 104 → 105 taken 44 times.
44 server.username = resolveUsername(username, address);
149
6/6
✓ Branch 108 → 109 taken 43 times.
✓ Branch 108 → 111 taken 1 time.
✓ Branch 110 → 111 taken 9 times.
✓ Branch 110 → 112 taken 34 times.
✓ Branch 113 → 114 taken 10 times.
✓ Branch 113 → 115 taken 34 times.
44 if (!server.valid() || server.security.isEmpty()) continue;
150
3/3
✓ Branch 115 → 116 taken 17 times.
✓ Branch 115 → 117 taken 17 times.
✓ Branch 118 → 119 taken 34 times.
34 (imap ? incoming : outgoing).append(server);
151 722 }
152
7/7
✓ Branch 148 → 149 taken 17 times.
✓ Branch 149 → 150 taken 16 times.
✓ Branch 149 → 152 taken 1 time.
✓ Branch 151 → 152 taken 2 times.
✓ Branch 151 → 153 taken 14 times.
✓ Branch 154 → 155 taken 3 times.
✓ Branch 154 → 156 taken 14 times.
17 if (reader.hasError() || incoming.isEmpty()) return {};
153 // Implicit TLS first (RFC 8314), then STARTTLS.
154 28 const auto preferSsl = [](const QList<Server> &list) {
155
2/2
✓ Branch 11 → 4 taken 29 times.
✓ Branch 11 → 12 taken 4 times.
33 for (const auto &server : list)
156
2/2
✓ Branch 7 → 8 taken 24 times.
✓ Branch 7 → 9 taken 5 times.
29 if (server.security == QLatin1String("ssl")) return server;
157
1/2
✗ Branch 13 → 15 not taken.
✓ Branch 13 → 16 taken 4 times.
4 return list.isEmpty() ? Server{} : list.first();
158 };
159
2/4
✓ Branch 161 → 162 taken 14 times.
✓ Branch 161 → 163 taken 14 times.
✗ Branch 220 → 221 not taken.
✗ Branch 220 → 222 not taken.
28 return {Result{preferSsl(incoming), preferSsl(outgoing), source, trusted}};
160 31 }
161
162 25 void MailServerDiscovery::start(const QString &address) {
163 25 cancel();
164
1/1
✓ Branch 3 → 4 taken 25 times.
25 m_address = address.trimmed();
165
2/2
✓ Branch 8 → 9 taken 25 times.
✓ Branch 9 → 10 taken 25 times.
25 m_domain = m_address.section('@', 1).toLower();
166 25 m_results.clear();
167 25 m_best = {};
168
2/4
✓ Branch 23 → 24 taken 75 times.
✓ Branch 23 → 25 taken 25 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
100 m_sources = {{"srv", "pending"}, {"autoconfig", "pending"}, {"probe", "pending"}};
169 25 m_running = true;
170 25 const quint64 run = ++m_run;
171
2/5
✓ Branch 26 → 27 taken 25 times.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 25 times.
✗ Branch 63 → 64 not taken.
✗ Branch 63 → 65 not taken.
25 m_owner = new QObject(this);
172 25 m_overall.start(m_overallMs);
173
5/5
✓ Branch 30 → 31 taken 25 times.
✓ Branch 34 → 35 taken 25 times.
✓ Branch 35 → 36 taken 25 times.
✓ Branch 39 → 32 taken 25 times.
✓ Branch 39 → 40 taken 25 times.
50 qCInfo(lcDiscovery) << "discovery started";
174 25 emit sourcesChanged();
175 25 runSrv(run);
176 25 runAutoconfig(run);
177 25 runProbe(run);
178 50 }
179
180 54 void MailServerDiscovery::cancel() {
181 54 m_running = false;
182 54 m_overall.stop();
183 // Deferred: cancel() may run inside a callback of a request it owns.
184
2/2
✓ Branch 3 → 4 taken 9 times.
✓ Branch 3 → 5 taken 45 times.
54 if (m_owner) m_owner->deleteLater();
185 54 m_owner = nullptr;
186 54 }
187
188 2 void MailServerDiscovery::lookupIspdb() {
189
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (m_domain.isEmpty()) return;
190
1/2
✓ Branch 5 → 6 taken 2 times.
✗ Branch 5 → 15 not taken.
2 if (!m_running) {
191 2 m_running = true;
192
2/12
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 8 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 2 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 61 not taken.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
2 m_owner = m_owner ? m_owner : new QObject(this);
193
1/1
✓ Branch 14 → 15 taken 2 times.
2 m_overall.start(m_overallMs);
194 }
195 2 const quint64 run = m_run;
196
1/1
✓ Branch 22 → 23 taken 2 times.
6 m_sources.insert(QStringLiteral("ispdb"), QStringLiteral("pending"));
197
1/1
✓ Branch 28 → 29 taken 2 times.
2 emit sourcesChanged();
198
5/5
✓ Branch 29 → 30 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 38 → 31 taken 2 times.
✓ Branch 38 → 39 taken 2 times.
4 qCInfo(lcDiscovery) << "ISPDB lookup requested";
199
1/1
✓ Branch 39 → 40 taken 2 times.
2 QPointer<MailServerDiscovery> guard(this);
200
3/3
✓ Branch 45 → 46 taken 2 times.
✓ Branch 46 → 47 taken 2 times.
✓ Branch 47 → 48 taken 2 times.
4 m_http(m_owner, QUrl(QStringLiteral("https://autoconfig.thunderbird.net/v1.1/") + m_domain),
201
1/3
✓ Branch 41 → 42 taken 2 times.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
4 [guard, run](int status, const QByteArray &body) {
202
3/6
✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 2 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 2 times.
2 if (!guard || !guard->current(run)) return;
203 2 QList<Result> results;
204
1/2
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 39 not taken.
2 if (status == 200) {
205
1/1
✓ Branch 16 → 17 taken 2 times.
2 results = parseAutoconfig(body, guard->m_address, QStringLiteral("ispdb"), false);
206
4/4
✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 37 → 24 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
4 for (auto &result : results)
207
3/3
✓ Branch 26 → 27 taken 2 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 34 taken 1 time.
3 result.trusted = hostInDomain(result.imap.host, guard->m_domain) &&
208
3/5
✓ Branch 29 → 30 taken 1 time.
✗ Branch 29 → 33 not taken.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 32 → 34 not taken.
1 (!result.smtp.valid() || hostInDomain(result.smtp.host, guard->m_domain));
209 }
210
1/1
✓ Branch 43 → 44 taken 2 times.
4 guard->sourceDone(QStringLiteral("ispdb"), results);
211 2 });
212 2 }
213
214 25 void MailServerDiscovery::runSrv(quint64 run) {
215 struct State {
216 QList<SrvRecord> imaps, submissions, submission;
217 int pending = 3;
218 };
219
1/1
✓ Branch 2 → 3 taken 25 times.
25 auto state = std::make_shared<State>();
220
1/1
✓ Branch 3 → 4 taken 25 times.
25 QPointer<MailServerDiscovery> guard(this);
221 69 const auto first = [](QList<SrvRecord> records) {
222
3/4
✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 7 taken 1 time.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 12 times.
82 records.removeIf([](const SrvRecord &record) { return record.target.isEmpty() || record.target == QLatin1String("."); });
223 69 std::sort(records.begin(), records.end(), [](const SrvRecord &a, const SrvRecord &b) { return a.priority < b.priority; });
224
2/2
✓ Branch 7 → 8 taken 57 times.
✓ Branch 7 → 9 taken 12 times.
69 return records.isEmpty() ? SrvRecord{} : records.first();
225 };
226 94 const auto finish = [guard, run, state, first, this] {
227
6/8
✓ Branch 3 → 4 taken 69 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 69 times.
✗ Branch 5 → 8 not taken.
✓ Branch 7 → 8 taken 46 times.
✓ Branch 7 → 9 taken 23 times.
✓ Branch 10 → 11 taken 46 times.
✓ Branch 10 → 12 taken 23 times.
69 if (!guard || !current(run) || --state->pending > 0) return;
228 23 Result result;
229 23 result.source = QStringLiteral("srv");
230
1/1
✓ Branch 20 → 21 taken 23 times.
23 const SrvRecord imap = first(state->imaps);
231
2/2
✓ Branch 23 → 24 taken 6 times.
✓ Branch 23 → 33 taken 17 times.
29 if (!imap.target.isEmpty()) result.imap = {imap.target, imap.port, QStringLiteral("ssl"), m_address};
232
2/2
✓ Branch 35 → 36 taken 23 times.
✓ Branch 39 → 40 taken 23 times.
23 const SrvRecord smtps = first(state->submissions), smtp = first(state->submission);
233
2/2
✓ Branch 42 → 43 taken 3 times.
✓ Branch 42 → 52 taken 20 times.
26 if (!smtps.target.isEmpty()) result.smtp = {smtps.target, smtps.port, QStringLiteral("ssl"), m_address};
234
2/2
✓ Branch 53 → 54 taken 3 times.
✓ Branch 53 → 63 taken 17 times.
23 else if (!smtp.target.isEmpty()) result.smtp = {smtp.target, smtp.port, QStringLiteral("starttls"), m_address};
235
3/3
✓ Branch 63 → 64 taken 23 times.
✓ Branch 64 → 65 taken 4 times.
✓ Branch 64 → 70 taken 19 times.
27 result.trusted = hostInDomain(result.imap.host, m_domain) &&
236
3/5
✓ Branch 66 → 67 taken 4 times.
✗ Branch 66 → 69 not taken.
✓ Branch 67 → 68 taken 4 times.
✓ Branch 68 → 69 taken 4 times.
✗ Branch 68 → 70 not taken.
4 (!result.smtp.valid() || hostInDomain(result.smtp.host, m_domain));
237
7/11
✓ Branch 72 → 73 taken 6 times.
✓ Branch 72 → 75 taken 17 times.
✓ Branch 79 → 80 taken 23 times.
✓ Branch 83 → 84 taken 6 times.
✓ Branch 83 → 87 taken 17 times.
✓ Branch 85 → 86 taken 6 times.
✓ Branch 85 → 87 taken 6 times.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 115 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
52 sourceDone(QStringLiteral("srv"), result.imap.valid() ? QList<Result>{result} : QList<Result>{});
238
1/7
✓ Branch 74 → 76 taken 6 times.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 122 not taken.
✗ Branch 118 → 119 not taken.
✗ Branch 118 → 122 not taken.
✗ Branch 120 → 121 not taken.
✗ Branch 120 → 122 not taken.
78 };
239
3/5
✓ Branch 8 → 9 taken 25 times.
✓ Branch 12 → 13 taken 25 times.
✓ Branch 13 → 14 taken 25 times.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
73 m_srv(m_owner, QStringLiteral("_imaps._tcp.") + m_domain, [state, finish](QList<SrvRecord> r) { state->imaps = r; finish(); });
240
2/2
✓ Branch 25 → 26 taken 25 times.
✓ Branch 26 → 27 taken 25 times.
50 m_srv(m_owner, QStringLiteral("_submissions._tcp.") + m_domain,
241
1/3
✓ Branch 21 → 22 taken 25 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
73 [state, finish](QList<SrvRecord> r) { state->submissions = r; finish(); });
242
2/2
✓ Branch 38 → 39 taken 25 times.
✓ Branch 39 → 40 taken 25 times.
50 m_srv(m_owner, QStringLiteral("_submission._tcp.") + m_domain,
243
1/3
✓ Branch 34 → 35 taken 25 times.
✗ Branch 101 → 102 not taken.
✗ Branch 101 → 103 not taken.
73 [state, finish](QList<SrvRecord> r) { state->submission = r; finish(); });
244
1/3
✓ Branch 46 → 47 taken 25 times.
✗ Branch 108 → 109 not taken.
✗ Branch 108 → 110 not taken.
25 QTimer::singleShot(m_sourceMs, m_owner, [guard, run, this] {
245
13/29
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 14 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 14 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 33 not taken.
✓ Branch 28 → 29 taken 1 time.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
2 if (guard && current(run) && m_sources.value("srv") == QLatin1String("pending")) sourceDone(QStringLiteral("srv"), {});
246 1 });
247 25 }
248
249 25 void MailServerDiscovery::runAutoconfig(quint64 run) {
250 const QList<QUrl> urls = {
251 50 QUrl(QStringLiteral("https://autoconfig.%1/mail/config-v1.1.xml?emailaddress=%2")
252
3/3
✓ Branch 7 → 8 taken 25 times.
✓ Branch 8 → 9 taken 25 times.
✓ Branch 9 → 10 taken 25 times.
50 .arg(m_domain, QString::fromUtf8(QUrl::toPercentEncoding(m_address)))),
253
3/5
✓ Branch 15 → 16 taken 25 times.
✓ Branch 19 → 20 taken 50 times.
✓ Branch 19 → 21 taken 25 times.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
125 QUrl(QStringLiteral("https://%1/.well-known/autoconfig/mail/config-v1.1.xml").arg(m_domain))};
254
1/1
✓ Branch 32 → 33 taken 25 times.
25 auto pending = std::make_shared<int>(int(urls.size()));
255
1/1
✓ Branch 33 → 34 taken 25 times.
25 auto found = std::make_shared<bool>(false);
256
1/1
✓ Branch 34 → 35 taken 25 times.
25 QPointer<MailServerDiscovery> guard(this);
257
2/2
✓ Branch 47 → 37 taken 50 times.
✓ Branch 47 → 48 taken 25 times.
75 for (const auto &url : urls) {
258
2/8
✓ Branch 41 → 42 taken 50 times.
✓ Branch 42 → 43 taken 50 times.
✗ Branch 103 → 104 not taken.
✗ Branch 103 → 105 not taken.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
50 m_http(m_owner, url, [guard, run, pending, found, this](int status, const QByteArray &body) {
259
6/8
✓ Branch 3 → 4 taken 46 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 36 times.
✓ Branch 5 → 8 taken 10 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 36 times.
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 12 taken 36 times.
46 if (!guard || !current(run) || *found) return;
260
4/8
✓ Branch 18 → 19 taken 10 times.
✓ Branch 18 → 20 taken 26 times.
✓ Branch 20 → 21 taken 10 times.
✓ Branch 20 → 22 taken 26 times.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
46 const auto results = status == 200 ? parseAutoconfig(body, m_address, QStringLiteral("autoconfig"), true)
261
3/3
✓ Branch 12 → 13 taken 10 times.
✓ Branch 12 → 17 taken 26 times.
✓ Branch 16 → 18 taken 10 times.
46 : QList<Result>{};
262
2/2
✓ Branch 23 → 24 taken 10 times.
✓ Branch 23 → 26 taken 26 times.
36 if (!results.isEmpty()) *found = true;
263
7/7
✓ Branch 27 → 28 taken 26 times.
✓ Branch 27 → 30 taken 10 times.
✓ Branch 29 → 30 taken 13 times.
✓ Branch 29 → 31 taken 13 times.
✓ Branch 32 → 33 taken 23 times.
✓ Branch 32 → 40 taken 13 times.
✓ Branch 36 → 37 taken 23 times.
59 if (!results.isEmpty() || --*pending == 0) sourceDone(QStringLiteral("autoconfig"), results);
264 36 });
265 }
266
1/3
✓ Branch 49 → 50 taken 25 times.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
25 QTimer::singleShot(m_sourceMs, m_owner, [guard, run, this] {
267
12/28
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 14 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 14 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 33 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
1 if (guard && current(run) && m_sources.value("autoconfig") == QLatin1String("pending"))
268
1/1
✓ Branch 28 → 29 taken 1 time.
2 sourceDone(QStringLiteral("autoconfig"), {});
269 1 });
270 100 }
271
272 25 void MailServerDiscovery::runProbe(quint64 run) {
273 struct State {
274 QString imap, smtp;
275 int pending = 4;
276 };
277
1/1
✓ Branch 2 → 3 taken 25 times.
25 auto state = std::make_shared<State>();
278
1/1
✓ Branch 3 → 4 taken 25 times.
25 QPointer<MailServerDiscovery> guard(this);
279 92 const auto finish = [guard, run, state, this] {
280
7/8
✓ Branch 3 → 4 taken 92 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 48 times.
✓ Branch 5 → 8 taken 44 times.
✓ Branch 7 → 8 taken 36 times.
✓ Branch 7 → 9 taken 12 times.
✓ Branch 10 → 11 taken 80 times.
✓ Branch 10 → 12 taken 12 times.
92 if (!guard || !current(run) || --state->pending > 0) return;
281 12 Result result;
282 12 result.source = QStringLiteral("probe");
283 12 result.trusted = true; // the names are built from the address domain
284
2/2
✓ Branch 20 → 21 taken 7 times.
✓ Branch 20 → 31 taken 5 times.
19 if (!state->imap.isEmpty()) result.imap = {state->imap, 993, QStringLiteral("ssl"), m_address};
285
2/2
✓ Branch 33 → 34 taken 3 times.
✓ Branch 33 → 44 taken 9 times.
15 if (!state->smtp.isEmpty()) result.smtp = {state->smtp, 465, QStringLiteral("ssl"), m_address};
286
7/11
✓ Branch 45 → 46 taken 7 times.
✓ Branch 45 → 48 taken 5 times.
✓ Branch 52 → 53 taken 12 times.
✓ Branch 56 → 57 taken 7 times.
✓ Branch 56 → 60 taken 5 times.
✓ Branch 58 → 59 taken 7 times.
✓ Branch 58 → 60 taken 7 times.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 76 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
31 sourceDone(QStringLiteral("probe"), result.imap.valid() ? QList<Result>{result} : QList<Result>{});
287
1/7
✓ Branch 47 → 49 taken 7 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 83 not taken.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 83 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
64 };
288 // Earlier names win: imap. before mail., smtp. before mail.
289 100 const auto probe = [this, state, finish](const QString &host, quint16 port, bool incoming, bool preferred) {
290
2/8
✓ Branch 5 → 6 taken 100 times.
✓ Branch 6 → 7 taken 100 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
100 m_tls(m_owner, host, port, [state, finish, host, incoming, preferred](bool ok) {
291
2/2
✓ Branch 2 → 3 taken 46 times.
✓ Branch 2 → 5 taken 46 times.
92 QString &slot = incoming ? state->imap : state->smtp;
292
7/8
✓ Branch 7 → 8 taken 19 times.
✓ Branch 7 → 12 taken 73 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 18 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 18 times.
✓ Branch 13 → 15 taken 74 times.
92 if (ok && (slot.isEmpty() || preferred)) slot = host;
293 92 finish();
294 92 });
295 125 };
296
2/2
✓ Branch 11 → 12 taken 25 times.
✓ Branch 12 → 13 taken 25 times.
50 probe(QStringLiteral("imap.") + m_domain, 993, true, true);
297
2/2
✓ Branch 19 → 20 taken 25 times.
✓ Branch 20 → 21 taken 25 times.
50 probe(QStringLiteral("mail.") + m_domain, 993, true, false);
298
2/2
✓ Branch 27 → 28 taken 25 times.
✓ Branch 28 → 29 taken 25 times.
50 probe(QStringLiteral("smtp.") + m_domain, 465, false, true);
299
2/2
✓ Branch 35 → 36 taken 25 times.
✓ Branch 36 → 37 taken 25 times.
50 probe(QStringLiteral("mail.") + m_domain, 465, false, false);
300
1/3
✓ Branch 41 → 42 taken 25 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
25 QTimer::singleShot(m_sourceMs, m_owner, [guard, run, this] {
301
13/29
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 14 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 14 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 14 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 23 not taken.
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 33 not taken.
✓ Branch 28 → 29 taken 1 time.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 36 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
2 if (guard && current(run) && m_sources.value("probe") == QLatin1String("pending")) sourceDone(QStringLiteral("probe"), {});
302 1 });
303 25 }
304
305 63 void MailServerDiscovery::sourceDone(const QString &source, const QList<Result> &results) {
306
4/5
✓ Branch 3 → 4 taken 63 times.
✓ Branch 5 → 6 taken 63 times.
✓ Branch 6 → 7 taken 63 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 63 times.
63 if (m_sources.value(source) != QLatin1String("pending")) return;
307
7/11
✓ Branch 13 → 14 taken 38 times.
✓ Branch 13 → 17 taken 25 times.
✓ Branch 21 → 22 taken 63 times.
✓ Branch 24 → 25 taken 25 times.
✓ Branch 24 → 26 taken 38 times.
✓ Branch 26 → 27 taken 38 times.
✓ Branch 26 → 28 taken 25 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
126 m_sources.insert(source, results.isEmpty() ? QStringLiteral("none") : QStringLiteral("found"));
308 63 m_results += results;
309
4/4
✓ Branch 29 → 30 taken 63 times.
✓ Branch 30 → 31 taken 63 times.
✓ Branch 44 → 32 taken 35 times.
✓ Branch 44 → 45 taken 63 times.
98 for (const auto &result : m_results)
310
8/8
✓ Branch 34 → 35 taken 15 times.
✓ Branch 34 → 38 taken 20 times.
✓ Branch 35 → 36 taken 15 times.
✓ Branch 36 → 37 taken 15 times.
✓ Branch 37 → 38 taken 5 times.
✓ Branch 37 → 39 taken 10 times.
✓ Branch 40 → 41 taken 25 times.
✓ Branch 40 → 42 taken 10 times.
35 if (!m_best.imap.valid() || rank(result) > rank(m_best)) m_best = result;
311
9/9
✓ Branch 45 → 46 taken 63 times.
✓ Branch 49 → 50 taken 63 times.
✓ Branch 50 → 51 taken 63 times.
✓ Branch 51 → 52 taken 63 times.
✓ Branch 53 → 54 taken 38 times.
✓ Branch 53 → 55 taken 25 times.
✓ Branch 56 → 57 taken 63 times.
✓ Branch 60 → 47 taken 63 times.
✓ Branch 60 → 61 taken 63 times.
126 qCInfo(lcDiscovery) << "discovery source" << source << (results.isEmpty() ? "none" : "found");
312 63 emit sourcesChanged();
313 63 finishIfDone();
314 }
315
316 63 void MailServerDiscovery::finishIfDone() {
317
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 63 times.
63 if (!m_running) return;
318 63 const bool allDone = std::none_of(m_sources.cbegin(), m_sources.cend(),
319
2/2
✓ Branch 3 → 4 taken 119 times.
✓ Branch 4 → 5 taken 119 times.
119 [](const QVariant &state) { return state == QLatin1String("pending"); });
320 // The first trusted, complete answer from the provider itself ends early;
321 // SRV waits for the provider's autoconfig, which names the preferred ports.
322
3/3
✓ Branch 8 → 9 taken 63 times.
✓ Branch 13 → 14 taken 63 times.
✓ Branch 14 → 15 taken 63 times.
126 const bool autoconfigDone = m_sources.value(QStringLiteral("autoconfig")) != QLatin1String("pending");
323
6/6
✓ Branch 21 → 22 taken 26 times.
✓ Branch 21 → 31 taken 37 times.
✓ Branch 22 → 23 taken 19 times.
✓ Branch 22 → 31 taken 7 times.
✓ Branch 25 → 26 taken 9 times.
✓ Branch 25 → 30 taken 10 times.
91 const bool early = m_best.complete() && m_best.trusted &&
324
2/2
✓ Branch 28 → 29 taken 5 times.
✓ Branch 28 → 31 taken 4 times.
28 (m_best.source == QLatin1String("autoconfig") ||
325
2/2
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 31 taken 4 times.
14 (m_best.source == QLatin1String("srv") && autoconfigDone));
326
4/4
✓ Branch 32 → 33 taken 48 times.
✓ Branch 32 → 35 taken 15 times.
✓ Branch 33 → 34 taken 37 times.
✓ Branch 33 → 35 taken 11 times.
63 if (!allDone && !early) return;
327 26 m_running = false;
328 26 m_overall.stop();
329
8/8
✓ Branch 36 → 37 taken 26 times.
✓ Branch 40 → 41 taken 26 times.
✓ Branch 41 → 42 taken 26 times.
✓ Branch 42 → 43 taken 26 times.
✓ Branch 43 → 44 taken 26 times.
✓ Branch 44 → 45 taken 26 times.
✓ Branch 48 → 38 taken 26 times.
✓ Branch 48 → 49 taken 26 times.
52 qCInfo(lcDiscovery) << "discovery finished, source" << m_best.source << "trusted" << m_best.trusted;
330 26 emit finished(m_best.imap.valid());
331 }
332