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