From 1fc58883052bd6b5fce373661f14afeb4cdc73ab Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:00:08 -0500 Subject: [PATCH] fix(api): return proper json objects --- .github/workflows/CI.yml | 1 - cmake/compile_definitions/common.cmake | 1 + cmake/compile_definitions/windows.cmake | 1 - cmake/dependencies/common.cmake | 1 + cmake/dependencies/nlohmann_json.cmake | 18 ++ cmake/dependencies/windows.cmake | 3 - docs/app_examples.md | 9 +- docs/building.md | 1 - src/confighttp.cpp | 252 ++++++++++-------------- src/nvhttp.cpp | 15 +- src/nvhttp.h | 6 +- src_assets/windows/assets/apps.json | 4 +- 12 files changed, 144 insertions(+), 168 deletions(-) create mode 100644 cmake/dependencies/nlohmann_json.cmake diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0a583c061a5..945d2d5387d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -887,7 +887,6 @@ jobs: mingw-w64-ucrt-x86_64-graphviz mingw-w64-ucrt-x86_64-MinHook mingw-w64-ucrt-x86_64-miniupnpc - mingw-w64-ucrt-x86_64-nlohmann-json mingw-w64-ucrt-x86_64-nodejs mingw-w64-ucrt-x86_64-nsis mingw-w64-ucrt-x86_64-onevpl diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index 0c576426eb9..723a1a7a540 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -149,6 +149,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} enet libdisplaydevice::display_device + nlohmann_json::nlohmann_json opus ${FFMPEG_LIBRARIES} ${Boost_LIBRARIES} diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 3ee287e0162..b169dbb5f48 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -76,7 +76,6 @@ list(PREPEND PLATFORM_LIBRARIES libstdc++.a libwinpthread.a minhook::minhook - nlohmann_json::nlohmann_json ntdll setupapi shlwapi diff --git a/cmake/dependencies/common.cmake b/cmake/dependencies/common.cmake index 27da728b631..c92b4777b86 100644 --- a/cmake/dependencies/common.cmake +++ b/cmake/dependencies/common.cmake @@ -16,6 +16,7 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server") add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice") # common dependencies +include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake") find_package(OpenSSL REQUIRED) find_package(PkgConfig REQUIRED) find_package(Threads REQUIRED) diff --git a/cmake/dependencies/nlohmann_json.cmake b/cmake/dependencies/nlohmann_json.cmake new file mode 100644 index 00000000000..c0029b42ecc --- /dev/null +++ b/cmake/dependencies/nlohmann_json.cmake @@ -0,0 +1,18 @@ +# +# Loads the nlohmann_json library giving the priority to the system package first, with a fallback to FetchContent. +# +include_guard(GLOBAL) + +find_package(nlohmann_json 3.11 QUIET GLOBAL) +if(NOT nlohmann_json_FOUND) + message(STATUS "nlohmann_json v3.11.x package not found in the system. Falling back to FetchContent.") + include(FetchContent) + + FetchContent_Declare( + json + URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz + URL_HASH MD5=c23a33f04786d85c29fda8d16b5f0efd + DOWNLOAD_EXTRACT_TIMESTAMP + ) + FetchContent_MakeAvailable(json) +endif() diff --git a/cmake/dependencies/windows.cmake b/cmake/dependencies/windows.cmake index 11a40ecf481..3faad7dfd41 100644 --- a/cmake/dependencies/windows.cmake +++ b/cmake/dependencies/windows.cmake @@ -1,8 +1,5 @@ # windows specific dependencies -# nlohmann_json -find_package(nlohmann_json CONFIG 3.11 REQUIRED) - # Make sure MinHook is installed find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED) find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED) diff --git a/docs/app_examples.md b/docs/app_examples.md index 0db2ad930de..0a814f75612 100644 --- a/docs/app_examples.md +++ b/docs/app_examples.md @@ -301,22 +301,19 @@ administrative privileges. Simply enable the elevated option in the WEB UI, or a This is an option for both prep-cmd and regular commands and will launch the process with the current user without a UAC prompt. -@note{It is important to write the values "true" and "false" as string values, not as the typical true/false -values in most JSON.} - **Example** ```json { "name": "Game With AntiCheat that Requires Admin", "output": "", "cmd": "ping 127.0.0.1", - "exclude-global-prep-cmd": "false", - "elevated": "true", + "exclude-global-prep-cmd": false, + "elevated": true, "prep-cmd": [ { "do": "powershell.exe -command \"Start-Streaming\"", "undo": "powershell.exe -command \"Stop-Streaming\"", - "elevated": "false" + "elevated": false } ], "image-path": "" diff --git a/docs/building.md b/docs/building.md index 3c27ac82baf..b7f5d402601 100644 --- a/docs/building.md +++ b/docs/building.md @@ -92,7 +92,6 @@ dependencies=( "mingw-w64-ucrt-x86_64-graphviz" # Optional, for docs "mingw-w64-ucrt-x86_64-MinHook" "mingw-w64-ucrt-x86_64-miniupnpc" - "mingw-w64-ucrt-x86_64-nlohmann-json" "mingw-w64-ucrt-x86_64-nodejs" "mingw-w64-ucrt-x86_64-nsis" "mingw-w64-ucrt-x86_64-onevpl" diff --git a/src/confighttp.cpp b/src/confighttp.cpp index f2a36441468..50a64539925 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -6,25 +6,20 @@ */ #define BOOST_BIND_GLOBAL_PLACEHOLDERS -#include "process.h" - +// standard includes #include +#include #include -#include -#include -#include - +// lib includes +#include +#include #include - #include - #include +#include -#include -#include -#include - +// local includes #include "config.h" #include "confighttp.h" #include "crypto.h" @@ -36,7 +31,7 @@ #include "network.h" #include "nvhttp.h" #include "platform/common.h" -#include "rtsp.h" +#include "process.h" #include "utility.h" #include "uuid.h" #include "version.h" @@ -45,7 +40,6 @@ using namespace std::literals; namespace confighttp { namespace fs = std::filesystem; - namespace pt = boost::property_tree; using https_server_t = SimpleWeb::Server; @@ -86,10 +80,8 @@ namespace confighttp { * @param output_tree The JSON tree to send. */ void - send_response(resp_https_t response, const pt::ptree &output_tree) { - std::ostringstream data; - pt::write_json(data, output_tree); - response->write(data.str()); + send_response(resp_https_t response, const nlohmann::json &output_tree) { + response->write(output_tree.dump()); } /** @@ -184,17 +176,14 @@ namespace confighttp { not_found(resp_https_t response, [[maybe_unused]] req_https_t request) { constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found; - pt::ptree tree; - tree.put("status_code", static_cast(code)); - tree.put("error", "Not Found"); - - std::ostringstream data; - pt::write_json(data, tree); + nlohmann::json tree; + tree["status_code"] = code; + tree["error"] = "Not Found"; SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); - response->write(code, data.str(), headers); + response->write(code, tree.dump(), headers); } /** @@ -207,18 +196,15 @@ namespace confighttp { bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") { constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_bad_request; - pt::ptree tree; - tree.put("status_code", static_cast(code)); - tree.put("status", false); - tree.put("error", error_message); - - std::ostringstream data; - pt::write_json(data, tree); + nlohmann::json tree; + tree["status_code"] = code; + tree["status"] = false; + tree["error"] = error_message; SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "application/json"); - response->write(code, data.str(), headers); + response->write(code, tree.dump(), headers); } /** @@ -530,65 +516,50 @@ namespace confighttp { BOOST_LOG(info) << config::stream.file_apps; try { // TODO: Input Validation - pt::ptree fileTree; - pt::ptree inputTree; - pt::ptree outputTree; - pt::read_json(ss, inputTree); - pt::read_json(config::stream.file_apps, fileTree); + nlohmann::json outputTree; + nlohmann::json inputTree = nlohmann::json::parse(ss); + std::ifstream file(config::stream.file_apps); + nlohmann::json fileTree = nlohmann::json::parse(file); - if (inputTree.get_child("prep-cmd").empty()) { + if (inputTree["prep-cmd"].empty()) { inputTree.erase("prep-cmd"); } - if (inputTree.get_child("detached").empty()) { + if (inputTree["detached"].empty()) { inputTree.erase("detached"); } - auto &apps_node = fileTree.get_child("apps"s); - int index = inputTree.get("index"); + auto &apps_node = fileTree["apps"]; + int index = inputTree["index"].get(); // cast to int for backwards compatibility inputTree.erase("index"); if (index == -1) { - apps_node.push_back(std::make_pair("", inputTree)); + apps_node.push_back(inputTree); } else { - // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick - pt::ptree newApps; - int i = 0; - for (const auto &[k, v] : apps_node) { + nlohmann::json newApps = nlohmann::json::array(); + for (size_t i = 0; i < apps_node.size(); ++i) { if (i == index) { - newApps.push_back(std::make_pair("", inputTree)); + newApps.push_back(inputTree); } else { - newApps.push_back(std::make_pair("", v)); + newApps.push_back(apps_node[i]); } - i++; } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps", newApps)); + fileTree["apps"] = newApps; } // Sort the apps array by name - std::vector apps_vector; - for (const auto &[k, v] : fileTree.get_child("apps")) { - apps_vector.push_back(v); - } - std::ranges::sort(apps_vector, [](const pt::ptree &a, const pt::ptree &b) { - return a.get("name") < b.get("name"); + std::sort(apps_node.begin(), apps_node.end(), [](const nlohmann::json &a, const nlohmann::json &b) { + return a["name"].get() < b["name"].get(); }); - pt::ptree sorted_apps; - for (const auto &app : apps_vector) { - sorted_apps.push_back(std::make_pair("", app)); - } - fileTree.erase("apps"); - fileTree.add_child("apps", sorted_apps); - - pt::write_json(config::stream.file_apps, fileTree); + std::ofstream outFile(config::stream.file_apps); + outFile << fileTree.dump(4); proc::refresh(config::stream.file_apps); - outputTree.put("status", true); + outputTree["status"] = true; send_response(response, outputTree); } catch (std::exception &e) { @@ -610,13 +581,14 @@ namespace confighttp { print_req(request); - pt::ptree outputTree; + nlohmann::json outputTree; try { - pt::ptree fileTree; - pt::ptree newApps; - pt::read_json(config::stream.file_apps, fileTree); - auto &apps_node = fileTree.get_child("apps"s); - int index = stoi(request->path_match[1]); + nlohmann::json fileTree; + nlohmann::json newApps = nlohmann::json::array(); + std::ifstream file(config::stream.file_apps); + fileTree = nlohmann::json::parse(file); + auto &apps_node = fileTree["apps"]; + int index = std::stoi(request->path_match[1]); if (index < 0 || index >= static_cast(apps_node.size())) { std::string error; @@ -630,21 +602,19 @@ namespace confighttp { return; } - // Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick - int i = 0; - for (const auto &[k, v] : apps_node) { - if (i++ != index) { - newApps.push_back(std::make_pair("", v)); + for (size_t i = 0; i < apps_node.size(); ++i) { + if (i != index) { + newApps.push_back(apps_node[i]); } } - fileTree.erase("apps"); - fileTree.push_back(std::make_pair("apps", newApps)); + fileTree["apps"] = newApps; - pt::write_json(config::stream.file_apps, fileTree); + std::ofstream outFile(config::stream.file_apps); + outFile << fileTree.dump(4); proc::refresh(config::stream.file_apps); - outputTree.put("status", true); - outputTree.put("result", "application "s + std::to_string(index) + " deleted"); + outputTree["status"] = true; + outputTree["result"] = "application " + std::to_string(index) + " deleted"; send_response(response, outputTree); } catch (std::exception &e) { @@ -672,12 +642,12 @@ namespace confighttp { if (!authenticate(response, request)) return; std::stringstream ss; - std::stringstream configStream; ss << request->content.rdbuf(); - pt::ptree outputTree; - pt::ptree inputTree; + nlohmann::json outputTree; + nlohmann::json inputTree; try { - pt::read_json(ss, inputTree); + // TODO: this is the only one with a try/catch? + inputTree = nlohmann::json::parse(ss); } catch (std::exception &e) { BOOST_LOG(warning) << "UploadCover: "sv << e.what(); @@ -685,12 +655,12 @@ namespace confighttp { return; } - auto key = inputTree.get("key", ""); + std::string key = inputTree.value("key", ""); if (key.empty()) { bad_request(response, request, "Cover key is required"); return; } - auto url = inputTree.get("url", ""); + std::string url = inputTree.value("url", ""); const std::string coverdir = platf::appdata().string() + "/covers/"; file_handler::make_directory(coverdir); @@ -707,13 +677,13 @@ namespace confighttp { } } else { - auto data = SimpleWeb::Crypto::Base64::decode(inputTree.get("data")); + auto data = SimpleWeb::Crypto::Base64::decode(inputTree.value("data", "")); std::ofstream imgfile(path); - imgfile.write(data.data(), (int) data.size()); + imgfile.write(data.data(), static_cast(data.size())); } - outputTree.put("status", true); - outputTree.put("path", path); + outputTree["status"] = true; + outputTree["path"] = path; send_response(response, outputTree); } @@ -730,15 +700,15 @@ namespace confighttp { print_req(request); - pt::ptree outputTree; - outputTree.put("status", true); - outputTree.put("platform", SUNSHINE_PLATFORM); - outputTree.put("version", PROJECT_VER); + nlohmann::json outputTree; + outputTree["status"] = true; + outputTree["platform"] = SUNSHINE_PLATFORM; + outputTree["version"] = PROJECT_VER; auto vars = config::parse_config(file_handler::read_file(config::sunshine.config_file.c_str())); for (auto &[name, value] : vars) { - outputTree.put(std::move(name), std::move(value)); + outputTree[name] = std::move(value); } send_response(response, outputTree); @@ -757,9 +727,9 @@ namespace confighttp { print_req(request); - pt::ptree outputTree; - outputTree.put("status", true); - outputTree.put("locale", config::sunshine.locale); + nlohmann::json outputTree; + outputTree["status"] = true; + outputTree["locale"] = config::sunshine.locale; send_response(response, outputTree); } @@ -785,21 +755,19 @@ namespace confighttp { print_req(request); std::stringstream ss; - std::stringstream configStream; ss << request->content.rdbuf(); try { // TODO: Input Validation - pt::ptree inputTree; - pt::ptree outputTree; - pt::read_json(ss, inputTree); - for (const auto &[k, v] : inputTree) { - std::string value = inputTree.get(k); - if (value.length() == 0 || value.compare("null") == 0) continue; - - configStream << k << " = " << value << std::endl; + std::stringstream configStream; + nlohmann::json outputTree; + nlohmann::json inputTree = nlohmann::json::parse(ss); + for (const auto &[k, v] : inputTree.items()) { + if (v.is_null() || (v.is_string() && v.get().empty())) continue; + + configStream << k << " = " << v.dump() << std::endl; } file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str()); - outputTree.put("status", true); + outputTree["status"] = true; send_response(response, outputTree); } catch (std::exception &e) { @@ -838,8 +806,8 @@ namespace confighttp { print_req(request); - pt::ptree outputTree; - outputTree.put("status", display_device::reset_persistence()); + nlohmann::json outputTree; + outputTree["status"] = display_device::reset_persistence(); send_response(response, outputTree); } @@ -873,16 +841,15 @@ namespace confighttp { try { // TODO: Input Validation - pt::ptree inputTree; - pt::ptree outputTree; - pt::read_json(ss, inputTree); - auto username = inputTree.count("currentUsername") > 0 ? inputTree.get("currentUsername") : ""; - auto newUsername = inputTree.get("newUsername"); - auto password = inputTree.count("currentPassword") > 0 ? inputTree.get("currentPassword") : ""; - auto newPassword = inputTree.count("newPassword") > 0 ? inputTree.get("newPassword") : ""; - auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get("confirmNewPassword") : ""; - if (newUsername.length() == 0) newUsername = username; - if (newUsername.length() == 0) { + nlohmann::json outputTree; + nlohmann::json inputTree = nlohmann::json::parse(ss); + std::string username = inputTree.value("currentUsername", ""); + std::string newUsername = inputTree.value("newUsername", ""); + std::string password = inputTree.value("currentPassword", ""); + std::string newPassword = inputTree.value("newPassword", ""); + std::string confirmPassword = inputTree.value("confirmNewPassword", ""); + if (newUsername.empty()) newUsername = username; + if (newUsername.empty()) { errors.emplace_back("Invalid Username"); } else { @@ -894,7 +861,7 @@ namespace confighttp { else { http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword); http::reload_user_creds(config::sunshine.credentials_file); - outputTree.put("status", true); + outputTree["status"] = true; } } else { @@ -945,12 +912,11 @@ namespace confighttp { try { // TODO: Input Validation - pt::ptree inputTree; - pt::ptree outputTree; - pt::read_json(ss, inputTree); - std::string pin = inputTree.get("pin"); - std::string name = inputTree.get("name"); - outputTree.put("status", nvhttp::pin(pin, name)); + nlohmann::json outputTree; + nlohmann::json inputTree = nlohmann::json::parse(ss); + std::string pin = inputTree.value("pin", ""); // TODO: Use an int directly + std::string name = inputTree.value("name", ""); + outputTree["status"] = nvhttp::pin(pin, name); send_response(response, outputTree); } catch (std::exception &e) { @@ -975,8 +941,8 @@ namespace confighttp { nvhttp::erase_all_clients(); proc::proc.terminate(); - pt::ptree outputTree; - outputTree.put("status", true); + nlohmann::json outputTree; + outputTree["status"] = true; send_response(response, outputTree); } @@ -1004,11 +970,10 @@ namespace confighttp { try { // TODO: Input Validation - pt::ptree inputTree; - pt::ptree outputTree; - pt::read_json(ss, inputTree); - std::string uuid = inputTree.get("uuid"); - outputTree.put("status", nvhttp::unpair_client(uuid)); + nlohmann::json outputTree; + nlohmann::json inputTree = nlohmann::json::parse(ss); + const std::string uuid = inputTree.value("uuid", ""); + outputTree["status"] = nvhttp::unpair_client(uuid); send_response(response, outputTree); } catch (std::exception &e) { @@ -1030,12 +995,11 @@ namespace confighttp { print_req(request); - const pt::ptree named_certs = nvhttp::get_all_clients(); + const nlohmann::json named_certs = nvhttp::get_all_clients(); - pt::ptree outputTree; - outputTree.put("status", false); - outputTree.add_child("named_certs", named_certs); - outputTree.put("status", true); + nlohmann::json outputTree; + outputTree["named_certs"] = named_certs; + outputTree["status"] = true; send_response(response, outputTree); } @@ -1054,8 +1018,8 @@ namespace confighttp { proc::proc.terminate(); - pt::ptree outputTree; - outputTree.put("status", true); + nlohmann::json outputTree; + outputTree["status"] = true; send_response(response, outputTree); } diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index e510d0837e6..b6cb7b02ab1 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -7,6 +7,7 @@ // standard includes #include +#include #include // lib includes @@ -17,7 +18,7 @@ #include #include #include -#include +#include // local includes #include "config.h" @@ -765,15 +766,15 @@ namespace nvhttp { response->close_connection_after_response = true; } - pt::ptree + nlohmann::json get_all_clients() { - pt::ptree named_cert_nodes; + nlohmann::json named_cert_nodes = nlohmann::json::array(); client_t &client = client_root; for (auto &named_cert : client.named_devices) { - pt::ptree named_cert_node; - named_cert_node.put("name"s, named_cert.name); - named_cert_node.put("uuid"s, named_cert.uuid); - named_cert_nodes.push_back(std::make_pair(""s, named_cert_node)); + nlohmann::json named_cert_node; + named_cert_node["name"] = named_cert.name; + named_cert_node["uuid"] = named_cert.uuid; + named_cert_nodes.push_back(named_cert_node); } return named_cert_nodes; diff --git a/src/nvhttp.h b/src/nvhttp.h index 1f8726c35e0..591c7b9e2d3 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -9,7 +9,7 @@ #include // lib includes -#include +#include // local includes #include "thread_safe.h" @@ -75,10 +75,10 @@ namespace nvhttp { * @brief Get all paired clients. * @return The list of all paired clients. * @examples - * boost::property_tree::ptree clients = nvhttp::get_all_clients(); + * nlohmann::json clients = nvhttp::get_all_clients(); * @examples_end */ - boost::property_tree::ptree + nlohmann::json get_all_clients(); /** diff --git a/src_assets/windows/assets/apps.json b/src_assets/windows/assets/apps.json index 30b6ada06dd..c8d4cb74952 100644 --- a/src_assets/windows/assets/apps.json +++ b/src_assets/windows/assets/apps.json @@ -8,8 +8,8 @@ { "name": "Steam Big Picture", "cmd": "steam://open/bigpicture", - "auto-detach": "true", - "wait-all": "true", + "auto-detach": true, + "wait-all": true, "image-path": "steam.png" } ]