1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <https/HTTPServer.h>
18
19 #include <https/ClientSocket.h>
20 #include <https/HTTPRequestResponse.h>
21 #include <https/Support.h>
22 #include "common/libs/utils/base64.h"
23
24 #include <android-base/logging.h>
25
26 #include <iostream>
27 #include <map>
28 #include <string>
29
30 #include <openssl/sha.h>
31
32 #define CC_SHA1_CTX SHA_CTX
33 #define CC_SHA1_Init SHA1_Init
34 #define CC_SHA1_Update SHA1_Update
35 #define CC_SHA1_Final SHA1_Final
36 #define CC_LONG size_t
37
HTTPServer(std::shared_ptr<RunLoop> runLoop,const char * iface,uint16_t port,ServerSocket::TransportType transportType,const std::optional<std::string> & certificate_pem_path,const std::optional<std::string> & private_key_pem_path)38 HTTPServer::HTTPServer(
39 std::shared_ptr<RunLoop> runLoop,
40 const char *iface,
41 uint16_t port,
42 ServerSocket::TransportType transportType,
43 const std::optional<std::string> &certificate_pem_path,
44 const std::optional<std::string> &private_key_pem_path)
45 : mRunLoop(runLoop),
46 mLocalPort(port),
47 mSocketTLS(
48 std::make_shared<ServerSocket>(
49 this,
50 transportType,
51 iface ? iface : "0.0.0.0",
52 port,
53 certificate_pem_path,
54 private_key_pem_path)) {
55 CHECK(mSocketTLS->initCheck() == 0);
56 }
57
getLocalPort() const58 uint16_t HTTPServer::getLocalPort() const {
59 return mLocalPort;
60 }
61
run()62 void HTTPServer::run() {
63 mSocketTLS->run(mRunLoop);
64 }
65
handleSingleRequest(ClientSocket * clientSocket,const uint8_t * data,size_t size,bool isEOS)66 bool HTTPServer::handleSingleRequest(
67 ClientSocket *clientSocket,
68 const uint8_t *data,
69 size_t size,
70 bool isEOS) {
71 (void)isEOS;
72
73 static const std::unordered_map<int32_t, std::string> kStatusMessage {
74 { 101, "Switching Protocols" },
75 { 200, "OK" },
76 { 400, "Bad Request" },
77 { 404, "Not Found" },
78 { 405, "Method Not Allowed" },
79 { 503, "Service Unavailable" },
80 { 505, "HTTP Version Not Supported" },
81 };
82
83 HTTPRequest request;
84 request.setTo(data, size);
85
86 int32_t httpResultCode;
87 std::string body;
88 std::unordered_map<std::string, std::string> responseHeaders;
89
90 if (request.initCheck() < 0) {
91 httpResultCode = 400; // Bad Request
92 } else if (request.getMethod() != "GET") {
93 httpResultCode = 405; // Method Not Allowed
94 } else if (request.getVersion() != "HTTP/1.1") {
95 httpResultCode = 505; // HTTP Version Not Supported
96 } else {
97 httpResultCode = 404;
98
99 auto path = request.getPath();
100
101 std::string query;
102
103 auto separatorPos = path.find('?');
104 if (separatorPos != std::string::npos) {
105 query = path.substr(separatorPos);
106 path.erase(separatorPos);
107 }
108
109 if (path == "/") { path = "/index.html"; }
110
111 bool done = false;
112
113 {
114 std::lock_guard autoLock(mContentLock);
115
116 auto it = mStaticFiles.find(path);
117
118 if (it != mStaticFiles.end()) {
119 handleStaticFileRequest(
120 it->second,
121 request,
122 &httpResultCode,
123 &responseHeaders,
124 &body);
125
126 done = true;
127 }
128 }
129
130 if (!done) {
131 std::lock_guard autoLock(mContentLock);
132
133 auto it = mWebSocketHandlerFactories.find(path);
134
135 if (it != mWebSocketHandlerFactories.end()) {
136 handleWebSocketRequest(
137 clientSocket,
138 it->second,
139 request,
140 &httpResultCode,
141 &responseHeaders,
142 &body);
143
144 done = true;
145 }
146 }
147
148 const auto remoteAddr = clientSocket->remoteAddr();
149 uint32_t ip = ntohl(remoteAddr.sin_addr.s_addr);
150
151 LOG(INFO)
152 << (ip >> 24)
153 << "."
154 << ((ip >> 16) & 0xff)
155 << "."
156 << ((ip >> 8) & 0xff)
157 << "."
158 << (ip & 0xff)
159 << ":"
160 << ntohs(remoteAddr.sin_port)
161 << " "
162 << httpResultCode << " \"" << path << "\"";
163 }
164
165 const std::string status =
166 std::to_string(httpResultCode)
167 + " "
168 + kStatusMessage.find(httpResultCode)->second;
169
170 bool closeConnection = false;
171
172 if (httpResultCode != 200 && httpResultCode != 101) {
173 body = "<h1>" + status + "</h1>";
174
175 responseHeaders["Connection"] = "close";
176 responseHeaders["Content-Type"] = "text/html";
177
178 closeConnection = true;
179 }
180
181 std::string value;
182 if (request.getHeaderField("Connection", &value) && value == "close") {
183 LOG(VERBOSE) << "Closing connection per client's request.";
184 closeConnection = true;
185 }
186
187 responseHeaders["Content-Length"] = std::to_string(body.size());
188
189 if (closeConnection) {
190 responseHeaders["Connection"] = "close";
191 }
192
193 std::string response;
194 response = "HTTP/1.1 " + status + "\r\n";
195
196 for (const auto &pair : responseHeaders) {
197 response += pair.first + ": " + pair.second + "\r\n";
198 }
199
200 response += "\r\n";
201
202 clientSocket->queueResponse(response, body);
203
204 return closeConnection;
205 }
206
addStaticFile(const char * at,const char * path,std::optional<std::string> mimeType)207 void HTTPServer::addStaticFile(
208 const char *at, const char *path, std::optional<std::string> mimeType) {
209 std::lock_guard autoLock(mContentLock);
210 mStaticFiles[at] = { path, mimeType };
211 }
212
addStaticContent(const char * at,const void * _data,size_t size,std::optional<std::string> mimeType)213 void HTTPServer::addStaticContent(
214 const char *at,
215 const void *_data,
216 size_t size,
217 std::optional<std::string> mimeType) {
218 if (!mimeType) {
219 // Note: unlike for static, file-based content, we guess the mime type
220 // based on the path we're mapping the content at, not the path it's
221 // originating from (since we don't know that for memory based content).
222 mimeType = GuessMimeType(at);
223 }
224
225 auto data = static_cast<const uint8_t *>(_data);
226
227 std::lock_guard autoLock(mContentLock);
228 mStaticFiles[at] = { std::vector<uint8_t>(data, data + size), mimeType };
229 }
230
addWebSocketHandlerFactory(const char * at,WebSocketHandlerFactory factory)231 void HTTPServer::addWebSocketHandlerFactory(
232 const char *at, WebSocketHandlerFactory factory) {
233 std::lock_guard autoLock(mContentLock);
234 mWebSocketHandlerFactories[at] = factory;
235 }
236
handleWebSocketRequest(ClientSocket * clientSocket,WebSocketHandlerFactory factory,const HTTPRequest & request,int32_t * httpResultCode,std::unordered_map<std::string,std::string> * responseHeaders,std::string * body)237 void HTTPServer::handleWebSocketRequest(
238 ClientSocket *clientSocket,
239 WebSocketHandlerFactory factory,
240 const HTTPRequest &request,
241 int32_t *httpResultCode,
242 std::unordered_map<std::string, std::string> *responseHeaders,
243 std::string *body) {
244 (void)body;
245
246 auto [status, handler] = factory();
247
248 if (status != 0 || !handler) {
249 *httpResultCode = 503; // Service unavailable.
250 return;
251 }
252
253 *httpResultCode = 400;
254
255 std::string value;
256 if (!request.getHeaderField("Connection", &value)
257 || (value != "Upgrade" && value != "keep-alive, Upgrade")) {
258 return;
259 }
260
261 if (!request.getHeaderField("Upgrade", &value) || value != "websocket") {
262 return;
263 }
264
265 if (!request.getHeaderField("Sec-WebSocket-Version", &value)) {
266 return;
267 }
268
269 char *end;
270 long version = strtol(value.c_str(), &end, 10);
271
272 if (end == value.c_str() || *end != '\0' || version < 13) {
273 return;
274 }
275
276 if (!request.getHeaderField("Sec-WebSocket-Key", &value)) {
277 return;
278 }
279
280 *httpResultCode = 101;
281
282 (*responseHeaders)["Connection"] = "Upgrade";
283 (*responseHeaders)["Upgrade"] = "websocket";
284
285 std::string tmp = value;
286 tmp += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
287
288 CC_SHA1_CTX ctx;
289 int res = CC_SHA1_Init(&ctx);
290 CHECK_EQ(res, 1);
291
292 res = CC_SHA1_Update(
293 &ctx, tmp.c_str(), static_cast<CC_LONG>(tmp.size()));
294
295 CHECK_EQ(res, 1);
296
297 unsigned char digest[20]; // 160 bit
298 res = CC_SHA1_Final(digest, &ctx);
299 CHECK_EQ(res, 1);
300
301 std::string acceptKey;
302 cuttlefish::EncodeBase64(digest, sizeof(digest), &acceptKey);
303
304 (*responseHeaders)["Sec-WebSocket-Accept"] = acceptKey;
305
306 clientSocket->setWebSocketHandler(handler);
307 }
308
handleStaticFileRequest(const StaticFileInfo & info,const HTTPRequest & request,int32_t * httpResultCode,std::unordered_map<std::string,std::string> * responseHeaders,std::string * body)309 void HTTPServer::handleStaticFileRequest(
310 const StaticFileInfo &info,
311 const HTTPRequest &request,
312 int32_t *httpResultCode,
313 std::unordered_map<std::string, std::string> *responseHeaders,
314 std::string *body) {
315 (void)request;
316
317 if (std::holds_alternative<std::string>(info.mPathOrContent)) {
318 const auto &path = std::get<std::string>(info.mPathOrContent);
319
320 std::unique_ptr<FILE, std::function<int(FILE *)>> file(
321 fopen(path.c_str(), "r"),
322 fclose);
323
324 if (!file) {
325 *httpResultCode = 404;
326 return;
327 }
328
329 fseek(file.get(), 0, SEEK_END);
330 long fileSize = ftell(file.get());
331 fseek(file.get(), 0, SEEK_SET);
332
333 (*responseHeaders)["Content-Length"] = std::to_string(fileSize);
334
335 if (info.mMimeType) {
336 (*responseHeaders)["Content-Type"] = *info.mMimeType;
337 } else {
338 (*responseHeaders)["Content-Type"] = GuessMimeType(path);
339 }
340
341 while (!feof(file.get())) {
342 char buffer[1024];
343 auto n = fread(buffer, 1, sizeof(buffer), file.get());
344
345 body->append(buffer, n);
346 }
347 } else {
348 CHECK(std::holds_alternative<std::vector<uint8_t>>(
349 info.mPathOrContent));
350
351 const auto &content =
352 std::get<std::vector<uint8_t>>(info.mPathOrContent);
353
354 body->append(content.begin(), content.end());
355
356 (*responseHeaders)["Content-Length"] = std::to_string(content.size());
357 }
358
359 *httpResultCode = 200;
360 }
361
362 // static
GuessMimeType(const std::string & path)363 std::string HTTPServer::GuessMimeType(const std::string &path) {
364 auto dotPos = path.rfind('.');
365 if (dotPos != std::string::npos) {
366 auto extension = std::string(path, dotPos + 1);
367
368 static std::unordered_map<std::string, std::string>
369 sMimeTypeByExtension {
370
371 { "html", "text/html" },
372 { "htm", "text/html" },
373 { "css", "text/css" },
374 { "js", "text/javascript" },
375 };
376
377 auto it = sMimeTypeByExtension.find(extension);
378 if (it != sMimeTypeByExtension.end()) {
379 return it->second;
380 }
381 }
382
383 return "application/octet-stream";
384 }
385
certificate_pem_path() const386 std::optional<std::string> HTTPServer::certificate_pem_path() const {
387 return mSocketTLS->certificate_pem_path();
388 }
389
private_key_pem_path() const390 std::optional<std::string> HTTPServer::private_key_pem_path() const {
391 return mSocketTLS->private_key_pem_path();
392 }
393