/* * Copyright (c) 2021, Kyle Pereira * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include namespace IMAP { enum class CommandType { Append, Authenticate, Capability, Copy, Check, Close, Create, Delete, Examine, Expunge, Fetch, Idle, List, ListSub, Login, Logout, Noop, Rename, Search, Select, Status, Store, Subscribe, UIDCopy, UIDFetch, UIDSearch, UIDStore, Unsubscribe, }; enum class MailboxFlag : unsigned { All = 1u << 0, Drafts = 1u << 1, Flagged = 1u << 2, HasChildren = 1u << 3, HasNoChildren = 1u << 4, Important = 1u << 5, Junk = 1u << 6, Marked = 1u << 7, NoInferiors = 1u << 8, NoSelect = 1u << 9, Sent = 1u << 10, Trash = 1u << 11, Unmarked = 1u << 12, Unknown = 1u << 13, }; enum class ResponseType : unsigned { Capability = 1u << 0, List = 1u << 1, Exists = 1u << 2, Recent = 1u << 3, Flags = 1u << 4, UIDNext = 1u << 5, UIDValidity = 1u << 6, Unseen = 1u << 7, PermanentFlags = 1u << 8, Fetch = 1u << 9, Search = 1u << 10, ListSub = 1u << 11, Expunged = 1u << 12, Bye = 1u << 13, Status = 1u << 14 }; enum class FetchResponseType : unsigned { Body = 1u << 1, UID = 1u << 2, InternalDate = 1u << 3, Envelope = 1u << 4, Flags = 1u << 5, BodyStructure = 1u << 6, }; enum class StatusItemType : unsigned { Recent = 1u << 1, UIDNext = 1u << 2, UIDValidity = 1u << 3, Unseen = 1u << 4, Messages = 1u << 5, }; class Parser; class StatusItem { public: [[nodiscard]] unsigned status_items() const { return m_status_items; } [[nodiscard]] bool contains_status_item_type(StatusItemType type) const { return (static_cast(type) & m_status_items) != 0; } void add_status_item_type(StatusItemType type) { m_status_items |= static_cast(type); } void set_mailbox(String&& mailbox) { m_mailbox = move(mailbox); } String& mailbox() { return m_mailbox; } unsigned get(StatusItemType type) const { VERIFY(contains_status_item_type(type)); switch (type) { case StatusItemType::Recent: return m_recent; case StatusItemType::UIDNext: return m_uid_next; case StatusItemType::UIDValidity: return m_uid_validity; case StatusItemType::Unseen: return m_unseen; case StatusItemType::Messages: return m_messages; } VERIFY_NOT_REACHED(); } void set(StatusItemType type, unsigned value) { add_status_item_type(type); switch (type) { case StatusItemType::Recent: m_recent = value; break; case StatusItemType::UIDNext: m_uid_next = value; break; case StatusItemType::UIDValidity: m_uid_validity = value; break; case StatusItemType::Unseen: m_unseen = value; break; case StatusItemType::Messages: m_uid_next = value; break; } } private: unsigned m_status_items { 0 }; unsigned m_messages { 0 }; unsigned m_recent { 0 }; unsigned m_uid_next { 0 }; unsigned m_uid_validity { 0 }; unsigned m_unseen { 0 }; String m_mailbox; }; struct Address { Optional name; Optional source_route; Optional mailbox; Optional host; }; struct Envelope { Optional date; // Format of date not specified. Optional subject; Optional> from; Optional> sender; Optional> reply_to; Optional> to; Optional> cc; Optional> bcc; Optional in_reply_to; Optional message_id; }; class BodyStructure; struct BodyExtension { AK::Variant, unsigned, Vector>> data; }; struct MultiPartBodyStructureData { Optional>> disposition; Vector> bodies; Vector langs; String media_type; Optional> params; Optional location; Optional> extensions; }; struct BodyStructureData { String type; String subtype; Optional id {}; Optional desc {}; String encoding; HashMap fields; unsigned bytes { 0 }; unsigned lines { 0 }; Optional envelope; Optional md5 {}; Optional>> disposition {}; Optional> langs {}; Optional location {}; Optional> extensions {}; }; class BodyStructure { friend Parser; public: explicit BodyStructure(BodyStructureData&& data) : m_data(move(data)) { } explicit BodyStructure(MultiPartBodyStructureData&& data) : m_data(move(data)) { } private: AK::Variant m_data; }; // Set -1 for '*' i.e highest possible value. struct Sequence { int start; int end; [[nodiscard]] String serialize() const; }; struct FetchCommand { enum class DataItemType { BodyStructure, Envelope, Flags, InternalDate, UID, PeekBody, BodySection }; struct DataItem { enum class SectionType { Header, HeaderFields, HeaderFieldsNot, Text, Parts }; struct Section { SectionType type; Optional> parts {}; bool ends_with_mime {}; Optional> headers {}; [[nodiscard]] String serialize() const; }; DataItemType type; Optional
section {}; bool partial_fetch { false }; int start { 0 }; int octets { 0 }; [[nodiscard]] String serialize() const; }; Vector sequence_set; Vector data_items; String serialize(); }; struct Command { public: CommandType type; int tag; Vector args; }; enum class ResponseStatus { Bad, No, OK, }; struct ListItem { unsigned flags; String reference; String name; }; class FetchResponseData { public: [[nodiscard]] unsigned response_type() const { return m_response_type; } [[nodiscard]] bool contains_response_type(FetchResponseType response_type) const { return (static_cast(response_type) & m_response_type) != 0; } void add_response_type(FetchResponseType type) { m_response_type |= static_cast(type); } void add_body_data(FetchCommand::DataItem&& data_item, Optional&& body) { add_response_type(FetchResponseType::Body); m_bodies.append({ move(data_item), move(body) }); } Vector>>& body_data() { VERIFY(contains_response_type(FetchResponseType::Body)); return m_bodies; } void set_uid(unsigned uid) { add_response_type(FetchResponseType::UID); m_uid = uid; } [[nodiscard]] unsigned uid() const { VERIFY(contains_response_type(FetchResponseType::UID)); return m_uid; } void set_internal_date(Core::DateTime time) { add_response_type(FetchResponseType::InternalDate); m_internal_date = time; } Core::DateTime& internal_date() { VERIFY(contains_response_type(FetchResponseType::InternalDate)); return m_internal_date; } void set_envelope(Envelope&& envelope) { add_response_type(FetchResponseType::Envelope); m_envelope = move(envelope); } Envelope& envelope() { VERIFY(contains_response_type(FetchResponseType::Envelope)); return m_envelope; } void set_flags(Vector&& flags) { add_response_type(FetchResponseType::Flags); m_flags = move(flags); } Vector& flags() { VERIFY(contains_response_type(FetchResponseType::Flags)); return m_flags; } void set_body_structure(BodyStructure&& structure) { add_response_type(FetchResponseType::BodyStructure); m_body_structure = move(structure); } BodyStructure& body_structure() { VERIFY(contains_response_type(FetchResponseType::BodyStructure)); return m_body_structure; } FetchResponseData() : m_body_structure(BodyStructureData {}) { } private: Vector m_flags; Vector>> m_bodies; Core::DateTime m_internal_date; Envelope m_envelope; unsigned m_uid { 0 }; unsigned m_response_type { 0 }; BodyStructure m_body_structure; }; String serialize_astring(StringView string); struct SearchKey { public: // clang-format off struct All { }; struct Answered { }; struct Bcc { String bcc; }; struct Cc { String cc; }; struct Deleted { }; struct Draft { }; struct From { String from; }; struct Header { String header; String value; }; struct Keyword { String keyword; }; struct Larger { unsigned number; }; struct New { }; struct Not { OwnPtr operand; }; struct Old { }; struct On { Core::DateTime date; }; struct Or { OwnPtr lhs; OwnPtr rhs; }; struct Recent { }; struct SearchKeys { Vector> keys; }; struct Seen { }; struct SentBefore { Core::DateTime date; }; struct SentOn { Core::DateTime date; }; struct SentSince { Core::DateTime date; }; struct SequenceSet { Sequence sequence; }; struct Since { Core::DateTime date; }; struct Smaller { unsigned number; }; struct Subject { String subject; }; struct Text { String text; }; struct To { String to; }; struct UID { unsigned uid; }; struct Unanswered { }; struct Undeleted { }; struct Undraft { }; struct Unkeyword { String flag_keyword; }; struct Unseen { }; // clang-format on Variant data; SearchKey(SearchKey&& other) noexcept : data(move(other.data)) { } template explicit SearchKey(T&& t) : data(std::forward(t)) { } SearchKey& operator=(SearchKey&& other) noexcept { if (this == &other) { return *this; } this->data = move(other.data); return *this; } [[nodiscard]] String serialize() const; }; class ResponseData { public: [[nodiscard]] unsigned response_type() const { return m_response_type; } ResponseData() : m_response_type(0) { } ResponseData(ResponseData&) = delete; ResponseData(ResponseData&&) = default; ResponseData& operator=(const ResponseData&) = delete; ResponseData& operator=(ResponseData&&) = default; [[nodiscard]] bool contains_response_type(ResponseType response_type) const { return (static_cast(response_type) & m_response_type) != 0; } void add_response_type(ResponseType response_type) { m_response_type = m_response_type | static_cast(response_type); } void add_capabilities(Vector&& capabilities) { m_capabilities = move(capabilities); add_response_type(ResponseType::Capability); } Vector& capabilities() { VERIFY(contains_response_type(ResponseType::Capability)); return m_capabilities; } void add_list_item(ListItem&& item) { add_response_type(ResponseType::List); m_list_items.append(move(item)); } Vector& list_items() { VERIFY(contains_response_type(ResponseType::List)); return m_list_items; } void add_lsub_item(ListItem&& item) { add_response_type(ResponseType::List); m_lsub_items.append(move(item)); } Vector& lsub_items() { VERIFY(contains_response_type(ResponseType::ListSub)); return m_lsub_items; } void set_exists(unsigned exists) { add_response_type(ResponseType::Exists); m_exists = exists; } [[nodiscard]] unsigned exists() const { VERIFY(contains_response_type(ResponseType::Exists)); return m_exists; } void set_recent(unsigned recent) { add_response_type(ResponseType::Recent); m_recent = recent; } [[nodiscard]] unsigned recent() const { VERIFY(contains_response_type(ResponseType::Recent)); return m_recent; } void set_uid_next(unsigned uid_next) { add_response_type(ResponseType::UIDNext); m_uid_next = uid_next; } [[nodiscard]] unsigned uid_next() const { VERIFY(contains_response_type(ResponseType::UIDNext)); return m_uid_next; } void set_uid_validity(unsigned uid_validity) { add_response_type(ResponseType::UIDValidity); m_uid_validity = uid_validity; } [[nodiscard]] unsigned uid_validity() const { VERIFY(contains_response_type(ResponseType::UIDValidity)); return m_uid_validity; } void set_unseen(unsigned unseen) { add_response_type(ResponseType::Unseen); m_unseen = unseen; } [[nodiscard]] unsigned unseen() const { VERIFY(contains_response_type(ResponseType::Unseen)); return m_unseen; } void set_flags(Vector&& flags) { m_response_type |= static_cast(ResponseType::Flags); m_flags = move(flags); } Vector& flags() { VERIFY(contains_response_type(ResponseType::Flags)); return m_flags; } void set_permanent_flags(Vector&& flags) { add_response_type(ResponseType::PermanentFlags); m_permanent_flags = move(flags); } Vector& permanent_flags() { VERIFY(contains_response_type(ResponseType::PermanentFlags)); return m_permanent_flags; } void add_fetch_response(unsigned message, FetchResponseData&& data) { add_response_type(ResponseType::Fetch); m_fetch_responses.append(Tuple { move(message), move(data) }); } Vector>& fetch_data() { VERIFY(contains_response_type(ResponseType::Fetch)); return m_fetch_responses; } void set_search_results(Vector&& results) { add_response_type(ResponseType::Search); m_search_results = move(results); } Vector& search_results() { VERIFY(contains_response_type(ResponseType::Search)); return m_search_results; } void add_expunged(unsigned message) { add_response_type(ResponseType::Expunged); m_expunged.append(message); } Vector& expunged() { VERIFY(contains_response_type(ResponseType::Expunged)); return m_expunged; } void set_bye(Optional message) { add_response_type(ResponseType::Bye); m_bye_message = move(message); } Optional& bye_message() { VERIFY(contains_response_type(ResponseType::Bye)); return m_bye_message; } void set_status(StatusItem&& status_item) { add_response_type(ResponseType::Status); m_status_item = move(status_item); } StatusItem& status_item() { return m_status_item; } private: unsigned m_response_type; Vector m_capabilities; Vector m_list_items; Vector m_lsub_items; Vector m_expunged; unsigned m_recent {}; unsigned m_exists {}; unsigned m_uid_next {}; unsigned m_uid_validity {}; unsigned m_unseen {}; Vector m_permanent_flags; Vector m_flags; Vector> m_fetch_responses; Vector m_search_results; Optional m_bye_message; StatusItem m_status_item; }; enum class StoreMethod { Replace, Add, Remove }; class SolidResponse { // Parser is allowed to set up fields friend class Parser; public: ResponseStatus status() { return m_status; } int tag() const { return m_tag; } ResponseData& data() { return m_data; } String response_text() { return m_response_text; }; SolidResponse() : SolidResponse(ResponseStatus::Bad, -1) { } SolidResponse(ResponseStatus status, int tag) : m_status(status) , m_tag(tag) , m_data(ResponseData()) { } private: ResponseStatus m_status; String m_response_text; unsigned m_tag; ResponseData m_data; }; struct ContinueRequest { String data; }; template class Promise : public Core::Object { C_OBJECT(Promise); private: Optional m_pending; public: Function on_resolved; void resolve(Result&& result) { m_pending = move(result); if (on_resolved) on_resolved(m_pending.value()); } bool is_resolved() { return m_pending.has_value(); }; Result await() { while (!is_resolved()) { Core::EventLoop::current().pump(); } return m_pending.release_value(); } // Converts a Promise to a Promise using a function func: A -> B template RefPtr> map(T func(Result&)) { RefPtr> new_promise = Promise::construct(); on_resolved = [new_promise, func](Result& result) mutable { auto t = func(result); new_promise->resolve(move(t)); }; return new_promise; } }; using Response = Variant; } // An RFC 2822 message // https://datatracker.ietf.org/doc/html/rfc2822 struct Message { String data; };