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: 87.5% 7 / 0 / 8
Functions: 87.5% 7 / 0 / 8
Branches: -% 0 / 0 / 0

service/ImapLineReader.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QByteArray>
4 #include <QString>
5 #include <QTemporaryFile>
6
7 #include <functional>
8 #include <memory>
9
10 #include "data/MailRequest.h"
11
12 class ImapCommandTable;
13 class QRegularExpressionMatch;
14 class QSslSocket;
15
16 // Response framing of one IMAP connection (Sprint 92 S92.1.1, extracted
17 // from ImapService): reads the socket, splits physical lines, streams
18 // literals out of the parser buffer (body literals stay bytes and spool to a
19 // private temp file above a threshold, T-87.C2), folds non-body literals
20 // into the logical line, enforces the buffer/literal limits and hands every
21 // complete logical line to the service's untagged/tagged/continuation
22 // dispatch.
23 //
24 // Owner: ImapService (std::unique_ptr), connection thread. Plain class; the
25 // socket's readyRead stays connected to ImapService::onReadyRead, which
26 // delegates here. The active body request (owned by the service until its
27 // body-fetch unit exists) is read and written through BodyRequest
28 // references; command budgets and the pending-command table through the
29 // command table; failure, dispatch, body completion and the two body
30 // signals through Deps (the emitter stays ImapService).
31 class ImapLineReader {
32 public:
33 // References to the active body request's correlation state.
34 struct BodyRequest {
35 qint64 &fetchUid;
36 qint64 &fetchLimit;
37 qint64 &requestBytes;
38 bool &requiresSelect;
39 quint64 &requestId;
40 };
41 struct Deps {
42 std::function<void(const QString &line)> handleUntagged;
43 std::function<void(const QString &line)> handleTagged;
44 std::function<void(const QString &line)> handleContinuation;
45 std::function<void(const QString &error)> failConnection;
46 std::function<void(BodyOutcome outcome, QByteArray bytes, const QString &error,
47 qint64 maxBytes)>
48 completeBodyRequest;
49 // ImapService signals.
50 std::function<void(qint64 uid, const QByteArray &rawBody)> rawBodyReceived;
51 std::function<void(qint64 uid, qint64 maxBytes)> bodyFetchTooLarge;
52 // MAILJD_E2E_TESTING only (present in every build for a stable layout).
53 std::function<void(const QString &phase, quint64 requestToken)> reportE2eTransportPhase;
54 // Monotonic milliseconds (S92.5.2; tests inject a fake clock).
55 std::function<qint64()> nowMs;
56 };
57 ImapLineReader(QSslSocket *socket, ImapCommandTable &commands, BodyRequest body,
58 Deps deps);
59
60 void onReadyRead();
61 // The socket-free part of onReadyRead: frames bytes received in any
62 // chunking (Sprint 102 fuzz target imap_line_assembler feeds it directly;
63 // the socket is not touched).
64 void feed(const QByteArray &incoming);
65 void processLine(const QString &line);
66 void completeLiteral();
67 // Drops all framing state of the session (connect, disconnect, failure).
68 void resetFraming();
69
70 // Framing state, reset by the service's session lifecycle (single owner:
71 // this reader).
72 39 QByteArray &readBuffer() { return m_readBuffer; }
73 ✗ quint64 receivedBytes() const { return m_receivedBytes; }
74 // S92.5.2: milliseconds since the last received byte (-1 before any).
75 qint64 quietMs() const;
76 13 qint64 &lastReceiveMs() { return m_lastReceiveMs; }
77 4 qint64 &literalBytesRemaining() { return m_literalBytesRemaining; }
78 2 QString &literalLine() { return m_literalLine; }
79 15 QByteArray &literalData() { return m_literalData; }
80 7 std::unique_ptr<QTemporaryFile> &bodySpool() { return m_bodySpool; }
81 4 qint64 &bodySpoolThreshold() { return m_bodySpoolThreshold; }
82 bool &discardingOversizedBody() { return m_discardingOversizedBody; }
83 bool &skipFetchLiteralRemainder() { return m_skipFetchLiteralRemainder; }
84 bool &isBodyLiteral() { return m_isBodyLiteral; }
85 qint64 &bodyLiteralUid() { return m_bodyLiteralUid; }
86 bool &discardingInvalidBody() { return m_discardingInvalidBody; }
87
88 static constexpr qint64 MAX_READ_BUFFER_SIZE = 4LL * 1024 * 1024;
89
90 private:
91 void completeBodyRequest(BodyOutcome outcome, QByteArray bytes = {},
92 const QString &error = {}, qint64 maxBytes = 0);
93 bool handleQuotedBody(const QByteArray &lineBytes);
94 bool startLiteral(const QString &line, const QRegularExpressionMatch &match);
95 bool startBodySpool();
96 void discardBodySpool();
97
98 QSslSocket *m_socket;
99 ImapCommandTable &m_commands;
100 BodyRequest m_body;
101 Deps m_deps;
102
103 QByteArray m_readBuffer;
104 quint64 m_receivedBytes = 0;
105 qint64 m_lastReceiveMs = -1;
106
107 // IMAP literal accumulation: {N}\r\n followed by N raw bytes
108 qint64 m_literalBytesRemaining = 0; // T-510: was int, overflow on >2GB
109 QString m_literalLine; // line that triggered the literal
110 QByteArray m_literalData; // accumulated raw bytes
111 // T-87.C2: body literals above the threshold spool to a private temp file
112 // instead of RAM; completeLiteral reads them back once for the transport.
113 std::unique_ptr<QTemporaryFile> m_bodySpool;
114 qint64 m_bodySpoolExpectedBytes = 0; // the announced literal size
115 bool m_bodySpoolWriteFailed = false; // S106-002: a short spool write
116 // Member (not constant) so tests can lower the threshold.
117 qint64 m_bodySpoolThreshold = 8LL * 1024 * 1024;
118 bool m_discardingOversizedBody = false;
119 bool m_skipFetchLiteralRemainder = false;
120
121 // Body literal bypass: when fetching BODY[], keep raw QByteArray
122 // instead of converting through QString (which corrupts binary MIME data)
123 bool m_isBodyLiteral = false;
124 qint64 m_bodyLiteralUid = -1;
125 bool m_discardingInvalidBody = false;
126 };
127