1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 09:07:45 +00:00

LibIMAP: Support for FETCH BodyStructure

This completes the implementation of the FETCH command.
This commit is contained in:
x-yl 2021-06-02 18:31:24 +04:00 committed by Ali Mohammad Pur
parent c152a9a594
commit 318709c8ca
4 changed files with 376 additions and 55 deletions

View file

@ -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()

View file

@ -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<String> name;
Optional<String> source_route;
Optional<String> mailbox;
Optional<String> host;
};
struct Envelope {
Optional<String> date; // Format of date not specified.
Optional<String> subject;
Optional<Vector<Address>> from;
Optional<Vector<Address>> sender;
Optional<Vector<Address>> reply_to;
Optional<Vector<Address>> to;
Optional<Vector<Address>> cc;
Optional<Vector<Address>> bcc;
Optional<String> in_reply_to;
Optional<String> message_id;
};
class BodyStructure;
struct BodyExtension {
AK::Variant<Optional<String>, unsigned, Vector<OwnPtr<BodyExtension>>> data;
};
struct MultiPartBodyStructureData {
Optional<Tuple<String, HashMap<String, String>>> disposition;
Vector<OwnPtr<BodyStructure>> bodies;
Vector<String> langs;
String media_type;
Optional<HashMap<String, String>> params;
Optional<String> location;
Optional<Vector<BodyExtension>> extensions;
};
struct BodyStructureData {
String type;
String subtype;
Optional<String> id {};
Optional<String> desc {};
String encoding;
HashMap<String, String> fields;
unsigned bytes { 0 };
unsigned lines { 0 };
Optional<Envelope> envelope;
Optional<String> md5 {};
Optional<Tuple<String, HashMap<String, String>>> disposition {};
Optional<Vector<String>> langs {};
Optional<String> location {};
Optional<Vector<BodyExtension>> 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<BodyStructureData, MultiPartBodyStructureData> 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<String> name;
Optional<String> source_route;
Optional<String> mailbox;
Optional<String> host;
};
struct Envelope {
Optional<String> date; // Format of date not specified.
Optional<String> subject;
Optional<Vector<Address>> from;
Optional<Vector<Address>> sender;
Optional<Vector<Address>> reply_to;
Optional<Vector<Address>> to;
Optional<Vector<Address>> cc;
Optional<Vector<Address>> bcc;
Optional<String> in_reply_to;
Optional<String> 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 {

View file

@ -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<String>(date.value()) : Optional<String>(),
subject.has_value() ? Optional<String>(subject.value()) : Optional<String>(),
from,
sender,
reply_to,
to,
cc,
bcc,
in_reply_to.has_value() ? Optional<String>(in_reply_to.value()) : Optional<String>(),
message_id.has_value() ? Optional<String>(message_id.value()) : Optional<String>(),
};
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<String>(date.value()) : AK::Optional<String>(),
subject.has_value() ? AK::Optional<String>(subject.value()) : AK::Optional<String>(),
from,
sender,
reply_to,
to,
cc,
bcc,
in_reply_to.has_value() ? AK::Optional<String>(in_reply_to.value()) : AK::Optional<String>(),
message_id.has_value() ? AK::Optional<String>(message_id.value()) : AK::Optional<String>(),
};
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<BodyStructure>(move(child)));
}
consume(" ");
data.media_type = parse_string();
if (!try_consume(")")) {
consume(" ");
data.params = try_consume("NIL") ? Optional<HashMap<String, String>>() : 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<String>() : Optional<String>(parse_string());
if (!try_consume(")")) {
consume(" ");
Vector<BodyExtension> 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<String>(id.value()) : Optional<String>(),
description.has_value() ? Optional<String>(description.value()) : Optional<String>(),
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<BodyExtension> 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<String>(id.value()) : Optional<String>(),
description.has_value() ? Optional<String>(description.value()) : Optional<String>(),
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<String>(id.value()) : Optional<String>(),
description.has_value() ? Optional<String>(description.value()) : Optional<String>(),
encoding,
params,
num_octets,
0,
{}
};
return BodyStructure(move(data));
}
}
Vector<String> Parser::parse_langs()
{
AK::Vector<String> langs;
if (!try_consume("(")) {
langs.append(parse_string());
} else {
while (!try_consume(")")) {
langs.append(parse_string());
try_consume(" ");
}
}
return langs;
}
Tuple<String, HashMap<String, String>> 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();
}
}
HashMap<String, String> Parser::parse_body_fields_params()
{
if (try_consume("NIL"))
return {};
HashMap<String, String> 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<String> {} };
} else if (try_consume("(")) {
Vector<OwnPtr<BodyExtension>> extensions;
while (!try_consume(")")) {
extensions.append(make<BodyExtension>(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() };
}
}
}

View file

@ -68,5 +68,12 @@ private:
Optional<Vector<Address>> parse_address_list();
Address parse_address();
StringView parse_astring();
HashMap<String, String> parse_body_fields_params();
BodyStructure parse_body_structure();
BodyStructure parse_one_part_body();
Tuple<String, HashMap<String, String>> parse_disposition();
Vector<String> parse_langs();
BodyExtension parse_body_extension();
Envelope parse_envelope();
};
}