Client.cpp 15 KB

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