Client.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  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. DeprecatedString pattern = DeprecatedString::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. ErrorOr<RefPtr<Promise<Optional<Response>>>> Client::send_command(Command&& command)
  157. {
  158. m_command_queue.append(move(command));
  159. m_current_command++;
  160. auto promise = Promise<Optional<Response>>::construct();
  161. m_pending_promises.append(promise);
  162. if (m_pending_promises.size() == 1)
  163. TRY(send_next_command());
  164. return promise;
  165. }
  166. template<typename T>
  167. RefPtr<Promise<Optional<T>>> cast_promise(RefPtr<Promise<Optional<Response>>> promise_variant)
  168. {
  169. auto new_promise = promise_variant->map<Optional<T>>(
  170. [](Optional<Response>& variant) {
  171. return variant.has_value() ? move(variant->get<T>()) : Optional<T>();
  172. });
  173. return new_promise;
  174. }
  175. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::login(StringView username, StringView password)
  176. {
  177. auto command = Command { CommandType::Login, m_current_command, { serialize_astring(username), serialize_astring(password) } };
  178. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  179. }
  180. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::list(StringView reference_name, StringView mailbox)
  181. {
  182. auto command = Command { CommandType::List, m_current_command,
  183. { DeprecatedString::formatted("\"{}\"", reference_name),
  184. DeprecatedString::formatted("\"{}\"", mailbox) } };
  185. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  186. }
  187. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::lsub(StringView reference_name, StringView mailbox)
  188. {
  189. auto command = Command { CommandType::ListSub, m_current_command,
  190. { DeprecatedString::formatted("\"{}\"", reference_name),
  191. DeprecatedString::formatted("\"{}\"", mailbox) } };
  192. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  193. }
  194. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::fetch(FetchCommand request, bool uid)
  195. {
  196. auto command = Command { uid ? CommandType::UIDFetch : CommandType::Fetch, m_current_command, { request.serialize() } };
  197. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  198. }
  199. ErrorOr<RefPtr<Promise<Optional<Response>>>> Client::send_simple_command(CommandType type)
  200. {
  201. auto command = Command { type, m_current_command, {} };
  202. return TRY(send_command(move(command)));
  203. }
  204. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::select(StringView string)
  205. {
  206. auto command = Command { CommandType::Select, m_current_command, { serialize_astring(string) } };
  207. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  208. }
  209. ErrorOr<void> Client::handle_parsed_response(ParseStatus&& parse_status)
  210. {
  211. if (!m_expecting_response) {
  212. if (!parse_status.successful) {
  213. dbgln("Parsing failed on unrequested data!");
  214. } else if (parse_status.response.has_value()) {
  215. unrequested_response_callback(move(parse_status.response.value().get<SolidResponse>().data()));
  216. }
  217. } else {
  218. bool should_send_next = false;
  219. if (!parse_status.successful) {
  220. m_expecting_response = false;
  221. m_pending_promises.first()->resolve({});
  222. m_pending_promises.remove(0);
  223. }
  224. if (parse_status.response.has_value()) {
  225. m_expecting_response = false;
  226. should_send_next = parse_status.response->has<SolidResponse>();
  227. m_pending_promises.first()->resolve(move(parse_status.response));
  228. m_pending_promises.remove(0);
  229. }
  230. if (should_send_next && !m_command_queue.is_empty()) {
  231. TRY(send_next_command());
  232. }
  233. }
  234. return {};
  235. }
  236. ErrorOr<void> Client::send_next_command()
  237. {
  238. auto command = m_command_queue.take_first();
  239. ByteBuffer buffer;
  240. auto tag = AK::DeprecatedString::formatted("A{} ", m_current_command);
  241. buffer += tag.to_byte_buffer();
  242. auto command_type = command_byte_buffer(command.type);
  243. buffer.append(command_type.data(), command_type.size());
  244. for (auto& arg : command.args) {
  245. buffer.append(" ", 1);
  246. buffer.append(arg.bytes().data(), arg.length());
  247. }
  248. TRY(send_raw(buffer));
  249. m_expecting_response = true;
  250. return {};
  251. }
  252. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::examine(StringView string)
  253. {
  254. auto command = Command { CommandType::Examine, m_current_command, { serialize_astring(string) } };
  255. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  256. }
  257. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::create_mailbox(StringView name)
  258. {
  259. auto command = Command { CommandType::Create, m_current_command, { serialize_astring(name) } };
  260. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  261. }
  262. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::delete_mailbox(StringView name)
  263. {
  264. auto command = Command { CommandType::Delete, m_current_command, { serialize_astring(name) } };
  265. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  266. }
  267. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::store(StoreMethod method, Sequence sequence_set, bool silent, Vector<DeprecatedString> const& flags, bool uid)
  268. {
  269. StringBuilder data_item_name;
  270. switch (method) {
  271. case StoreMethod::Replace:
  272. data_item_name.append("FLAGS"sv);
  273. break;
  274. case StoreMethod::Add:
  275. data_item_name.append("+FLAGS"sv);
  276. break;
  277. case StoreMethod::Remove:
  278. data_item_name.append("-FLAGS"sv);
  279. break;
  280. }
  281. if (silent) {
  282. data_item_name.append(".SILENT"sv);
  283. }
  284. StringBuilder flags_builder;
  285. flags_builder.append('(');
  286. flags_builder.join(' ', flags);
  287. flags_builder.append(')');
  288. 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() } };
  289. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  290. }
  291. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::search(Optional<DeprecatedString> charset, Vector<SearchKey>&& keys, bool uid)
  292. {
  293. Vector<DeprecatedString> args;
  294. if (charset.has_value()) {
  295. args.append("CHARSET "sv);
  296. args.append(charset.value());
  297. }
  298. for (auto const& item : keys) {
  299. args.append(item.serialize());
  300. }
  301. auto command = Command { uid ? CommandType::UIDSearch : CommandType::Search, m_current_command, args };
  302. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  303. }
  304. ErrorOr<RefPtr<Promise<Optional<ContinueRequest>>>> Client::idle()
  305. {
  306. auto promise = TRY(send_simple_command(CommandType::Idle));
  307. return cast_promise<ContinueRequest>(promise);
  308. }
  309. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::finish_idle()
  310. {
  311. auto promise = Promise<Optional<Response>>::construct();
  312. m_pending_promises.append(promise);
  313. MUST(send_raw("DONE"sv));
  314. m_expecting_response = true;
  315. return cast_promise<SolidResponse>(promise);
  316. }
  317. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::status(StringView mailbox, Vector<StatusItemType> const& types)
  318. {
  319. Vector<DeprecatedString> args;
  320. for (auto type : types) {
  321. switch (type) {
  322. case StatusItemType::Recent:
  323. args.append("RECENT"sv);
  324. break;
  325. case StatusItemType::UIDNext:
  326. args.append("UIDNEXT"sv);
  327. break;
  328. case StatusItemType::UIDValidity:
  329. args.append("UIDVALIDITY"sv);
  330. break;
  331. case StatusItemType::Unseen:
  332. args.append("UNSEEN"sv);
  333. break;
  334. case StatusItemType::Messages:
  335. args.append("MESSAGES"sv);
  336. break;
  337. }
  338. }
  339. StringBuilder types_list;
  340. types_list.append('(');
  341. types_list.join(' ', args);
  342. types_list.append(')');
  343. auto command = Command { CommandType::Status, m_current_command, { mailbox, types_list.to_deprecated_string() } };
  344. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  345. }
  346. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::append(StringView mailbox, Message&& message, Optional<Vector<DeprecatedString>> flags, Optional<Core::DateTime> date_time)
  347. {
  348. Vector<DeprecatedString> args = { mailbox };
  349. if (flags.has_value()) {
  350. StringBuilder flags_sb;
  351. flags_sb.append('(');
  352. flags_sb.join(' ', flags.value());
  353. flags_sb.append(')');
  354. args.append(flags_sb.to_deprecated_string());
  355. }
  356. if (date_time.has_value())
  357. args.append(date_time.value().to_deprecated_string("\"%d-%b-%Y %H:%M:%S +0000\""sv));
  358. args.append(DeprecatedString::formatted("{{{}}}", message.data.length()));
  359. auto continue_req = TRY(send_command(Command { CommandType::Append, m_current_command, args }));
  360. auto response_promise = Promise<Optional<Response>>::construct();
  361. m_pending_promises.append(response_promise);
  362. continue_req->on_resolution = [this, message2 { move(message) }](auto& data) -> ErrorOr<void> {
  363. if (!data.has_value()) {
  364. TRY(handle_parsed_response({ .successful = false, .response = {} }));
  365. } else {
  366. TRY(send_raw(message2.data));
  367. m_expecting_response = true;
  368. }
  369. return {};
  370. };
  371. return cast_promise<SolidResponse>(response_promise);
  372. }
  373. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::subscribe(StringView mailbox)
  374. {
  375. auto command = Command { CommandType::Subscribe, m_current_command, { serialize_astring(mailbox) } };
  376. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  377. }
  378. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::unsubscribe(StringView mailbox)
  379. {
  380. auto command = Command { CommandType::Unsubscribe, m_current_command, { serialize_astring(mailbox) } };
  381. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  382. }
  383. ErrorOr<RefPtr<Promise<Optional<Response>>>> Client::authenticate(StringView method)
  384. {
  385. auto command = Command { CommandType::Authenticate, m_current_command, { method } };
  386. return TRY(send_command(move(command)));
  387. }
  388. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::rename(StringView from, StringView to)
  389. {
  390. auto command = Command { CommandType::Rename, m_current_command, { serialize_astring(from), serialize_astring(to) } };
  391. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  392. }
  393. ErrorOr<RefPtr<Promise<Optional<SolidResponse>>>> Client::copy(Sequence sequence_set, StringView name, bool uid)
  394. {
  395. auto command = Command {
  396. uid ? CommandType::UIDCopy : CommandType::Copy, m_current_command, { sequence_set.serialize(), serialize_astring(name) }
  397. };
  398. return cast_promise<SolidResponse>(TRY(send_command(move(command))));
  399. }
  400. void Client::close()
  401. {
  402. m_socket->close();
  403. }
  404. bool Client::is_open()
  405. {
  406. return m_socket->is_open();
  407. }
  408. }