Client.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
  4. * Copyright (c) 2022, Thomas Keppler <serenity@tkeppler.de>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <AK/Base64.h>
  9. #include <AK/Debug.h>
  10. #include <AK/LexicalPath.h>
  11. #include <AK/MemoryStream.h>
  12. #include <AK/QuickSort.h>
  13. #include <AK/StringBuilder.h>
  14. #include <AK/URL.h>
  15. #include <LibCore/DateTime.h>
  16. #include <LibCore/DeprecatedFile.h>
  17. #include <LibCore/DirIterator.h>
  18. #include <LibCore/File.h>
  19. #include <LibCore/MappedFile.h>
  20. #include <LibCore/MimeData.h>
  21. #include <LibHTTP/HttpRequest.h>
  22. #include <LibHTTP/HttpResponse.h>
  23. #include <WebServer/Client.h>
  24. #include <WebServer/Configuration.h>
  25. #include <stdio.h>
  26. #include <sys/stat.h>
  27. #include <unistd.h>
  28. namespace WebServer {
  29. Client::Client(NonnullOwnPtr<Core::BufferedTCPSocket> socket, Core::Object* parent)
  30. : Core::Object(parent)
  31. , m_socket(move(socket))
  32. {
  33. }
  34. void Client::die()
  35. {
  36. m_socket->close();
  37. deferred_invoke([this] { remove_from_parent(); });
  38. }
  39. void Client::start()
  40. {
  41. m_socket->on_ready_to_read = [this] {
  42. StringBuilder builder;
  43. auto maybe_buffer = ByteBuffer::create_uninitialized(m_socket->buffer_size());
  44. if (maybe_buffer.is_error()) {
  45. warnln("Could not create buffer for client: {}", maybe_buffer.error());
  46. die();
  47. return;
  48. }
  49. auto buffer = maybe_buffer.release_value();
  50. for (;;) {
  51. auto maybe_can_read = m_socket->can_read_without_blocking();
  52. if (maybe_can_read.is_error()) {
  53. warnln("Failed to get the blocking status for the socket: {}", maybe_can_read.error());
  54. die();
  55. return;
  56. }
  57. if (!maybe_can_read.value())
  58. break;
  59. auto maybe_bytes_read = m_socket->read_until_any_of(buffer, Array { "\r"sv, "\n"sv, "\r\n"sv });
  60. if (maybe_bytes_read.is_error()) {
  61. warnln("Failed to read a line from the request: {}", maybe_bytes_read.error());
  62. die();
  63. return;
  64. }
  65. if (m_socket->is_eof()) {
  66. die();
  67. break;
  68. }
  69. builder.append(StringView { maybe_bytes_read.value() });
  70. builder.append("\r\n"sv);
  71. }
  72. auto request = builder.to_byte_buffer().release_value_but_fixme_should_propagate_errors();
  73. dbgln_if(WEBSERVER_DEBUG, "Got raw request: '{}'", DeprecatedString::copy(request));
  74. auto maybe_did_handle = handle_request(request);
  75. if (maybe_did_handle.is_error()) {
  76. warnln("Failed to handle the request: {}", maybe_did_handle.error());
  77. }
  78. die();
  79. };
  80. }
  81. ErrorOr<bool> Client::handle_request(ReadonlyBytes raw_request)
  82. {
  83. auto request_or_error = HTTP::HttpRequest::from_raw_request(raw_request);
  84. if (!request_or_error.has_value())
  85. return false;
  86. auto& request = request_or_error.value();
  87. auto resource_decoded = URL::percent_decode(request.resource());
  88. if constexpr (WEBSERVER_DEBUG) {
  89. dbgln("Got HTTP request: {} {}", request.method_name(), request.resource());
  90. for (auto& header : request.headers()) {
  91. dbgln(" {} => {}", header.name, header.value);
  92. }
  93. }
  94. if (request.method() != HTTP::HttpRequest::Method::GET) {
  95. TRY(send_error_response(501, request));
  96. return false;
  97. }
  98. // Check for credentials if they are required
  99. if (Configuration::the().credentials().has_value()) {
  100. bool has_authenticated = verify_credentials(request.headers());
  101. if (!has_authenticated) {
  102. auto const basic_auth_header = TRY("WWW-Authenticate: Basic realm=\"WebServer\", charset=\"UTF-8\""_string);
  103. Vector<String> headers {};
  104. TRY(headers.try_append(basic_auth_header));
  105. TRY(send_error_response(401, request, move(headers)));
  106. return false;
  107. }
  108. }
  109. auto requested_path = TRY(String::from_deprecated_string(LexicalPath::join("/"sv, resource_decoded).string()));
  110. dbgln_if(WEBSERVER_DEBUG, "Canonical requested path: '{}'", requested_path);
  111. StringBuilder path_builder;
  112. path_builder.append(Configuration::the().document_root_path());
  113. path_builder.append(requested_path);
  114. auto real_path = TRY(path_builder.to_string());
  115. if (Core::DeprecatedFile::is_directory(real_path.bytes_as_string_view())) {
  116. if (!resource_decoded.ends_with('/')) {
  117. StringBuilder red;
  118. red.append(requested_path);
  119. red.append("/"sv);
  120. TRY(send_redirect(red.to_deprecated_string(), request));
  121. return true;
  122. }
  123. StringBuilder index_html_path_builder;
  124. index_html_path_builder.append(real_path);
  125. index_html_path_builder.append("/index.html"sv);
  126. auto index_html_path = TRY(index_html_path_builder.to_string());
  127. if (!Core::DeprecatedFile::exists(index_html_path)) {
  128. TRY(handle_directory_listing(requested_path, real_path, request));
  129. return true;
  130. }
  131. real_path = index_html_path;
  132. }
  133. auto file = Core::DeprecatedFile::construct(real_path.bytes_as_string_view());
  134. if (!file->open(Core::OpenMode::ReadOnly)) {
  135. TRY(send_error_response(404, request));
  136. return false;
  137. }
  138. if (file->is_device()) {
  139. TRY(send_error_response(403, request));
  140. return false;
  141. }
  142. auto stream = TRY(Core::File::open(real_path.bytes_as_string_view(), Core::File::OpenMode::Read));
  143. auto const info = ContentInfo {
  144. .type = TRY(String::from_utf8(Core::guess_mime_type_based_on_filename(real_path.bytes_as_string_view()))),
  145. .length = TRY(Core::DeprecatedFile::size(real_path.bytes_as_string_view()))
  146. };
  147. TRY(send_response(*stream, request, move(info)));
  148. return true;
  149. }
  150. ErrorOr<void> Client::send_response(Stream& response, HTTP::HttpRequest const& request, ContentInfo content_info)
  151. {
  152. StringBuilder builder;
  153. builder.append("HTTP/1.0 200 OK\r\n"sv);
  154. builder.append("Server: WebServer (SerenityOS)\r\n"sv);
  155. builder.append("X-Frame-Options: SAMEORIGIN\r\n"sv);
  156. builder.append("X-Content-Type-Options: nosniff\r\n"sv);
  157. builder.append("Pragma: no-cache\r\n"sv);
  158. if (content_info.type == "text/plain")
  159. builder.appendff("Content-Type: {}; charset=utf-8\r\n", content_info.type);
  160. else
  161. builder.appendff("Content-Type: {}\r\n", content_info.type);
  162. builder.appendff("Content-Length: {}\r\n", content_info.length);
  163. builder.append("\r\n"sv);
  164. auto builder_contents = TRY(builder.to_byte_buffer());
  165. TRY(m_socket->write(builder_contents));
  166. log_response(200, request);
  167. char buffer[PAGE_SIZE];
  168. do {
  169. auto size = TRY(response.read({ buffer, sizeof(buffer) })).size();
  170. if (response.is_eof() && size == 0)
  171. break;
  172. ReadonlyBytes write_buffer { buffer, size };
  173. while (!write_buffer.is_empty()) {
  174. auto nwritten = TRY(m_socket->write(write_buffer));
  175. if (nwritten == 0) {
  176. dbgln("EEEEEE got 0 bytes written!");
  177. }
  178. write_buffer = write_buffer.slice(nwritten);
  179. }
  180. } while (true);
  181. auto keep_alive = false;
  182. if (auto it = request.headers().find_if([](auto& header) { return header.name.equals_ignoring_ascii_case("Connection"sv); }); !it.is_end()) {
  183. if (it->value.trim_whitespace().equals_ignoring_ascii_case("keep-alive"sv))
  184. keep_alive = true;
  185. }
  186. if (!keep_alive)
  187. m_socket->close();
  188. return {};
  189. }
  190. ErrorOr<void> Client::send_redirect(StringView redirect_path, HTTP::HttpRequest const& request)
  191. {
  192. StringBuilder builder;
  193. builder.append("HTTP/1.0 301 Moved Permanently\r\n"sv);
  194. builder.append("Location: "sv);
  195. builder.append(redirect_path);
  196. builder.append("\r\n"sv);
  197. builder.append("\r\n"sv);
  198. auto builder_contents = TRY(builder.to_byte_buffer());
  199. TRY(m_socket->write(builder_contents));
  200. log_response(301, request);
  201. return {};
  202. }
  203. static DeprecatedString folder_image_data()
  204. {
  205. static DeprecatedString cache;
  206. if (cache.is_empty()) {
  207. auto file = Core::MappedFile::map("/res/icons/16x16/filetype-folder.png"sv).release_value_but_fixme_should_propagate_errors();
  208. // FIXME: change to TRY() and make method fallible
  209. cache = MUST(encode_base64(file->bytes())).to_deprecated_string();
  210. }
  211. return cache;
  212. }
  213. static DeprecatedString file_image_data()
  214. {
  215. static DeprecatedString cache;
  216. if (cache.is_empty()) {
  217. auto file = Core::MappedFile::map("/res/icons/16x16/filetype-unknown.png"sv).release_value_but_fixme_should_propagate_errors();
  218. // FIXME: change to TRY() and make method fallible
  219. cache = MUST(encode_base64(file->bytes())).to_deprecated_string();
  220. }
  221. return cache;
  222. }
  223. ErrorOr<void> Client::handle_directory_listing(String const& requested_path, String const& real_path, HTTP::HttpRequest const& request)
  224. {
  225. StringBuilder builder;
  226. builder.append("<!DOCTYPE html>\n"sv);
  227. builder.append("<html>\n"sv);
  228. builder.append("<head><meta charset=\"utf-8\">\n"sv);
  229. builder.append("<title>Index of "sv);
  230. builder.append(escape_html_entities(requested_path));
  231. builder.append("</title><style>\n"sv);
  232. builder.append(".folder { width: 16px; height: 16px; background-image: url('data:image/png;base64,"sv);
  233. builder.append(folder_image_data());
  234. builder.append("'); }\n"sv);
  235. builder.append(".file { width: 16px; height: 16px; background-image: url('data:image/png;base64,"sv);
  236. builder.append(file_image_data());
  237. builder.append("'); }\n"sv);
  238. builder.append("</style></head><body>\n"sv);
  239. builder.append("<h1>Index of "sv);
  240. builder.append(escape_html_entities(requested_path));
  241. builder.append("</h1>\n"sv);
  242. builder.append("<hr>\n"sv);
  243. builder.append("<code><table>\n"sv);
  244. Core::DirIterator dt(real_path.bytes_as_string_view());
  245. Vector<DeprecatedString> names;
  246. while (dt.has_next())
  247. names.append(dt.next_path());
  248. quick_sort(names);
  249. for (auto& name : names) {
  250. StringBuilder path_builder;
  251. path_builder.append(real_path);
  252. path_builder.append('/');
  253. // NOTE: In the root directory of the webserver, ".." should be equal to ".", since we don't want
  254. // the user to see e.g. the size of the parent directory (and it isn't unveiled, so stat fails).
  255. if (requested_path == "/" && name == "..")
  256. path_builder.append("."sv);
  257. else
  258. path_builder.append(name);
  259. struct stat st;
  260. memset(&st, 0, sizeof(st));
  261. int rc = stat(path_builder.to_deprecated_string().characters(), &st);
  262. if (rc < 0) {
  263. perror("stat");
  264. }
  265. bool is_directory = S_ISDIR(st.st_mode);
  266. builder.append("<tr>"sv);
  267. builder.appendff("<td><div class=\"{}\"></div></td>", is_directory ? "folder" : "file");
  268. builder.append("<td><a href=\""sv);
  269. builder.append(URL::percent_encode(name));
  270. // NOTE: For directories, we append a slash so we don't always hit the redirect case,
  271. // which adds a slash anyways.
  272. if (is_directory)
  273. builder.append('/');
  274. builder.append("\">"sv);
  275. builder.append(escape_html_entities(name));
  276. builder.append("</a></td><td>&nbsp;</td>"sv);
  277. builder.appendff("<td>{:10}</td><td>&nbsp;</td>", st.st_size);
  278. builder.append("<td>"sv);
  279. builder.append(Core::DateTime::from_timestamp(st.st_mtime).to_deprecated_string());
  280. builder.append("</td>"sv);
  281. builder.append("</tr>\n"sv);
  282. }
  283. builder.append("</table></code>\n"sv);
  284. builder.append("<hr>\n"sv);
  285. builder.append("<i>Generated by WebServer (SerenityOS)</i>\n"sv);
  286. builder.append("</body>\n"sv);
  287. builder.append("</html>\n"sv);
  288. auto response = builder.to_deprecated_string();
  289. FixedMemoryStream stream { response.bytes() };
  290. return send_response(stream, request, { .type = TRY("text/html"_string), .length = response.length() });
  291. }
  292. ErrorOr<void> Client::send_error_response(unsigned code, HTTP::HttpRequest const& request, Vector<String> const& headers)
  293. {
  294. auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(code);
  295. StringBuilder content_builder;
  296. content_builder.append("<!DOCTYPE html><html><body><h1>"sv);
  297. content_builder.appendff("{} ", code);
  298. content_builder.append(reason_phrase);
  299. content_builder.append("</h1></body></html>"sv);
  300. StringBuilder header_builder;
  301. header_builder.appendff("HTTP/1.0 {} ", code);
  302. header_builder.append(reason_phrase);
  303. header_builder.append("\r\n"sv);
  304. for (auto& header : headers) {
  305. header_builder.append(header);
  306. header_builder.append("\r\n"sv);
  307. }
  308. header_builder.append("Content-Type: text/html; charset=UTF-8\r\n"sv);
  309. header_builder.appendff("Content-Length: {}\r\n", content_builder.length());
  310. header_builder.append("\r\n"sv);
  311. TRY(m_socket->write(TRY(header_builder.to_byte_buffer())));
  312. TRY(m_socket->write(TRY(content_builder.to_byte_buffer())));
  313. log_response(code, request);
  314. return {};
  315. }
  316. void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
  317. {
  318. outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_deprecated_string(), code, request.method_name(), request.url().serialize().substring(1));
  319. }
  320. bool Client::verify_credentials(Vector<HTTP::HttpRequest::Header> const& headers)
  321. {
  322. VERIFY(Configuration::the().credentials().has_value());
  323. auto& configured_credentials = Configuration::the().credentials().value();
  324. for (auto& header : headers) {
  325. if (header.name.equals_ignoring_ascii_case("Authorization"sv)) {
  326. auto provided_credentials = HTTP::HttpRequest::parse_http_basic_authentication_header(header.value);
  327. if (provided_credentials.has_value() && configured_credentials.username == provided_credentials->username && configured_credentials.password == provided_credentials->password)
  328. return true;
  329. }
  330. }
  331. return false;
  332. }
  333. }