diff options
author | x-yl <kylepereira@mail.com> | 2021-06-02 18:36:42 +0400 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2021-06-11 23:58:28 +0430 |
commit | a6339297eccf3f9d0d81f7f12746add081b6cd31 (patch) | |
tree | 6a5263cebd195794208bc31484569cf6734665d3 /Userland/Libraries | |
parent | 318709c8ca6f107a8eb6b1b7613f3b52c1267306 (diff) | |
download | serenity-a6339297eccf3f9d0d81f7f12746add081b6cd31.zip |
LibIMAP: Support for the SEARCH command
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibIMAP/Client.cpp | 18 | ||||
-rw-r--r-- | Userland/Libraries/LibIMAP/Client.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibIMAP/Objects.cpp | 74 | ||||
-rw-r--r-- | Userland/Libraries/LibIMAP/Objects.h | 87 | ||||
-rw-r--r-- | Userland/Libraries/LibIMAP/Parser.cpp | 8 |
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"); |