Client.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /*
  2. * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/OwnPtr.h>
  7. #include <LibIMAP/Client.h>
  8. namespace IMAP {
  9. Client::Client(StringView host, u16 port, NonnullOwnPtr<Core::Socket> socket)
  10. : m_host(host)
  11. , m_port(port)
  12. , m_socket(move(socket))
  13. , m_connect_pending(Promise<Empty>::construct())
  14. {
  15. setup_callbacks();
  16. }
  17. Client::Client(Client&& other)
  18. : m_host(other.m_host)
  19. , m_port(other.m_port)
  20. , m_socket(move(other.m_socket))
  21. , m_connect_pending(move(other.m_connect_pending))
  22. {
  23. setup_callbacks();
  24. }
  25. void Client::setup_callbacks()
  26. {
  27. m_socket->on_ready_to_read = [&] {
  28. auto maybe_error = on_ready_to_receive();
  29. if (maybe_error.is_error()) {
  30. dbgln("Error receiving from the socket: {}", maybe_error.error());
  31. close();
  32. }
  33. };
  34. }
  35. ErrorOr<NonnullOwnPtr<Client>> Client::connect_tls(StringView host, u16 port)
  36. {
  37. auto tls_socket = TRY(TLS::TLSv12::connect(host, port));
  38. dbgln("connecting to {}:{}", host, port);
  39. return adopt_nonnull_own_or_enomem(new (nothrow) Client(host, port, move(tls_socket)));
  40. }
  41. ErrorOr<NonnullOwnPtr<Client>> Client::connect_plaintext(StringView host, u16 port)
  42. {
  43. auto socket = TRY(Core::TCPSocket::connect(host, port));
  44. dbgln("Connected to {}:{}", host, port);
  45. return adopt_nonnull_own_or_enomem(new (nothrow) Client(host, port, move(socket)));
  46. }
  47. bool Client::verify_response_is_complete()
  48. {
  49. // FIXME: This is still more of a heuristic than a proper approach.
  50. // I would imagine this breaks if we happen to get an email that
  51. // contains this pattern we're looking for.
  52. dbgln("Waiting for a complete IMAP response, buffer size is now {}", m_buffer.size());
  53. Vector<StringView> statuses = { "OK"sv, "BAD"sv, "NO"sv };
  54. auto slice_size = m_buffer.size() >= 100 ? 100 : m_buffer.size(); // Arbitrary slice size, should contain what we're looking for.
  55. auto slice_data = MUST(m_buffer.slice(m_buffer.size() - slice_size, slice_size));
  56. StringView slice = StringView(slice_data);
  57. for (auto status : statuses) {
  58. ByteString pattern = ByteString::formatted("A{} {}", m_current_command, status);
  59. if (slice.contains(pattern)) {
  60. dbgln("IMAP server replied {}, sending to parser", pattern);
  61. return true;
  62. }
  63. }
  64. return false;
  65. }
  66. ErrorOr<void> Client::on_ready_to_receive()
  67. {
  68. if (!TRY(m_socket->can_read_without_blocking()))
  69. return {};
  70. auto pending_bytes = TRY(m_socket->pending_bytes());
  71. auto receive_buffer = TRY(m_buffer.get_bytes_for_writing(pending_bytes));
  72. TRY(m_socket->read_until_filled(receive_buffer));
  73. // Once we get server hello we can start sending.
  74. if (m_connect_pending) {
  75. m_connect_pending->resolve({});
  76. m_connect_pending.clear();
  77. m_buffer.clear();
  78. return {};
  79. }
  80. // Don't try parsing until we have a complete response.
  81. if (verify_response_is_complete()) {
  82. auto response = m_parser.parse(move(m_buffer), m_expecting_response);
  83. TRY(handle_parsed_response(move(response)));
  84. m_buffer.clear();
  85. }
  86. return {};
  87. }
  88. static ReadonlyBytes command_byte_buffer(CommandType command)
  89. {
  90. switch (command) {
  91. case CommandType::Noop:
  92. return "NOOP"sv.bytes();
  93. case CommandType::Capability:
  94. return "CAPABILITY"sv.bytes();
  95. case CommandType::Logout:
  96. return "LOGOUT"sv.bytes();
  97. case CommandType ::Idle:
  98. return "IDLE"sv.bytes();
  99. case CommandType::Login:
  100. return "LOGIN"sv.bytes();
  101. case CommandType::List:
  102. return "LIST"sv.bytes();
  103. case CommandType::Select:
  104. return "SELECT"sv.bytes();
  105. case CommandType::Fetch:
  106. return "FETCH"sv.bytes();
  107. case CommandType::Store:
  108. return "STORE"sv.bytes();
  109. case CommandType::Copy:
  110. return "COPY"sv.bytes();
  111. case CommandType::Create:
  112. return "CREATE"sv.bytes();
  113. case CommandType::Delete:
  114. return "DELETE"sv.bytes();
  115. case CommandType::Search:
  116. return "SEARCH"sv.bytes();
  117. case CommandType::UIDFetch:
  118. return "UID FETCH"sv.bytes();
  119. case CommandType::UIDStore:
  120. return "UID STORE"sv.bytes();
  121. case CommandType::UIDCopy:
  122. return "UID COPY"sv.bytes();
  123. case CommandType::UIDSearch:
  124. return "UID SEARCH"sv.bytes();
  125. case CommandType::Append:
  126. return "APPEND"sv.bytes();
  127. case CommandType::Examine:
  128. return "EXAMINE"sv.bytes();
  129. case CommandType::ListSub:
  130. return "LSUB"sv.bytes();
  131. case CommandType::Expunge:
  132. return "EXPUNGE"sv.bytes();
  133. case CommandType::Subscribe:
  134. return "SUBSCRIBE"sv.bytes();
  135. case CommandType::Unsubscribe:
  136. return "UNSUBSCRIBE"sv.bytes();
  137. case CommandType::Authenticate:
  138. return "AUTHENTICATE"sv.bytes();
  139. case CommandType::Check:
  140. return "CHECK"sv.bytes();
  141. case CommandType::Close:
  142. return "CLOSE"sv.bytes();
  143. case CommandType::Rename:
  144. return "RENAME"sv.bytes();
  145. case CommandType::Status:
  146. return "STATUS"sv.bytes();
  147. }
  148. VERIFY_NOT_REACHED();
  149. }
  150. ErrorOr<void> Client::send_raw(StringView data)
  151. {
  152. TRY(m_socket->write_until_depleted(data.bytes()));
  153. TRY(m_socket->write_until_depleted("\r\n"sv.bytes()));
  154. return {};
  155. }
  156. NonnullRefPtr<Promise<Response>> Client::send_command(Command&& command)
  157. {
  158. m_command_queue.append(move(command));
  159. m_current_command++;
  160. auto promise = Promise<Response>::construct();
  161. m_pending_promises.append(promise);
  162. if (m_pending_promises.size() == 1) {
  163. auto maybe_error = send_next_command();
  164. if (maybe_error.is_error())
  165. promise->reject(maybe_error.release_error());
  166. }
  167. return promise;
  168. }
  169. template<typename T>
  170. NonnullRefPtr<Promise<T>> cast_promise(NonnullRefPtr<Promise<Response>> promise_variant)
  171. {
  172. auto new_promise = promise_variant->map<T>(
  173. [](Response& variant) {
  174. return move(variant.get<T>());
  175. });
  176. return new_promise;
  177. }
  178. NonnullRefPtr<Promise<SolidResponse>> Client::login(StringView username, StringView password)
  179. {
  180. auto command = Command { CommandType::Login, m_current_command, { serialize_astring(username), serialize_astring(password) } };
  181. return cast_promise<SolidResponse>(send_command(move(command)));
  182. }
  183. NonnullRefPtr<Promise<SolidResponse>> Client::list(StringView reference_name, StringView mailbox)
  184. {
  185. auto command = Command { CommandType::List, m_current_command,
  186. { ByteString::formatted("\"{}\"", reference_name),
  187. ByteString::formatted("\"{}\"", mailbox) } };
  188. return cast_promise<SolidResponse>(send_command(move(command)));
  189. }
  190. NonnullRefPtr<Promise<SolidResponse>> Client::lsub(StringView reference_name, StringView mailbox)
  191. {
  192. auto command = Command { CommandType::ListSub, m_current_command,
  193. { ByteString::formatted("\"{}\"", reference_name),
  194. ByteString::formatted("\"{}\"", mailbox) } };
  195. return cast_promise<SolidResponse>(send_command(move(command)));
  196. }
  197. NonnullRefPtr<Promise<SolidResponse>> Client::fetch(FetchCommand request, bool uid)
  198. {
  199. auto command = Command { uid ? CommandType::UIDFetch : CommandType::Fetch, m_current_command, { request.serialize() } };
  200. return cast_promise<SolidResponse>(send_command(move(command)));
  201. }
  202. NonnullRefPtr<Promise<Response>> Client::send_simple_command(CommandType type)
  203. {
  204. auto command = Command { type, m_current_command, {} };
  205. return send_command(move(command));
  206. }
  207. NonnullRefPtr<Promise<SolidResponse>> Client::select(StringView string)
  208. {
  209. auto command = Command { CommandType::Select, m_current_command, { serialize_astring(string) } };
  210. return cast_promise<SolidResponse>(send_command(move(command)));
  211. }
  212. ErrorOr<void> Client::handle_parsed_response(ParseStatus&& parse_status)
  213. {
  214. if (!m_expecting_response) {
  215. if (!parse_status.successful) {
  216. dbgln("Parsing failed on unrequested data!");
  217. } else if (parse_status.response.has_value()) {
  218. unrequested_response_callback(move(parse_status.response.value().get<SolidResponse>().data()));
  219. }
  220. } else {
  221. bool should_send_next = false;
  222. if (!parse_status.successful) {
  223. m_expecting_response = false;
  224. m_pending_promises.take_first()->reject(Error::from_string_literal("Failed to parse message"));
  225. }
  226. if (parse_status.response.has_value()) {
  227. m_expecting_response = false;
  228. should_send_next = parse_status.response->has<SolidResponse>();
  229. m_pending_promises.take_first()->resolve(parse_status.response.release_value());
  230. }
  231. if (should_send_next && !m_command_queue.is_empty()) {
  232. TRY(send_next_command());
  233. }
  234. }
  235. return {};
  236. }
  237. ErrorOr<void> Client::send_next_command()
  238. {
  239. auto command = m_command_queue.take_first();
  240. ByteBuffer buffer;
  241. auto tag = AK::ByteString::formatted("A{} ", m_current_command);
  242. buffer += tag.to_byte_buffer();
  243. auto command_type = command_byte_buffer(command.type);
  244. buffer.append(command_type.data(), command_type.size());
  245. for (auto& arg : command.args) {
  246. buffer.append(" ", 1);
  247. buffer.append(arg.bytes().data(), arg.length());
  248. }
  249. TRY(send_raw(buffer));
  250. m_expecting_response = true;
  251. return {};
  252. }
  253. NonnullRefPtr<Promise<SolidResponse>> Client::examine(StringView string)
  254. {
  255. auto command = Command { CommandType::Examine, m_current_command, { serialize_astring(string) } };
  256. return cast_promise<SolidResponse>(send_command(move(command)));
  257. }
  258. NonnullRefPtr<Promise<SolidResponse>> Client::create_mailbox(StringView name)
  259. {
  260. auto command = Command { CommandType::Create, m_current_command, { serialize_astring(name) } };
  261. return cast_promise<SolidResponse>(send_command(move(command)));
  262. }
  263. NonnullRefPtr<Promise<SolidResponse>> Client::delete_mailbox(StringView name)
  264. {
  265. auto command = Command { CommandType::Delete, m_current_command, { serialize_astring(name) } };
  266. return cast_promise<SolidResponse>(send_command(move(command)));
  267. }
  268. NonnullRefPtr<Promise<SolidResponse>> Client::store(StoreMethod method, Sequence sequence_set, bool silent, Vector<ByteString> const& flags, bool uid)
  269. {
  270. StringBuilder data_item_name;
  271. switch (method) {
  272. case StoreMethod::Replace:
  273. data_item_name.append("FLAGS"sv);
  274. break;
  275. case StoreMethod::Add:
  276. data_item_name.append("+FLAGS"sv);
  277. break;
  278. case StoreMethod::Remove:
  279. data_item_name.append("-FLAGS"sv);
  280. break;
  281. }
  282. if (silent) {
  283. data_item_name.append(".SILENT"sv);
  284. }
  285. StringBuilder flags_builder;
  286. flags_builder.append('(');
  287. flags_builder.join(' ', flags);
  288. flags_builder.append(')');
  289. auto command = Command { uid ? CommandType::UIDStore : CommandType::Store, m_current_command, { sequence_set.serialize(), data_item_name.to_byte_string(), flags_builder.to_byte_string() } };
  290. return cast_promise<SolidResponse>(send_command(move(command)));
  291. }
  292. NonnullRefPtr<Promise<SolidResponse>> Client::search(Optional<ByteString> charset, Vector<SearchKey>&& keys, bool uid)
  293. {
  294. Vector<ByteString> args;
  295. if (charset.has_value()) {
  296. args.append("CHARSET "sv);
  297. args.append(charset.value());
  298. }
  299. for (auto const& item : keys) {
  300. args.append(item.serialize());
  301. }
  302. auto command = Command { uid ? CommandType::UIDSearch : CommandType::Search, m_current_command, args };
  303. return cast_promise<SolidResponse>(send_command(move(command)));
  304. }
  305. NonnullRefPtr<Promise<ContinueRequest>> Client::idle()
  306. {
  307. auto promise = send_simple_command(CommandType::Idle);
  308. return cast_promise<ContinueRequest>(promise);
  309. }
  310. NonnullRefPtr<Promise<SolidResponse>> Client::finish_idle()
  311. {
  312. auto promise = Promise<Response>::construct();
  313. m_pending_promises.append(promise);
  314. MUST(send_raw("DONE"sv));
  315. m_expecting_response = true;
  316. return cast_promise<SolidResponse>(promise);
  317. }
  318. NonnullRefPtr<Promise<SolidResponse>> Client::status(StringView mailbox, Vector<StatusItemType> const& types)
  319. {
  320. Vector<ByteString> args;
  321. for (auto type : types) {
  322. switch (type) {
  323. case StatusItemType::Recent:
  324. args.append("RECENT"sv);
  325. break;
  326. case StatusItemType::UIDNext:
  327. args.append("UIDNEXT"sv);
  328. break;
  329. case StatusItemType::UIDValidity:
  330. args.append("UIDVALIDITY"sv);
  331. break;
  332. case StatusItemType::Unseen:
  333. args.append("UNSEEN"sv);
  334. break;
  335. case StatusItemType::Messages:
  336. args.append("MESSAGES"sv);
  337. break;
  338. }
  339. }
  340. StringBuilder types_list;
  341. types_list.append('(');
  342. types_list.join(' ', args);
  343. types_list.append(')');
  344. auto command = Command { CommandType::Status, m_current_command, { mailbox, types_list.to_byte_string() } };
  345. return cast_promise<SolidResponse>(send_command(move(command)));
  346. }
  347. NonnullRefPtr<Promise<SolidResponse>> Client::append(StringView mailbox, Message&& message, Optional<Vector<ByteString>> flags, Optional<Core::DateTime> date_time)
  348. {
  349. Vector<ByteString> args = { mailbox };
  350. if (flags.has_value()) {
  351. StringBuilder flags_sb;
  352. flags_sb.append('(');
  353. flags_sb.join(' ', flags.value());
  354. flags_sb.append(')');
  355. args.append(flags_sb.to_byte_string());
  356. }
  357. if (date_time.has_value())
  358. args.append(date_time.value().to_byte_string("\"%d-%b-%Y %H:%M:%S +0000\""sv));
  359. args.append(ByteString::formatted("{{{}}}", message.data.length()));
  360. auto continue_req = send_command(Command { CommandType::Append, m_current_command, args });
  361. auto response_promise = Promise<Response>::construct();
  362. m_pending_promises.append(response_promise);
  363. continue_req->on_resolution = [this, message2 { move(message) }](auto&) -> ErrorOr<void> {
  364. TRY(send_raw(message2.data));
  365. m_expecting_response = true;
  366. return {};
  367. };
  368. continue_req->on_rejection = [this](Error&) {
  369. // NOTE: This never fails.
  370. MUST(handle_parsed_response({ .successful = false, .response = {} }));
  371. };
  372. return cast_promise<SolidResponse>(response_promise);
  373. }
  374. NonnullRefPtr<Promise<SolidResponse>> Client::subscribe(StringView mailbox)
  375. {
  376. auto command = Command { CommandType::Subscribe, m_current_command, { serialize_astring(mailbox) } };
  377. return cast_promise<SolidResponse>(send_command(move(command)));
  378. }
  379. NonnullRefPtr<Promise<SolidResponse>> Client::unsubscribe(StringView mailbox)
  380. {
  381. auto command = Command { CommandType::Unsubscribe, m_current_command, { serialize_astring(mailbox) } };
  382. return cast_promise<SolidResponse>(send_command(move(command)));
  383. }
  384. NonnullRefPtr<Promise<Response>> Client::authenticate(StringView method)
  385. {
  386. auto command = Command { CommandType::Authenticate, m_current_command, { method } };
  387. return send_command(move(command));
  388. }
  389. NonnullRefPtr<Promise<SolidResponse>> Client::rename(StringView from, StringView to)
  390. {
  391. auto command = Command { CommandType::Rename, m_current_command, { serialize_astring(from), serialize_astring(to) } };
  392. return cast_promise<SolidResponse>(send_command(move(command)));
  393. }
  394. NonnullRefPtr<Promise<SolidResponse>> Client::copy(Sequence sequence_set, StringView name, bool uid)
  395. {
  396. auto command = Command {
  397. uid ? CommandType::UIDCopy : CommandType::Copy, m_current_command, { sequence_set.serialize(), serialize_astring(name) }
  398. };
  399. return cast_promise<SolidResponse>(send_command(move(command)));
  400. }
  401. void Client::close()
  402. {
  403. m_socket->close();
  404. }
  405. bool Client::is_open()
  406. {
  407. return m_socket->is_open();
  408. }
  409. }