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