service/ManageSieveProtocol.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QList> | ||
| 5 | #include <QMap> | ||
| 6 | #include <QString> | ||
| 7 | #include <QStringList> | ||
| 8 | #include <QVariantMap> | ||
| 9 | |||
| 10 | // ManageSieve (RFC 5804) wire format (Sprint 95 S95.1.1/S95.1.2). | ||
| 11 | // | ||
| 12 | // Pure functions: response tokenizing, response classification, | ||
| 13 | // capability parsing and command formatting. No sockets — the client state | ||
| 14 | // machine (ManageSieveClient) feeds received bytes in and writes the | ||
| 15 | // formatted commands out, so every framing rule is unit-testable. | ||
| 16 | // | ||
| 17 | // Invariants: | ||
| 18 | // - Server lines end with CRLF; bare LF is accepted. | ||
| 19 | // - Server literals are "{n}" CRLF + n octets (also "{n+}"); the client | ||
| 20 | // always sends non-synchronizing literals "{n+}" (RFC 5804 §4). | ||
| 21 | // - A line longer than the line limit or a literal larger than the literal | ||
| 22 | // limit is a protocol error, never an unbounded buffer. | ||
| 23 | namespace ManageSieve { | ||
| 24 | |||
| 25 | struct Token { | ||
| 26 | enum class Kind { Atom, String, LeftParen, RightParen }; | ||
| 27 | Kind kind = Kind::Atom; | ||
| 28 | QByteArray text; // Atom: as sent; String: decoded bytes | ||
| 29 | }; | ||
| 30 | |||
| 31 | enum class LineStatus { Complete, Incomplete, Error }; | ||
| 32 | |||
| 33 | struct LineLimits { | ||
| 34 | int maxLine = 64 * 1024; | ||
| 35 | int maxLiteral = 1024 * 1024 + 64 * 1024; | ||
| 36 | // S106-099: LISTSCRIPTS answers are capped in entries and name bytes. | ||
| 37 | int maxListEntries = 10000; | ||
| 38 | qint64 maxListBytes = 4 * 1024 * 1024; | ||
| 39 | }; | ||
| 40 | |||
| 41 | // Reads one logical line (which may contain literals) from the front of | ||
| 42 | // `buffer`. On Complete, `consumed` is the number of bytes to drop. | ||
| 43 | LineStatus readLine(const QByteArray &buffer, const LineLimits &limits, QList<Token> *tokens, | ||
| 44 | int *consumed, QString *error); | ||
| 45 | |||
| 46 | struct Response { | ||
| 47 | enum class Status { None, Ok, No, Bye }; | ||
| 48 | Status status = Status::None; | ||
| 49 | QString code; // upper case, e.g. "WARNINGS", "QUOTA/MAXSCRIPTS" | ||
| 50 | QStringList codeArgs; // e.g. REFERRAL URL | ||
| 51 | QString text; // human-readable text (UTF-8) | ||
| 52 | |||
| 53 | bool isFinal() const { return status != Status::None; } | ||
| 54 | 2823 | bool ok() const { return status == Status::Ok; } | |
| 55 | }; | ||
| 56 | |||
| 57 | // Classifies a tokenized line: a final response (OK/NO/BYE) or a data line. | ||
| 58 | Response parseResponse(const QList<Token> &tokens); | ||
| 59 | bool isFinalResponse(const QList<Token> &tokens); | ||
| 60 | |||
| 61 | struct Capabilities { | ||
| 62 | QString implementation; | ||
| 63 | QStringList sieveExtensions; // "SIEVE" capability, in server order | ||
| 64 | QStringList saslMechanisms; // upper case | ||
| 65 | bool startTls = false; | ||
| 66 | QString version; // "1.0" when CHECKSCRIPT etc. exist | ||
| 67 | int maxRedirects = -1; // -1 = not announced | ||
| 68 | QStringList notifyMethods; | ||
| 69 | QString language; | ||
| 70 | QString owner; | ||
| 71 | bool unauthenticate = false; | ||
| 72 | |||
| 73 |
1/4✗ Branch 3 → 4 not taken.
✓ Branch 3 → 7 taken 215 times.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
|
215 | bool isEmpty() const { return implementation.isEmpty() && sieveExtensions.isEmpty(); } |
| 74 | 9247 | bool supports(const QString &extension) const { return sieveExtensions.contains(extension); } | |
| 75 | 724 | bool hasCheckScript() const { return !version.isEmpty(); } | |
| 76 | QStringList comparators() const; // "i;ascii-numeric", … from comparator-* | ||
| 77 | QVariantMap toVariantMap() const; // read-only map for QML (D-4) | ||
| 78 | }; | ||
| 79 | |||
| 80 | // Applies one capability data line ("NAME" ["value"]) to `capabilities`. | ||
| 81 | void applyCapabilityLine(const QList<Token> &tokens, Capabilities *capabilities); | ||
| 82 | |||
| 83 | struct ScriptEntry { | ||
| 84 | QString name; | ||
| 85 | bool active = false; | ||
| 86 | }; | ||
| 87 | |||
| 88 | // "name" [ACTIVE] | ||
| 89 | bool parseScriptEntry(const QList<Token> &tokens, ScriptEntry *entry); | ||
| 90 | |||
| 91 | // One error position reported by CHECKSCRIPT/PUTSCRIPT (Pigeonhole and | ||
| 92 | // Cyrus wording: "line 12: …", "line 3, column 7: …"). | ||
| 93 | struct ServerError { | ||
| 94 | int line = 0; | ||
| 95 | int column = 0; | ||
| 96 | QString message; | ||
| 97 | }; | ||
| 98 | QList<ServerError> parseServerErrors(const QString &text); | ||
| 99 | |||
| 100 | // Command formatting. Strings without CR/LF/NUL/quotes-only content are sent | ||
| 101 | // quoted; everything else as a non-synchronizing literal. | ||
| 102 | QByteArray quotedOrLiteral(const QByteArray &value); | ||
| 103 | QByteArray literal(const QByteArray &value); | ||
| 104 | QByteArray authenticatePlain(const QByteArray &username, const QByteArray &password); | ||
| 105 | QByteArray command(const QByteArray &name, const QList<QByteArray> &arguments = {}); | ||
| 106 | |||
| 107 | } // namespace ManageSieve | ||
| 108 |