mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 04:27:44 +00:00
LibIMAP: Support for STORE and STATUS
This commit is contained in:
parent
a6339297ec
commit
076c708d0a
4 changed files with 199 additions and 0 deletions
|
@ -126,12 +126,18 @@ static ReadonlyBytes command_byte_buffer(CommandType command)
|
||||||
return "SELECT"sv.bytes();
|
return "SELECT"sv.bytes();
|
||||||
case CommandType::Fetch:
|
case CommandType::Fetch:
|
||||||
return "FETCH"sv.bytes();
|
return "FETCH"sv.bytes();
|
||||||
|
case CommandType::Store:
|
||||||
|
return "STORE"sv.bytes();
|
||||||
case CommandType::Search:
|
case CommandType::Search:
|
||||||
return "SEARCH"sv.bytes();
|
return "SEARCH"sv.bytes();
|
||||||
case CommandType::UIDFetch:
|
case CommandType::UIDFetch:
|
||||||
return "UID FETCH"sv.bytes();
|
return "UID FETCH"sv.bytes();
|
||||||
|
case CommandType::UIDStore:
|
||||||
|
return "UID STORE"sv.bytes();
|
||||||
case CommandType::UIDSearch:
|
case CommandType::UIDSearch:
|
||||||
return "UID SEARCH"sv.bytes();
|
return "UID SEARCH"sv.bytes();
|
||||||
|
case CommandType::Status:
|
||||||
|
return "STATUS"sv.bytes();
|
||||||
}
|
}
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
@ -248,6 +254,32 @@ void Client::send_next_command()
|
||||||
send_raw(buffer);
|
send_raw(buffer);
|
||||||
m_expecting_response = true;
|
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)
|
RefPtr<Promise<Optional<SolidResponse>>> Client::search(Optional<String> charset, Vector<SearchKey>&& keys, bool uid)
|
||||||
{
|
{
|
||||||
Vector<String> args;
|
Vector<String> args;
|
||||||
|
@ -276,6 +308,36 @@ RefPtr<Promise<Optional<SolidResponse>>> Client::finish_idle()
|
||||||
return cast_promise<SolidResponse>(promise);
|
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()
|
void Client::close()
|
||||||
{
|
{
|
||||||
if (m_tls) {
|
if (m_tls) {
|
||||||
|
|
|
@ -26,8 +26,10 @@ public:
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> select(StringView string);
|
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>>> search(Optional<String> charset, Vector<SearchKey>&& search_keys, bool uid);
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> fetch(FetchCommand request, 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<ContinueRequest>>> idle();
|
||||||
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
|
RefPtr<Promise<Optional<SolidResponse>>> finish_idle();
|
||||||
|
RefPtr<Promise<Optional<SolidResponse>>> status(StringView mailbox, Vector<StatusItemType> const& types);
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,11 @@ enum class CommandType {
|
||||||
Noop,
|
Noop,
|
||||||
Search,
|
Search,
|
||||||
Select,
|
Select,
|
||||||
|
Status,
|
||||||
|
Store,
|
||||||
UIDFetch,
|
UIDFetch,
|
||||||
UIDSearch,
|
UIDSearch,
|
||||||
|
UIDStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MailboxFlag : unsigned {
|
enum class MailboxFlag : unsigned {
|
||||||
|
@ -60,6 +63,7 @@ enum class ResponseType : unsigned {
|
||||||
Fetch = 1u << 9,
|
Fetch = 1u << 9,
|
||||||
Search = 1u << 10,
|
Search = 1u << 10,
|
||||||
Bye = 1u << 13,
|
Bye = 1u << 13,
|
||||||
|
Status = 1u << 14
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class FetchResponseType : unsigned {
|
enum class FetchResponseType : unsigned {
|
||||||
|
@ -71,8 +75,86 @@ enum class FetchResponseType : unsigned {
|
||||||
BodyStructure = 1u << 6,
|
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 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 {
|
struct Address {
|
||||||
Optional<String> name;
|
Optional<String> name;
|
||||||
Optional<String> source_route;
|
Optional<String> source_route;
|
||||||
|
@ -564,6 +646,17 @@ public:
|
||||||
return m_bye_message;
|
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:
|
private:
|
||||||
unsigned m_response_type;
|
unsigned m_response_type;
|
||||||
|
|
||||||
|
@ -581,6 +674,13 @@ private:
|
||||||
Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
|
Vector<Tuple<unsigned, FetchResponseData>> m_fetch_responses;
|
||||||
Vector<unsigned> m_search_results;
|
Vector<unsigned> m_search_results;
|
||||||
Optional<String> m_bye_message;
|
Optional<String> m_bye_message;
|
||||||
|
StatusItem m_status_item;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StoreMethod {
|
||||||
|
Replace,
|
||||||
|
Add,
|
||||||
|
Remove
|
||||||
};
|
};
|
||||||
|
|
||||||
class SolidResponse {
|
class SolidResponse {
|
||||||
|
|
|
@ -194,6 +194,41 @@ void Parser::parse_untagged()
|
||||||
auto message = parse_while([](u8 x) { return x != '\r'; });
|
auto message = parse_while([](u8 x) { return x != '\r'; });
|
||||||
consume("\r\n");
|
consume("\r\n");
|
||||||
m_response.data().set_bye(message.is_empty() ? Optional<String>() : Optional<String>(message));
|
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 {
|
} else {
|
||||||
auto x = parse_while([](u8 x) { return x != '\r'; });
|
auto x = parse_while([](u8 x) { return x != '\r'; });
|
||||||
consume("\r\n");
|
consume("\r\n");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue