LibIMAP: Support for STORE and STATUS

This commit is contained in:
x-yl 2021-06-02 18:40:41 +04:00 committed by Ali Mohammad Pur
parent a6339297ec
commit 076c708d0a
Notes: sideshowbarker 2024-07-18 12:24:44 +09:00
4 changed files with 199 additions and 0 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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 {

View File

@ -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");