Client.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. /*
  2. * Copyright (c) 2022, Florent Castelli <florent.castelli@gmail.com>
  3. * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include "Client.h"
  9. #include "Session.h"
  10. #include <AK/Debug.h>
  11. #include <AK/JsonParser.h>
  12. #include <LibCore/DateTime.h>
  13. #include <LibCore/MemoryStream.h>
  14. #include <LibHTTP/HttpRequest.h>
  15. #include <LibHTTP/HttpResponse.h>
  16. namespace WebDriver {
  17. Atomic<unsigned> Client::s_next_session_id;
  18. NonnullOwnPtrVector<Session> Client::s_sessions;
  19. Vector<Client::Route> Client::s_routes = {
  20. { HTTP::HttpRequest::Method::POST, { "session" }, &Client::handle_post_session },
  21. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id" }, &Client::handle_delete_session },
  22. { HTTP::HttpRequest::Method::GET, { "status" }, &Client::handle_get_status },
  23. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "url" }, &Client::handle_post_url },
  24. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "url" }, &Client::handle_get_url },
  25. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "title" }, &Client::handle_get_title },
  26. { HTTP::HttpRequest::Method::DELETE, { "session", ":session_id", "window" }, &Client::handle_delete_window },
  27. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "refresh" }, &Client::handle_refresh },
  28. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "back" }, &Client::handle_back },
  29. { HTTP::HttpRequest::Method::POST, { "session", ":session_id", "forward" }, &Client::handle_forward },
  30. { HTTP::HttpRequest::Method::GET, { "session", ":session_id", "cookie" }, &Client::handle_get_all_cookies },
  31. };
  32. Client::Client(NonnullOwnPtr<Core::Stream::BufferedTCPSocket> socket, Core::Object* parent)
  33. : Core::Object(parent)
  34. , m_socket(move(socket))
  35. {
  36. }
  37. void Client::die()
  38. {
  39. m_socket->close();
  40. deferred_invoke([this] { remove_from_parent(); });
  41. }
  42. void Client::start()
  43. {
  44. m_socket->on_ready_to_read = [this] {
  45. StringBuilder builder;
  46. // FIXME: All this should be moved to LibHTTP and be made spec compliant
  47. auto maybe_buffer = ByteBuffer::create_uninitialized(m_socket->buffer_size());
  48. if (maybe_buffer.is_error()) {
  49. warnln("Could not create buffer for client: {}", maybe_buffer.error());
  50. die();
  51. return;
  52. }
  53. auto buffer = maybe_buffer.release_value();
  54. for (;;) {
  55. auto maybe_can_read = m_socket->can_read_without_blocking();
  56. if (maybe_can_read.is_error()) {
  57. warnln("Failed to get the blocking status for the socket: {}", maybe_can_read.error());
  58. die();
  59. return;
  60. }
  61. if (!maybe_can_read.value())
  62. break;
  63. auto maybe_data = m_socket->read(buffer);
  64. if (maybe_data.is_error()) {
  65. warnln("Failed to read data from the request: {}", maybe_data.error());
  66. die();
  67. return;
  68. }
  69. if (m_socket->is_eof()) {
  70. die();
  71. break;
  72. }
  73. builder.append(StringView(maybe_data.value()));
  74. }
  75. auto request = builder.to_byte_buffer();
  76. auto http_request_or_error = HTTP::HttpRequest::from_raw_request(request);
  77. if (!http_request_or_error.has_value())
  78. return;
  79. auto http_request = http_request_or_error.release_value();
  80. auto body_or_error = read_body_as_json(http_request);
  81. if (body_or_error.is_error()) {
  82. warnln("Failed to read the request body: {}", body_or_error.error());
  83. die();
  84. return;
  85. }
  86. auto maybe_did_handle = handle_request(http_request, body_or_error.value());
  87. if (maybe_did_handle.is_error()) {
  88. warnln("Failed to handle the request: {}", maybe_did_handle.error());
  89. }
  90. die();
  91. };
  92. }
  93. ErrorOr<JsonValue> Client::read_body_as_json(HTTP::HttpRequest const& request)
  94. {
  95. // If we received a multipart body here, this would fail badly.
  96. unsigned content_length = 0;
  97. for (auto const& header : request.headers()) {
  98. if (header.name.equals_ignoring_case("Content-Length"sv)) {
  99. content_length = header.value.to_int(TrimWhitespace::Yes).value_or(0);
  100. break;
  101. }
  102. }
  103. if (!content_length)
  104. return JsonValue();
  105. // FIXME: Check the Content-Type is actually application/json
  106. JsonParser json_parser(request.body());
  107. return json_parser.parse();
  108. }
  109. ErrorOr<bool> Client::handle_request(HTTP::HttpRequest const& request, JsonValue const& body)
  110. {
  111. if constexpr (WEBDRIVER_DEBUG) {
  112. dbgln("Got HTTP request: {} {}", request.method_name(), request.resource());
  113. if (!body.is_null())
  114. dbgln("Body: {}", body.to_string());
  115. }
  116. auto routing_result_match = match_route(request.method(), request.resource());
  117. if (routing_result_match.is_error()) {
  118. auto error = routing_result_match.release_error();
  119. dbgln_if(WEBDRIVER_DEBUG, "Failed to match route: {}", error);
  120. TRY(send_error_response(error, request));
  121. return false;
  122. }
  123. auto routing_result = routing_result_match.release_value();
  124. auto result = (this->*routing_result.handler)(routing_result.parameters, body);
  125. if (result.is_error()) {
  126. dbgln_if(WEBDRIVER_DEBUG, "Error in calling route handler: {}", result.error());
  127. TRY(send_error_response(result.release_error(), request));
  128. return false;
  129. }
  130. auto object = result.release_value();
  131. TRY(send_response(object.to_string(), request));
  132. return true;
  133. }
  134. // https://w3c.github.io/webdriver/#dfn-send-a-response
  135. ErrorOr<void> Client::send_response(StringView content, HTTP::HttpRequest const& request)
  136. {
  137. // FIXME: Implement to spec.
  138. StringBuilder builder;
  139. builder.append("HTTP/1.0 200 OK\r\n"sv);
  140. builder.append("Server: WebDriver (SerenityOS)\r\n"sv);
  141. builder.append("X-Frame-Options: SAMEORIGIN\r\n"sv);
  142. builder.append("X-Content-Type-Options: nosniff\r\n"sv);
  143. builder.append("Pragma: no-cache\r\n"sv);
  144. builder.append("Content-Type: application/json; charset=utf-8\r\n"sv);
  145. builder.appendff("Content-Length: {}\r\n", content.length());
  146. builder.append("\r\n"sv);
  147. auto builder_contents = builder.to_byte_buffer();
  148. TRY(m_socket->write(builder_contents));
  149. TRY(m_socket->write(content.bytes()));
  150. log_response(200, request);
  151. auto keep_alive = false;
  152. if (auto it = request.headers().find_if([](auto& header) { return header.name.equals_ignoring_case("Connection"sv); }); !it.is_end()) {
  153. if (it->value.trim_whitespace().equals_ignoring_case("keep-alive"sv))
  154. keep_alive = true;
  155. }
  156. if (!keep_alive)
  157. m_socket->close();
  158. return {};
  159. }
  160. // https://w3c.github.io/webdriver/#dfn-send-an-error
  161. ErrorOr<void> Client::send_error_response(HttpError const& error, HTTP::HttpRequest const& request)
  162. {
  163. // FIXME: Implement to spec.
  164. dbgln("send_error_response: {} {}: {}", error.http_status, error.error, error.message);
  165. auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(error.http_status);
  166. auto result = JsonObject();
  167. result.set("error", error.error);
  168. result.set("message", error.message);
  169. result.set("stacktrace", "");
  170. StringBuilder content_builder;
  171. result.serialize(content_builder);
  172. StringBuilder header_builder;
  173. header_builder.appendff("HTTP/1.0 {} ", error.http_status);
  174. header_builder.append(reason_phrase);
  175. header_builder.append("\r\n"sv);
  176. header_builder.append("Content-Type: application/json; charset=UTF-8\r\n"sv);
  177. header_builder.appendff("Content-Length: {}\r\n", content_builder.length());
  178. header_builder.append("\r\n"sv);
  179. TRY(m_socket->write(header_builder.to_byte_buffer()));
  180. TRY(m_socket->write(content_builder.to_byte_buffer()));
  181. log_response(error.http_status, request);
  182. return {};
  183. }
  184. void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
  185. {
  186. outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, request.method_name(), request.resource());
  187. }
  188. // https://w3c.github.io/webdriver/#dfn-match-a-request
  189. ErrorOr<Client::RoutingResult, HttpError> Client::match_route(HTTP::HttpRequest::Method method, String resource)
  190. {
  191. // FIXME: Implement to spec.
  192. dbgln_if(WEBDRIVER_DEBUG, "match_route({}, {})", HTTP::to_string(method), resource);
  193. // https://w3c.github.io/webdriver/webdriver-spec.html#routing-requests
  194. if (!resource.starts_with(m_prefix))
  195. return HttpError { 404, "unknown command", "The resource doesn't start with the prefix." };
  196. Vector<StringView> resource_split = resource.substring_view(m_prefix.length()).split_view('/', true);
  197. Vector<StringView> parameters;
  198. bool matched_path = false;
  199. for (auto const& route : Client::s_routes) {
  200. dbgln_if(WEBDRIVER_DEBUG, "- Checking {} {}", HTTP::to_string(route.method), String::join("/"sv, route.path));
  201. if (resource_split.size() != route.path.size()) {
  202. dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Wrong length");
  203. continue;
  204. }
  205. bool match = true;
  206. for (size_t i = 0; i < route.path.size(); ++i) {
  207. if (route.path[i].starts_with(':')) {
  208. parameters.append(resource_split[i]);
  209. continue;
  210. }
  211. if (route.path[i] != resource_split[i]) {
  212. match = false;
  213. parameters.clear();
  214. dbgln_if(WEBDRIVER_DEBUG, "-> Discarding: Part `{}` does not match `{}`", route.path[i], resource_split[i]);
  215. break;
  216. }
  217. }
  218. if (match && route.method == method) {
  219. dbgln_if(WEBDRIVER_DEBUG, "-> Matched! :^)");
  220. return RoutingResult { route.handler, parameters };
  221. }
  222. matched_path = true;
  223. }
  224. // Matched a path, but didn't match a known method
  225. if (matched_path) {
  226. dbgln_if(WEBDRIVER_DEBUG, "- A path matched, but method didn't. :^(");
  227. return HttpError { 405, "unknown method", "The command matched a known URL but did not match a method for that URL." };
  228. }
  229. // Didn't have any match
  230. dbgln_if(WEBDRIVER_DEBUG, "- No matches. :^(");
  231. return HttpError { 404, "unknown command", "The command was not recognized." };
  232. }
  233. ErrorOr<Session*, HttpError> Client::find_session_with_id(StringView session_id)
  234. {
  235. auto session_id_or_error = session_id.to_uint<>();
  236. if (!session_id_or_error.has_value())
  237. return HttpError { 404, "invalid session id", "Invalid session id" };
  238. for (auto& session : Client::s_sessions) {
  239. if (session.session_id() == session_id_or_error.value())
  240. return &session;
  241. }
  242. return HttpError { 404, "invalid session id", "Invalid session id" };
  243. }
  244. void Client::close_session(unsigned session_id)
  245. {
  246. bool found = Client::s_sessions.remove_first_matching([&](auto const& it) {
  247. return it->session_id() == session_id;
  248. });
  249. if (found)
  250. dbgln_if(WEBDRIVER_DEBUG, "Shut down session {}", session_id);
  251. else
  252. dbgln_if(WEBDRIVER_DEBUG, "Unable to shut down session {}: Not found", session_id);
  253. }
  254. JsonValue Client::make_json_value(JsonValue const& value)
  255. {
  256. JsonObject result;
  257. result.set("value", value);
  258. return result;
  259. }
  260. // POST /session https://w3c.github.io/webdriver/#dfn-new-sessions
  261. ErrorOr<JsonValue, HttpError> Client::handle_post_session(Vector<StringView>, JsonValue const&)
  262. {
  263. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session");
  264. // FIXME: 1. If the maximum active sessions is equal to the length of the list of active sessions,
  265. // return error with error code session not created.
  266. // FIXME: 2. If the remote end is an intermediary node, take implementation-defined steps that either
  267. // result in returning an error with error code session not created, or in returning a
  268. // success with data that is isomorphic to that returned by remote ends according to the
  269. // rest of this algorithm. If an error is not returned, the intermediary node must retain a
  270. // reference to the session created on the upstream node as the associated session such
  271. // that commands may be forwarded to this associated session on subsequent commands.
  272. // FIXME: 3. If the maximum active sessions is equal to the length of the list of active sessions,
  273. // return error with error code session not created.
  274. // FIXME: 4. Let capabilities be the result of trying to process capabilities with parameters as an argument.
  275. // FIXME: 5. If capabilities’s is null, return error with error code session not created.
  276. // 6. Let session id be the result of generating a UUID.
  277. // FIXME: Actually create a UUID.
  278. auto session_id = Client::s_next_session_id++;
  279. // 7. Let session be a new session with the session ID of session id.
  280. NonnullOwnPtr<Session> session = make<Session>(session_id, *this);
  281. auto start_result = session->start();
  282. if (start_result.is_error()) {
  283. return HttpError { 500, "Failed to start session", start_result.error().string_literal() };
  284. }
  285. // FIXME: 8. Set the current session to session.
  286. // FIXME: 9. Run any WebDriver new session algorithm defined in external specifications,
  287. // with arguments session and capabilities.
  288. // 10. Append session to active sessions.
  289. Client::s_sessions.append(move(session));
  290. // 11. Let body be a JSON Object initialized with:
  291. JsonObject body;
  292. // "sessionId"
  293. // session id
  294. body.set("sessionId", String::number(session_id));
  295. // FIXME: "capabilities"
  296. // capabilities
  297. // FIXME: 12. Initialize the following from capabilities:
  298. // NOTE: See spec for steps
  299. // FIXME: 13. Set the webdriver-active flag to true.
  300. // FIXME: 14. Set the current top-level browsing context for session with the top-level browsing context
  301. // of the UA’s current browsing context.
  302. // FIXME: 15. Set the request queue to a new queue.
  303. // 16. Return success with data body.
  304. return make_json_value(body);
  305. }
  306. // DELETE /session/{session id} https://w3c.github.io/webdriver/#dfn-delete-session
  307. ErrorOr<JsonValue, HttpError> Client::handle_delete_session(Vector<StringView> parameters, JsonValue const&)
  308. {
  309. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>");
  310. // 1. If the current session is an active session, try to close the session.
  311. Session* session = TRY(find_session_with_id(parameters[0]));
  312. auto stop_result = session->stop();
  313. if (stop_result.is_error()) {
  314. return HttpError { 500, "unsupported operation", stop_result.error().string_literal() };
  315. }
  316. // 2. Return success with data null.
  317. return make_json_value(JsonValue());
  318. }
  319. // GET /status https://w3c.github.io/webdriver/#dfn-status
  320. ErrorOr<JsonValue, HttpError> Client::handle_get_status(Vector<StringView>, JsonValue const&)
  321. {
  322. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /status");
  323. // 1. Let body be a new JSON Object with the following properties:
  324. // "ready"
  325. // The remote end’s readiness state.
  326. // "message"
  327. // An implementation-defined string explaining the remote end’s readiness state.
  328. // FIXME: Report if we are somehow not ready.
  329. JsonObject body;
  330. body.set("ready", true);
  331. body.set("message", "Ready to start some sessions!");
  332. // 2. Return success with data body.
  333. return body;
  334. }
  335. // POST /session/{session id}/url https://w3c.github.io/webdriver/#dfn-navigate-to
  336. ErrorOr<JsonValue, HttpError> Client::handle_post_url(Vector<StringView> parameters, JsonValue const& payload)
  337. {
  338. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/url");
  339. Session* session = TRY(find_session_with_id(parameters[0]));
  340. // NOTE: Spec steps handled in Session::post_url().
  341. auto result = TRY(session->post_url(payload));
  342. return make_json_value(result);
  343. }
  344. // GET /session/{session id}/url https://w3c.github.io/webdriver/#dfn-get-current-url
  345. ErrorOr<JsonValue, HttpError> Client::handle_get_url(Vector<StringView> parameters, JsonValue const&)
  346. {
  347. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/url");
  348. Session* session = TRY(find_session_with_id(parameters[0]));
  349. // NOTE: Spec steps handled in Session::get_url().
  350. auto result = TRY(session->get_url());
  351. return make_json_value(result);
  352. }
  353. // GET /session/{session id}/title https://w3c.github.io/webdriver/#dfn-get-title
  354. ErrorOr<JsonValue, HttpError> Client::handle_get_title(Vector<StringView> parameters, JsonValue const&)
  355. {
  356. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/title");
  357. Session* session = TRY(find_session_with_id(parameters[0]));
  358. // NOTE: Spec steps handled in Session::get_title().
  359. auto result = TRY(session->get_title());
  360. return make_json_value(result);
  361. }
  362. // DELETE /session/{session id}/window https://w3c.github.io/webdriver/#dfn-close-window
  363. ErrorOr<JsonValue, HttpError> Client::handle_delete_window(Vector<StringView> parameters, JsonValue const&)
  364. {
  365. dbgln_if(WEBDRIVER_DEBUG, "Handling DELETE /session/<session_id>/window");
  366. Session* session = TRY(find_session_with_id(parameters[0]));
  367. // NOTE: Spec steps handled in Session::delete_window().
  368. TRY(unwrap_result(session->delete_window()));
  369. return make_json_value(JsonValue());
  370. }
  371. // POST /session/{session id}/refresh https://w3c.github.io/webdriver/#dfn-refresh
  372. ErrorOr<JsonValue, HttpError> Client::handle_refresh(Vector<StringView> parameters, JsonValue const&)
  373. {
  374. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/refresh");
  375. Session* session = TRY(find_session_with_id(parameters[0]));
  376. // NOTE: Spec steps handled in Session::refresh().
  377. auto result = TRY(session->refresh());
  378. return make_json_value(result);
  379. }
  380. // POST /session/{session id}/back https://w3c.github.io/webdriver/#dfn-back
  381. ErrorOr<JsonValue, HttpError> Client::handle_back(Vector<StringView> parameters, JsonValue const&)
  382. {
  383. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/back");
  384. Session* session = TRY(find_session_with_id(parameters[0]));
  385. // NOTE: Spec steps handled in Session::back().
  386. auto result = TRY(session->back());
  387. return make_json_value(result);
  388. }
  389. // POST /session/{session id}/forward https://w3c.github.io/webdriver/#dfn-forward
  390. ErrorOr<JsonValue, HttpError> Client::handle_forward(Vector<StringView> parameters, JsonValue const&)
  391. {
  392. dbgln_if(WEBDRIVER_DEBUG, "Handling POST /session/<session_id>/forward");
  393. Session* session = TRY(find_session_with_id(parameters[0]));
  394. // NOTE: Spec steps handled in Session::forward().
  395. auto result = TRY(session->forward());
  396. return make_json_value(result);
  397. }
  398. // GET /session/{session id}/cookie https://w3c.github.io/webdriver/#dfn-get-all-cookies
  399. ErrorOr<JsonValue, HttpError> Client::handle_get_all_cookies(Vector<StringView> parameters, JsonValue const&)
  400. {
  401. dbgln_if(WEBDRIVER_DEBUG, "Handling GET /session/<session_id>/cookie");
  402. Session* session = TRY(find_session_with_id(parameters[0]));
  403. // NOTE: Spec steps handled in Session::get_all_cookies().
  404. auto cookies = TRY(session->get_all_cookies());
  405. return make_json_value(cookies);
  406. }
  407. }