mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-08 12:19:37 +03:00
LibIMAP: Support for STORE and STATUS
This commit is contained in:
parent
a6339297ec
commit
076c708d0a
Notes:
sideshowbarker
2024-07-18 12:24:44 +09:00
Author: https://github.com/X-yl Commit: https://github.com/SerenityOS/serenity/commit/076c708d0a9 Pull-request: https://github.com/SerenityOS/serenity/pull/7735 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/MaxWipfli Reviewed-by: https://github.com/alimpfard
@ -126,12 +126,18 @@ static ReadonlyBytes command_byte_buffer(CommandType command)
|
||||
return "SELECT"sv.bytes();
|
||||
case CommandType::Fetch:
|
||||
return "FETCH"sv.bytes();
|
||||
case CommandType::Store:
|
||||
return "STORE"sv.bytes();
|
||||
case CommandType::Search:
|
||||
return "SEARCH"sv.bytes();
|
||||
case CommandType::UIDFetch:
|
||||
return "UID FETCH"sv.bytes();
|
||||
case CommandType::UIDStore:
|
||||
return "UID STORE"sv.bytes();
|
||||
case CommandType::UIDSearch:
|
||||
return "UID SEARCH"sv.bytes();
|
||||
case CommandType::Status:
|
||||
return "STATUS"sv.bytes();
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
@ -248,6 +254,32 @@ void Client::send_next_command()
|
||||
send_raw(buffer);
|
||||
m_expecting_response = true;
|
||||
}
|
||||
RefPtr<Promise<Optional<SolidResponse>>> Client::store(StoreMethod method, Sequence sequence_set, bool silent, Vector<String> const& flags, bool uid)
|
||||
{
|
||||
StringBuilder data_item_name;
|
||||
switch (method) {
|
||||
case StoreMethod::Replace:
|
||||
data_item_name.append("FLAGS");
|
||||
break;
|
||||
case StoreMethod::Add:
|
||||
data_item_name.append("+FLAGS");
|
||||
break;
|
||||
case StoreMethod::Remove:
|
||||
data_item_name.append("-FLAGS");
|
||||
break;
|
||||
}
|
||||
if (silent) {
|
||||
data_item_name.append(".SILENT");
|
||||
}
|
||||
|
||||
StringBuilder flags_builder;
|
||||
flags_builder.append('(');
|
||||
flags_builder.join(" ", flags);
|
||||
flags_builder.append(')');
|
||||
|
||||
auto command = Command { uid ? CommandType::UIDStore : CommandType::Store, m_current_command, { sequence_set.serialize(), data_item_name.build(), flags_builder.build() } };
|
||||
return cast_promise<SolidResponse>(send_command(move(command)));
|
||||
}
|
||||
RefPtr<Promise<Optional<SolidResponse>>> Client::search(Optional<String> charset, Vector<SearchKey>&& keys, bool uid)
|
||||
{
|
||||
Vector<String> args;
|
||||
@ -276,6 +308,36 @@ RefPtr<Promise<Optional<SolidResponse>>> Client::finish_idle()
|
||||
return cast_promise<SolidResponse>(promise);
|
||||
}
|
||||
|
||||
RefPtr<Promise<Optional<SolidResponse>>> Client::status(StringView mailbox, Vector<StatusItemType> const& types)
|
||||
{
|
||||
Vector<String> args;
|
||||
for (auto type : types) {
|
||||
switch (type) {
|
||||
case StatusItemType::Recent:
|
||||
args.append("RECENT");
|
||||
break;
|
||||
case StatusItemType::UIDNext:
|
||||
args.append("UIDNEXT");
|
||||
break;
|
||||
case StatusItemType::UIDValidity:
|
||||
args.append("UIDVALIDITY");
|
||||
break;
|
||||
case StatusItemType::Unseen:
|
||||
args.append("UNSEEN");
|
||||
break;
|
||||
case StatusItemType::Messages:
|
||||
args.append("MESSAGES");
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringBuilder types_list;
|
||||
types_list.append('(');
|
||||
types_list.join(" ", args);
|
||||
types_list.append(')');
|
||||
auto command = Command { CommandType::Status, m_current_command, { mailbox, types_list.build() } };
|
||||
return cast_promise<SolidResponse>(send_command(move(command)));
|
||||
}
|
||||
|
||||
void Client::close()
|
||||
{
|
||||
if (m_tls) {
|
||||
|
@ -26,8 +26,10 @@ public:
|
||||
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<SolidResponse>>> store(StoreMethod, Sequence, bool silent, Vector<String> const& flags, bool uid);
|
||||
RefPtr<Promise<Optional<ContinueRequest>>> idle();
|
||||
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
|
||||
RefPtr<Promise<Optional<SolidResponse>>> status(StringView mailbox, Vector<StatusItemType> const& types);
|
||||
|
||||
void close();
|
||||
|
||||
|
@ -26,8 +26,11 @@ enum class CommandType {
|
||||
Noop,
|
||||
Search,
|
||||
Select,
|
||||
Status,
|
||||
Store,
|
||||
UIDFetch,
|
||||
UIDSearch,
|
||||
UIDStore,
|
||||
};
|
||||
|
||||
enum class MailboxFlag : unsigned {
|
||||
@ -60,6 +63,7 @@ enum class ResponseType : unsigned {
|
||||
Fetch = 1u << 9,
|
||||
Search = 1u << 10,
|
||||
Bye = 1u << 13,
|
||||
Status = 1u << 14
|
||||
};
|
||||
|
||||
enum class FetchResponseType : unsigned {
|
||||
@ -71,8 +75,86 @@ enum class FetchResponseType : unsigned {
|
||||
BodyStructure = 1u << 6,
|
||||
};
|
||||
|
||||
enum class StatusItemType : unsigned {
|
||||
Recent = 1u << 1,
|
||||
UIDNext = 1u << 2,
|
||||
UIDValidity = 1u << 3,
|
||||
Unseen = 1u << 4,
|
||||
Messages = 1u << 5,
|
||||
};
|
||||
|
||||
class Parser;
|
||||
|
||||
class StatusItem {
|
||||
public:
|
||||
[[nodiscard]] unsigned status_items() const
|
||||
{
|
||||
return m_status_items;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool contains_status_item_type(StatusItemType type) const
|
||||
{
|
||||
return (static_cast<unsigned>(type) & m_status_items) != 0;
|
||||
}
|
||||
|
||||
void add_status_item_type(StatusItemType type)
|
||||
{
|
||||
m_status_items |= static_cast<unsigned>(type);
|
||||
}
|
||||
|
||||
void set_mailbox(String&& mailbox) { m_mailbox = move(mailbox); }
|
||||
String& mailbox() { return m_mailbox; }
|
||||
|
||||
unsigned get(StatusItemType type) const
|
||||
{
|
||||
VERIFY(contains_status_item_type(type));
|
||||
switch (type) {
|
||||
case StatusItemType::Recent:
|
||||
return m_recent;
|
||||
case StatusItemType::UIDNext:
|
||||
return m_uid_next;
|
||||
case StatusItemType::UIDValidity:
|
||||
return m_uid_validity;
|
||||
case StatusItemType::Unseen:
|
||||
return m_unseen;
|
||||
case StatusItemType::Messages:
|
||||
return m_messages;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
void set(StatusItemType type, unsigned value)
|
||||
{
|
||||
add_status_item_type(type);
|
||||
switch (type) {
|
||||
case StatusItemType::Recent:
|
||||
m_recent = value;
|
||||
break;
|
||||
case StatusItemType::UIDNext:
|
||||
m_uid_next = value;
|
||||
break;
|
||||
case StatusItemType::UIDValidity:
|
||||
m_uid_validity = value;
|
||||
break;
|
||||
case StatusItemType::Unseen:
|
||||
m_unseen = value;
|
||||
break;
|
||||
case StatusItemType::Messages:
|
||||
m_uid_next = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned m_status_items { 0 };
|
||||
unsigned m_messages { 0 };
|
||||
unsigned m_recent { 0 };
|
||||
unsigned m_uid_next { 0 };
|
||||
unsigned m_uid_validity { 0 };
|
||||
unsigned m_unseen { 0 };
|
||||
String m_mailbox;
|
||||
};
|
||||
|
||||
struct Address {
|
||||
Optional<String> name;
|
||||
Optional<String> source_route;
|
||||
@ -564,6 +646,17 @@ public:
|
||||
return m_bye_message;
|
||||
}
|
||||
|
||||
void set_status(StatusItem&& status_item)
|
||||
{
|
||||
add_response_type(ResponseType::Status);
|
||||
m_status_item = move(status_item);
|
||||
}
|
||||
|
||||
StatusItem& status_item()
|
||||
{
|
||||
return m_status_item;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned m_response_type;
|
||||
|
||||
@ -581,6 +674,13 @@ private:
|
||||
Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
|
||||
Vector<unsigned> m_search_results;
|
||||
Optional<String> m_bye_message;
|
||||
StatusItem m_status_item;
|
||||
};
|
||||
|
||||
enum class StoreMethod {
|
||||
Replace,
|
||||
Add,
|
||||
Remove
|
||||
};
|
||||
|
||||
class SolidResponse {
|
||||
|
@ -194,6 +194,41 @@ void Parser::parse_untagged()
|
||||
auto message = parse_while([](u8 x) { return x != '\r'; });
|
||||
consume("\r\n");
|
||||
m_response.data().set_bye(message.is_empty() ? Optional<String>() : Optional<String>(message));
|
||||
} else if (try_consume("STATUS")) {
|
||||
consume(" ");
|
||||
auto mailbox = parse_astring();
|
||||
consume(" (");
|
||||
auto status_item = StatusItem();
|
||||
status_item.set_mailbox(mailbox);
|
||||
while (!try_consume(")")) {
|
||||
auto status_att = parse_atom();
|
||||
consume(" ");
|
||||
auto value = parse_number();
|
||||
|
||||
auto type = StatusItemType::Recent;
|
||||
if (status_att.matches("MESSAGES")) {
|
||||
type = StatusItemType::Messages;
|
||||
} else if (status_att.matches("UNSEEN")) {
|
||||
type = StatusItemType::Unseen;
|
||||
} else if (status_att.matches("UIDNEXT")) {
|
||||
type = StatusItemType::UIDNext;
|
||||
} else if (status_att.matches("UIDVALIDITY")) {
|
||||
type = StatusItemType::UIDValidity;
|
||||
} else if (status_att.matches("RECENT")) {
|
||||
type = StatusItemType::Recent;
|
||||
} else {
|
||||
dbgln("Unmatched status attribute: {}", status_att);
|
||||
m_parsing_failed = true;
|
||||
}
|
||||
|
||||
status_item.set(type, value);
|
||||
|
||||
if (!at_end() && m_buffer[position] != ')')
|
||||
consume(" ");
|
||||
}
|
||||
m_response.data().set_status(move(status_item));
|
||||
try_consume(" "); // Not in the spec but the Outlook server sends a space for some reason.
|
||||
consume("\r\n");
|
||||
} else {
|
||||
auto x = parse_while([](u8 x) { return x != '\r'; });
|
||||
consume("\r\n");
|
||||
|
Loading…
Reference in New Issue
Block a user