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:
parent
1e9dfdcdcc
commit
c152a9a594
6 changed files with 509 additions and 0 deletions
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue