Serenity Operating System
at master 160 lines 6.9 kB view raw
1/* 2 * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibCore/ArgsParser.h> 8#include <LibCore/DeprecatedFile.h> 9#include <LibCore/EventLoop.h> 10#include <LibCore/GetPassword.h> 11#include <LibIMAP/Client.h> 12#include <LibMain/Main.h> 13 14ErrorOr<int> serenity_main(Main::Arguments arguments) 15{ 16 if (pledge("stdio inet tty rpath unix", nullptr) < 0) { 17 perror("pledge"); 18 return 1; 19 } 20 21 DeprecatedString host; 22 int port; 23 bool tls { false }; 24 25 DeprecatedString username; 26 Core::SecretString password; 27 28 bool interactive_password; 29 30 Core::ArgsParser args_parser; 31 args_parser.add_option(interactive_password, "Prompt for password with getpass", "interactive", 'i'); 32 args_parser.add_option(tls, "Connect with TLS (IMAPS)", "secure", 's'); 33 args_parser.add_positional_argument(host, "IMAP host", "host"); 34 args_parser.add_positional_argument(port, "Port to connect to", "port"); 35 args_parser.add_positional_argument(username, "Username", "username"); 36 args_parser.parse(arguments); 37 38 if (interactive_password) { 39 password = TRY(Core::get_password()); 40 } else { 41 auto standard_input = Core::DeprecatedFile::standard_input(); 42 password = Core::SecretString::take_ownership(standard_input->read_all()); 43 } 44 45 Core::EventLoop loop; 46 auto client = TRY(tls ? IMAP::Client::connect_tls(host, port) : IMAP::Client::connect_plaintext(host, port)); 47 TRY(client->connection_promise()->await()); 48 49 auto response = TRY(client->login(username, password.view())->await()).release_value(); 50 outln("[LOGIN] Login response: {}", response.response_text()); 51 52 response = move(TRY(client->send_simple_command(IMAP::CommandType::Capability)->await()).value().get<IMAP::SolidResponse>()); 53 outln("[CAPABILITY] First capability: {}", response.data().capabilities().first()); 54 bool idle_supported = !response.data().capabilities().find_if([](auto capability) { return capability.equals_ignoring_ascii_case("IDLE"sv); }).is_end(); 55 56 response = TRY(client->list(""sv, "*"sv)->await()).release_value(); 57 outln("[LIST] First mailbox: {}", response.data().list_items().first().name); 58 59 auto mailbox = "Inbox"sv; 60 response = TRY(client->select(mailbox)->await()).release_value(); 61 outln("[SELECT] Select response: {}", response.response_text()); 62 63 auto message = Message { 64 "From: John Doe <jdoe@machine.example>\r\n" 65 "To: Mary Smith <mary@example.net>\r\n" 66 "Subject: Saying Hello\r\n" 67 "Date: Fri, 21 Nov 1997 09:55:06 -0600\r\n" 68 "Message-ID: <1234@local.machine.example>\r\n" 69 "\r\n" 70 "This is a message just to say hello.\r\n" 71 "So, \"Hello\"." 72 }; 73 auto promise = client->append("INBOX"sv, move(message)); 74 response = TRY(promise->await()).release_value(); 75 outln("[APPEND] Response: {}", response.response_text()); 76 77 Vector<IMAP::SearchKey> keys; 78 keys.append(IMAP::SearchKey { 79 IMAP::SearchKey::From { "jdoe@machine.example" } }); 80 keys.append(IMAP::SearchKey { 81 IMAP::SearchKey::Subject { "Saying Hello" } }); 82 response = TRY(client->search({}, move(keys), false)->await()).release_value(); 83 84 Vector<unsigned> search_results = move(response.data().search_results()); 85 auto added_message = search_results.first(); 86 outln("[SEARCH] Number of results: {}", search_results.size()); 87 88 response = TRY(client->status("INBOX"sv, { IMAP::StatusItemType::Recent, IMAP::StatusItemType::Messages })->await()).release_value(); 89 outln("[STATUS] Recent items: {}", response.data().status_item().get(IMAP::StatusItemType::Recent)); 90 91 for (auto item : search_results) { 92 // clang-format off 93 // clang formats this very badly 94 auto fetch_command = IMAP::FetchCommand { 95 .sequence_set = { { (int)item, (int)item } }, 96 .data_items = { 97 IMAP::FetchCommand::DataItem { 98 .type = IMAP::FetchCommand::DataItemType::BodyStructure 99 }, 100 IMAP::FetchCommand::DataItem { 101 .type = IMAP::FetchCommand::DataItemType::BodySection, 102 .section = IMAP::FetchCommand::DataItem::Section { 103 .type = IMAP::FetchCommand::DataItem::SectionType::HeaderFields, 104 .headers = { { "Subject" } } 105 } 106 }, 107 IMAP::FetchCommand::DataItem { 108 .type = IMAP::FetchCommand::DataItemType::BodySection, 109 .section = IMAP::FetchCommand::DataItem::Section { 110 .type = IMAP::FetchCommand::DataItem::SectionType::Parts, 111 .parts = { { 1 } } 112 }, 113 .partial_fetch = true, 114 .start = 0, 115 .octets = 8192 116 } 117 } 118 }; 119 // clang-format on 120 121 auto fetch_response = TRY(client->fetch(fetch_command, false)->await()).release_value(); 122 outln("[FETCH] Subject of search result: {}", 123 fetch_response.data() 124 .fetch_data() 125 .first() 126 .get<IMAP::FetchResponseData>() 127 .body_data() 128 .find_if([](Tuple<IMAP::FetchCommand::DataItem, Optional<DeprecatedString>>& data) { 129 const auto data_item = data.get<0>(); 130 return data_item.section.has_value() && data_item.section->type == IMAP::FetchCommand::DataItem::SectionType::HeaderFields; 131 }) 132 ->get<1>() 133 .value()); 134 } 135 136 // FIXME: There is a discrepancy between IMAP::Sequence wanting signed ints 137 // and IMAP search results returning unsigned ones. Find which one is 138 // more correct and fix this. 139 response = TRY(client->store(IMAP::StoreMethod::Add, { static_cast<int>(added_message), static_cast<int>(added_message) }, false, { "\\Deleted" }, false)->await()).release_value(); 140 outln("[STORE] Store response: {}", response.response_text()); 141 142 response = move(TRY(client->send_simple_command(IMAP::CommandType::Expunge)->await()).release_value().get<IMAP::SolidResponse>()); 143 outln("[EXPUNGE] Number of expunged entries: {}", response.data().expunged().size()); 144 145 if (idle_supported) { 146 VERIFY(TRY(client->idle()->await()).has_value()); 147 sleep(3); 148 response = TRY(client->finish_idle()->await()).release_value(); 149 outln("[IDLE] Idle response: {}", response.response_text()); 150 } else { 151 outln("[IDLE] Skipped. No IDLE support."); 152 } 153 154 response = move(TRY(client->send_simple_command(IMAP::CommandType::Logout)->await()).release_value().get<IMAP::SolidResponse>()); 155 outln("[LOGOUT] Bye: {}", response.data().bye_message().value()); 156 157 client->close(); 158 159 return 0; 160}