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