Serenity Operating System
1/*
2 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "Client.h"
28#include <AK/FileSystemPath.h>
29#include <AK/StringBuilder.h>
30#include <LibCore/DateTime.h>
31#include <LibCore/DirIterator.h>
32#include <LibCore/File.h>
33#include <LibCore/HttpRequest.h>
34#include <stdio.h>
35#include <sys/stat.h>
36#include <time.h>
37#include <unistd.h>
38
39namespace WebServer {
40
41Client::Client(NonnullRefPtr<Core::TCPSocket> socket, Core::Object* parent)
42 : Core::Object(parent)
43 , m_socket(socket)
44{
45}
46
47void Client::die()
48{
49 remove_from_parent();
50}
51
52void Client::start()
53{
54 m_socket->on_ready_to_read = [this] {
55 auto raw_request = m_socket->read_all();
56 if (raw_request.is_null()) {
57 die();
58 return;
59 }
60
61 dbg() << "Got raw request: '" << String::copy(raw_request) << "'";
62
63 handle_request(move(raw_request));
64 die();
65 };
66}
67
68void Client::handle_request(ByteBuffer raw_request)
69{
70 auto request_or_error = Core::HttpRequest::from_raw_request(raw_request);
71 if (!request_or_error.has_value())
72 return;
73 auto& request = request_or_error.value();
74
75 dbg() << "Got HTTP request: " << request.method_name() << " " << request.resource();
76 for (auto& header : request.headers()) {
77 dbg() << " " << header.name << " => " << header.value;
78 }
79
80 if (request.method() != Core::HttpRequest::Method::GET) {
81 send_error_response(403, "Forbidden, bro!", request);
82 return;
83 }
84
85 auto requested_path = canonicalized_path(request.resource());
86 dbg() << "Canonical requested path: '" << requested_path << "'";
87
88 StringBuilder path_builder;
89 path_builder.append("/www/");
90 path_builder.append(requested_path);
91 auto real_path = path_builder.to_string();
92
93 if (Core::File::is_directory(real_path)) {
94
95 if (!request.resource().ends_with("/")) {
96 StringBuilder red;
97
98 red.append(requested_path);
99 red.append("/");
100
101 send_redirect(red.to_string(), request);
102 return;
103 }
104
105 StringBuilder index_html_path_builder;
106 index_html_path_builder.append(real_path);
107 index_html_path_builder.append("/index.html");
108 auto index_html_path = index_html_path_builder.to_string();
109 if (!Core::File::exists(index_html_path)) {
110 handle_directory_listing(requested_path, real_path, request);
111 return;
112 }
113 real_path = index_html_path;
114 }
115
116 auto file = Core::File::construct(real_path);
117 if (!file->open(Core::File::ReadOnly)) {
118 send_error_response(404, "Not found, bro!", request);
119 return;
120 }
121
122 send_response(file->read_all(), request);
123}
124
125void Client::send_response(StringView response, const Core::HttpRequest& request)
126{
127 StringBuilder builder;
128 builder.append("HTTP/1.0 200 OK\r\n");
129 builder.append("Server: WebServer (SerenityOS)\r\n");
130 builder.append("Content-Type: text/html\r\n");
131 builder.append("\r\n");
132
133 m_socket->write(builder.to_string());
134 m_socket->write(response);
135
136 log_response(200, request);
137}
138
139void Client::send_redirect(StringView redirect_path, const Core::HttpRequest& request)
140{
141 StringBuilder builder;
142 builder.append("HTTP/1.0 301 Moved Permanently\r\n");
143 builder.append("Location: ");
144 builder.append(redirect_path);
145 builder.append("\r\n");
146 builder.append("\r\n");
147
148 m_socket->write(builder.to_string());
149
150 log_response(301, request);
151}
152
153void Client::handle_directory_listing(const String& requested_path, const String& real_path, const Core::HttpRequest& request)
154{
155 StringBuilder builder;
156
157 builder.append("<!DOCTYPE html>\n");
158 builder.append("<html>\n");
159 builder.append("<head><title>Index of ");
160 builder.append(escape_html_entities(requested_path));
161 builder.append("</title></head>\n");
162 builder.append("<body>\n");
163 builder.append("<h1>Index of ");
164 builder.append(escape_html_entities(requested_path));
165 builder.append("</h1>\n");
166 builder.append("<hr>\n");
167 builder.append("<pre>\n");
168
169 Core::DirIterator dt(real_path);
170 while (dt.has_next()) {
171 auto name = dt.next_path();
172 builder.append("<a href=\"");
173 // FIXME: urlencode
174 builder.append(name);
175 builder.append("\">");
176 builder.append(escape_html_entities(name));
177 builder.append("</a>");
178 for (size_t i = 0; i < (40 - name.length()); ++i)
179 builder.append(' ');
180
181 StringBuilder path_builder;
182 path_builder.append(real_path);
183 path_builder.append('/');
184 path_builder.append(name);
185 struct stat st;
186 memset(&st, 0, sizeof(st));
187 int rc = stat(path_builder.to_string().characters(), &st);
188 if (rc < 0) {
189 perror("stat");
190 }
191 builder.appendf(" %10d", st.st_size);
192 builder.appendf(" ");
193 builder.append(Core::DateTime::from_timestamp(st.st_mtime).to_string());
194 builder.append("\n");
195 }
196
197 builder.append("</pre>\n");
198 builder.append("<hr>\n");
199 builder.append("<i>Generated by WebServer (SerenityOS)</i>\n");
200 builder.append("</body>\n");
201 builder.append("</html>\n");
202
203 send_response(builder.to_string(), request);
204}
205
206void Client::send_error_response(unsigned code, const StringView& message, const Core::HttpRequest& request)
207{
208 StringBuilder builder;
209 builder.appendf("HTTP/1.0 %u ", code);
210 builder.append(message);
211 builder.append("\r\n\r\n");
212 builder.append("<!DOCTYPE html><html><body><h1>");
213 builder.appendf("%u ", code);
214 builder.append(message);
215 builder.append("</h1></body></html>");
216 m_socket->write(builder.to_string());
217
218 log_response(code, request);
219}
220
221void Client::log_response(unsigned code, const Core::HttpRequest& request)
222{
223 printf("%s :: %03u :: %s %s\n",
224 Core::DateTime::now().to_string().characters(),
225 code,
226 request.method_name().characters(),
227 request.resource().characters());
228}
229
230}