service/MailServerDiscovery.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | #include <QCoreApplication> | ||
| 3 | #include <QList> | ||
| 4 | #include <QObject> | ||
| 5 | #include <QSet> | ||
| 6 | #include <QString> | ||
| 7 | #include <QTimer> | ||
| 8 | #include <QUrl> | ||
| 9 | #include <QVariantMap> | ||
| 10 | #include <functional> | ||
| 11 | |||
| 12 | // Sprint 100 S100.1.8 (D-6, SF-02): finds the incoming and outgoing servers | ||
| 13 | // of an address without the password. Sources run in parallel, each at most | ||
| 14 | // 4 s, all together at most 8 s, TLS/HTTPS only (rule 11): | ||
| 15 | // 1. DNS SRV (RFC 6186/8314): _imaps._tcp, _submissions._tcp, _submission._tcp | ||
| 16 | // 2. Thunderbird autoconfig XML on the address domain (HTTPS, no redirects) | ||
| 17 | // 3. TLS handshake with imap./mail./smtp.<domain> on 993/465 | ||
| 18 | // 4. the Thunderbird ISPDB — only after lookupIspdb() (it sends the | ||
| 19 | // domain to a third party, D-6) | ||
| 20 | // A result is *trusted* when it comes from HTTPS autoconfig on the address | ||
| 21 | // domain or names hosts inside the address domain; otherwise the setup | ||
| 22 | // shows the hosts before the password is sent (§6.2.2 step ③). | ||
| 23 | // Network access goes through three seams so the provider matrix replays | ||
| 24 | // recorded answers (tests/fixtures/autoconfig/). | ||
| 25 | class MailServerDiscovery final : public QObject { | ||
| 26 | Q_OBJECT | ||
| 27 | public: | ||
| 28 | struct Server { | ||
| 29 | QString host; | ||
| 30 | quint16 port = 0; | ||
| 31 | QString security; // "ssl" or "starttls" | ||
| 32 | QString username; // resolved from the template (address, local part) | ||
| 33 | bool operator==(const Server &) const = default; | ||
| 34 |
4/4✓ Branch 3 → 4 taken 311 times.
✓ Branch 3 → 6 taken 99 times.
✓ Branch 4 → 5 taken 310 times.
✓ Branch 4 → 6 taken 1 time.
|
410 | bool valid() const { return !host.isEmpty() && port > 0; } |
| 35 | }; | ||
| 36 | struct Result { | ||
| 37 | Server imap, smtp; | ||
| 38 | QString source; // "autoconfig", "srv", "probe", "ispdb" | ||
| 39 | bool trusted = false; | ||
| 40 |
4/4✓ Branch 3 → 4 taken 75 times.
✓ Branch 3 → 7 taken 33 times.
✓ Branch 5 → 6 taken 62 times.
✓ Branch 5 → 7 taken 13 times.
|
108 | bool complete() const { return imap.valid() && smtp.valid(); } |
| 41 | }; | ||
| 42 | struct SrvRecord { | ||
| 43 | QString target; | ||
| 44 | quint16 port = 0; | ||
| 45 | int priority = 0; | ||
| 46 | }; | ||
| 47 | using SrvLookup = std::function<void(QObject *owner, const QString &name, std::function<void(QList<SrvRecord>)>)>; | ||
| 48 | // status 0 = network/TLS failure; redirects are never followed. | ||
| 49 | using HttpGet = std::function<void(QObject *owner, const QUrl &url, std::function<void(int status, QByteArray body)>)>; | ||
| 50 | using TlsProbe = std::function<void(QObject *owner, const QString &host, quint16 port, std::function<void(bool)>)>; | ||
| 51 | |||
| 52 | explicit MailServerDiscovery(QObject *parent = nullptr); | ||
| 53 | void setSeams(SrvLookup srv, HttpGet http, TlsProbe tls); | ||
| 54 | 8 | void setTimeouts(int sourceMs, int overallMs) { m_sourceMs = sourceMs; m_overallMs = overallMs; } | |
| 55 | // Starts the sources for the address' domain. Earlier runs are cancelled. | ||
| 56 | void start(const QString &address); | ||
| 57 | // D-6: the explicit "Look up in the Thunderbird database". | ||
| 58 | void lookupIspdb(); | ||
| 59 | void cancel(); | ||
| 60 | 1 | bool running() const { return m_running; } | |
| 61 | QString domain() const { return m_domain; } | ||
| 62 | // Per source: "pending", "found", "none" (srv, autoconfig, probe, ispdb). | ||
| 63 | 12 | QVariantMap sources() const { return m_sources; } | |
| 64 | 24 | Result best() const { return m_best; } | |
| 65 | |||
| 66 | // Pure helpers (unit-tested). | ||
| 67 | static QList<Result> parseAutoconfig(const QByteArray &xml, const QString &address, const QString &source, | ||
| 68 | bool trusted); | ||
| 69 | static QString resolveUsername(const QString &pattern, const QString &address); | ||
| 70 | static bool hostInDomain(const QString &host, const QString &domain); | ||
| 71 | static int rank(const Result &result); | ||
| 72 | |||
| 73 | signals: | ||
| 74 | void sourcesChanged(); | ||
| 75 | void finished(bool found); | ||
| 76 | |||
| 77 | private: | ||
| 78 | void sourceDone(const QString &source, const QList<Result> &results); | ||
| 79 | void finishIfDone(); | ||
| 80 | void runSrv(quint64 run); | ||
| 81 | void runAutoconfig(quint64 run); | ||
| 82 | void runProbe(quint64 run); | ||
| 83 |
3/4✓ Branch 2 → 3 taken 158 times.
✓ Branch 2 → 5 taken 54 times.
✓ Branch 3 → 4 taken 158 times.
✗ Branch 3 → 5 not taken.
|
212 | bool current(quint64 run) const { return m_running && run == m_run; } |
| 84 | QString m_address, m_domain; | ||
| 85 | QVariantMap m_sources; | ||
| 86 | QList<Result> m_results; | ||
| 87 | Result m_best; | ||
| 88 | bool m_running = false; | ||
| 89 | quint64 m_run = 0; | ||
| 90 | int m_sourceMs = 4000; | ||
| 91 | int m_overallMs = 8000; | ||
| 92 | QTimer m_overall; | ||
| 93 | QObject *m_owner = nullptr; // parent of the in-flight requests of one run | ||
| 94 | SrvLookup m_srv; | ||
| 95 | HttpGet m_http; | ||
| 96 | TlsProbe m_tls; | ||
| 97 | }; | ||
| 98 |