diff --git a/src/browser/window_launcher.cxx b/src/browser/window_launcher.cxx index 67e2407..4145f15 100644 --- a/src/browser/window_launcher.cxx +++ b/src/browser/window_launcher.cxx @@ -32,8 +32,6 @@ #define URLPLATFORM "mac" #endif -CefRefPtr SaveFileFromPost(CefRefPtr, const std::filesystem::path::value_type*); - struct FilePicker: public CefRunFileDialogCallback, Browser::ResourceHandler { FilePicker(CefRefPtr browser, std::vector accept_filters): accept_filters(accept_filters), callback(nullptr), browser_host(browser->GetHost()), ResourceHandler("text/plain") { } @@ -151,6 +149,12 @@ CefRefPtr Browser::Launcher::GetRequestHandler() { return this; } +#define ROUTE(API, FUNC) if (path == "/" API) { return this->FUNC(request, query); } +#if defined(BOLT_PLUGINS) +#define ROUTEIFPLUGINS(API, FUNC) if (path == "/" API) { return this->FUNC(request, query); } +#else +#define ROUTEIFPLUGINS(API, FUNC) if (path == "/" API) { QSENDNOTSUPPORTED(); } +#endif CefRefPtr Browser::Launcher::GetResourceRequestHandler( CefRefPtr browser, CefRefPtr frame, @@ -235,296 +239,28 @@ CefRefPtr Browser::Launcher::GetResourceRequestHandle // internal API endpoints - only allowed if it's a request from internal URL to internal URL if (is_internal_target && is_internal_initiator) { - - // instruction to launch RS3 .deb - if (path == "/launch-rs3-deb") { - return this->LaunchRs3Deb(request, query); - } - - // instruction to launch RS3 .exe - if (path == "/launch-rs3-exe") { - return this->LaunchRs3Exe(request, query); - } - - // instruction to launch RS3 app (mac) - if (path == "/launch-rs3-app") { - return this->LaunchRs3App(request, query); - } - - // instruction to launch OSRS .exe - if (path == "/launch-osrs-exe") { - return this->LaunchOsrsExe(request, query); - } - - // instruction to launch OSRS app (mac) - if (path == "/launch-osrs-app") { - return this->LaunchOsrsApp(request, query); - } - - // instruction to launch RuneLite.jar - if (path == "/launch-runelite-jar") { - return this->LaunchRuneliteJar(request, query, false); - } - - // instruction to launch RuneLite.jar with --configure - if (path == "/launch-runelite-jar-configure") { - return this->LaunchRuneliteJar(request, query, true); - } - - // instruction to launch RuneLite.jar - if (path == "/launch-hdos-jar") { - return this->LaunchHdosJar(request, query); - } - - // instruction to save user config file to disk - if (path == "/save-config") { - return SaveFileFromPost(request, this->config_path.c_str()); - } - - // instruction to save user credentials to disk - if (path == "/save-credentials") { - return SaveFileFromPost(request, this->creds_path.c_str()); - } - - // instruction to save plugin config to disk - if (path == "/save-plugin-config") { -#if defined(BOLT_PLUGINS) - return SaveFileFromPost(request, this->plugin_config_path.c_str()); -#else - QSENDNOTSUPPORTED(); -#endif - } - - // request for list of connected game clients - if (path == "/list-game-clients") { -#if defined(BOLT_PLUGINS) - this->UpdateClientList(true); - QSENDOK(); -#else - QSENDNOTSUPPORTED(); -#endif - } - - // request for the contents of a JSON file - doesn't actually validate the contents - if (path == "/read-json-file") { -#if defined(BOLT_PLUGINS) - QSTRING path; - bool has_path = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { - PQSTRING(path) - }); - QREQPARAM(path); - std::ifstream file(path, std::ios::in | std::ios::binary); - if (!file.fail()) { - std::stringstream ss; - ss << file.rdbuf(); - std::string str = ss.str(); - file.close(); - return new Browser::ResourceHandler(str, 200, "application/json"); - } else { - QSENDNOTFOUND(); - } -#else - QSENDNOTSUPPORTED(); -#endif - } - - // request to send a message to a game client to start a plugin - if (path == "/start-plugin") { -#if defined(BOLT_PLUGINS) - CefString id; - bool has_id = false; - CefString path; - bool has_path = false; - CefString main; - bool has_main = false; - uint64_t client; - bool has_client = false; - bool client_valid = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { - PQCEFSTRING(id) - PQCEFSTRING(path) - PQCEFSTRING(main) - PQINT(client) - }); - QREQPARAM(id); - QREQPARAM(main); - QREQPARAMINT(client); - std::string sid = id; - std::string spath; - if (has_path) { - spath = path; - } else { - std::filesystem::path p = this->data_dir; - p.append("plugins"); - p.append(sid); - spath = p.string() + '/'; - } - this->client->StartPlugin(client, sid, spath, std::string(main)); - QSENDOK(); -#else - QSENDNOTSUPPORTED(); -#endif - } - - // request to stop a plugin that's currently running for a specific client - if (path == "/stop-plugin") { -#if defined(BOLT_PLUGINS) - uint64_t client; - bool has_client = false; - bool client_valid = false; - uint64_t uid; - bool has_uid = false; - bool uid_valid = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { - PQINT(client) - PQINT(uid) - }); - QREQPARAMINT(client); - QREQPARAMINT(uid); - this->client->StopPlugin(client, uid); - QSENDOK(); -#else - QSENDNOTSUPPORTED(); -#endif - } - - // request to unpack a file using libarchive and save the contents in a plugin data directory, - // creating it if it doesn't exist, or overwriting any prior contents if it does - if (path == "/install-plugin") { -#if defined(HAS_LIBARCHIVE) && defined(BOLT_PLUGINS) - CefRefPtr post_data = request->GetPostData(); - QSENDBADREQUESTIF(!post_data || post_data->GetElementCount() != 1); - - QSTRING id; - bool has_id = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { - PQSTRING(id) - }); - QREQPARAM(id); - std::filesystem::path plugin_dir = this->data_dir; - plugin_dir.append("plugins"); - plugin_dir.append(id); - std::error_code err; - QSENDSYSTEMERRORIF(std::filesystem::remove_all(plugin_dir, err) == -1); - QSENDSYSTEMERRORIF(!std::filesystem::create_directories(plugin_dir, err)); - - CefPostData::ElementVector elements; - post_data->GetElements(elements); - size_t file_length = elements[0]->GetBytesCount(); - char* archive_file = new char[file_length]; - elements[0]->GetBytes(file_length, archive_file); - - struct archive* archive = archive_read_new(); - archive_read_support_format_all(archive); - archive_read_support_filter_all(archive); - archive_read_open_memory(archive, archive_file, file_length); - - char buf[65536]; - struct archive_entry* entry; - while (true) { - const int r = archive_read_next_header(archive, &entry); - if (r == ARCHIVE_EOF) break; - if (r != ARCHIVE_OK) { - archive_read_close(archive); - archive_read_free(archive); - delete[] archive_file; - QSENDSTR("file is malformed", 400); - } - std::filesystem::path p = plugin_dir; - const char* pathname = archive_entry_pathname(entry); - if (pathname[0] == '\0' || pathname[strlen(pathname) - 1] == '/') continue; - p.append(pathname); - std::filesystem::create_directories(p.parent_path(), err); - std::ofstream ofs(p, std::ofstream::binary); - if (ofs.fail()) { - archive_read_close(archive); - archive_read_free(archive); - delete[] archive_file; - QSENDSTR("failed to create files", 500); - } - - const la_int64_t entry_size = archive_entry_size(entry); - la_int64_t written = 0; - while (written < entry_size) { - const la_ssize_t w = archive_read_data(archive, buf, sizeof(buf)); - ofs.write(buf, w); - written += w; - } - ofs.close(); - } - - archive_read_close(archive); - archive_read_free(archive); - delete[] archive_file; - QSENDOK(); -#else - QSENDNOTSUPPORTED(); -#endif - } - - // gets the bolt.json from a plugin data directory by id - if (path == "/get-plugindir-json") { - QSTRING id; - bool has_id = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { - PQSTRING(id) - }); - QREQPARAM(id); - std::filesystem::path plugin_dir = this->data_dir; - plugin_dir.append("plugins"); - plugin_dir.append(id); - plugin_dir.append("bolt.json"); - std::ifstream file(plugin_dir, std::ios::in | std::ios::binary); - if (!file.fail()) { - std::stringstream ss; - ss << file.rdbuf(); - std::string str = ss.str(); - file.close(); - return new Browser::ResourceHandler(str, 200, "application/json"); - } else { - QSENDNOTFOUND(); - } - } - - // instruction to try to open an external URL in the user's browser - if (path == "/open-external-url") { - CefRefPtr post_data = request->GetPostData(); - QSENDBADREQUESTIF(!post_data || post_data->GetElementCount() != 1); - CefPostData::ElementVector elements; - post_data->GetElements(elements); - size_t byte_count = elements[0]->GetBytesCount(); - char* url = new char[byte_count + 1]; - elements[0]->GetBytes(byte_count, url); - url[byte_count] = '\0'; - this->OpenExternalUrl(url); - delete[] url; - QSENDOK(); - } - - // instruction to try to open Bolt's data directory in the user's file explorer - if (path == "/browse-data") { - if (!this->BrowseData()) { - QSENDSTR("Error creating process", 500); - } - QSENDOK(); - } - - // instruction to open a file picker for .jar files - if (path == "/jar-file-picker") { - return new FilePicker(browser, {".jar"}); - } - - // instruction to open a file picker for .json files - if (path == "/json-file-picker") { - return new FilePicker(browser, {".json"}); - } - - // instruction to close the window which sent this request - if (path == "/close") { - browser->GetHost()->CloseBrowser(false); - QSENDOK(); - } + ROUTE("launch-rs3-deb", LaunchRs3Deb) + ROUTE("launch-rs3-exe", LaunchRs3Exe) + ROUTE("launch-rs3-app", LaunchRs3App) + ROUTE("launch-osrs-exe", LaunchOsrsExe) + ROUTE("launch-osrs-app", LaunchOsrsApp) + ROUTE("launch-runelite-jar", LaunchRuneliteJarNormal) + ROUTE("launch-runelite-jar-configure", LaunchRuneliteJarConfigure) + ROUTE("launch-hdos-jar", LaunchHdosJar) + ROUTE("save-config", SaveConfig) + ROUTE("save-credentials", SaveCredentials) + ROUTE("open-external-url", OpenExternalUrl) + ROUTE("browse-data", BrowseData) + ROUTE("jar-file-picker", JarFilePicker) + ROUTE("json-file-picker", JsonFilePicker) + ROUTE("close", Close) + ROUTEIFPLUGINS("save-plugin-config", SavePluginConfig) + ROUTEIFPLUGINS("list-game-clients", ListGameClients) + ROUTEIFPLUGINS("read-json-file", ReadJsonFile) + ROUTEIFPLUGINS("start-plugin", StartPlugin) + ROUTEIFPLUGINS("stop-plugin", StopPlugin) + ROUTEIFPLUGINS("install-plugin", InstallPlugin) + ROUTEIFPLUGINS("get-plugindir-json", GetPluginDirJson) } // internal hashmap of filenames - allowed to fetch these either if the request is from an internal origin, @@ -641,7 +377,227 @@ CefString Browser::Launcher::BuildURL() const { return url.str(); } +CefRefPtr Browser::Launcher::LaunchRuneliteJarNormal(CefRefPtr request, std::string_view query) { + return this->LaunchRuneliteJar(request, query, false); +} + +CefRefPtr Browser::Launcher::LaunchRuneliteJarConfigure(CefRefPtr request, std::string_view query) { + return this->LaunchRuneliteJar(request, query, true); +} + +CefRefPtr Browser::Launcher::SaveConfig(CefRefPtr request, std::string_view _) { + return SaveFileFromPost(request, this->config_path.c_str()); +} + +CefRefPtr Browser::Launcher::SaveCredentials(CefRefPtr request, std::string_view _) { + return SaveFileFromPost(request, this->creds_path.c_str()); +} + +CefRefPtr Browser::Launcher::OpenExternalUrl(CefRefPtr request, std::string_view _) { + CefRefPtr post_data = request->GetPostData(); + QSENDBADREQUESTIF(!post_data || post_data->GetElementCount() != 1); + CefPostData::ElementVector elements; + post_data->GetElements(elements); + size_t byte_count = elements[0]->GetBytesCount(); + char* url = new char[byte_count + 1]; + elements[0]->GetBytes(byte_count, url); + url[byte_count] = '\0'; + this->OpenExternalUrl(url); + delete[] url; + QSENDOK(); +} + +CefRefPtr Browser::Launcher::BrowseData(CefRefPtr request, std::string_view query) { + if (BrowseFile(this->data_dir)) { + QSENDSTR("Error creating process", 500); + } + QSENDOK(); +} + +CefRefPtr Browser::Launcher::JarFilePicker(CefRefPtr request, std::string_view query) { + return new FilePicker(browser, {".jar"}); +} + +CefRefPtr Browser::Launcher::JsonFilePicker(CefRefPtr request, std::string_view query) { + return new FilePicker(browser, {".json"}); +} + +CefRefPtr Browser::Launcher::Close(CefRefPtr request, std::string_view query) { + browser->GetHost()->CloseBrowser(false); + QSENDOK(); +} + + #if defined(BOLT_PLUGINS) +CefRefPtr Browser::Launcher::SavePluginConfig(CefRefPtr request, std::string_view _) { + return SaveFileFromPost(request, this->plugin_config_path.c_str()); +} + +CefRefPtr Browser::Launcher::ListGameClients(CefRefPtr request, std::string_view _) { + this->UpdateClientList(true); + QSENDOK(); +} + +CefRefPtr Browser::Launcher::ReadJsonFile(CefRefPtr request, std::string_view query) { + QSTRING path; + bool has_path = false; + ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQSTRING(path) + }); + QREQPARAM(path); + std::ifstream file(path, std::ios::in | std::ios::binary); + if (!file.fail()) { + std::stringstream ss; + ss << file.rdbuf(); + std::string str = ss.str(); + file.close(); + return new Browser::ResourceHandler(str, 200, "application/json"); + } else { + QSENDNOTFOUND(); + } +} + +CefRefPtr Browser::Launcher::StartPlugin(CefRefPtr request, std::string_view query) { + CefString id; + bool has_id = false; + CefString path; + bool has_path = false; + CefString main; + bool has_main = false; + uint64_t client; + bool has_client = false; + bool client_valid = false; + ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQCEFSTRING(id) + PQCEFSTRING(path) + PQCEFSTRING(main) + PQINT(client) + }); + QREQPARAM(id); + QREQPARAM(main); + QREQPARAMINT(client); + std::string sid = id; + std::string spath; + if (has_path) { + spath = path; + } else { + std::filesystem::path p = this->data_dir; + p.append("plugins"); + p.append(sid); + spath = p.string() + '/'; + } + this->client->StartPlugin(client, sid, spath, std::string(main)); + QSENDOK(); +} + +CefRefPtr Browser::Launcher::StopPlugin(CefRefPtr request, std::string_view query) { + uint64_t client; + bool has_client = false; + bool client_valid = false; + uint64_t uid; + bool has_uid = false; + bool uid_valid = false; + ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQINT(client) + PQINT(uid) + }); + QREQPARAMINT(client); + QREQPARAMINT(uid); + this->client->StopPlugin(client, uid); + QSENDOK(); +} + +CefRefPtr Browser::Launcher::InstallPlugin(CefRefPtr request, std::string_view query) { + CefRefPtr post_data = request->GetPostData(); + QSENDBADREQUESTIF(!post_data || post_data->GetElementCount() != 1); + + QSTRING id; + bool has_id = false; + ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQSTRING(id) + }); + QREQPARAM(id); + std::filesystem::path plugin_dir = this->data_dir; + plugin_dir.append("plugins"); + plugin_dir.append(id); + std::error_code err; + QSENDSYSTEMERRORIF(std::filesystem::remove_all(plugin_dir, err) == -1); + QSENDSYSTEMERRORIF(!std::filesystem::create_directories(plugin_dir, err)); + + CefPostData::ElementVector elements; + post_data->GetElements(elements); + size_t file_length = elements[0]->GetBytesCount(); + char* archive_file = new char[file_length]; + elements[0]->GetBytes(file_length, archive_file); + + struct archive* archive = archive_read_new(); + archive_read_support_format_all(archive); + archive_read_support_filter_all(archive); + archive_read_open_memory(archive, archive_file, file_length); + + char buf[65536]; + struct archive_entry* entry; + while (true) { + const int r = archive_read_next_header(archive, &entry); + if (r == ARCHIVE_EOF) break; + if (r != ARCHIVE_OK) { + archive_read_close(archive); + archive_read_free(archive); + delete[] archive_file; + QSENDSTR("file is malformed", 400); + } + std::filesystem::path p = plugin_dir; + const char* pathname = archive_entry_pathname(entry); + if (pathname[0] == '\0' || pathname[strlen(pathname) - 1] == '/') continue; + p.append(pathname); + std::filesystem::create_directories(p.parent_path(), err); + std::ofstream ofs(p, std::ofstream::binary); + if (ofs.fail()) { + archive_read_close(archive); + archive_read_free(archive); + delete[] archive_file; + QSENDSTR("failed to create files", 500); + } + + const la_int64_t entry_size = archive_entry_size(entry); + la_int64_t written = 0; + while (written < entry_size) { + const la_ssize_t w = archive_read_data(archive, buf, sizeof(buf)); + ofs.write(buf, w); + written += w; + } + ofs.close(); + } + + archive_read_close(archive); + archive_read_free(archive); + delete[] archive_file; + QSENDOK(); +} + +CefRefPtr Browser::Launcher::GetPluginDirJson(CefRefPtr request, std::string_view query) { + QSTRING id; + bool has_id = false; + ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQSTRING(id) + }); + QREQPARAM(id); + std::filesystem::path plugin_dir = this->data_dir; + plugin_dir.append("plugins"); + plugin_dir.append(id); + plugin_dir.append("bolt.json"); + std::ifstream file(plugin_dir, std::ios::in | std::ios::binary); + if (!file.fail()) { + std::stringstream ss; + ss << file.rdbuf(); + std::string str = ss.str(); + file.close(); + return new Browser::ResourceHandler(str, 200, "application/json"); + } else { + QSENDNOTFOUND(); + } +} + void Browser::Launcher::UpdateClientList(bool need_lock_mutex) const { CefRefPtr message = CefProcessMessage::Create("__bolt_clientlist"); CefRefPtr list = message->GetArgumentList(); diff --git a/src/browser/window_launcher.hxx b/src/browser/window_launcher.hxx index 39fa65c..6faad5a 100644 --- a/src/browser/window_launcher.hxx +++ b/src/browser/window_launcher.hxx @@ -27,10 +27,6 @@ namespace Browser { /// Attempts to open the given URL externally in the user's browser void OpenExternalUrl(char* url) const; - /// Attempts to open Bolt's data directory externally in the user's file explorer. - /// Returns true on success, false on failure. - bool BrowseData() const; - /// Builds and returns the URL for the launcher to open, including reading config files and /// inserting their contents into the query params CefString BuildURL() const; @@ -53,9 +49,29 @@ namespace Browser { CefRefPtr LaunchRs3App(CefRefPtr, std::string_view); CefRefPtr LaunchOsrsExe(CefRefPtr, std::string_view); CefRefPtr LaunchOsrsApp(CefRefPtr, std::string_view); + CefRefPtr LaunchRuneliteJarNormal(CefRefPtr, std::string_view); + CefRefPtr LaunchRuneliteJarConfigure(CefRefPtr, std::string_view); CefRefPtr LaunchRuneliteJar(CefRefPtr, std::string_view, bool configure); CefRefPtr LaunchHdosJar(CefRefPtr, std::string_view); + CefRefPtr SaveConfig(CefRefPtr, std::string_view); + CefRefPtr SaveCredentials(CefRefPtr, std::string_view); + CefRefPtr OpenExternalUrl(CefRefPtr, std::string_view); + CefRefPtr BrowseData(CefRefPtr, std::string_view); + CefRefPtr JarFilePicker(CefRefPtr, std::string_view); + CefRefPtr JsonFilePicker(CefRefPtr, std::string_view); + CefRefPtr Close(CefRefPtr, std::string_view); + +#if defined(BOLT_PLUGINS) + CefRefPtr SavePluginConfig(CefRefPtr, std::string_view); + CefRefPtr ListGameClients(CefRefPtr, std::string_view); + CefRefPtr ReadJsonFile(CefRefPtr, std::string_view); + CefRefPtr StartPlugin(CefRefPtr, std::string_view); + CefRefPtr StopPlugin(CefRefPtr, std::string_view); + CefRefPtr InstallPlugin(CefRefPtr, std::string_view); + CefRefPtr GetPluginDirJson(CefRefPtr, std::string_view); +#endif + private: CefRefPtr file_manager; std::filesystem::path data_dir; @@ -81,4 +97,7 @@ namespace Browser { }; } +CefRefPtr SaveFileFromPost(CefRefPtr, const std::filesystem::path::value_type*); +bool BrowseFile(std::filesystem::path&); + #endif diff --git a/src/browser/window_launcher_posix.cxx b/src/browser/window_launcher_posix.cxx index 99e6efb..c24df9e 100644 --- a/src/browser/window_launcher_posix.cxx +++ b/src/browser/window_launcher_posix.cxx @@ -493,11 +493,11 @@ void Browser::Launcher::OpenExternalUrl(char* url) const { if (pid == 0) execv(arg_env, argv); } -bool Browser::Launcher::BrowseData() const { +bool BrowseFile(std::filesystem::path& dir) { char arg_env[] = "/usr/bin/env"; char arg_xdg_open[] = "xdg-open"; - std::string dir = this->data_dir; - char* argv[] { arg_env, arg_xdg_open, dir.data(), nullptr }; + std::string p = dir; + char* argv[] { arg_env, arg_xdg_open, p.data(), nullptr }; pid_t pid = fork(); if (pid == 0) execv(arg_env, argv); return pid > 0; diff --git a/src/browser/window_launcher_win.cxx b/src/browser/window_launcher_win.cxx index fb0c155..ac88f63 100644 --- a/src/browser/window_launcher_win.cxx +++ b/src/browser/window_launcher_win.cxx @@ -541,10 +541,10 @@ void Browser::Launcher::OpenExternalUrl(char* u) const { delete[] buf; } -bool Browser::Launcher::BrowseData() const { +bool Browser::Launcher::BrowseFile(std::filesystem::path& dir) const { // MSDN: "If [ShellExecuteW] succeeds, it returns a value greater than 32." - const uintptr_t ret = reinterpret_cast(ShellExecuteW(NULL, L"explore", this->data_dir.c_str(), NULL, NULL, SW_SHOW)); + const uintptr_t ret = reinterpret_cast(ShellExecuteW(NULL, L"explore", dir.c_str(), NULL, NULL, SW_SHOW)); if (ret > 32) return true; - fmt::println(stderr, "BrowseData failed: ShellExecuteW returned {}", ret); + fmt::println(stderr, "BrowseFile failed: ShellExecuteW returned {}", ret); return false; }