Client.cpp 12 KB

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