Client.cpp 9.0 KB

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