mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 19:37:36 +00:00
LibIMAP: Support for FETCH BodyStructure
This completes the implementation of the FETCH command.
This commit is contained in:
parent
c152a9a594
commit
318709c8ca
4 changed files with 376 additions and 55 deletions
|
@ -74,7 +74,7 @@ String FetchCommand::DataItem::serialize() const
|
||||||
return "UID";
|
return "UID";
|
||||||
case DataItemType::PeekBody:
|
case DataItemType::PeekBody:
|
||||||
TODO();
|
TODO();
|
||||||
case DataItemType::BodySection:
|
case DataItemType::BodySection: {
|
||||||
StringBuilder sb;
|
StringBuilder sb;
|
||||||
sb.appendff("BODY[{}]", section.value().serialize());
|
sb.appendff("BODY[{}]", section.value().serialize());
|
||||||
if (partial_fetch) {
|
if (partial_fetch) {
|
||||||
|
@ -83,6 +83,9 @@ String FetchCommand::DataItem::serialize() const
|
||||||
|
|
||||||
return sb.build();
|
return sb.build();
|
||||||
}
|
}
|
||||||
|
case DataItemType::BodyStructure:
|
||||||
|
return "BODYSTRUCTURE";
|
||||||
|
}
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
String FetchCommand::serialize()
|
String FetchCommand::serialize()
|
||||||
|
|
|
@ -65,10 +65,84 @@ enum class FetchResponseType : unsigned {
|
||||||
InternalDate = 1u << 3,
|
InternalDate = 1u << 3,
|
||||||
Envelope = 1u << 4,
|
Envelope = 1u << 4,
|
||||||
Flags = 1u << 5,
|
Flags = 1u << 5,
|
||||||
|
BodyStructure = 1u << 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser;
|
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.
|
// Set -1 for '*' i.e highest possible value.
|
||||||
struct Sequence {
|
struct Sequence {
|
||||||
int start;
|
int start;
|
||||||
|
@ -79,6 +153,7 @@ struct Sequence {
|
||||||
|
|
||||||
struct FetchCommand {
|
struct FetchCommand {
|
||||||
enum class DataItemType {
|
enum class DataItemType {
|
||||||
|
BodyStructure,
|
||||||
Envelope,
|
Envelope,
|
||||||
Flags,
|
Flags,
|
||||||
InternalDate,
|
InternalDate,
|
||||||
|
@ -140,25 +215,6 @@ struct ListItem {
|
||||||
String name;
|
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 {
|
class FetchResponseData {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] unsigned response_type() const
|
[[nodiscard]] unsigned response_type() const
|
||||||
|
@ -236,7 +292,20 @@ public:
|
||||||
return m_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()
|
FetchResponseData()
|
||||||
|
: m_body_structure(BodyStructureData {})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +316,7 @@ private:
|
||||||
Envelope m_envelope;
|
Envelope m_envelope;
|
||||||
unsigned m_uid { 0 };
|
unsigned m_uid { 0 };
|
||||||
unsigned m_response_type { 0 };
|
unsigned m_response_type { 0 };
|
||||||
|
BodyStructure m_body_structure;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ResponseData {
|
class ResponseData {
|
||||||
|
|
|
@ -225,41 +225,14 @@ FetchResponseData Parser::parse_fetch_response()
|
||||||
while (!try_consume(")")) {
|
while (!try_consume(")")) {
|
||||||
auto data_item = parse_fetch_data_item();
|
auto data_item = parse_fetch_data_item();
|
||||||
switch (data_item.type) {
|
switch (data_item.type) {
|
||||||
case FetchCommand::DataItemType::Envelope: {
|
case FetchCommand::DataItemType::BodyStructure: {
|
||||||
consume(" (");
|
consume(" (");
|
||||||
auto date = parse_nstring();
|
auto structure = parse_body_structure();
|
||||||
consume(" ");
|
fetch_response.set_body_structure(move(structure));
|
||||||
auto subject = parse_nstring();
|
break;
|
||||||
consume(" ");
|
}
|
||||||
auto from = parse_address_list();
|
case FetchCommand::DataItemType::Envelope: {
|
||||||
consume(" ");
|
fetch_response.set_envelope(parse_envelope());
|
||||||
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));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FetchCommand::DataItemType::Flags: {
|
case FetchCommand::DataItemType::Flags: {
|
||||||
|
@ -297,6 +270,236 @@ FetchResponseData Parser::parse_fetch_response()
|
||||||
consume("\r\n");
|
consume("\r\n");
|
||||||
return fetch_response;
|
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()
|
StringView Parser::parse_literal_string()
|
||||||
{
|
{
|
||||||
|
@ -510,6 +713,10 @@ FetchCommand::DataItem Parser::parse_fetch_data_item()
|
||||||
return FetchCommand::DataItem {
|
return FetchCommand::DataItem {
|
||||||
.type = FetchCommand::DataItemType::Envelope
|
.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 {
|
} else {
|
||||||
dbgln("msg_attr not matched: {}", msg_attr);
|
dbgln("msg_attr not matched: {}", msg_attr);
|
||||||
m_parsing_failed = true;
|
m_parsing_failed = true;
|
||||||
|
@ -557,4 +764,38 @@ StringView Parser::parse_astring()
|
||||||
else
|
else
|
||||||
return parse_atom();
|
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() };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -68,5 +68,12 @@ private:
|
||||||
Optional<Vector<Address>> parse_address_list();
|
Optional<Vector<Address>> parse_address_list();
|
||||||
Address parse_address();
|
Address parse_address();
|
||||||
StringView parse_astring();
|
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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue