service/Rfc2822Builder.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QDateTime> | ||
| 5 | #include <QString> | ||
| 6 | #include <QStringList> | ||
| 7 | |||
| 8 | // T-183: Builds RFC-2822 compliant email messages. | ||
| 9 | // Used by the composer backend (QuickComposeBackend) for sending (SMTP), | ||
| 10 | // draft saving (IMAP APPEND) and Sent-folder copies (IMAP APPEND), and by | ||
| 11 | // the invitation reply (iMIP). | ||
| 12 | class Rfc2822Builder { | ||
| 13 | public: | ||
| 14 | 403 | Rfc2822Builder() = default; | |
| 15 | |||
| 16 | // Header setters | ||
| 17 | void setFrom(const QString &name, const QString &email); | ||
| 18 | void setTo(const QStringList &recipients); | ||
| 19 | void setCc(const QStringList &cc); | ||
| 20 | void setBcc(const QStringList &bcc); | ||
| 21 | void setSubject(const QString &subject); | ||
| 22 | void setDate(const QDateTime &date = QDateTime::currentDateTimeUtc()); | ||
| 23 | void setMessageId(const QString &messageId = {}); | ||
| 24 | void setInReplyTo(const QString &messageId); | ||
| 25 | void setReferences(const QStringList &messageIds); | ||
| 26 | |||
| 27 | enum class BccHeader { Omit, Include }; | ||
| 28 | |||
| 29 | // Sprint 108 S108.2.4 (D-27, D-15, D-21): optional headers. Priority 1 | ||
| 30 | // (high) and 5 (low) write Importance and X-Priority; 3 writes nothing. | ||
| 31 | 5 | void setReplyTo(const QStringList &addresses) { m_replyTo = addresses; } | |
| 32 | 67 | void setPriority(int priority) { m_priority = priority; } | |
| 33 | 5 | void setDispositionNotificationTo(const QString &address) { m_dispositionNotificationTo = address; } | |
| 34 | // A private X-MailJD-* header (the resumable draft's identity, format and | ||
| 35 | // reference, D-21). Other names are ignored; values are header-safe. | ||
| 36 | void setMailJdHeader(const QString &name, const QString &value); | ||
| 37 | |||
| 38 | // Body | ||
| 39 | void setBodyText(const QString &plainText); | ||
| 40 | // Sprint 108 S108.2.4 (D-4, D-8): a formatted mail — the body becomes | ||
| 41 | // multipart/alternative of the text and this HTML (MailHtmlWriter output); | ||
| 42 | // inline images make the HTML a multipart/related with their cid: parts. | ||
| 43 | // Without HTML every build is byte-identical to the plain-text builder (G2). | ||
| 44 | 34 | void setBodyHtml(const QString &html) { m_bodyHtml = html; } | |
| 45 | struct InlineImage { | ||
| 46 | QString contentId; // without <> | ||
| 47 | QString filename; | ||
| 48 | QByteArray data; | ||
| 49 | QByteArray mimeType; // e.g. "image/png" | ||
| 50 | }; | ||
| 51 | 6 | void addInlineImage(const InlineImage &image) { m_inlineImages.append(image); } | |
| 52 | // Sprint 93 S93.5.2: an iMIP message (RFC 6047 §2.4): the body becomes | ||
| 53 | // multipart/alternative of the text and | ||
| 54 | // "text/calendar; method=<method>; charset=UTF-8". Not combined with | ||
| 55 | // attachments (attachments win). | ||
| 56 | void setCalendarPart(const QByteArray &calendar, const QString &method); | ||
| 57 | |||
| 58 | // Attachments | ||
| 59 | struct Attachment { | ||
| 60 | QString filename; | ||
| 61 | QByteArray data; | ||
| 62 | QByteArray mimeType; // e.g. "application/octet-stream" | ||
| 63 | }; | ||
| 64 | void addAttachment(const Attachment &attachment); | ||
| 65 | // Shared per-file size budget (F1): matches ubiquitous MTA limits and keeps | ||
| 66 | // a single attachment from exhausting RAM. The size is checked BEFORE any | ||
| 67 | // read, so oversize files never enter memory; the post-read check covers | ||
| 68 | // files that grew concurrently. Callers may pass a tighter budget. | ||
| 69 | static constexpr qint64 MaxAttachmentBytes = 25LL * 1024 * 1024; | ||
| 70 | // T-622/FUNC-07: Returns false if file cannot be opened | ||
| 71 | // F1: Returns false without reading when the file exceeds maxBytes. | ||
| 72 | bool addAttachmentFromFile(const QString &filePath, | ||
| 73 | qint64 maxBytes = MaxAttachmentBytes, | ||
| 74 | qint64 *loadedBytes = nullptr); | ||
| 75 | |||
| 76 | // D-17: the MIME type of a file from its content and name (QMimeDatabase); | ||
| 77 | // "application/octet-stream" when unknown. | ||
| 78 | static QByteArray mimeTypeForFile(const QString &filePath, const QByteArray &head = {}); | ||
| 79 | |||
| 80 | // Build the final RFC-2822 message (CRLF line-endings, proper encoding) | ||
| 81 | QByteArray build(BccHeader bccHeader) const; | ||
| 82 | |||
| 83 | // S108.0.3: MIME boundaries become "----=_Part_<seed>_<n>" (n counts up | ||
| 84 | // per build) instead of random UUIDs, so golden outputs are byte-stable. | ||
| 85 | // Only the MIME oracles and fuzz targets use it. | ||
| 86 | 14 | void setDeterministicBoundaries(const QByteArray &seed) { m_boundarySeed = seed; } | |
| 87 | |||
| 88 | // Access generated Message-ID (valid after build()) | ||
| 89 | 84 | QString messageId() const { return m_messageId; } | |
| 90 | // The Message-ID header value of a built message ("" when it has none). | ||
| 91 | static QString messageIdOf(const QByteArray &message); | ||
| 92 | |||
| 93 | // Utility: RFC 2047 encode a header value if it contains non-ASCII | ||
| 94 | // Longest whitespace-free run sent as plain text; a longer one is sent as | ||
| 95 | // encoded words, so a header line never exceeds RFC 5322's 998 bytes. | ||
| 96 | static constexpr qsizetype kMaxUnfoldableToken = 900; | ||
| 97 | static QByteArray encodeHeader(const QString &value); | ||
| 98 | |||
| 99 | // Utility: Encode address "Name <email>" with RFC-2047 for the name part | ||
| 100 | static QByteArray encodeAddress(const QString &name, const QString &email); | ||
| 101 | |||
| 102 | // Utility: Encode a list of addresses for To/Cc/Bcc headers | ||
| 103 | static QByteArray encodeAddressList(const QStringList &addresses); | ||
| 104 | |||
| 105 | // Utility: Quoted-Printable encode a UTF-8 body | ||
| 106 | static QByteArray encodeQuotedPrintable(const QByteArray &data); | ||
| 107 | |||
| 108 | private: | ||
| 109 | QByteArray generateBoundary() const; | ||
| 110 | void appendAttachmentParts(QByteArray &msg, const QByteArray &boundary) const; | ||
| 111 | void appendFormattedBody(QByteArray &msg) const; | ||
| 112 | static QByteArray generateMessageId(const QString &senderAddress); | ||
| 113 | |||
| 114 | QString m_fromName; | ||
| 115 | QString m_fromEmail; | ||
| 116 | QStringList m_to; | ||
| 117 | QStringList m_cc; | ||
| 118 | QStringList m_bcc; | ||
| 119 | QString m_subject; | ||
| 120 | QDateTime m_date; | ||
| 121 | mutable QString m_messageId; | ||
| 122 | QString m_inReplyTo; | ||
| 123 | QStringList m_references; | ||
| 124 | QString m_bodyText; | ||
| 125 | QString m_bodyHtml; | ||
| 126 | QList<InlineImage> m_inlineImages; | ||
| 127 | QStringList m_replyTo; | ||
| 128 | int m_priority = 3; | ||
| 129 | QString m_dispositionNotificationTo; | ||
| 130 | QList<QPair<QByteArray, QString>> m_mailJdHeaders; | ||
| 131 | QByteArray m_calendar; | ||
| 132 | QByteArray m_calendarMethod; | ||
| 133 | QList<Attachment> m_attachments; | ||
| 134 | QByteArray m_boundarySeed; | ||
| 135 | mutable int m_boundaryCounter = 0; | ||
| 136 | }; | ||
| 137 |