Client.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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. ErrorOr<void> Client::on_ready_to_receive()
  48. {
  49. if (!TRY(m_socket->can_read_without_blocking()))
  50. return {};
  51. auto pending_bytes = TRY(m_socket->pending_bytes());
  52. auto receive_buffer = TRY(m_buffer.get_bytes_for_writing(pending_bytes));
  53. TRY(m_socket->read_until_filled(receive_buffer));
  54. // Once we get server hello we can start sending.
  55. if (m_connect_pending) {
  56. TRY(m_connect_pending->resolve({}));
  57. m_connect_pending.clear();
  58. m_buffer.clear();
  59. return {};
  60. }
  61. if (m_buffer[m_buffer.size() - 1] == '\n') {
  62. // Don't try parsing until we have a complete line.
  63. auto response = m_parser.parse(move(m_buffer), m_expecting_response);
  64. TRY(handle_parsed_response(move(response)));
  65. m_buffer.clear();
  66. }
  67. return {};
  68. }
  69. static ReadonlyBytes command_byte_buffer(CommandType command)
  70. {
  71. switch (command) {
  72. case CommandType::Noop:
  73. return "NOOP"sv.bytes();
  74. case CommandType::Capability:
  75. return "CAPABILITY"sv.bytes();
  76. case CommandType::Logout:
  77. return "LOGOUT"sv.bytes();
  78. case CommandType ::Idle:
  79. return "IDLE"sv.bytes();
  80. case CommandType::Login:
  81. return "LOGIN"sv.bytes();
  82. case CommandType::List:
  83. return "LIST"sv.bytes();
  84. case CommandType::Select:
  85. return "SELECT"sv.bytes();
  86. case CommandType::Fetch:
  87. return "FETCH"sv.bytes();
  88. case CommandType::Store:
  89. return "STORE"sv.bytes();
  90. case CommandType::Copy:
  91. return "COPY"sv.bytes();
  92. case CommandType::Create:
  93. return "CREATE"sv.bytes();
  94. case CommandType::Delete:
  95. return "DELETE"sv.bytes();
  96. case CommandType::Search:
  97. return "SEARCH"sv.bytes();
  98. case CommandType::UIDFetch:
  99. return "UID FETCH"sv.bytes();
  100. case CommandType::UIDStore:
  101. return "UID STORE"sv.bytes();
  102. case CommandType::UIDCopy:
  103. return "UID COPY"sv.bytes();
  104. case CommandType::UIDSearch:
  105. return "UID SEARCH"sv.bytes();
  106. case CommandType::Append:
  107. return "APPEND"sv.bytes();
  108. case CommandType::Examine:
  109. return "EXAMINE"sv.bytes();
  110. case CommandType::ListSub:
  111. return "LSUB"sv.bytes();
  112. case CommandType::Expunge:
  113. return "EXPUNGE"sv.bytes();
  114. case CommandType::Subscribe:
  115. return "SUBSCRIBE"sv.bytes();
  116. case CommandType::Unsubscribe:
  117. return "UNSUBSCRIBE"sv.bytes();
  118. case CommandType::Authenticate:
  119. return "AUTHENTICATE"sv.bytes();
  120. case CommandType::Check:
  121. return "CHECK"sv.bytes();
  122. case CommandType::Close:
  123. return "CLOSE"sv.bytes();
  124. case CommandType::Rename:
  125. return "RENAME"sv.bytes();
  126. case CommandType::Status:
  127. return "STATUS"sv.bytes();
  128. }
  129. VERIFY_NOT_REACHED();
  130. }
  131. ErrorOr<void> Client::send_raw(StringView data)
  132. {
  133. TRY(m_socket->write_until_depleted(data.bytes()));
  134. TRY(m_socket->write_until_depleted("\r\n"sv.bytes()));
  135. return {};
  136. }
  137. RefPtr<Promise<Optional<Response>>> Client::send_command(Command&& command)
  138. {
  139. m_command_queue.append(move(command));
  140. m_current_command++;
  141. auto promise = Promise<Optional<Response>>::construct();
  142. m_pending_promises.append(promise);
  143. if (m_pending_promises.size() == 1)
  144. MUST(send_next_command());
  145. return promise;
  146. }
  147. template<typename T>
  148. RefPtr<Promise<Optional<T>>> cast_promise(RefPtr<Promise<Optional<Response>>> promise_variant)
  149. {
  150. auto new_promise = promise_variant->map<Optional<T>>(
  151. [](Optional<Response>& variant) {
  152. return variant.has_value() ? move(variant->get<T>()) : Optional<T>();
  153. });
  154. return new_promise;
  155. }
  156. RefPtr<Promise<Optional<SolidResponse>>> Client::login(StringView username, StringView password)
  157. {
  158. auto command = Command { CommandType::Login, m_current_command, { serialize_astring(username), serialize_astring(password) } };
  159. return cast_promise<SolidResponse>(send_command(move(command)));
  160. }
  161. RefPtr<Promise<Optional<SolidResponse>>> Client::list(StringView reference_name, StringView mailbox)
  162. {
  163. auto command = Command { CommandType::List, m_current_command,
  164. { DeprecatedString::formatted("\"{}\"", reference_name),
  165. DeprecatedString::formatted("\"{}\"", mailbox) } };
  166. return cast_promise<SolidResponse>(send_command(move(command)));
  167. }
  168. RefPtr<Promise<Optional<SolidResponse>>> Client::lsub(StringView reference_name, StringView mailbox)
  169. {
  170. auto command = Command { CommandType::ListSub, m_current_command,
  171. { DeprecatedString::formatted("\"{}\"", reference_name),
  172. DeprecatedString::formatted("\"{}\"", mailbox) } };
  173. return cast_promise<SolidResponse>(send_command(move(command)));
  174. }
  175. RefPtr<Promise<Optional<SolidResponse>>> Client::fetch(FetchCommand request, bool uid)
  176. {
  177. auto command = Command { uid ? CommandType::UIDFetch : CommandType::Fetch, m_current_command, { request.serialize() } };
  178. return cast_promise<SolidResponse>(send_command(move(command)));
  179. }
  180. RefPtr<Promise<Optional<Response>>> Client::send_simple_command(CommandType type)
  181. {
  182. auto command = Command { type, m_current_command, {} };
  183. return send_command(move(command));
  184. }
  185. RefPtr<Promise<Optional<SolidResponse>>> Client::select(StringView string)
  186. {
  187. auto command = Command { CommandType::Select, m_current_command, { serialize_astring(string) } };
  188. return cast_promise<SolidResponse>(send_command(move(command)));
  189. }
  190. ErrorOr<void> Client::handle_parsed_response(ParseStatus&& parse_status)
  191. {
  192. if (!m_expecting_response) {
  193. if (!parse_status.successful) {
  194. dbgln("Parsing failed on unrequested data!");
  195. } else if (parse_status.response.has_value()) {
  196. unrequested_response_callback(move(parse_status.response.value().get<SolidResponse>().data()));
  197. }
  198. } else {
  199. bool should_send_next = false;
  200. if (!parse_status.successful) {
  201. m_expecting_response = false;
  202. TRY(m_pending_promises.first()->resolve({}));
  203. m_pending_promises.remove(0);
  204. }
  205. if (parse_status.response.has_value()) {
  206. m_expecting_response = false;
  207. should_send_next = parse_status.response->has<SolidResponse>();
  208. TRY(m_pending_promises.first()->resolve(move(parse_status.response)));
  209. m_pending_promises.remove(0);
  210. }
  211. if (should_send_next && !m_command_queue.is_empty()) {
  212. TRY(send_next_command());
  213. }
  214. }
  215. return {};
  216. }
  217. ErrorOr<void> Client::send_next_command()
  218. {
  219. auto command = m_command_queue.take_first();
  220. ByteBuffer buffer;
  221. auto tag = AK::DeprecatedString::formatted("A{} ", m_current_command);
  222. buffer += tag.to_byte_buffer();
  223. auto command_type = command_byte_buffer(command.type);
  224. buffer.append(command_type.data(), command_type.size());
  225. for (auto& arg : command.args) {
  226. buffer.append(" ", 1);
  227. buffer.append(arg.bytes().data(), arg.length());
  228. }
  229. TRY(send_raw(buffer));
  230. m_expecting_response = true;
  231. return {};
  232. }
  233. RefPtr<Promise<Optional<SolidResponse>>> Client::examine(StringView string)
  234. {
  235. auto command = Command { CommandType::Examine, m_current_command, { serialize_astring(string) } };
  236. return cast_promise<SolidResponse>(send_command(move(command)));
  237. }
  238. RefPtr<Promise<Optional<SolidResponse>>> Client::create_mailbox(StringView name)
  239. {
  240. auto command = Command { CommandType::Create, m_current_command, { serialize_astring(name) } };
  241. return cast_promise<SolidResponse>(send_command(move(command)));
  242. }
  243. RefPtr<Promise<Optional<SolidResponse>>> Client::delete_mailbox(StringView name)
  244. {
  245. auto command = Command { CommandType::Delete, m_current_command, { serialize_astring(name) } };
  246. return cast_promise<SolidResponse>(send_command(move(command)));
  247. }
  248. RefPtr<Promise<Optional<SolidResponse>>> Client::store(StoreMethod method, Sequence sequence_set, bool silent, Vector<DeprecatedString> const& flags, bool uid)
  249. {
  250. StringBuilder data_item_name;
  251. switch (method) {
  252. case StoreMethod::Replace:
  253. data_item_name.append("FLAGS"sv);
  254. break;
  255. case StoreMethod::Add:
  256. data_item_name.append("+FLAGS"sv);
  257. break;
  258. case StoreMethod::Remove:
  259. data_item_name.append("-FLAGS"sv);
  260. break;
  261. }
  262. if (silent) {
  263. data_item_name.append(".SILENT"sv);
  264. }
  265. StringBuilder flags_builder;
  266. flags_builder.append('(');
  267. flags_builder.join(' ', flags);
  268. flags_builder.append(')');
  269. auto command = Command { uid ? CommandType::UIDStore : CommandType::Store, m_current_command, { sequence_set.serialize(), data_item_name.to_deprecated_string(), flags_builder.to_deprecated_string() } };
  270. return cast_promise<SolidResponse>(send_command(move(command)));
  271. }
  272. RefPtr<Promise<Optional<SolidResponse>>> Client::search(Optional<DeprecatedString> charset, Vector<SearchKey>&& keys, bool uid)
  273. {
  274. Vector<DeprecatedString> args;
  275. if (charset.has_value()) {
  276. args.append("CHARSET "sv);
  277. args.append(charset.value());
  278. }
  279. for (auto const& item : keys) {
  280. args.append(item.serialize());
  281. }
  282. auto command = Command { uid ? CommandType::UIDSearch : CommandType::Search, m_current_command, args };
  283. return cast_promise<SolidResponse>(send_command(move(command)));
  284. }
  285. RefPtr<Promise<Optional<ContinueRequest>>> Client::idle()
  286. {
  287. auto promise = send_simple_command(CommandType::Idle);
  288. return cast_promise<ContinueRequest>(promise);
  289. }
  290. RefPtr<Promise<Optional<SolidResponse>>> Client::finish_idle()
  291. {
  292. auto promise = Promise<Optional<Response>>::construct();
  293. m_pending_promises.append(promise);
  294. MUST(send_raw("DONE"sv));
  295. m_expecting_response = true;
  296. return cast_promise<SolidResponse>(promise);
  297. }
  298. RefPtr<Promise<Optional<SolidResponse>>> Client::status(StringView mailbox, Vector<StatusItemType> const& types)
  299. {
  300. Vector<DeprecatedString> args;
  301. for (auto type : types) {
  302. switch (type) {
  303. case StatusItemType::Recent:
  304. args.append("RECENT"sv);
  305. break;
  306. case StatusItemType::UIDNext:
  307. args.append("UIDNEXT"sv);
  308. break;
  309. case StatusItemType::UIDValidity:
  310. args.append("UIDVALIDITY"sv);
  311. break;
  312. case StatusItemType::Unseen:
  313. args.append("UNSEEN"sv);
  314. break;
  315. case StatusItemType::Messages:
  316. args.append("MESSAGES"sv);
  317. break;
  318. }
  319. }
  320. StringBuilder types_list;
  321. types_list.append('(');
  322. types_list.join(' ', args);
  323. types_list.append(')');
  324. auto command = Command { CommandType::Status, m_current_command, { mailbox, types_list.to_deprecated_string() } };
  325. return cast_promise<SolidResponse>(send_command(move(command)));
  326. }
  327. RefPtr<Promise<Optional<SolidResponse>>> Client::append(StringView mailbox, Message&& message, Optional<Vector<DeprecatedString>> flags, Optional<Core::DateTime> date_time)
  328. {
  329. Vector<DeprecatedString> args = { mailbox };
  330. if (flags.has_value()) {
  331. StringBuilder flags_sb;
  332. flags_sb.append('(');
  333. flags_sb.join(' ', flags.value());
  334. flags_sb.append(')');
  335. args.append(flags_sb.to_deprecated_string());
  336. }
  337. if (date_time.has_value())
  338. args.append(date_time.value().to_deprecated_string("\"%d-%b-%Y %H:%M:%S +0000\""sv));
  339. args.append(DeprecatedString::formatted("{{{}}}", message.data.length()));
  340. auto continue_req = send_command(Command { CommandType::Append, m_current_command, args });
  341. auto response_promise = Promise<Optional<Response>>::construct();
  342. m_pending_promises.append(response_promise);
  343. continue_req->on_resolved = [this, message2 { move(message) }](auto& data) -> ErrorOr<void> {
  344. if (!data.has_value()) {
  345. TRY(handle_parsed_response({ .successful = false, .response = {} }));
  346. } else {
  347. TRY(send_raw(message2.data));
  348. m_expecting_response = true;
  349. }
  350. return {};
  351. };
  352. return cast_promise<SolidResponse>(response_promise);
  353. }
  354. RefPtr<Promise<Optional<SolidResponse>>> Client::subscribe(StringView mailbox)
  355. {
  356. auto command = Command { CommandType::Subscribe, m_current_command, { serialize_astring(mailbox) } };
  357. return cast_promise<SolidResponse>(send_command(move(command)));
  358. }
  359. RefPtr<Promise<Optional<SolidResponse>>> Client::unsubscribe(StringView mailbox)
  360. {
  361. auto command = Command { CommandType::Unsubscribe, m_current_command, { serialize_astring(mailbox) } };
  362. return cast_promise<SolidResponse>(send_command(move(command)));
  363. }
  364. RefPtr<Promise<Optional<Response>>> Client::authenticate(StringView method)
  365. {
  366. auto command = Command { CommandType::Authenticate, m_current_command, { method } };
  367. return send_command(move(command));
  368. }
  369. RefPtr<Promise<Optional<SolidResponse>>> Client::rename(StringView from, StringView to)
  370. {
  371. auto command = Command { CommandType::Rename, m_current_command, { serialize_astring(from), serialize_astring(to) } };
  372. return cast_promise<SolidResponse>(send_command(move(command)));
  373. }
  374. RefPtr<Promise<Optional<SolidResponse>>> Client::copy(Sequence sequence_set, StringView name, bool uid)
  375. {
  376. auto command = Command {
  377. uid ? CommandType::UIDCopy : CommandType::Copy, m_current_command, { sequence_set.serialize(), serialize_astring(name) }
  378. };
  379. return cast_promise<SolidResponse>(send_command(move(command)));
  380. }
  381. void Client::close()
  382. {
  383. m_socket->close();
  384. }
  385. bool Client::is_open()
  386. {
  387. return m_socket->is_open();
  388. }
  389. }