service/ImapConnectionSetup.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QString> | ||
| 4 | #include <QStringList> | ||
| 5 | |||
| 6 | #include <functional> | ||
| 7 | |||
| 8 | #include "data/AccountConfig.h" | ||
| 9 | #include "service/ImapResponseParser.h" | ||
| 10 | #include "service/ImapService.h" | ||
| 11 | |||
| 12 | class ImapCommandTable; | ||
| 13 | class QObject; | ||
| 14 | class QSslSocket; | ||
| 15 | class QTimer; | ||
| 16 | |||
| 17 | // Connection setup of one IMAP connection (Sprint 92 S92.1.3, extracted | ||
| 18 | // from ImapService): TCP/TLS establishment callbacks, the session-setup | ||
| 19 | // deadline, greeting → CAPABILITY → optional STARTTLS → post-TLS | ||
| 20 | // CAPABILITY → LOGIN (with the post-login capability set), TCP keepalive | ||
| 21 | // tuning and the account credentials (zeroed after use). | ||
| 22 | // | ||
| 23 | // Owner: ImapService (std::unique_ptr), connection thread. Plain class; its | ||
| 24 | // setup timer is a child of the service, which connects it to | ||
| 25 | // ImapService::onTimeout; the socket's connected/encrypted/disconnected | ||
| 26 | // signals are connected to this unit at the former positions. The | ||
| 27 | // negotiated capability list stays with the service (read by every | ||
| 28 | // command) and is written here by reference. connectToServer() stays in | ||
| 29 | // the service because it resets the state of every unit (plan correction, | ||
| 30 | // SPRINT-92.md §9.1); it reaches this unit's state through the accessors. | ||
| 31 | class ImapConnectionSetup { | ||
| 32 | public: | ||
| 33 | using State = ImapService::State; | ||
| 34 | using SetupPhase = ImapService::SetupPhase; | ||
| 35 | using DisconnectReason = ImapService::DisconnectReason; | ||
| 36 | |||
| 37 | struct Deps { | ||
| 38 | std::function<void(State state)> setState; | ||
| 39 | std::function<void(const QString &error, DisconnectReason reason)> failConnection; | ||
| 40 | std::function<bool()> readBufferEmpty; | ||
| 41 | // MAILJD_E2E_TESTING only (present in every build for a stable layout). | ||
| 42 | std::function<void(const QString &phase, quint64 requestToken)> reportE2eTransportPhase; | ||
| 43 | }; | ||
| 44 | ImapConnectionSetup(QObject *owner, QSslSocket *socket, ImapCommandTable &commands, | ||
| 45 | QStringList &capabilities, Deps deps); | ||
| 46 | |||
| 47 | void onConnected(); | ||
| 48 | void onEncrypted(); | ||
| 49 | bool beginLogin(); | ||
| 50 | // T-720: Apply SO_KEEPALIVE + platform-specific interval tuning to the | ||
| 51 | // connected socket. Called once from onConnected()/onEncrypted(). | ||
| 52 | void tuneKeepAlive(); | ||
| 53 | void clearCredentials(); | ||
| 54 | // Untagged greeting and CAPABILITY responses during negotiation. | ||
| 55 | // preAuthenticated: the greeting was "* PREAUTH" (RFC 3501 §7.1.4). | ||
| 56 | void handleGreeting(const QString &data, bool preAuthenticated = false); | ||
| 57 | void collectCapabilities(const QString &data); | ||
| 58 | // Tagged completions of the setup commands. | ||
| 59 | void handleTaggedCapability(const TaggedResponse &response); | ||
| 60 | void handleTaggedStartTls(const TaggedResponse &response); | ||
| 61 | void handleTaggedLogin(const TaggedResponse &response); | ||
| 62 | |||
| 63 | // Setup state shared with the service's session lifecycle (single owner: | ||
| 64 | // this unit). | ||
| 65 | 102 | ImapConfig &config() { return m_config; } | |
| 66 | 2430 | SetupPhase &setupPhase() { return m_setupPhase; } | |
| 67 | 2429 | QStringList &pendingCapabilities() { return m_pendingCapabilities; } | |
| 68 | 78 | bool &keepAliveTuned() { return m_keepAliveTuned; } | |
| 69 | 5287 | QTimer *setupTimer() const { return m_timeoutTimer; } | |
| 70 | |||
| 71 | // SEC-2026-07-21-12: deadline for the pre-auth setup phases (greeting, | ||
| 72 | // TLS handshake, CAPABILITY, LOGIN). Without this, a silent server can | ||
| 73 | // stall the account indefinitely after TCP connect — no error, no | ||
| 74 | // reconnect. The timer is re-armed on each setup-state transition and | ||
| 75 | // stopped once Authenticated is reached (command timeout takes over). | ||
| 76 | static constexpr int SESSION_SETUP_TIMEOUT_MS = 30000; // 30 seconds | ||
| 77 | |||
| 78 | private: | ||
| 79 | 270 | void setState(State state) { m_deps.setState(state); } | |
| 80 | 16 | void failConnection(const QString &error, | |
| 81 | DisconnectReason reason = DisconnectReason::ProtocolError) { | ||
| 82 | 16 | m_deps.failConnection(error, reason); | |
| 83 | 16 | } | |
| 84 | 133 | void reportE2eTransportPhase(const QString &phase, quint64 requestToken = 0) { | |
| 85 | 133 | m_deps.reportE2eTransportPhase(phase, requestToken); | |
| 86 | 133 | } | |
| 87 | |||
| 88 | QSslSocket *m_socket; | ||
| 89 | ImapCommandTable &m_commands; | ||
| 90 | QStringList &m_capabilities; // ImapService::m_capabilities | ||
| 91 | Deps m_deps; | ||
| 92 | |||
| 93 | QTimer *m_timeoutTimer = nullptr; | ||
| 94 | ImapConfig m_config; | ||
| 95 | QStringList m_pendingCapabilities; | ||
| 96 | SetupPhase m_setupPhase = SetupPhase::AwaitingGreeting; | ||
| 97 | // T-720: True once tuneKeepAlive() ran (test seam for native tuning). | ||
| 98 | bool m_keepAliveTuned = false; | ||
| 99 | }; | ||
| 100 |