summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorx-yl <kylepereira@mail.com>2021-06-02 18:36:42 +0400
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2021-06-11 23:58:28 +0430
commita6339297eccf3f9d0d81f7f12746add081b6cd31 (patch)
tree6a5263cebd195794208bc31484569cf6734665d3
parent318709c8ca6f107a8eb6b1b7613f3b52c1267306 (diff)
downloadserenity-a6339297eccf3f9d0d81f7f12746add081b6cd31.zip
LibIMAP: Support for the SEARCH command
-rw-r--r--Userland/Libraries/LibIMAP/Client.cpp18
-rw-r--r--Userland/Libraries/LibIMAP/Client.h1
-rw-r--r--Userland/Libraries/LibIMAP/Objects.cpp74
-rw-r--r--Userland/Libraries/LibIMAP/Objects.h87
-rw-r--r--Userland/Libraries/LibIMAP/Parser.cpp8
5 files changed, 188 insertions, 0 deletions
diff --git a/Userland/Libraries/LibIMAP/Client.cpp b/Userland/Libraries/LibIMAP/Client.cpp
index f84480128c..21d623ec35 100644
--- a/Userland/Libraries/LibIMAP/Client.cpp
+++ b/Userland/Libraries/LibIMAP/Client.cpp
@@ -126,8 +126,12 @@ static ReadonlyBytes command_byte_buffer(CommandType command)
return "SELECT"sv.bytes();
case CommandType::Fetch:
return "FETCH"sv.bytes();
+ case CommandType::Search:
+ return "SEARCH"sv.bytes();
case CommandType::UIDFetch:
return "UID FETCH"sv.bytes();
+ case CommandType::UIDSearch:
+ return "UID SEARCH"sv.bytes();
}
VERIFY_NOT_REACHED();
}
@@ -244,6 +248,20 @@ void Client::send_next_command()
send_raw(buffer);
m_expecting_response = true;
}
+RefPtr<Promise<Optional<SolidResponse>>> Client::search(Optional<String> charset, Vector<SearchKey>&& keys, bool uid)
+{
+ Vector<String> args;
+ if (charset.has_value()) {
+ args.append("CHARSET ");
+ args.append(charset.value());
+ }
+ for (const auto& item : keys) {
+ args.append(item.serialize());
+ }
+ auto command = Command { uid ? CommandType::UIDSearch : CommandType::Search, m_current_command, args };
+ return cast_promise<SolidResponse>(send_command(move(command)));
+}
+
RefPtr<Promise<Optional<ContinueRequest>>> Client::idle()
{
auto promise = send_simple_command(CommandType::Idle);
diff --git a/Userland/Libraries/LibIMAP/Client.h b/Userland/Libraries/LibIMAP/Client.h
index aed53dcf24..e1e99b6a3a 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>>> search(Optional<String> charset, Vector<SearchKey>&& search_keys, bool uid);
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 d534fac6b8..1b99fb428b 100644
--- a/Userland/Libraries/LibIMAP/Objects.cpp
+++ b/Userland/Libraries/LibIMAP/Objects.cpp
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <AK/CharacterTypes.h>
#include <LibIMAP/Objects.h>
namespace IMAP {
@@ -112,4 +113,77 @@ String FetchCommand::serialize()
return AK::String::formatted("{} ({})", sequence_builder.build(), data_items_builder.build());
}
+String serialize_astring(StringView string)
+{
+ // Try to send an atom
+ auto is_non_atom_char = [](char x) {
+ auto non_atom_chars = { '(', ')', '{', ' ', '%', '*', '"', '\\', ']' };
+ return AK::find(non_atom_chars.begin(), non_atom_chars.end(), x) != non_atom_chars.end();
+ };
+ auto is_atom = all_of(string.begin(), string.end(), [&](auto ch) { return is_ascii_control(ch) && !is_non_atom_char(ch); });
+ if (is_atom) {
+ return string;
+ }
+
+ // Try to quote
+ auto can_be_quoted = !(string.contains('\n') || string.contains('\r'));
+ if (can_be_quoted) {
+ auto escaped_str = string.to_string();
+ escaped_str.replace("\\", "\\\\");
+ escaped_str.replace("\"", "\\\"");
+ return String::formatted("\"{}\"", escaped_str);
+ }
+
+ // Just send a literal
+ return String::formatted("{{{}}}\r\n{}", string.length(), string);
+}
+String SearchKey::serialize() const
+{
+ return data.visit(
+ [&](Empty const&) { VERIFY_NOT_REACHED(); return String("The compiler complains if you remove this."); },
+ [&](All const&) { return String("ALL"); },
+ [&](Answered const&) { return String("ANSWERED"); },
+ [&](Bcc const& x) { return String::formatted("BCC {}", serialize_astring(x.bcc)); },
+ [&](Cc const& x) { return String::formatted("CC {}", serialize_astring(x.cc)); },
+ [&](Deleted const&) { return String("DELETED"); },
+ [&](Draft const&) { return String("DRAFT"); },
+ [&](From const& x) { return String::formatted("FROM {}", serialize_astring(x.from)); },
+ [&](Header const& x) { return String::formatted("HEADER {} {}", serialize_astring(x.header), serialize_astring(x.value)); },
+ [&](Keyword const& x) { return String::formatted("KEYWORD {}", x.keyword); },
+ [&](Larger const& x) { return String::formatted("LARGER {}", x.number); },
+ [&](New const&) { return String("NEW"); },
+ [&](Not const& x) { return String::formatted("NOT {}", x.operand->serialize()); },
+ [&](Old const&) { return String("OLD"); },
+ [&](On const& x) { return String::formatted("ON {}", x.date.to_string("%d-%b-%Y")); },
+ [&](Or const& x) { return String::formatted("OR {} {}", x.lhs->serialize(), x.rhs->serialize()); },
+ [&](Recent const&) { return String("RECENT"); },
+ [&](SearchKeys const& x) {
+ StringBuilder sb;
+ sb.append("(");
+ bool first = true;
+ for (const auto& item : x.keys) {
+ if (!first)
+ sb.append(", ");
+ sb.append(item->serialize());
+ first = false;
+ }
+ return sb.build();
+ },
+ [&](Seen const&) { return String("SEEN"); },
+ [&](SentBefore const& x) { return String::formatted("SENTBEFORE {}", x.date.to_string("%d-%b-%Y")); },
+ [&](SentOn const& x) { return String::formatted("SENTON {}", x.date.to_string("%d-%b-%Y")); },
+ [&](SentSince const& x) { return String::formatted("SENTSINCE {}", x.date.to_string("%d-%b-%Y")); },
+ [&](SequenceSet const& x) { return x.sequence.serialize(); },
+ [&](Since const& x) { return String::formatted("SINCE {}", x.date.to_string("%d-%b-%Y")); },
+ [&](Smaller const& x) { return String::formatted("SMALLER {}", x.number); },
+ [&](Subject const& x) { return String::formatted("SUBJECT {}", serialize_astring(x.subject)); },
+ [&](Text const& x) { return String::formatted("TEXT {}", serialize_astring(x.text)); },
+ [&](To const& x) { return String::formatted("TO {}", serialize_astring(x.to)); },
+ [&](UID const& x) { return String::formatted("UID {}", x.uid); },
+ [&](Unanswered const&) { return String("UNANSWERED"); },
+ [&](Undeleted const&) { return String("UNDELETED"); },
+ [&](Undraft const&) { return String("UNDRAFT"); },
+ [&](Unkeyword const& x) { return String::formatted("UNKEYWORD {}", serialize_astring(x.flag_keyword)); },
+ [&](Unseen const&) { return String("UNSEEN"); });
+}
}
diff --git a/Userland/Libraries/LibIMAP/Objects.h b/Userland/Libraries/LibIMAP/Objects.h
index 66ba6436d6..0dd87cb044 100644
--- a/Userland/Libraries/LibIMAP/Objects.h
+++ b/Userland/Libraries/LibIMAP/Objects.h
@@ -24,8 +24,10 @@ enum class CommandType {
Login,
Logout,
Noop,
+ Search,
Select,
UIDFetch,
+ UIDSearch,
};
enum class MailboxFlag : unsigned {
@@ -56,6 +58,7 @@ enum class ResponseType : unsigned {
Unseen = 1u << 7,
PermanentFlags = 1u << 8,
Fetch = 1u << 9,
+ Search = 1u << 10,
Bye = 1u << 13,
};
@@ -319,6 +322,77 @@ private:
BodyStructure m_body_structure;
};
+String serialize_astring(StringView string);
+
+struct SearchKey {
+public:
+ // clang-format off
+ struct All { };
+ struct Answered { };
+ struct Bcc { String bcc; };
+ struct Cc { String cc; };
+ struct Deleted { };
+ struct Draft { };
+ struct From { String from; };
+ struct Header { String header; String value; };
+ struct Keyword { String keyword; };
+ struct Larger { unsigned number; };
+ struct New { };
+ struct Not { OwnPtr<SearchKey> operand; };
+ struct Old { };
+ struct On { Core::DateTime date; };
+ struct Or { OwnPtr<SearchKey> lhs; OwnPtr<SearchKey> rhs; };
+ struct Recent { };
+ struct SearchKeys { Vector<OwnPtr<SearchKey>> keys; };
+ struct Seen { };
+ struct SentBefore { Core::DateTime date; };
+ struct SentOn { Core::DateTime date; };
+ struct SentSince { Core::DateTime date; };
+ struct SequenceSet { Sequence sequence; };
+ struct Since { Core::DateTime date; };
+ struct Smaller { unsigned number; };
+ struct Subject { String subject; };
+ struct Text { String text; };
+ struct To { String to; };
+ struct UID { unsigned uid; };
+ struct Unanswered { };
+ struct Undeleted { };
+ struct Undraft { };
+ struct Unkeyword { String flag_keyword; };
+ struct Unseen { };
+ // clang-format on
+
+ Variant<Empty, All, Answered, Bcc, Cc, Deleted, Draft, From, Header, Keyword,
+ Larger, New, Not, Old, On, Or, Recent, SearchKeys, Seen, SentBefore, SentOn,
+ SentSince, SequenceSet, Since, Smaller, Subject, Text, To, UID, Unanswered,
+ Undeleted, Undraft, Unkeyword, Unseen>
+ data;
+
+ SearchKey(SearchKey&& other) noexcept
+ : data(move(other.data))
+ {
+ }
+
+ template<typename T>
+ explicit SearchKey(T&& t)
+ : data(std::forward<T>(t))
+ {
+ }
+
+ SearchKey& operator=(SearchKey&& other) noexcept
+ {
+ if (this == &other) {
+ return *this;
+ }
+
+ this->data = move(other.data);
+
+ return *this;
+ }
+
+ [[nodiscard]] String serialize() const;
+};
+
class ResponseData {
public:
[[nodiscard]] unsigned response_type() const
@@ -466,6 +540,18 @@ public:
return m_fetch_responses;
}
+ void set_search_results(Vector<unsigned>&& results)
+ {
+ add_response_type(ResponseType::Search);
+ m_search_results = move(results);
+ }
+
+ Vector<unsigned>& search_results()
+ {
+ VERIFY(contains_response_type(ResponseType::Search));
+ return m_search_results;
+ }
+
void set_bye(Optional<String> message)
{
add_response_type(ResponseType::Bye);
@@ -493,6 +579,7 @@ private:
Vector<String> m_permanent_flags;
Vector<String> m_flags;
Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
+ Vector<unsigned> m_search_results;
Optional<String> m_bye_message;
};
diff --git a/Userland/Libraries/LibIMAP/Parser.cpp b/Userland/Libraries/LibIMAP/Parser.cpp
index 2e0c8d7530..8ed562017a 100644
--- a/Userland/Libraries/LibIMAP/Parser.cpp
+++ b/Userland/Libraries/LibIMAP/Parser.cpp
@@ -182,6 +182,14 @@ void Parser::parse_untagged()
parse_while([](u8 x) { return x != '\r'; });
consume("\r\n");
}
+ } else if (try_consume("SEARCH")) {
+ Vector<unsigned> ids;
+ while (!try_consume("\r\n")) {
+ consume(" ");
+ auto id = parse_number();
+ ids.append(id);
+ }
+ m_response.data().set_search_results(move(ids));
} else if (try_consume("BYE")) {
auto message = parse_while([](u8 x) { return x != '\r'; });
consume("\r\n");