summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibIMAP
diff options
context:
space:
mode:
authorx-yl <kylepereira@mail.com>2021-06-02 17:32:03 +0400
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-06-11 23:58:28 +0430
commitc152a9a594dc6bc077856ef94722626f06535d50 (patch)
tree2d1eb9071fb501bbc1d702f156b477b765cf8cd7 /Userland/Libraries/LibIMAP
parent1e9dfdcdcccccf48bdb2a534a091183047ae7699 (diff)
downloadserenity-c152a9a594dc6bc077856ef94722626f06535d50.zip
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.
Diffstat (limited to 'Userland/Libraries/LibIMAP')
-rw-r--r--Userland/Libraries/LibIMAP/Client.cpp10
-rw-r--r--Userland/Libraries/LibIMAP/Client.h1
-rw-r--r--Userland/Libraries/LibIMAP/Objects.cpp101
-rw-r--r--Userland/Libraries/LibIMAP/Objects.h185
-rw-r--r--Userland/Libraries/LibIMAP/Parser.cpp206
-rw-r--r--Userland/Libraries/LibIMAP/Parser.h6
6 files changed, 509 insertions, 0 deletions
diff --git a/Userland/Libraries/LibIMAP/Client.cpp b/Userland/Libraries/LibIMAP/Client.cpp
index f18c617f9a..f84480128c 100644
--- a/Userland/Libraries/LibIMAP/Client.cpp
+++ b/Userland/Libraries/LibIMAP/Client.cpp
@@ -124,6 +124,10 @@ static ReadonlyBytes command_byte_buffer(CommandType command)
return "LIST"sv.bytes();
case CommandType::Select:
return "SELECT"sv.bytes();
+ case CommandType::Fetch:
+ return "FETCH"sv.bytes();
+ case CommandType::UIDFetch:
+ return "UID FETCH"sv.bytes();
}
VERIFY_NOT_REACHED();
}
@@ -177,6 +181,12 @@ RefPtr<Promise<Optional<SolidResponse>>> Client::list(StringView reference_name,
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)
{
auto command = Command { type, m_current_command, {} };
diff --git a/Userland/Libraries/LibIMAP/Client.h b/Userland/Libraries/LibIMAP/Client.h
index 8ff6fc8802..aed53dcf24 100644
--- a/Userland/Libraries/LibIMAP/Client.h
+++ b/Userland/Libraries/LibIMAP/Client.h
@@ -24,6 +24,7 @@ public:
RefPtr<Promise<Optional<SolidResponse>>> login(StringView username, StringView password);
RefPtr<Promise<Optional<SolidResponse>>> list(StringView reference_name, StringView mailbox_name);
RefPtr<Promise<Optional<SolidResponse>>> select(StringView string);
+ RefPtr<Promise<Optional<SolidResponse>>> fetch(FetchCommand request, bool uid);
RefPtr<Promise<Optional<ContinueRequest>>> idle();
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
diff --git a/Userland/Libraries/LibIMAP/Objects.cpp b/Userland/Libraries/LibIMAP/Objects.cpp
index 11c8222c06..8ac3efafe5 100644
--- a/Userland/Libraries/LibIMAP/Objects.cpp
+++ b/Userland/Libraries/LibIMAP/Objects.cpp
@@ -8,4 +8,105 @@
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());
+}
}
diff --git a/Userland/Libraries/LibIMAP/Objects.h b/Userland/Libraries/LibIMAP/Objects.h
index 6f102ce8ce..4123845b9b 100644
--- a/Userland/Libraries/LibIMAP/Objects.h
+++ b/Userland/Libraries/LibIMAP/Objects.h
@@ -18,12 +18,14 @@
namespace IMAP {
enum class CommandType {
Capability,
+ Fetch,
Idle,
List,
Login,
Logout,
Noop,
Select,
+ UIDFetch,
};
enum class MailboxFlag : unsigned {
@@ -53,11 +55,72 @@ enum class ResponseType : unsigned {
UIDValidity = 1u << 6,
Unseen = 1u << 7,
PermanentFlags = 1u << 8,
+ Fetch = 1u << 9,
Bye = 1u << 13,
};
+enum class FetchResponseType : unsigned {
+ Body = 1u << 1,
+ UID = 1u << 2,
+ InternalDate = 1u << 3,
+ Envelope = 1u << 4,
+ Flags = 1u << 5,
+};
+
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 {
public:
CommandType type;
@@ -77,6 +140,115 @@ 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
+ {
+ 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 {
public:
[[nodiscard]] unsigned response_type() const
@@ -212,6 +384,18 @@ public:
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)
{
add_response_type(ResponseType::Bye);
@@ -238,6 +422,7 @@ private:
unsigned m_unseen {};
Vector<String> m_permanent_flags;
Vector<String> m_flags;
+ Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
Optional<String> m_bye_message;
};
diff --git a/Userland/Libraries/LibIMAP/Parser.cpp b/Userland/Libraries/LibIMAP/Parser.cpp
index 15b7a9df27..bff181aad5 100644
--- a/Userland/Libraries/LibIMAP/Parser.cpp
+++ b/Userland/Libraries/LibIMAP/Parser.cpp
@@ -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();
}
+} \ No newline at end of file
diff --git a/Userland/Libraries/LibIMAP/Parser.h b/Userland/Libraries/LibIMAP/Parser.h
index 663c0a9286..88863f689f 100644
--- a/Userland/Libraries/LibIMAP/Parser.h
+++ b/Userland/Libraries/LibIMAP/Parser.h
@@ -60,7 +60,13 @@ private:
ListItem parse_list_item();
+ FetchCommand::DataItem parse_fetch_data_item();
+
+ FetchResponseData parse_fetch_response();
+
StringView parse_literal_string();
+ Optional<Vector<Address>> parse_address_list();
+ Address parse_address();
StringView parse_astring();
};
}