Client.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. * Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
  6. * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include <AK/ByteBuffer.h>
  11. #include <AK/Debug.h>
  12. #include <AK/Format.h>
  13. #include <AK/JsonObject.h>
  14. #include <AK/JsonParser.h>
  15. #include <AK/JsonValue.h>
  16. #include <AK/Span.h>
  17. #include <AK/StringBuilder.h>
  18. #include <AK/StringView.h>
  19. #include <LibCore/DateTime.h>
  20. #include <LibHTTP/HttpResponse.h>
  21. #include <LibWeb/WebDriver/Client.h>
  22. namespace Web::WebDriver {
  23. using RouteHandler = Response (*)(Client&, Parameters, JsonValue);
  24. struct Route {
  25. HTTP::HttpRequest::Method method {};
  26. StringView path;
  27. RouteHandler handler { nullptr };
  28. };
  29. struct MatchedRoute {
  30. RouteHandler handler;
  31. Vector<StringView> parameters;
  32. };
  33. // clang-format off
  34. // This would be formatted rather badly.
  35. #define ROUTE(method, path, handler) \
  36. Route { \
  37. HTTP::HttpRequest::method, \
  38. path, \
  39. [](auto& client, auto parameters, auto payload) { \
  40. return client.handler(parameters, move(payload)); \
  41. } \
  42. }
  43. // clang-format on
  44. // https://w3c.github.io/webdriver/#dfn-endpoints
  45. static constexpr auto s_webdriver_endpoints = Array {
  46. ROUTE(POST, "/session"sv, new_session),
  47. ROUTE(DELETE, "/session/:session_id"sv, delete_session),
  48. ROUTE(GET, "/status"sv, get_status),
  49. ROUTE(GET, "/session/:session_id/timeouts"sv, get_timeouts),
  50. ROUTE(POST, "/session/:session_id/timeouts"sv, set_timeouts),
  51. ROUTE(POST, "/session/:session_id/url"sv, navigate_to),
  52. ROUTE(GET, "/session/:session_id/url"sv, get_current_url),
  53. ROUTE(POST, "/session/:session_id/back"sv, back),
  54. ROUTE(POST, "/session/:session_id/forward"sv, forward),
  55. ROUTE(POST, "/session/:session_id/refresh"sv, refresh),
  56. ROUTE(GET, "/session/:session_id/title"sv, get_title),
  57. ROUTE(GET, "/session/:session_id/window"sv, get_window_handle),
  58. ROUTE(DELETE, "/session/:session_id/window"sv, close_window),
  59. ROUTE(GET, "/session/:session_id/window/handles"sv, get_window_handles),
  60. ROUTE(GET, "/session/:session_id/window/rect"sv, get_window_rect),
  61. ROUTE(POST, "/session/:session_id/window/rect"sv, set_window_rect),
  62. ROUTE(POST, "/session/:session_id/window/maximize"sv, maximize_window),
  63. ROUTE(POST, "/session/:session_id/window/minimize"sv, minimize_window),
  64. ROUTE(POST, "/session/:session_id/element"sv, find_element),
  65. ROUTE(POST, "/session/:session_id/elements"sv, find_elements),
  66. ROUTE(POST, "/session/:session_id/element/:element_id/element"sv, find_element_from_element),
  67. ROUTE(POST, "/session/:session_id/element/:element_id/elements"sv, find_elements_from_element),
  68. ROUTE(GET, "/session/:session_id/element/:element_id/selected"sv, is_element_selected),
  69. ROUTE(GET, "/session/:session_id/element/:element_id/attribute/:name"sv, get_element_attribute),
  70. ROUTE(GET, "/session/:session_id/element/:element_id/property/:name"sv, get_element_property),
  71. ROUTE(GET, "/session/:session_id/element/:element_id/css/:name"sv, get_element_css_value),
  72. ROUTE(GET, "/session/:session_id/element/:element_id/text"sv, get_element_text),
  73. ROUTE(GET, "/session/:session_id/element/:element_id/name"sv, get_element_tag_name),
  74. ROUTE(GET, "/session/:session_id/element/:element_id/rect"sv, get_element_rect),
  75. ROUTE(GET, "/session/:session_id/element/:element_id/enabled"sv, is_element_enabled),
  76. ROUTE(GET, "/session/:session_id/source"sv, get_source),
  77. ROUTE(POST, "/session/:session_id/execute/sync"sv, execute_script),
  78. ROUTE(POST, "/session/:session_id/execute/async"sv, execute_async_script),
  79. ROUTE(GET, "/session/:session_id/cookie"sv, get_all_cookies),
  80. ROUTE(GET, "/session/:session_id/cookie/:name"sv, get_named_cookie),
  81. ROUTE(POST, "/session/:session_id/cookie"sv, add_cookie),
  82. ROUTE(DELETE, "/session/:session_id/cookie/:name"sv, delete_cookie),
  83. ROUTE(DELETE, "/session/:session_id/cookie"sv, delete_all_cookies),
  84. ROUTE(GET, "/session/:session_id/screenshot"sv, take_screenshot),
  85. ROUTE(GET, "/session/:session_id/element/:element_id/screenshot"sv, take_element_screenshot),
  86. };
  87. // https://w3c.github.io/webdriver/#dfn-match-a-request
  88. static ErrorOr<MatchedRoute, Error> match_route(HTTP::HttpRequest const& request)
  89. {
  90. dbgln_if(WEBDRIVER_DEBUG, "match_route({}, {})", HTTP::to_string(request.method()), request.resource());
  91. auto request_path = request.resource().view();
  92. Vector<StringView> parameters;
  93. auto next_segment = [](auto& path) -> Optional<StringView> {
  94. if (auto index = path.find('/'); index.has_value() && (*index + 1) < path.length()) {
  95. path = path.substring_view(*index + 1);
  96. if (index = path.find('/'); index.has_value())
  97. return path.substring_view(0, *index);
  98. return path;
  99. }
  100. path = {};
  101. return {};
  102. };
  103. for (auto const& route : s_webdriver_endpoints) {
  104. dbgln_if(WEBDRIVER_DEBUG, "- Checking {} {}", HTTP::to_string(route.method), route.path);
  105. if (route.method != request.method())
  106. continue;
  107. auto route_path = route.path;
  108. Optional<bool> match;
  109. auto on_failed_match = [&]() {
  110. request_path = request.resource();
  111. parameters.clear();
  112. match = false;
  113. };
  114. while (!match.has_value()) {
  115. auto request_segment = next_segment(request_path);
  116. auto route_segment = next_segment(route_path);
  117. if (!request_segment.has_value() && !route_segment.has_value())
  118. match = true;
  119. else if (request_segment.has_value() != route_segment.has_value())
  120. on_failed_match();
  121. else if (route_segment->starts_with(':'))
  122. parameters.append(*request_segment);
  123. else if (request_segment != route_segment)
  124. on_failed_match();
  125. }
  126. if (*match) {
  127. dbgln_if(WEBDRIVER_DEBUG, "- Found match with parameters={}", parameters);
  128. return MatchedRoute { route.handler, parameters };
  129. }
  130. }
  131. return Error::from_code(ErrorCode::UnknownCommand, "The command was not recognized.");
  132. }
  133. Client::Client(NonnullOwnPtr<Core::Stream::BufferedTCPSocket> socket, Core::Object* parent)
  134. : Core::Object(parent)
  135. , m_socket(move(socket))
  136. {
  137. m_socket->on_ready_to_read = [this] {
  138. if (auto result = on_ready_to_read(); result.is_error()) {
  139. result.error().visit(
  140. [](AK::Error const& error) {
  141. warnln("Internal error: {}", error);
  142. },
  143. [this](WebDriver::Error const& error) {
  144. if (send_error_response(error).is_error())
  145. warnln("Could not send error response");
  146. });
  147. die();
  148. }
  149. m_request = {};
  150. };
  151. }
  152. Client::~Client()
  153. {
  154. m_socket->close();
  155. }
  156. void Client::die()
  157. {
  158. deferred_invoke([this] { remove_from_parent(); });
  159. }
  160. ErrorOr<void, Client::WrappedError> Client::on_ready_to_read()
  161. {
  162. // FIXME: All this should be moved to LibHTTP and be made spec compliant.
  163. auto buffer = TRY(ByteBuffer::create_uninitialized(m_socket->buffer_size()));
  164. StringBuilder builder;
  165. for (;;) {
  166. if (!TRY(m_socket->can_read_without_blocking()))
  167. break;
  168. auto data = TRY(m_socket->read(buffer));
  169. TRY(builder.try_append(StringView { data }));
  170. if (m_socket->is_eof())
  171. break;
  172. }
  173. m_request = HTTP::HttpRequest::from_raw_request(builder.to_byte_buffer());
  174. if (!m_request.has_value())
  175. return {};
  176. auto body = TRY(read_body_as_json());
  177. TRY(handle_request(move(body)));
  178. return {};
  179. }
  180. ErrorOr<JsonValue, Client::WrappedError> Client::read_body_as_json()
  181. {
  182. // FIXME: If we received a multipart body here, this would fail badly.
  183. // FIXME: Check the Content-Type is actually application/json.
  184. size_t content_length = 0;
  185. for (auto const& header : m_request->headers()) {
  186. if (header.name.equals_ignoring_case("Content-Length"sv)) {
  187. content_length = header.value.to_uint<size_t>(TrimWhitespace::Yes).value_or(0);
  188. break;
  189. }
  190. }
  191. if (content_length == 0)
  192. return JsonValue {};
  193. JsonParser json_parser(m_request->body());
  194. return TRY(json_parser.parse());
  195. }
  196. ErrorOr<void, Client::WrappedError> Client::handle_request(JsonValue body)
  197. {
  198. if constexpr (WEBDRIVER_DEBUG) {
  199. dbgln("Got HTTP request: {} {}", m_request->method_name(), m_request->resource());
  200. if (!body.is_null())
  201. dbgln("Body: {}", body.to_string());
  202. }
  203. auto const& [handler, parameters] = TRY(match_route(*m_request));
  204. auto result = TRY((*handler)(*this, parameters, move(body)));
  205. return send_success_response(move(result));
  206. }
  207. ErrorOr<void, Client::WrappedError> Client::send_success_response(JsonValue result)
  208. {
  209. auto content = result.serialized<StringBuilder>();
  210. StringBuilder builder;
  211. builder.append("HTTP/1.0 200 OK\r\n"sv);
  212. builder.append("Server: WebDriver (SerenityOS)\r\n"sv);
  213. builder.append("X-Frame-Options: SAMEORIGIN\r\n"sv);
  214. builder.append("X-Content-Type-Options: nosniff\r\n"sv);
  215. builder.append("Pragma: no-cache\r\n"sv);
  216. builder.append("Content-Type: application/json; charset=utf-8\r\n"sv);
  217. builder.appendff("Content-Length: {}\r\n", content.length());
  218. builder.append("\r\n"sv);
  219. auto builder_contents = builder.to_byte_buffer();
  220. TRY(m_socket->write(builder_contents));
  221. while (!content.is_empty()) {
  222. auto bytes_sent = TRY(m_socket->write(content.bytes()));
  223. content = content.substring_view(bytes_sent);
  224. }
  225. bool keep_alive = false;
  226. if (auto it = m_request->headers().find_if([](auto& header) { return header.name.equals_ignoring_case("Connection"sv); }); !it.is_end())
  227. keep_alive = it->value.trim_whitespace().equals_ignoring_case("keep-alive"sv);
  228. if (!keep_alive)
  229. die();
  230. log_response(200);
  231. return {};
  232. }
  233. ErrorOr<void, Client::WrappedError> Client::send_error_response(Error const& error)
  234. {
  235. // FIXME: Implement to spec.
  236. dbgln_if(WEBDRIVER_DEBUG, "Sending error response: {} {}: {}", error.http_status, error.error, error.message);
  237. auto reason = HTTP::HttpResponse::reason_phrase_for_code(error.http_status);
  238. JsonObject result;
  239. result.set("error", error.error);
  240. result.set("message", error.message);
  241. result.set("stacktrace", "");
  242. if (error.data.has_value())
  243. result.set("data", *error.data);
  244. StringBuilder content_builder;
  245. result.serialize(content_builder);
  246. StringBuilder header_builder;
  247. header_builder.appendff("HTTP/1.0 {} {}\r\n", error.http_status, reason);
  248. header_builder.append("Content-Type: application/json; charset=UTF-8\r\n"sv);
  249. header_builder.appendff("Content-Length: {}\r\n", content_builder.length());
  250. header_builder.append("\r\n"sv);
  251. TRY(m_socket->write(header_builder.to_byte_buffer()));
  252. TRY(m_socket->write(content_builder.to_byte_buffer()));
  253. log_response(error.http_status);
  254. return {};
  255. }
  256. void Client::log_response(unsigned code)
  257. {
  258. outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, m_request->method_name(), m_request->resource());
  259. }
  260. }