1 // license:BSD-3-Clause
2 // copyright-holders:Miodrag Milanovic
3 /***************************************************************************
4
5 http.cpp
6
7 HTTP server handling
8
9 ***************************************************************************/
10
11 #include "emu.h"
12
13 #ifdef __sun
14 #define ASIO_DISABLE_DEV_POLL
15 #define ASIO_HAS_EPOLL
16 #endif
17
18 #include "server_ws_impl.hpp"
19 #include "server_http_impl.hpp"
20 #include <fstream>
21
22 #include <inttypes.h>
23 #include <stdint.h>
24
25
26 const static struct mapping
27 {
28 const char* extension;
29 const char* mime_type;
30 } mappings[] =
31 {
32 { "aac", "audio/aac" },
33 { "aat", "application/font-sfnt" },
34 { "aif", "audio/x-aif" },
35 { "arj", "application/x-arj-compressed" },
36 { "asf", "video/x-ms-asf" },
37 { "avi", "video/x-msvideo" },
38 { "bmp", "image/bmp" },
39 { "cff", "application/font-sfnt" },
40 { "css", "text/css" },
41 { "csv", "text/csv" },
42 { "doc", "application/msword" },
43 { "eps", "application/postscript" },
44 { "exe", "application/octet-stream" },
45 { "gif", "image/gif" },
46 { "gz", "application/x-gunzip" },
47 { "htm", "text/html" },
48 { "html", "text/html" },
49 { "ico", "image/x-icon" },
50 { "ief", "image/ief" },
51 { "jpeg", "image/jpeg" },
52 { "jpg", "image/jpeg" },
53 { "jpm", "image/jpm" },
54 { "jpx", "image/jpx" },
55 { "js", "application/javascript" },
56 { "json", "application/json" },
57 { "lay", "text/xml" },
58 { "m3u", "audio/x-mpegurl" },
59 { "m4v", "video/x-m4v" },
60 { "mid", "audio/x-midi" },
61 { "mov", "video/quicktime" },
62 { "mp3", "audio/mpeg" },
63 { "mp4", "video/mp4" },
64 { "mpeg", "video/mpeg" },
65 { "mpg", "video/mpeg" },
66 { "oga", "audio/ogg" },
67 { "ogg", "audio/ogg" },
68 { "ogv", "video/ogg" },
69 { "otf", "application/font-sfnt" },
70 { "pct", "image/x-pct" },
71 { "pdf", "application/pdf" },
72 { "pfr", "application/font-tdpfr" },
73 { "pict", "image/pict" },
74 { "png", "image/png" },
75 { "ppt", "application/x-mspowerpoint" },
76 { "ps", "application/postscript" },
77 { "qt", "video/quicktime" },
78 { "ra", "audio/x-pn-realaudio" },
79 { "ram", "audio/x-pn-realaudio" },
80 { "rar", "application/x-arj-compressed" },
81 { "rgb", "image/x-rgb" },
82 { "rtf", "application/rtf" },
83 { "sgm", "text/sgml" },
84 { "shtm", "text/html" },
85 { "shtml", "text/html" },
86 { "sil", "application/font-sfnt" },
87 { "svg", "image/svg+xml" },
88 { "swf", "application/x-shockwave-flash" },
89 { "tar", "application/x-tar" },
90 { "tgz", "application/x-tar-gz" },
91 { "tif", "image/tiff" },
92 { "tiff", "image/tiff" },
93 { "torrent", "application/x-bittorrent" },
94 { "ttf", "application/font-sfnt" },
95 { "txt", "text/plain" },
96 { "wav", "audio/x-wav" },
97 { "webm", "video/webm" },
98 { "woff", "application/font-woff" },
99 { "wrl", "model/vrml" },
100 { "xhtml", "application/xhtml+xml" },
101 { "xls", "application/x-msexcel" },
102 { "xml", "text/xml" },
103 { "xsl", "application/xml" },
104 { "xslt", "application/xml" },
105 { "zip", "application/x-zip-compressed" }
106 };
107
extension_to_type(const std::string & extension)108 static std::string extension_to_type(const std::string& extension)
109 {
110 for (mapping m : mappings)
111 {
112 if (m.extension == extension)
113 {
114 return m.mime_type;
115 }
116 }
117
118 return "text/plain";
119 }
120
121 /** An HTTP Request. */
122 struct http_request_impl : public http_manager::http_request
123 {
124 public:
125 std::shared_ptr<webpp::Request> m_request;
126 std::size_t m_query;
127 std::size_t m_fragment;
128 std::size_t m_path_end;
129 std::size_t m_query_end;
130
http_request_implhttp_request_impl131 http_request_impl(std::shared_ptr<webpp::Request> request) : m_request(std::move(request)) {
132 std::size_t len = m_request->path.length();
133
134 m_fragment = m_request->path.find('#');
135 m_query_end = m_fragment == std::string::npos ? len : m_fragment;
136
137 m_query = m_request->path.find('?');
138 m_path_end = m_query == std::string::npos ? m_query_end : m_query;
139 }
140
141 virtual ~http_request_impl() = default;
142
143 /** Retrieves the requested resource. */
get_resourcehttp_request_impl144 virtual const std::string get_resource() {
145 // The entire resource: path, query and fragment.
146 return m_request->path;
147 }
148
149 /** Returns the path part of the requested resource. */
get_pathhttp_request_impl150 virtual const std::string get_path() {
151 return m_request->path.substr(0, m_path_end);
152 }
153
154 /** Returns the query part of the requested resource. */
get_queryhttp_request_impl155 virtual const std::string get_query() {
156 return m_query == std::string::npos ? "" : m_request->path.substr(m_query, m_query_end);
157 }
158
159 /** Returns the fragment part of the requested resource. */
get_fragmenthttp_request_impl160 virtual const std::string get_fragment() {
161 return m_fragment == std::string::npos ? "" : m_request->path.substr(m_fragment);
162 }
163
164 /** Retrieves a header from the HTTP request. */
get_headerhttp_request_impl165 virtual const std::string get_header(const std::string &header_name) {
166 auto i = m_request->header.find(header_name);
167 if (i != m_request->header.end()) {
168 return (*i).second;
169 } else {
170 return "";
171 }
172 }
173
174 /** Retrieves a header from the HTTP request. */
get_headershttp_request_impl175 virtual const std::list<std::string> get_headers(const std::string &header_name) {
176 std::list<std::string> result;
177 auto range = m_request->header.equal_range(header_name);
178 for (auto i = range.first; i != range.second; i++) {
179 result.push_back((*i).second);
180 }
181 return result;
182 }
183
184 /** Returns the body that was submitted with the HTTP request. */
get_bodyhttp_request_impl185 virtual const std::string get_body() {
186 // TODO(cbrunschen): What to return here - http_server::Request has a 'content' feld that is never filled in!
187 return "";
188 }
189 };
190
191 /** An HTTP response. */
192 struct http_response_impl : public http_manager::http_response {
193 std::shared_ptr<webpp::Response> m_response;
194 int m_status;
195 std::string m_content_type;
196 std::stringstream m_headers;
197 std::stringstream m_body;
198
http_response_implhttp_response_impl199 http_response_impl(std::shared_ptr<webpp::Response> response) : m_response(std::move(response)) { }
200
201 virtual ~http_response_impl() = default;
202
203 /** Sets the HTTP status to be returned to the client. */
set_statushttp_response_impl204 virtual void set_status(int status) {
205 m_status = status;
206 }
207
208 /** Sets the HTTP content type to be returned to the client. */
set_content_typehttp_response_impl209 virtual void set_content_type(const std::string &content_type) {
210 m_content_type = content_type;
211 }
212
213 /** Sets the body to be sent to the client. */
set_bodyhttp_response_impl214 virtual void set_body(const std::string &body) {
215 m_body.str("");
216 append_body(body);
217 }
218
219 /** Appends something to the body to be sent to the client. */
append_bodyhttp_response_impl220 virtual void append_body(const std::string &body) {
221 m_body << body;
222 }
223
224 /** Sends the response to the client. */
sendhttp_response_impl225 void send() {
226 m_response->type(m_content_type);
227 m_response->status(m_status);
228 m_response->send(m_body.str());
229 }
230 };
231
232 struct websocket_endpoint_impl : public http_manager::websocket_endpoint {
233 /** The underlying edpoint. */
234 webpp::ws_server::Endpoint *m_endpoint;
235
websocket_endpoint_implwebsocket_endpoint_impl236 websocket_endpoint_impl(webpp::ws_server::Endpoint *endpoint,
237 http_manager::websocket_open_handler on_open,
238 http_manager::websocket_message_handler on_message,
239 http_manager::websocket_close_handler on_close,
240 http_manager::websocket_error_handler on_error)
241 : m_endpoint(endpoint) {
242 this->on_open = std::move(on_open);
243 this->on_message = std::move(on_message);
244 this->on_close = std::move(on_close);
245 this->on_error = std::move(on_error);
246 }
247 };
248
249 struct websocket_connection_impl : public http_manager::websocket_connection {
250 /** The server */
251 webpp::ws_server *m_wsserver;
252 /* The underlying Commection. */
253 std::weak_ptr<webpp::Connection> m_connection;
websocket_connection_implwebsocket_connection_impl254 websocket_connection_impl(webpp::ws_server *server, std::shared_ptr<webpp::Connection> connection)
255 : m_wsserver(server), m_connection(connection) { }
256
257 /** Sends a message to the client that is connected on the other end of this Websocket connection. */
send_messagewebsocket_connection_impl258 virtual void send_message(const std::string &payload, int opcode) {
259 if (auto connection = m_connection.lock()) {
260 std::shared_ptr<webpp::ws_server::SendStream> message_stream = std::make_shared<webpp::ws_server::SendStream>();
261 (*message_stream) << payload;
262 m_wsserver->send(connection, message_stream, nullptr, opcode | 0x80);
263 }
264 }
265
266 /** Closes this open Websocket connection. */
closewebsocket_connection_impl267 virtual void close() {
268 if (auto connection = m_connection.lock()) {
269 m_wsserver->send_close(connection, 1000 /* normal close */);
270 }
271 }
272 };
273
http_manager(bool active,short port,const char * root)274 http_manager::http_manager(bool active, short port, const char *root)
275 : m_active(active), m_io_context(std::make_shared<asio::io_context>()), m_root(root)
276 {
277 if (!active) return;
278
279 m_server = std::make_unique<webpp::http_server>();
280 m_server->m_config.port = port;
281 m_server->set_io_context(m_io_context);
282 m_wsserver = std::make_unique<webpp::ws_server>();
283
284 auto& endpoint = m_wsserver->m_endpoint["/"];
285
286 m_server->on_get([root](auto response, auto request) {
287 std::string doc_root = root;
288
289 auto request_impl = std::make_shared<http_request_impl>(request);
290
291 std::string path = request_impl->get_path();
292 // If path ends in slash (i.e. is a directory) then add "index.html".
293 if (path[path.size() - 1] == '/')
294 {
295 path += "index.html";
296 }
297
298 // Determine the file extension.
299 std::size_t last_slash_pos = path.find_last_of('/');
300 std::size_t last_dot_pos = path.find_last_of('.');
301 std::string extension;
302 if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
303 {
304 extension = path.substr(last_dot_pos + 1);
305 }
306
307 // Open the file to send back.
308 std::string full_path = doc_root + path;
309 std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
310 if (!is)
311 {
312 response->status(400).send("Error");
313 }
314 else
315 {
316 // Fill out the reply to be sent to the client.
317 std::string content;
318 char buf[512];
319 while (is.read(buf, sizeof(buf)).gcount() > 0)
320 content.append(buf, size_t(is.gcount()));
321
322 response->type(extension_to_type(extension));
323 response->status(200).send(content);
324 }
325 });
326
327 endpoint.on_open = [&](auto connection) {
328 auto send_stream = std::make_shared<webpp::ws_server::SendStream>();
329 *send_stream << "update_machine";
330 m_wsserver->send(connection, send_stream);
331 };
332
333 m_server->on_upgrade = [this](auto socket, auto request) {
334 auto connection = std::make_shared<webpp::ws_server::Connection>(socket);
335 connection->method = std::move(request->method);
336 connection->path = std::move(request->path);
337 connection->http_version = std::move(request->http_version);
338 connection->header = std::move(request->header);
339 connection->remote_endpoint_address = std::move(request->remote_endpoint_address);
340 connection->remote_endpoint_port = request->remote_endpoint_port;
341 m_wsserver->upgrade(connection);
342 };
343
344 m_server->start();
345
346 m_server_thread = std::thread([this]() {
347 m_io_context->run();
348 });
349 }
350
~http_manager()351 http_manager::~http_manager()
352 {
353 if (!m_server) return;
354
355 m_server->stop();
356 m_io_context->stop();
357 if (m_server_thread.joinable())
358 m_server_thread.join();
359 }
360
on_get(http_manager::http_handler handler,std::shared_ptr<webpp::Response> response,std::shared_ptr<webpp::Request> request)361 static void on_get(http_manager::http_handler handler, std::shared_ptr<webpp::Response> response, std::shared_ptr<webpp::Request> request) {
362 auto request_impl = std::make_shared<http_request_impl>(request);
363 auto response_impl = std::make_shared<http_response_impl>(response);
364
365 handler(request_impl, response_impl);
366
367 response_impl->send();
368 }
369
on_open(http_manager::websocket_endpoint_ptr endpoint,std::shared_ptr<webpp::Connection> connection)370 void http_manager::on_open(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection) {
371 std::lock_guard<std::mutex> lock(m_connections_mutex);
372 webpp::ws_server *ws_server = m_wsserver.get();
373
374 http_manager::websocket_connection_ptr connection_impl = std::make_shared<websocket_connection_impl>(ws_server, connection);
375 m_connections[connection.get()] = connection_impl;
376
377 if (endpoint->on_open) {
378 endpoint->on_open(connection_impl);
379 }
380 }
381
on_message(http_manager::websocket_endpoint_ptr endpoint,std::shared_ptr<webpp::Connection> connection,const std::string & payload,int opcode)382 void http_manager::on_message(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection, const std::string &payload, int opcode) {
383 if (endpoint->on_message) {
384 std::lock_guard<std::mutex> lock(m_connections_mutex);
385 auto i = m_connections.find(connection.get());
386 if (i != m_connections.end()) {
387 http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
388 endpoint->on_message(websocket_connection_impl, payload, opcode);
389 }
390 }
391 }
392
on_close(http_manager::websocket_endpoint_ptr endpoint,std::shared_ptr<webpp::Connection> connection,int status,const std::string & reason)393 void http_manager::on_close(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection,
394 int status, const std::string& reason) {
395 std::lock_guard<std::mutex> lock(m_connections_mutex);
396 auto i = m_connections.find(connection.get());
397 if (i != m_connections.end()) {
398 if (endpoint->on_close) {
399 http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
400 endpoint->on_close(websocket_connection_impl, status, reason);
401 }
402
403 m_connections.erase(connection.get());
404 }
405 }
406
on_error(http_manager::websocket_endpoint_ptr endpoint,std::shared_ptr<webpp::Connection> connection,const std::error_code & error_code)407 void http_manager::on_error(http_manager::websocket_endpoint_ptr endpoint, std::shared_ptr<webpp::Connection> connection,
408 const std::error_code& error_code) {
409 std::lock_guard<std::mutex> lock(m_connections_mutex);
410 auto i = m_connections.find(connection.get());
411 if (i != m_connections.end()) {
412 if (endpoint->on_error) {
413 http_manager::websocket_connection_ptr websocket_connection_impl = (*i).second;
414 endpoint->on_error(websocket_connection_impl, error_code);
415 }
416
417 m_connections.erase(connection.get());
418 }
419 }
420
read_file(std::ostream & os,const std::string & path)421 bool http_manager::read_file(std::ostream &os, const std::string &path) {
422 std::ostringstream full_path;
423 full_path << m_root << path;
424 util::core_file::ptr f;
425 osd_file::error e = util::core_file::open(full_path.str(), OPEN_FLAG_READ, f);
426 if (e == osd_file::error::NONE)
427 {
428 int c;
429 while ((c = f->getc()) >= 0)
430 {
431 os.put(c);
432 }
433 }
434 return e == osd_file::error::NONE;
435 }
436
serve_document(http_request_ptr request,http_response_ptr response,const std::string & filename)437 void http_manager::serve_document(http_request_ptr request, http_response_ptr response, const std::string &filename) {
438 if (!m_active) return;
439
440 std::ostringstream os;
441 if (read_file(os, filename))
442 {
443 response->set_status(200);
444 response->set_body(os.str());
445 }
446 else
447 {
448 response->set_status(500);
449 }
450 }
451
serve_template(http_request_ptr request,http_response_ptr response,const std::string & filename,substitution substitute,char init,char term)452 void http_manager::serve_template(http_request_ptr request, http_response_ptr response,
453 const std::string &filename, substitution substitute, char init, char term)
454 {
455 if (!m_active) return;
456
457 // printf("webserver: serving template '%s' at path '%s'\n", filename.c_str(), request->get_path().c_str());
458 std::stringstream ss;
459 if (read_file(ss, filename))
460 {
461 std::ostringstream os;
462 while (ss.good())
463 {
464 std::string s;
465 getline(ss, s, init);
466 os << s;
467 if (ss.good())
468 {
469 // printf("webserver: found initiator '%c'\n", init);
470 getline(ss, s, term);
471 if (ss.good())
472 {
473 if (substitute(s))
474 {
475 os << s;
476 }
477 else
478 {
479 os << init << s << term;
480 }
481 }
482 else
483 {
484 // printf("webserver: reached end before terminator\n");
485 os << init;
486 os << s;
487 }
488 }
489 else
490 {
491 // printf("webserver: reached end before initiator\n");
492 }
493 }
494
495 response->set_status(200);
496 response->set_body(os.str());
497 }
498 else
499 {
500 response->set_status(500);
501 }
502 }
503
add_http_handler(const std::string & path,http_manager::http_handler handler)504 void http_manager::add_http_handler(const std::string &path, http_manager::http_handler handler)
505 {
506 if (!m_active) return;
507
508 using namespace std::placeholders;
509 m_server->on_get(path, std::bind(on_get, handler, _1, _2));
510
511 std::lock_guard<std::mutex> lock(m_handlers_mutex);
512 m_handlers.emplace(path, handler);
513 }
514
remove_http_handler(const std::string & path)515 void http_manager::remove_http_handler(const std::string &path) {
516 if (!m_active) return;
517
518 m_server->remove_handler(path);
519
520 std::lock_guard<std::mutex> lock(m_handlers_mutex);
521 m_handlers.erase(path);
522 }
523
clear()524 void http_manager::clear() {
525 if (!m_active) return;
526
527 m_server->clear();
528
529 std::lock_guard<std::mutex> lock(m_handlers_mutex);
530 m_handlers.clear();
531 }
532
add_endpoint(const std::string & path,http_manager::websocket_open_handler on_open,http_manager::websocket_message_handler on_message,http_manager::websocket_close_handler on_close,http_manager::websocket_error_handler on_error)533 http_manager::websocket_endpoint_ptr http_manager::add_endpoint(const std::string &path,
534 http_manager::websocket_open_handler on_open,
535 http_manager::websocket_message_handler on_message,
536 http_manager::websocket_close_handler on_close,
537 http_manager::websocket_error_handler on_error) {
538 if (!m_active) return std::shared_ptr<websocket_endpoint_impl>(nullptr);
539
540 auto i = m_endpoints.find(path);
541 if (i == m_endpoints.end()) {
542 using namespace std::placeholders;
543
544 auto &endpoint = m_wsserver->m_endpoint[path];
545 webpp::ws_server::Endpoint *endpoint_ptr(&endpoint);
546 auto endpoint_impl = std::make_shared<websocket_endpoint_impl>(endpoint_ptr, on_open, on_message, on_close, on_error);
547
548 endpoint.on_open = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection) {
549 this->on_open(endpoint_impl, std::move(connection));
550 };
551
552 endpoint.on_message = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, std::shared_ptr<webpp::ws_server::Message> message) {
553 std::string payload = message->string();
554 int opcode = message->fin_rsv_opcode & 0x0f;
555 this->on_message(endpoint_impl, std::move(connection), payload, opcode);
556 };
557
558 endpoint.on_close = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, int status, const std::string& reason) {
559 this->on_close(endpoint_impl, std::move(connection), status, reason);
560 };
561
562 endpoint.on_error = [&, this, endpoint_impl](std::shared_ptr<webpp::Connection> connection, const std::error_code& error_code) {
563 this->on_error(endpoint_impl, std::move(connection), error_code);
564 };
565
566 m_endpoints[path] = endpoint_impl;
567 return endpoint_impl;
568 } else {
569 return (*i).second;
570 }
571 }
572
remove_endpoint(const std::string & path)573 void http_manager::remove_endpoint(const std::string &path) {
574 if (!m_active) return;
575
576 m_endpoints.erase(path);
577 }
578