diff --git a/sunshine/confighttp.cpp b/sunshine/confighttp.cpp index aa923f11..8241c9fe 100644 --- a/sunshine/confighttp.cpp +++ b/sunshine/confighttp.cpp @@ -30,233 +30,269 @@ std::string read_file(std::string path); namespace confighttp { - using namespace std::literals; - constexpr auto PORT_HTTP = 47990; +using namespace std::literals; +constexpr auto PORT_HTTP = 47990; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; - using https_server_t = SimpleWeb::Server; +using https_server_t = SimpleWeb::Server; - using args_t = SimpleWeb::CaseInsensitiveMultimap; - using resp_https_t = std::shared_ptr::Response>; - using req_https_t = std::shared_ptr::Request>; +using args_t = SimpleWeb::CaseInsensitiveMultimap; +using resp_https_t = std::shared_ptr::Response>; +using req_https_t = std::shared_ptr::Request>; - enum class op_e +enum class op_e +{ + ADD, + REMOVE +}; + +template +void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + pt::ptree tree; + tree.put("root..status_code", 404); + + std::ostringstream data; + + pt::write_xml(data, tree); + response->write(data.str()); + + *response << "HTTP/1.1 404 NOT FOUND\r\n" + << data.str(); +} + +void getIndexPage(resp_https_t response, req_https_t request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "index.html"); + response->write(header + content); +} + +template +void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "pin.html"); + response->write(header + content); +} + +template +void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "apps.html"); + response->write(header + content); +} + +template +void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "clients.html"); + response->write(header + content); +} + +template +void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) +{ + std::string header = read_file(WEB_DIR "header.html"); + std::string content = read_file(WEB_DIR "config.html"); + response->write(header + content); +} + +void getApps(resp_https_t response, req_https_t request) +{ + std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + response->write(content); +} + +void saveApp(resp_https_t response, req_https_t request) +{ + std::stringstream ss; + ss << request->content.rdbuf(); + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree inputTree, fileTree; + try + { + //TODO: Input Validation + pt::read_json(ss, inputTree); + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = inputTree.get("index"); + BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); + if (inputTree.get_child("prep-cmd").empty()) + inputTree.erase("prep-cmd"); + inputTree.erase("index"); + if (index == -1) { - ADD, - REMOVE - }; - - template - void not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - pt::ptree tree; - tree.put("root..status_code", 404); - - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - - *response << "HTTP/1.1 404 NOT FOUND\r\n" - << data.str(); + apps_node.push_back(std::make_pair("", inputTree)); } - - void getIndexPage(resp_https_t response, req_https_t request) + else { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "index.html"); - response->write(header + content); - } - - template - void getPinPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "pin.html"); - response->write(header + content); - } - - template - void getAppsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "apps.html"); - response->write(header + content); - } - - template - void getClientsPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "clients.html"); - response->write(header + content); - } - - template - void getConfigPage(std::shared_ptr::Response> response, std::shared_ptr::Request> request) - { - std::string header = read_file(WEB_DIR "header.html"); - std::string content = read_file(WEB_DIR "config.html"); - response->write(header + content); - } - - void getApps(resp_https_t response, req_https_t request) - { - std::string content = read_file(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - response->write(content); - } - - void saveApp(resp_https_t response, req_https_t request) - { - std::stringstream ss; - ss << request->content.rdbuf(); - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree inputTree,fileTree; - try { - //TODO: Input Validation - pt::read_json(ss, inputTree); - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); - BOOST_LOG(info) << inputTree.get_child("prep-cmd").empty(); - if(inputTree.get_child("prep-cmd").empty())inputTree.erase("prep-cmd"); - inputTree.erase("index"); - if(index == -1){ - apps_node.push_back(std::make_pair("",inputTree)); - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i == index){ - newApps.push_back(std::make_pair("",inputTree)); - } else { - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid Input JSON"); - return; - } - } - - void deleteApp(resp_https_t response, req_https_t request) - { - pt::ptree outputTree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_json(data, outputTree); - response->write(data.str()); - }); - pt::ptree fileTree; - try { - pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); - BOOST_LOG(info) << index; - if(index <= 0){ - outputTree.put("status","false"); - outputTree.put("error","Invalid Index"); - return; - } else { - //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick - pt::ptree newApps; - int i = 0; - for (const auto& kv : apps_node) { - if(i != index){ - newApps.push_back(std::make_pair("",kv.second)); - } - i++; - } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps",newApps)); - } - pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); - outputTree.put("status","true"); - proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); - } catch (std::exception &e) { - BOOST_LOG(warning) << e.what(); - outputTree.put("status","false"); - outputTree.put("error","Invalid File JSON"); - return; - } - } - - void start(std::shared_ptr shutdown_event) - { - auto ctx = std::make_shared(boost::asio::ssl::context::tls); - ctx->use_certificate_chain_file(config::nvhttp.cert); - ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); - https_server_t http_server { ctx, 0 }; - http_server.default_resource = not_found; - http_server.resource["^/$"]["GET"] = getIndexPage; - http_server.resource["^/pin$"]["GET"] = getPinPage; - http_server.resource["^/apps$"]["GET"] = getAppsPage; - http_server.resource["^/api/apps$"]["GET"] = getApps; - http_server.resource["^/api/apps$"]["POST"] = saveApp; - http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; - http_server.resource["^/clients$"]["GET"] = getClientsPage; - http_server.resource["^/config$"]["GET"] = getConfigPage; - http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; - http_server.config.reuse_address = true; - http_server.config.address = "0.0.0.0"s; - http_server.config.port = PORT_HTTP; - - try + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i == index) { - http_server.bind(); - BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + newApps.push_back(std::make_pair("", inputTree)); } - catch (boost::system::system_error &err) + else { - BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); - - shutdown_event->raise(true); - return; + newApps.push_back(std::make_pair("", kv.second)); } - - std::thread tcp{&https_server_t::accept_and_run, &http_server}; - - // Wait for any event - shutdown_event->view(); - - http_server.stop(); - - tcp.join(); + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Input JSON"); + return; + } +} + +void deleteApp(resp_https_t response, req_https_t request) +{ + pt::ptree outputTree; + auto g = util::fail_guard([&]() { + std::ostringstream data; + + pt::write_json(data, outputTree); + response->write(data.str()); + }); + pt::ptree fileTree; + try + { + pt::read_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + auto &apps_node = fileTree.get_child("apps"s); + int index = stoi(request->path_match[1]); + BOOST_LOG(info) << index; + if (index <= 0) + { + outputTree.put("status", "false"); + outputTree.put("error", "Invalid Index"); + return; + } + else + { + //Unfortuantely Boost PT does not allow to directly edit the array, copt should do the trick + pt::ptree newApps; + int i = 0; + for (const auto &kv : apps_node) + { + if (i != index) + { + newApps.push_back(std::make_pair("", kv.second)); + } + i++; + } + fileTree.erase("apps"); + fileTree.push_back(std::make_pair("apps", newApps)); + } + pt::write_json(SUNSHINE_ASSETS_DIR "/" APPS_JSON, fileTree); + outputTree.put("status", "true"); + proc::refresh(SUNSHINE_ASSETS_DIR "/" APPS_JSON); + } + catch (std::exception &e) + { + BOOST_LOG(warning) << e.what(); + outputTree.put("status", "false"); + outputTree.put("error", "Invalid File JSON"); + return; + } +} + +void start(std::shared_ptr shutdown_event) +{ + auto ctx = std::make_shared(boost::asio::ssl::context::tls); + ctx->use_certificate_chain_file(config::nvhttp.cert); + ctx->use_private_key_file(config::nvhttp.pkey, boost::asio::ssl::context::pem); + https_server_t http_server{ctx, 0}; + http_server.default_resource = not_found; + http_server.resource["^/$"]["GET"] = getIndexPage; + http_server.resource["^/pin$"]["GET"] = getPinPage; + http_server.resource["^/apps$"]["GET"] = getAppsPage; + http_server.resource["^/api/apps$"]["GET"] = getApps; + http_server.resource["^/api/apps$"]["POST"] = saveApp; + http_server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp; + http_server.resource["^/clients$"]["GET"] = getClientsPage; + http_server.resource["^/config$"]["GET"] = getConfigPage; + http_server.resource["^/pin/([0-9]+)$"]["GET"] = nvhttp::pin; + http_server.config.reuse_address = true; + http_server.config.address = "0.0.0.0"s; + http_server.config.port = PORT_HTTP; + + try + { + http_server.bind(); + BOOST_LOG(info) << "Configuration UI available at [https://localhost:"sv << PORT_HTTP << "]"; + } + catch (boost::system::system_error &err) + { + BOOST_LOG(fatal) << "Couldn't bind http server to ports ["sv << PORT_HTTP << "]: "sv << err.what(); + + shutdown_event->raise(true); + return; + } + auto accept_and_run = [&](auto *http_server) { + try + { + http_server->accept_and_run(); + } + catch (boost::system::system_error &err) + { + // It's possible the exception gets thrown after calling http_server->stop() from a different thread + if (shutdown_event->peek()) + { + return; + } + + BOOST_LOG(fatal) << "Couldn't start Configuration HTTP server to ports ["sv << PORT_HTTPS << ", "sv << PORT_HTTP << "]: "sv << err.what(); + shutdown_event->raise(true); + return; + } + }; + std::thread tcp{accept_and_run, &http_server}; + + // Wait for any event + shutdown_event->view(); + + http_server.stop(); + + tcp.join(); +} } std::string read_file(std::string path) { - std::ifstream in(path); + std::ifstream in(path); - std::string input; - std::string base64_cert; + std::string input; + std::string base64_cert; - //FIXME: Being unable to read file could result in infinite loop - while (!in.eof()) - { - std::getline(in, input); - base64_cert += input + '\n'; - } + //FIXME: Being unable to read file could result in infinite loop + while (!in.eof()) + { + std::getline(in, input); + base64_cert += input + '\n'; + } - return base64_cert; + return base64_cert; } \ No newline at end of file