1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 21:48:13 +00:00

LibIMAP: Support for the FETCH command (*mostly)

This commit doesn't include support for FETCH BODY, because it's a bit
big already. Rest assured, FETCH is the most complicated IMAP command,
and we'll go back to simple boring ones shortly.
This commit is contained in:
x-yl 2021-06-02 17:32:03 +04:00 committed by Ali Mohammad Pur
parent 1e9dfdcdcc
commit c152a9a594
6 changed files with 509 additions and 0 deletions

View file

@ -137,6 +137,9 @@ void Parser::parse_untagged()
} else if (data_type.matches("RECENT")) {
m_response.data().set_recent(number.value());
consume("\r\n");
} else if (data_type.matches("FETCH")) {
auto fetch_response = parse_fetch_response();
m_response.data().add_fetch_response(number.value(), move(fetch_response));
}
return;
}
@ -214,6 +217,87 @@ Optional<StringView> Parser::parse_nstring()
return { parse_string() };
}
FetchResponseData Parser::parse_fetch_response()
{
consume(" (");
auto fetch_response = FetchResponseData();
while (!try_consume(")")) {
auto data_item = parse_fetch_data_item();
switch (data_item.type) {
case FetchCommand::DataItemType::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() ? 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;
}
case FetchCommand::DataItemType::Flags: {
consume(" ");
auto flags = parse_list(+[](StringView x) { return String(x); });
fetch_response.set_flags(move(flags));
break;
}
case FetchCommand::DataItemType::InternalDate: {
consume(" \"");
auto date_view = parse_while([](u8 x) { return x != '"'; });
consume("\"");
auto date = Core::DateTime::parse("%d-%b-%Y %H:%M:%S %z", date_view).value();
fetch_response.set_internal_date(date);
break;
}
case FetchCommand::DataItemType::UID: {
consume(" ");
fetch_response.set_uid(parse_number());
break;
}
case FetchCommand::DataItemType::PeekBody:
// Spec doesn't allow for this in a response.
m_parsing_failed = true;
break;
case FetchCommand::DataItemType::BodySection: {
auto body = parse_nstring();
fetch_response.add_body_data(move(data_item), body.has_value() ? body.release_value() : Optional<String>());
break;
}
}
if (!at_end() && m_buffer[position] != ')')
consume(" ");
}
consume("\r\n");
return fetch_response;
}
StringView Parser::parse_literal_string()
{
consume("{");
@ -351,4 +435,126 @@ StringView Parser::parse_while(Function<bool(u8)> should_consume)
return StringView(m_buffer.data() + position - chars, chars);
}
FetchCommand::DataItem Parser::parse_fetch_data_item()
{
auto msg_attr = parse_while([](u8 x) { return is_ascii_alpha(x) != 0; });
if (msg_attr.equals_ignoring_case("BODY") && try_consume("[")) {
auto data_item = FetchCommand::DataItem {
.type = FetchCommand::DataItemType::BodySection,
.section = { {} }
};
auto section_type = parse_while([](u8 x) { return x != ']' && x != ' '; });
if (section_type.equals_ignoring_case("HEADER.FIELDS")) {
data_item.section->type = FetchCommand::DataItem::SectionType::HeaderFields;
data_item.section->headers = Vector<String>();
consume(" ");
auto headers = parse_list(+[](StringView x) { return x; });
for (auto& header : headers) {
data_item.section->headers->append(header);
}
consume("]");
} else if (section_type.equals_ignoring_case("HEADER.FIELDS.NOT")) {
data_item.section->type = FetchCommand::DataItem::SectionType::HeaderFieldsNot;
data_item.section->headers = Vector<String>();
consume(" (");
auto headers = parse_list(+[](StringView x) { return x; });
for (auto& header : headers) {
data_item.section->headers->append(header);
}
consume("]");
} else if (is_ascii_digit(section_type[0])) {
data_item.section->type = FetchCommand::DataItem::SectionType::Parts;
data_item.section->parts = Vector<int>();
while (!try_consume("]")) {
auto num = parse_number();
if (num != (unsigned)-1) {
data_item.section->parts->append((int)num);
continue;
}
auto atom = parse_atom();
if (atom.equals_ignoring_case("MIME")) {
data_item.section->ends_with_mime = true;
continue;
}
}
} else if (section_type.equals_ignoring_case("TEXT")) {
data_item.section->type = FetchCommand::DataItem::SectionType::Text;
} else if (section_type.equals_ignoring_case("HEADER")) {
data_item.section->type = FetchCommand::DataItem::SectionType::Header;
} else {
dbgln("Unmatched section type {}", section_type);
m_parsing_failed = true;
}
if (try_consume("<")) {
auto start = parse_number();
data_item.partial_fetch = true;
data_item.start = (int)start;
consume(">");
}
try_consume(" ");
return data_item;
} else if (msg_attr.equals_ignoring_case("FLAGS")) {
return FetchCommand::DataItem {
.type = FetchCommand::DataItemType::Flags
};
} else if (msg_attr.equals_ignoring_case("UID")) {
return FetchCommand::DataItem {
.type = FetchCommand::DataItemType::UID
};
} else if (msg_attr.equals_ignoring_case("INTERNALDATE")) {
return FetchCommand::DataItem {
.type = FetchCommand::DataItemType::InternalDate
};
} else if (msg_attr.equals_ignoring_case("ENVELOPE")) {
return FetchCommand::DataItem {
.type = FetchCommand::DataItemType::Envelope
};
} else {
dbgln("msg_attr not matched: {}", msg_attr);
m_parsing_failed = true;
return FetchCommand::DataItem {};
}
}
Optional<Vector<Address>> Parser::parse_address_list()
{
if (try_consume("NIL"))
return {};
auto addresses = Vector<Address>();
consume("(");
while (!try_consume(")")) {
addresses.append(parse_address());
if (!at_end() && m_buffer[position] != ')')
consume(" ");
}
return { addresses };
}
Address Parser::parse_address()
{
consume("(");
auto address = Address();
// I hate this so much. Why is there no Optional.map??
auto name = parse_nstring();
address.name = name.has_value() ? Optional<String>(name.value()) : Optional<String>();
consume(" ");
auto source_route = parse_nstring();
address.source_route = source_route.has_value() ? Optional<String>(source_route.value()) : Optional<String>();
consume(" ");
auto mailbox = parse_nstring();
address.mailbox = mailbox.has_value() ? Optional<String>(mailbox.value()) : Optional<String>();
consume(" ");
auto host = parse_nstring();
address.host = host.has_value() ? Optional<String>(host.value()) : Optional<String>();
consume(")");
return address;
}
StringView Parser::parse_astring()
{
if (!at_end() && (m_buffer[position] == '{' || m_buffer[position] == '"'))
return parse_string();
else
return parse_atom();
}
}