diff --git a/Userland/Libraries/LibIMAP/Objects.cpp b/Userland/Libraries/LibIMAP/Objects.cpp index 8ac3efafe5..d534fac6b8 100644 --- a/Userland/Libraries/LibIMAP/Objects.cpp +++ b/Userland/Libraries/LibIMAP/Objects.cpp @@ -74,7 +74,7 @@ String FetchCommand::DataItem::serialize() const return "UID"; case DataItemType::PeekBody: TODO(); - case DataItemType::BodySection: + case DataItemType::BodySection: { StringBuilder sb; sb.appendff("BODY[{}]", section.value().serialize()); if (partial_fetch) { @@ -83,6 +83,9 @@ String FetchCommand::DataItem::serialize() const return sb.build(); } + case DataItemType::BodyStructure: + return "BODYSTRUCTURE"; + } VERIFY_NOT_REACHED(); } String FetchCommand::serialize() diff --git a/Userland/Libraries/LibIMAP/Objects.h b/Userland/Libraries/LibIMAP/Objects.h index 4123845b9b..66ba6436d6 100644 --- a/Userland/Libraries/LibIMAP/Objects.h +++ b/Userland/Libraries/LibIMAP/Objects.h @@ -65,10 +65,84 @@ enum class FetchResponseType : unsigned { InternalDate = 1u << 3, Envelope = 1u << 4, Flags = 1u << 5, + BodyStructure = 1u << 6, }; class Parser; +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; @@ -79,6 +153,7 @@ struct Sequence { struct FetchCommand { enum class DataItemType { + BodyStructure, Envelope, Flags, InternalDate, @@ -140,25 +215,6 @@ struct ListItem { String name; }; -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 FetchResponseData { public: [[nodiscard]] unsigned response_type() const @@ -236,7 +292,20 @@ public: 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 {}) { } @@ -247,6 +316,7 @@ private: Envelope m_envelope; unsigned m_uid { 0 }; unsigned m_response_type { 0 }; + BodyStructure m_body_structure; }; class ResponseData { diff --git a/Userland/Libraries/LibIMAP/Parser.cpp b/Userland/Libraries/LibIMAP/Parser.cpp index bff181aad5..2e0c8d7530 100644 --- a/Userland/Libraries/LibIMAP/Parser.cpp +++ b/Userland/Libraries/LibIMAP/Parser.cpp @@ -225,41 +225,14 @@ FetchResponseData Parser::parse_fetch_response() while (!try_consume(")")) { auto data_item = parse_fetch_data_item(); switch (data_item.type) { - case FetchCommand::DataItemType::Envelope: { + case FetchCommand::DataItemType::BodyStructure: { consume(" ("); - auto date = parse_nstring(); - consume(" "); - auto subject = parse_nstring(); - consume(" "); - auto from = parse_address_list(); - consume(" "); - auto sender = parse_address_list(); - consume(" "); - auto reply_to = parse_address_list(); - consume(" "); - auto to = parse_address_list(); - consume(" "); - auto cc = parse_address_list(); - consume(" "); - auto bcc = parse_address_list(); - consume(" "); - auto in_reply_to = parse_nstring(); - consume(" "); - auto message_id = parse_nstring(); - consume(")"); - Envelope envelope = { - date.has_value() ? Optional(date.value()) : Optional(), - subject.has_value() ? Optional(subject.value()) : Optional(), - from, - sender, - reply_to, - to, - cc, - bcc, - in_reply_to.has_value() ? Optional(in_reply_to.value()) : Optional(), - message_id.has_value() ? Optional(message_id.value()) : Optional(), - }; - fetch_response.set_envelope(move(envelope)); + auto structure = parse_body_structure(); + fetch_response.set_body_structure(move(structure)); + break; + } + case FetchCommand::DataItemType::Envelope: { + fetch_response.set_envelope(parse_envelope()); break; } case FetchCommand::DataItemType::Flags: { @@ -297,6 +270,236 @@ FetchResponseData Parser::parse_fetch_response() consume("\r\n"); return fetch_response; } +Envelope Parser::parse_envelope() +{ + consume(" ("); + auto date = parse_nstring(); + consume(" "); + auto subject = parse_nstring(); + consume(" "); + auto from = parse_address_list(); + consume(" "); + auto sender = parse_address_list(); + consume(" "); + auto reply_to = parse_address_list(); + consume(" "); + auto to = parse_address_list(); + consume(" "); + auto cc = parse_address_list(); + consume(" "); + auto bcc = parse_address_list(); + consume(" "); + auto in_reply_to = parse_nstring(); + consume(" "); + auto message_id = parse_nstring(); + consume(")"); + Envelope envelope = { + date.has_value() ? AK::Optional(date.value()) : AK::Optional(), + subject.has_value() ? AK::Optional(subject.value()) : AK::Optional(), + from, + sender, + reply_to, + to, + cc, + bcc, + in_reply_to.has_value() ? AK::Optional(in_reply_to.value()) : AK::Optional(), + message_id.has_value() ? AK::Optional(message_id.value()) : AK::Optional(), + }; + return envelope; +} +BodyStructure Parser::parse_body_structure() +{ + if (!at_end() && m_buffer[position] == '(') { + auto data = MultiPartBodyStructureData(); + while (try_consume("(")) { + auto child = parse_body_structure(); + data.bodies.append(make(move(child))); + } + consume(" "); + data.media_type = parse_string(); + + if (!try_consume(")")) { + consume(" "); + data.params = try_consume("NIL") ? Optional>() : parse_body_fields_params(); + if (!try_consume(")")) { + consume(" "); + if (!try_consume("NIL")) { + data.disposition = { parse_disposition() }; + } + + if (!try_consume(")")) { + consume(" "); + if (!try_consume("NIL")) { + data.langs = { parse_langs() }; + } + + if (!try_consume(")")) { + consume(" "); + data.location = try_consume("NIL") ? Optional() : Optional(parse_string()); + + if (!try_consume(")")) { + consume(" "); + Vector extensions; + while (!try_consume(")")) { + extensions.append(parse_body_extension()); + try_consume(" "); + } + data.extensions = { move(extensions) }; + } + } + } + } + } + + return BodyStructure(move(data)); + } else { + return parse_one_part_body(); + } +} +BodyStructure Parser::parse_one_part_body() +{ + auto type = parse_string(); + consume(" "); + auto subtype = parse_string(); + consume(" "); + if (type.equals_ignoring_case("TEXT")) { + // body-type-text + auto params = parse_body_fields_params(); + consume(" "); + auto id = parse_nstring(); + consume(" "); + auto description = parse_nstring(); + consume(" "); + auto encoding = parse_string(); + consume(" "); + auto num_octets = parse_number(); + consume(" "); + auto num_lines = parse_number(); + + auto data = BodyStructureData { + type, + subtype, + id.has_value() ? Optional(id.value()) : Optional(), + description.has_value() ? Optional(description.value()) : Optional(), + encoding, + params, + num_octets, + num_lines, + {} + }; + + if (!try_consume(")")) { + consume(" "); + auto md5 = parse_nstring(); + if (md5.has_value()) + data.md5 = { md5.value() }; + if (!try_consume(")")) { + consume(" "); + if (!try_consume("NIL")) { + auto disposition = parse_disposition(); + data.disposition = { disposition }; + } + + if (!try_consume(")")) { + consume(" "); + if (!try_consume("NIL")) { + data.langs = { parse_langs() }; + } + + if (!try_consume(")")) { + consume(" "); + auto location = parse_nstring(); + if (location.has_value()) + data.location = { location.value() }; + + Vector extensions; + while (!try_consume(")")) { + extensions.append(parse_body_extension()); + try_consume(" "); + } + data.extensions = { move(extensions) }; + } + } + } + } + + return BodyStructure(move(data)); + } else if (type.equals_ignoring_case("MESSAGE") && subtype.equals_ignoring_case("RFC822")) { + // body-type-message + auto params = parse_body_fields_params(); + consume(" "); + auto id = parse_nstring(); + consume(" "); + auto description = parse_nstring(); + consume(" "); + auto encoding = parse_string(); + consume(" "); + auto num_octets = parse_number(); + consume(" "); + auto envelope = parse_envelope(); + + BodyStructureData data { + type, + subtype, + id.has_value() ? Optional(id.value()) : Optional(), + description.has_value() ? Optional(description.value()) : Optional(), + encoding, + params, + num_octets, + 0, + envelope + }; + + return BodyStructure(move(data)); + } else { + // body-type-basic + auto params = parse_body_fields_params(); + consume(" "); + auto id = parse_nstring(); + consume(" "); + auto description = parse_nstring(); + consume(" "); + auto encoding = parse_string(); + consume(" "); + auto num_octets = parse_number(); + consume(" "); + + BodyStructureData data { + type, + subtype, + id.has_value() ? Optional(id.value()) : Optional(), + description.has_value() ? Optional(description.value()) : Optional(), + encoding, + params, + num_octets, + 0, + {} + }; + + return BodyStructure(move(data)); + } +} +Vector Parser::parse_langs() +{ + AK::Vector langs; + if (!try_consume("(")) { + langs.append(parse_string()); + } else { + while (!try_consume(")")) { + langs.append(parse_string()); + try_consume(" "); + } + } + return langs; +} +Tuple> Parser::parse_disposition() +{ + auto disposition_type = parse_string(); + consume(" "); + auto disposition_vals = parse_body_fields_params(); + consume(")"); + return { move(disposition_type), move(disposition_vals) }; +} StringView Parser::parse_literal_string() { @@ -510,6 +713,10 @@ FetchCommand::DataItem Parser::parse_fetch_data_item() return FetchCommand::DataItem { .type = FetchCommand::DataItemType::Envelope }; + } else if (msg_attr.equals_ignoring_case("BODY") || msg_attr.equals_ignoring_case("BODYSTRUCTURE")) { + return FetchCommand::DataItem { + .type = FetchCommand::DataItemType::BodyStructure + }; } else { dbgln("msg_attr not matched: {}", msg_attr); m_parsing_failed = true; @@ -557,4 +764,38 @@ StringView Parser::parse_astring() else return parse_atom(); } -} \ No newline at end of file +HashMap Parser::parse_body_fields_params() +{ + if (try_consume("NIL")) + return {}; + + HashMap fields; + consume("("); + while (!try_consume(")")) { + auto key = parse_string(); + consume(" "); + auto value = parse_string(); + fields.set(key, value); + try_consume(" "); + } + + return fields; +} +BodyExtension Parser::parse_body_extension() +{ + if (try_consume("NIL")) { + return BodyExtension { Optional {} }; + } else if (try_consume("(")) { + Vector> extensions; + while (!try_consume(")")) { + extensions.append(make(parse_body_extension())); + try_consume(" "); + } + return BodyExtension { move(extensions) }; + } else if (!at_end() && (m_buffer[position] == '"' || m_buffer[position] == '{')) { + return BodyExtension { { parse_string() } }; + } else { + return BodyExtension { parse_number() }; + } +} +} diff --git a/Userland/Libraries/LibIMAP/Parser.h b/Userland/Libraries/LibIMAP/Parser.h index 88863f689f..b55e917e67 100644 --- a/Userland/Libraries/LibIMAP/Parser.h +++ b/Userland/Libraries/LibIMAP/Parser.h @@ -68,5 +68,12 @@ private: Optional> parse_address_list(); Address parse_address(); StringView parse_astring(); + HashMap parse_body_fields_params(); + BodyStructure parse_body_structure(); + BodyStructure parse_one_part_body(); + Tuple> parse_disposition(); + Vector parse_langs(); + BodyExtension parse_body_extension(); + Envelope parse_envelope(); }; }