mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:47:44 +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
|
@ -124,6 +124,10 @@ static ReadonlyBytes command_byte_buffer(CommandType command)
|
||||||
return "LIST"sv.bytes();
|
return "LIST"sv.bytes();
|
||||||
case CommandType::Select:
|
case CommandType::Select:
|
||||||
return "SELECT"sv.bytes();
|
return "SELECT"sv.bytes();
|
||||||
|
case CommandType::Fetch:
|
||||||
|
return "FETCH"sv.bytes();
|
||||||
|
case CommandType::UIDFetch:
|
||||||
|
return "UID FETCH"sv.bytes();
|
||||||
}
|
}
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
@ -177,6 +181,12 @@ RefPtr<Promise<Optional<SolidResponse>>> Client::list(StringView reference_name,
|
||||||
return cast_promise<SolidResponse>(send_command(move(command)));
|
return cast_promise<SolidResponse>(send_command(move(command)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<Promise<Optional<SolidResponse>>> Client::fetch(FetchCommand request, bool uid)
|
||||||
|
{
|
||||||
|
auto command = Command { uid ? CommandType::UIDFetch : CommandType::Fetch, m_current_command, { request.serialize() } };
|
||||||
|
return cast_promise<SolidResponse>(send_command(move(command)));
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<Promise<Optional<Response>>> Client::send_simple_command(CommandType type)
|
RefPtr<Promise<Optional<Response>>> Client::send_simple_command(CommandType type)
|
||||||
{
|
{
|
||||||
auto command = Command { type, m_current_command, {} };
|
auto command = Command { type, m_current_command, {} };
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> login(StringView username, StringView password);
|
RefPtr<Promise<Optional<SolidResponse>>> login(StringView username, StringView password);
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> list(StringView reference_name, StringView mailbox_name);
|
RefPtr<Promise<Optional<SolidResponse>>> list(StringView reference_name, StringView mailbox_name);
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> select(StringView string);
|
RefPtr<Promise<Optional<SolidResponse>>> select(StringView string);
|
||||||
|
RefPtr<Promise<Optional<SolidResponse>>> fetch(FetchCommand request, bool uid);
|
||||||
RefPtr<Promise<Optional<ContinueRequest>>> idle();
|
RefPtr<Promise<Optional<ContinueRequest>>> idle();
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
|
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,105 @@
|
||||||
|
|
||||||
namespace IMAP {
|
namespace IMAP {
|
||||||
|
|
||||||
|
String Sequence::serialize() const
|
||||||
|
{
|
||||||
|
if (start == end) {
|
||||||
|
return AK::String::formatted("{}", start);
|
||||||
|
} else {
|
||||||
|
auto start_char = start != -1 ? String::formatted("{}", start) : "*";
|
||||||
|
auto end_char = end != -1 ? String::formatted("{}", end) : "*";
|
||||||
|
return String::formatted("{}:{}", start_char, end_char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String FetchCommand::DataItem::Section::serialize() const
|
||||||
|
{
|
||||||
|
StringBuilder headers_builder;
|
||||||
|
switch (type) {
|
||||||
|
case SectionType::Header:
|
||||||
|
return "HEADER";
|
||||||
|
case SectionType::HeaderFields:
|
||||||
|
case SectionType::HeaderFieldsNot: {
|
||||||
|
if (type == SectionType::HeaderFields)
|
||||||
|
headers_builder.append("HEADER.FIELDS (");
|
||||||
|
else
|
||||||
|
headers_builder.append("HEADERS.FIELDS.NOT (");
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for (auto& field : headers.value()) {
|
||||||
|
if (!first)
|
||||||
|
headers_builder.append(" ");
|
||||||
|
headers_builder.append(field);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
headers_builder.append(")");
|
||||||
|
return headers_builder.build();
|
||||||
|
}
|
||||||
|
case SectionType::Text:
|
||||||
|
return "TEXT";
|
||||||
|
case SectionType::Parts: {
|
||||||
|
StringBuilder sb;
|
||||||
|
bool first = true;
|
||||||
|
for (int part : parts.value()) {
|
||||||
|
if (!first)
|
||||||
|
sb.append(".");
|
||||||
|
sb.appendff("{}", part);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (ends_with_mime) {
|
||||||
|
sb.append(".MIME");
|
||||||
|
}
|
||||||
|
return sb.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
String FetchCommand::DataItem::serialize() const
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case DataItemType::Envelope:
|
||||||
|
return "ENVELOPE";
|
||||||
|
case DataItemType::Flags:
|
||||||
|
return "FLAGS";
|
||||||
|
case DataItemType::InternalDate:
|
||||||
|
return "INTERNALDATE";
|
||||||
|
case DataItemType::UID:
|
||||||
|
return "UID";
|
||||||
|
case DataItemType::PeekBody:
|
||||||
|
TODO();
|
||||||
|
case DataItemType::BodySection:
|
||||||
|
StringBuilder sb;
|
||||||
|
sb.appendff("BODY[{}]", section.value().serialize());
|
||||||
|
if (partial_fetch) {
|
||||||
|
sb.appendff("<{}.{}>", start, octets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.build();
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
String FetchCommand::serialize()
|
||||||
|
{
|
||||||
|
StringBuilder sequence_builder;
|
||||||
|
bool first = true;
|
||||||
|
for (auto& sequence : sequence_set) {
|
||||||
|
if (!first) {
|
||||||
|
sequence_builder.append(",");
|
||||||
|
}
|
||||||
|
sequence_builder.append(sequence.serialize());
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder data_items_builder;
|
||||||
|
first = true;
|
||||||
|
for (auto& data_item : data_items) {
|
||||||
|
if (!first) {
|
||||||
|
data_items_builder.append(" ");
|
||||||
|
}
|
||||||
|
data_items_builder.append(data_item.serialize());
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AK::String::formatted("{} ({})", sequence_builder.build(), data_items_builder.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,14 @@
|
||||||
namespace IMAP {
|
namespace IMAP {
|
||||||
enum class CommandType {
|
enum class CommandType {
|
||||||
Capability,
|
Capability,
|
||||||
|
Fetch,
|
||||||
Idle,
|
Idle,
|
||||||
List,
|
List,
|
||||||
Login,
|
Login,
|
||||||
Logout,
|
Logout,
|
||||||
Noop,
|
Noop,
|
||||||
Select,
|
Select,
|
||||||
|
UIDFetch,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MailboxFlag : unsigned {
|
enum class MailboxFlag : unsigned {
|
||||||
|
@ -53,11 +55,72 @@ enum class ResponseType : unsigned {
|
||||||
UIDValidity = 1u << 6,
|
UIDValidity = 1u << 6,
|
||||||
Unseen = 1u << 7,
|
Unseen = 1u << 7,
|
||||||
PermanentFlags = 1u << 8,
|
PermanentFlags = 1u << 8,
|
||||||
|
Fetch = 1u << 9,
|
||||||
Bye = 1u << 13,
|
Bye = 1u << 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class FetchResponseType : unsigned {
|
||||||
|
Body = 1u << 1,
|
||||||
|
UID = 1u << 2,
|
||||||
|
InternalDate = 1u << 3,
|
||||||
|
Envelope = 1u << 4,
|
||||||
|
Flags = 1u << 5,
|
||||||
|
};
|
||||||
|
|
||||||
class Parser;
|
class Parser;
|
||||||
|
|
||||||
|
// Set -1 for '*' i.e highest possible value.
|
||||||
|
struct Sequence {
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
|
||||||
|
[[nodiscard]] String serialize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FetchCommand {
|
||||||
|
enum class DataItemType {
|
||||||
|
Envelope,
|
||||||
|
Flags,
|
||||||
|
InternalDate,
|
||||||
|
UID,
|
||||||
|
PeekBody,
|
||||||
|
BodySection
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DataItem {
|
||||||
|
enum class SectionType {
|
||||||
|
Header,
|
||||||
|
HeaderFields,
|
||||||
|
HeaderFieldsNot,
|
||||||
|
Text,
|
||||||
|
Parts
|
||||||
|
};
|
||||||
|
struct Section {
|
||||||
|
SectionType type;
|
||||||
|
|
||||||
|
Optional<Vector<int>> parts {};
|
||||||
|
bool ends_with_mime {};
|
||||||
|
|
||||||
|
Optional<Vector<String>> headers {};
|
||||||
|
|
||||||
|
[[nodiscard]] String serialize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DataItemType type;
|
||||||
|
|
||||||
|
Optional<Section> section {};
|
||||||
|
bool partial_fetch { false };
|
||||||
|
int start { 0 };
|
||||||
|
int octets { 0 };
|
||||||
|
|
||||||
|
[[nodiscard]] String serialize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<Sequence> sequence_set;
|
||||||
|
Vector<DataItem> data_items;
|
||||||
|
|
||||||
|
String serialize();
|
||||||
|
};
|
||||||
struct Command {
|
struct Command {
|
||||||
public:
|
public:
|
||||||
CommandType type;
|
CommandType type;
|
||||||
|
@ -77,6 +140,115 @@ 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 {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] unsigned response_type() const
|
||||||
|
{
|
||||||
|
return m_response_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool contains_response_type(FetchResponseType response_type) const
|
||||||
|
{
|
||||||
|
return (static_cast<unsigned>(response_type) & m_response_type) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_response_type(FetchResponseType type)
|
||||||
|
{
|
||||||
|
m_response_type |= static_cast<unsigned>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_body_data(FetchCommand::DataItem&& data_item, Optional<String>&& body)
|
||||||
|
{
|
||||||
|
add_response_type(FetchResponseType::Body);
|
||||||
|
m_bodies.append({ move(data_item), move(body) });
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Tuple<FetchCommand::DataItem, Optional<String>>>& body_data()
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(FetchResponseType::Body));
|
||||||
|
return m_bodies;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_uid(unsigned uid)
|
||||||
|
{
|
||||||
|
add_response_type(FetchResponseType::UID);
|
||||||
|
m_uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] unsigned uid() const
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(FetchResponseType::UID));
|
||||||
|
return m_uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_internal_date(Core::DateTime time)
|
||||||
|
{
|
||||||
|
add_response_type(FetchResponseType::InternalDate);
|
||||||
|
m_internal_date = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::DateTime& internal_date()
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(FetchResponseType::InternalDate));
|
||||||
|
return m_internal_date;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_envelope(Envelope&& envelope)
|
||||||
|
{
|
||||||
|
add_response_type(FetchResponseType::Envelope);
|
||||||
|
m_envelope = move(envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
Envelope& envelope()
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(FetchResponseType::Envelope));
|
||||||
|
return m_envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_flags(Vector<String>&& flags)
|
||||||
|
{
|
||||||
|
add_response_type(FetchResponseType::Flags);
|
||||||
|
m_flags = move(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<String>& flags()
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(FetchResponseType::Flags));
|
||||||
|
return m_flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchResponseData()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector<String> m_flags;
|
||||||
|
Vector<Tuple<FetchCommand::DataItem, Optional<String>>> m_bodies;
|
||||||
|
Core::DateTime m_internal_date;
|
||||||
|
Envelope m_envelope;
|
||||||
|
unsigned m_uid { 0 };
|
||||||
|
unsigned m_response_type { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
class ResponseData {
|
class ResponseData {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] unsigned response_type() const
|
[[nodiscard]] unsigned response_type() const
|
||||||
|
@ -212,6 +384,18 @@ public:
|
||||||
return m_permanent_flags;
|
return m_permanent_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_fetch_response(unsigned message, FetchResponseData&& data)
|
||||||
|
{
|
||||||
|
add_response_type(ResponseType::Fetch);
|
||||||
|
m_fetch_responses.append(Tuple<unsigned, FetchResponseData> { move(message), move(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Tuple<unsigned, FetchResponseData>>& fetch_data()
|
||||||
|
{
|
||||||
|
VERIFY(contains_response_type(ResponseType::Fetch));
|
||||||
|
return m_fetch_responses;
|
||||||
|
}
|
||||||
|
|
||||||
void set_bye(Optional<String> message)
|
void set_bye(Optional<String> message)
|
||||||
{
|
{
|
||||||
add_response_type(ResponseType::Bye);
|
add_response_type(ResponseType::Bye);
|
||||||
|
@ -238,6 +422,7 @@ private:
|
||||||
unsigned m_unseen {};
|
unsigned m_unseen {};
|
||||||
Vector<String> m_permanent_flags;
|
Vector<String> m_permanent_flags;
|
||||||
Vector<String> m_flags;
|
Vector<String> m_flags;
|
||||||
|
Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
|
||||||
Optional<String> m_bye_message;
|
Optional<String> m_bye_message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,9 @@ void Parser::parse_untagged()
|
||||||
} else if (data_type.matches("RECENT")) {
|
} else if (data_type.matches("RECENT")) {
|
||||||
m_response.data().set_recent(number.value());
|
m_response.data().set_recent(number.value());
|
||||||
consume("\r\n");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -214,6 +217,87 @@ Optional<StringView> Parser::parse_nstring()
|
||||||
return { parse_string() };
|
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()
|
StringView Parser::parse_literal_string()
|
||||||
{
|
{
|
||||||
consume("{");
|
consume("{");
|
||||||
|
@ -351,4 +435,126 @@ StringView Parser::parse_while(Function<bool(u8)> should_consume)
|
||||||
return StringView(m_buffer.data() + position - chars, chars);
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,13 @@ private:
|
||||||
|
|
||||||
ListItem parse_list_item();
|
ListItem parse_list_item();
|
||||||
|
|
||||||
|
FetchCommand::DataItem parse_fetch_data_item();
|
||||||
|
|
||||||
|
FetchResponseData parse_fetch_response();
|
||||||
|
|
||||||
StringView parse_literal_string();
|
StringView parse_literal_string();
|
||||||
|
Optional<Vector<Address>> parse_address_list();
|
||||||
|
Address parse_address();
|
||||||
StringView parse_astring();
|
StringView parse_astring();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue