Client.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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/MappedFile.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/DirIterator.h>
  17. #include <LibCore/File.h>
  18. #include <LibCore/FileStream.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(NonnullRefPtr<Core::TCPSocket> socket, Core::Object* parent)
  29. : Core::Object(parent)
  30. , m_socket(socket)
  31. {
  32. }
  33. void Client::die()
  34. {
  35. deferred_invoke([this] { remove_from_parent(); });
  36. }
  37. void Client::start()
  38. {
  39. m_socket->on_ready_to_read = [this] {
  40. StringBuilder builder;
  41. for (;;) {
  42. auto line = m_socket->read_line();
  43. if (line.is_empty())
  44. break;
  45. builder.append(line);
  46. builder.append("\r\n");
  47. }
  48. auto request = builder.to_byte_buffer();
  49. dbgln_if(WEBSERVER_DEBUG, "Got raw request: '{}'", String::copy(request));
  50. handle_request(request);
  51. die();
  52. };
  53. }
  54. void Client::handle_request(ReadonlyBytes raw_request)
  55. {
  56. auto request_or_error = HTTP::HttpRequest::from_raw_request(raw_request);
  57. if (!request_or_error.has_value())
  58. return;
  59. auto& request = request_or_error.value();
  60. if constexpr (WEBSERVER_DEBUG) {
  61. dbgln("Got HTTP request: {} {}", request.method_name(), request.resource());
  62. for (auto& header : request.headers()) {
  63. dbgln(" {} => {}", header.name, header.value);
  64. }
  65. }
  66. if (request.method() != HTTP::HttpRequest::Method::GET) {
  67. send_error_response(501, request);
  68. return;
  69. }
  70. // Check for credentials if they are required
  71. if (Configuration::the().credentials().has_value()) {
  72. bool has_authenticated = verify_credentials(request.headers());
  73. if (!has_authenticated) {
  74. send_error_response(401, request, { "WWW-Authenticate: Basic realm=\"WebServer\", charset=\"UTF-8\"" });
  75. return;
  76. }
  77. }
  78. auto requested_path = LexicalPath::join("/", request.resource()).string();
  79. dbgln_if(WEBSERVER_DEBUG, "Canonical requested path: '{}'", requested_path);
  80. StringBuilder path_builder;
  81. path_builder.append(Configuration::the().root_path());
  82. path_builder.append(requested_path);
  83. auto real_path = path_builder.to_string();
  84. if (Core::File::is_directory(real_path)) {
  85. if (!request.resource().ends_with("/")) {
  86. StringBuilder red;
  87. red.append(requested_path);
  88. red.append("/");
  89. send_redirect(red.to_string(), request);
  90. return;
  91. }
  92. StringBuilder index_html_path_builder;
  93. index_html_path_builder.append(real_path);
  94. index_html_path_builder.append("/index.html");
  95. auto index_html_path = index_html_path_builder.to_string();
  96. if (!Core::File::exists(index_html_path)) {
  97. handle_directory_listing(requested_path, real_path, request);
  98. return;
  99. }
  100. real_path = index_html_path;
  101. }
  102. auto file = Core::File::construct(real_path);
  103. if (!file->open(Core::OpenMode::ReadOnly)) {
  104. send_error_response(404, request);
  105. return;
  106. }
  107. if (file->is_device()) {
  108. send_error_response(403, request);
  109. return;
  110. }
  111. Core::InputFileStream stream { file };
  112. send_response(stream, request, Core::guess_mime_type_based_on_filename(real_path));
  113. }
  114. void Client::send_response(InputStream& response, HTTP::HttpRequest const& request, String const& content_type)
  115. {
  116. StringBuilder builder;
  117. builder.append("HTTP/1.0 200 OK\r\n");
  118. builder.append("Server: WebServer (SerenityOS)\r\n");
  119. builder.append("X-Frame-Options: SAMEORIGIN\r\n");
  120. builder.append("X-Content-Type-Options: nosniff\r\n");
  121. builder.append("Pragma: no-cache\r\n");
  122. builder.append("Content-Type: ");
  123. builder.append(content_type);
  124. builder.append("\r\n");
  125. builder.append("\r\n");
  126. m_socket->write(builder.to_string());
  127. log_response(200, request);
  128. char buffer[PAGE_SIZE];
  129. do {
  130. auto size = response.read({ buffer, sizeof(buffer) });
  131. if (response.unreliable_eof() && size == 0)
  132. break;
  133. m_socket->write({ buffer, size });
  134. } while (true);
  135. }
  136. void Client::send_redirect(StringView redirect_path, HTTP::HttpRequest const& request)
  137. {
  138. StringBuilder builder;
  139. builder.append("HTTP/1.0 301 Moved Permanently\r\n");
  140. builder.append("Location: ");
  141. builder.append(redirect_path);
  142. builder.append("\r\n");
  143. builder.append("\r\n");
  144. m_socket->write(builder.to_string());
  145. log_response(301, request);
  146. }
  147. static String folder_image_data()
  148. {
  149. static String cache;
  150. if (cache.is_empty()) {
  151. auto file_or_error = MappedFile::map("/res/icons/16x16/filetype-folder.png");
  152. VERIFY(!file_or_error.is_error());
  153. cache = encode_base64(file_or_error.value()->bytes());
  154. }
  155. return cache;
  156. }
  157. static String file_image_data()
  158. {
  159. static String cache;
  160. if (cache.is_empty()) {
  161. auto file_or_error = MappedFile::map("/res/icons/16x16/filetype-unknown.png");
  162. VERIFY(!file_or_error.is_error());
  163. cache = encode_base64(file_or_error.value()->bytes());
  164. }
  165. return cache;
  166. }
  167. void Client::handle_directory_listing(String const& requested_path, String const& real_path, HTTP::HttpRequest const& request)
  168. {
  169. StringBuilder builder;
  170. builder.append("<!DOCTYPE html>\n");
  171. builder.append("<html>\n");
  172. builder.append("<head><title>Index of ");
  173. builder.append(escape_html_entities(requested_path));
  174. builder.append("</title><style>\n");
  175. builder.append(".folder { width: 16px; height: 16px; background-image: url('data:image/png;base64,");
  176. builder.append(folder_image_data());
  177. builder.append("'); }\n");
  178. builder.append(".file { width: 16px; height: 16px; background-image: url('data:image/png;base64,");
  179. builder.append(file_image_data());
  180. builder.append("'); }\n");
  181. builder.append("</style></head><body>\n");
  182. builder.append("<h1>Index of ");
  183. builder.append(escape_html_entities(requested_path));
  184. builder.append("</h1>\n");
  185. builder.append("<hr>\n");
  186. builder.append("<code><table>\n");
  187. Core::DirIterator dt(real_path);
  188. Vector<String> names;
  189. while (dt.has_next())
  190. names.append(dt.next_path());
  191. quick_sort(names);
  192. for (auto& name : names) {
  193. StringBuilder path_builder;
  194. path_builder.append(real_path);
  195. path_builder.append('/');
  196. // NOTE: In the root directory of the webserver, ".." should be equal to ".", since we don't want
  197. // the user to see e.g. the size of the parent directory (and it isn't unveiled, so stat fails).
  198. if (requested_path == "/" && name == "..")
  199. path_builder.append(".");
  200. else
  201. path_builder.append(name);
  202. struct stat st;
  203. memset(&st, 0, sizeof(st));
  204. int rc = stat(path_builder.to_string().characters(), &st);
  205. if (rc < 0) {
  206. perror("stat");
  207. }
  208. bool is_directory = S_ISDIR(st.st_mode);
  209. builder.append("<tr>");
  210. builder.appendff("<td><div class=\"{}\"></div></td>", is_directory ? "folder" : "file");
  211. builder.append("<td><a href=\"");
  212. builder.append(URL::percent_encode(name));
  213. // NOTE: For directories, we append a slash so we don't always hit the redirect case,
  214. // which adds a slash anyways.
  215. if (is_directory)
  216. builder.append('/');
  217. builder.append("\">");
  218. builder.append(escape_html_entities(name));
  219. builder.append("</a></td><td>&nbsp;</td>");
  220. builder.appendff("<td>{:10}</td><td>&nbsp;</td>", st.st_size);
  221. builder.append("<td>");
  222. builder.append(Core::DateTime::from_timestamp(st.st_mtime).to_string());
  223. builder.append("</td>");
  224. builder.append("</tr>\n");
  225. }
  226. builder.append("</table></code>\n");
  227. builder.append("<hr>\n");
  228. builder.append("<i>Generated by WebServer (SerenityOS)</i>\n");
  229. builder.append("</body>\n");
  230. builder.append("</html>\n");
  231. auto response = builder.to_string();
  232. InputMemoryStream stream { response.bytes() };
  233. send_response(stream, request, "text/html");
  234. }
  235. void Client::send_error_response(unsigned code, HTTP::HttpRequest const& request, Vector<String> const& headers)
  236. {
  237. auto reason_phrase = HTTP::HttpResponse::reason_phrase_for_code(code);
  238. StringBuilder builder;
  239. builder.appendff("HTTP/1.0 {} ", code);
  240. builder.append(reason_phrase);
  241. builder.append("\r\n");
  242. for (auto& header : headers) {
  243. builder.append(header);
  244. builder.append("\r\n");
  245. }
  246. builder.append("Content-Type: text/html; charset=UTF-8\r\n");
  247. builder.append("\r\n");
  248. builder.append("<!DOCTYPE html><html><body><h1>");
  249. builder.appendff("{} ", code);
  250. builder.append(reason_phrase);
  251. builder.append("</h1></body></html>");
  252. m_socket->write(builder.to_string());
  253. log_response(code, request);
  254. }
  255. void Client::log_response(unsigned code, HTTP::HttpRequest const& request)
  256. {
  257. outln("{} :: {:03d} :: {} {}", Core::DateTime::now().to_string(), code, request.method_name(), request.resource());
  258. }
  259. bool Client::verify_credentials(Vector<HTTP::HttpRequest::Header> const& headers)
  260. {
  261. VERIFY(Configuration::the().credentials().has_value());
  262. auto& configured_credentials = Configuration::the().credentials().value();
  263. for (auto& header : headers) {
  264. if (header.name.equals_ignoring_case("Authorization")) {
  265. auto provided_credentials = HTTP::HttpRequest::parse_http_basic_authentication_header(header.value);
  266. if (provided_credentials.has_value() && configured_credentials.username == provided_credentials->username && configured_credentials.password == provided_credentials->password)
  267. return true;
  268. }
  269. }
  270. return false;
  271. }
  272. }