From 536ad19ec358f74fabd7542abc1bf75da73bf008 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 16 Jul 2023 05:35:09 +0100 Subject: [PATCH] various: rework resource handlers CefResourceRequestHandlers are now per-window using polymorphism. This allows us to have way more maintainable handlers for specific internal URLs and per-OS implementations, too. --- CMakeLists.txt | 7 +- src/browser.cxx | 31 +- src/browser.hxx | 20 +- src/browser/client.cxx | 507 +------------------------- src/browser/client.hxx | 8 - src/browser/resource_handler.cxx | 56 +++ src/browser/resource_handler.hxx | 37 ++ src/browser/window_launcher.cxx | 1 + src/browser/window_launcher.hxx | 39 ++ src/browser/window_launcher_linux.cxx | 432 ++++++++++++++++++++++ 10 files changed, 596 insertions(+), 542 deletions(-) create mode 100644 src/browser/resource_handler.cxx create mode 100644 src/browser/resource_handler.hxx create mode 100644 src/browser/window_launcher.cxx create mode 100644 src/browser/window_launcher.hxx create mode 100644 src/browser/window_launcher_linux.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index e53ab82..19015d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,8 +23,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CEF_ROOT}/cmake") find_package(CEF REQUIRED) add_subdirectory(${CEF_LIBCEF_DLL_WRAPPER_PATH} libcef_dll_wrapper) +set(WINDOW_LAUNCHER_OS_SPECIFIC src/browser/window_launcher_linux.cxx) + # This line needs to be updated manually with any new/deleted object files; cmake discourages GLOBbing source files -add_executable(bolt modules/fmt/src/format.cc src/main.cxx src/browser.cxx src/browser/app.cxx src/browser/client.cxx) +add_executable(bolt + modules/fmt/src/format.cc src/main.cxx src/browser.cxx src/browser/app.cxx src/browser/client.cxx + src/browser/resource_handler.cxx src/browser/window_launcher.cxx ${WINDOW_LAUNCHER_OS_SPECIFIC} +) # Various build properties target_include_directories(bolt PUBLIC ${CEF_ROOT} modules/fmt/include) diff --git a/src/browser.cxx b/src/browser.cxx index 946f75a..0f11ff4 100644 --- a/src/browser.cxx +++ b/src/browser.cxx @@ -5,10 +5,20 @@ #include -Browser::Window::Window(Kind kind, CefRefPtr client, Browser::Details details, CefString url, bool show_devtools): - kind(kind), show_devtools(show_devtools), details(details), window(nullptr), browser_view(nullptr), browser(nullptr), pending_child(nullptr) +Browser::Window::Window(CefRefPtr client, Browser::Details details, CefString url, bool show_devtools): + show_devtools(show_devtools), details(details), window(nullptr), browser_view(nullptr), browser(nullptr), pending_child(nullptr) { fmt::print("[B] Browser::Window constructor, this={}\n", reinterpret_cast(this)); + this->Init(client, details, url, show_devtools); +} + +Browser::Window::Window(Browser::Details details, bool show_devtools): + show_devtools(show_devtools), details(details), window(nullptr), browser_view(nullptr), browser(nullptr), pending_child(nullptr) +{ + fmt::print("[B] Browser::Window popup constructor, this={}\n", reinterpret_cast(this)); +} + +void Browser::Window::Init(CefRefPtr client, Browser::Details details, CefString url, bool show_devtools) { CefBrowserSettings browser_settings; browser_settings.background_color = CefColorSetARGB(0, 0, 0, 0); if (details.controls_overlay) { @@ -19,13 +29,6 @@ Browser::Window::Window(Kind kind, CefRefPtr client, Browser::Details this->browser_view = CefBrowserView::CreateBrowserView(client, url, browser_settings, nullptr, nullptr, this); } CefWindow::CreateTopLevelWindow(this); - -} - -Browser::Window::Window(Kind kind, Browser::Details details, bool show_devtools): - kind(kind), show_devtools(show_devtools), details(details), window(nullptr), browser_view(nullptr), browser(nullptr), pending_child(nullptr) -{ - fmt::print("[B] Browser::Window popup constructor, this={}\n", reinterpret_cast(this)); } void Browser::Window::OnWindowCreated(CefRefPtr window) { @@ -113,7 +116,7 @@ CefRefPtr Browser::Window::GetDelegateForPopupBrowserVie .controls_overlay = false, .is_devtools = is_devtools, }; - this->pending_child = new Browser::Window(this->kind, details, this->show_devtools); + this->pending_child = new Browser::Window(details, this->show_devtools); return this->pending_child; } @@ -157,14 +160,6 @@ cef_chrome_toolbar_type_t Browser::Window::GetChromeToolbarType() { return CEF_CTT_NONE; } -bool Browser::Window::IsLauncher() const { - return this->kind == Kind::Launcher; -} - -bool Browser::Window::IsApp() const { - return this->kind == Kind::Applet; -} - bool Browser::Window::HasBrowser(CefRefPtr browser) const { if (this->browser->IsSame(browser)) { return true; diff --git a/src/browser.hxx b/src/browser.hxx index d987dbf..ccb462f 100644 --- a/src/browser.hxx +++ b/src/browser.hxx @@ -7,13 +7,6 @@ #include "include/views/cef_browser_view.h" namespace Browser { - /// The purpose of a browser. Affects what kind of requests the browser is allowed to make and - /// how they're handled. Children and devtools will have the same Kind as the parent. - enum Kind { - Launcher, - Applet, - }; - /// Represents a visible browser window on the user's screen. This struct wraps a single pointer, /// so it is safe to store anywhere and move around during operation. /// The window will exist either until the user closes it or Window::CloseBrowser() is called. @@ -24,14 +17,14 @@ namespace Browser { /// https://github.com/chromiumembedded/cef/blob/5735/include/views/cef_browser_view_delegate.h /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_request_handler.h struct Window: CefWindowDelegate, CefBrowserViewDelegate, CefRequestHandler { - Window(Kind, CefRefPtr client, Details, CefString, bool); - Window(Kind, Details, bool); + /// Calls this->Init internally + Window(CefRefPtr client, Details, CefString, bool); - /// Returns true if this window is Kind::Launcher - bool IsLauncher() const; + /// Does not call this->Init internally + Window(Details, bool); - /// Returns true if this window is Kind::App - bool IsApp() const; + /// Initialise with a browser_view. Should be called from a constructor, if at all. + void Init(CefRefPtr client, Details, CefString, bool); /// Returns true if the given browser is this window or one of its children, otherwise false bool HasBrowser(CefRefPtr) const; @@ -79,7 +72,6 @@ namespace Browser { ) override; private: - Kind kind; bool show_devtools; Details details; CefRefPtr window; diff --git a/src/browser/client.cxx b/src/browser/client.cxx index 28d8699..83db447 100644 --- a/src/browser/client.cxx +++ b/src/browser/client.cxx @@ -1,98 +1,14 @@ #include "client.hxx" -#include "include/cef_parser.h" -#include "include/cef_life_span_handler.h" -#include "include/internal/cef_types.h" -#include "include/internal/cef_types_wrappers.h" +#include "window_launcher.hxx" -#if defined(__linux__) -#include -#include -#endif +#include "include/cef_life_span_handler.h" #include #include #include #include -#include #include -extern char **environ; - -/// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_request_handler.h -/// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_handler.h -struct ResourceHandler: public CefResourceRequestHandler, CefResourceHandler { - ResourceHandler(const unsigned char* data, size_t len, int status, CefString mime): - data(data), data_len(len), status(status), mime(mime), has_location(false), cursor(0) { } - ResourceHandler(const unsigned char* data, size_t len, int status, CefString mime, CefString location): - data(data), data_len(len), status(status), mime(mime), location(location), has_location(true), cursor(0) { } - - bool Open(CefRefPtr, bool& handle_request, CefRefPtr) override { - handle_request = true; - return true; - } - - void GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) override { - response->SetStatus(this->status); - response->SetMimeType(this->mime); - if (this->has_location) { - response->SetHeaderByName("Location", this->location, false); - } - response_length = this->data_len; - } - - bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr) override { - if (this->cursor == this->data_len) { - // "To indicate response completion set |bytes_read| to 0 and return false." - bytes_read = 0; - return false; - } - if (this->cursor + bytes_to_read <= this->data_len) { - // requested read is entirely in bounds - bytes_read = bytes_to_read; - memcpy(data_out, this->data + this->cursor, bytes_read); - this->cursor += bytes_to_read; - } else { - // read only to end of string - bytes_read = this->data_len - this->cursor; - memcpy(data_out, this->data + this->cursor, bytes_read); - this->cursor = this->data_len; - } - return true; - } - - bool Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr) override { - if (this->cursor + bytes_to_skip <= this->data_len) { - // skip in bounds - bytes_skipped = bytes_to_skip; - this->cursor += bytes_to_skip; - } else { - // skip to end of string - bytes_skipped = this->data_len - this->cursor; - this->cursor = this->data_len; - } - return true; - } - - void Cancel() override { - this->cursor = this->data_len; - } - - CefRefPtr GetResourceHandler(CefRefPtr, CefRefPtr, CefRefPtr) override { - return this; - } - - private: - const unsigned char* data; - size_t data_len; - int status; - CefString mime; - CefString location; - bool has_location; - size_t cursor; - IMPLEMENT_REFCOUNTING(ResourceHandler); - DISALLOW_COPY_AND_ASSIGN(ResourceHandler); -}; - Browser::Client::Client(CefRefPtr app, std::filesystem::path data_dir): show_devtools(true), data_dir(data_dir) { CefString mime_type_html = "text/html"; CefString mime_type_js = "application/javascript"; @@ -101,12 +17,6 @@ Browser::Client::Client(CefRefPtr app, std::filesystem::path data_ this->internal_pages["/oauth.html"] = InternalFile("html/oauth.html", mime_type_html); this->internal_pages["/game_auth.html"] = InternalFile("html/game_auth.html", mime_type_html); this->internal_pages["/frame.html"] = InternalFile("html/frame.html", mime_type_html); - this->env_count = 0; - char** env = environ; - while (*env != nullptr) { - this->env_count += 1; - env += 1; - } } CefRefPtr Browser::Client::GetLifeSpanHandler() { @@ -129,25 +39,9 @@ void Browser::Client::OnContextInitialized() { .frame = true, .controls_overlay = false, }; - std::string url = this->internal_url + this->launcher_uri; - -#if defined(__linux__) - std::filesystem::path hash_path = this->data_dir; - hash_path.append("rs3linux.sha256"); - int file = open(hash_path.c_str(), O_RDONLY); - if (file != -1) { - char buf[64]; - ssize_t r = read(file, buf, 64); - if (r == 64) { - url += "&rs3_linux_installed_hash="; - url.append(buf, 64); - } - } - close(file); -#endif this->windows_lock.lock(); - CefRefPtr w = new Browser::Window(Browser::Kind::Launcher, this, details, url, this->show_devtools); + CefRefPtr w = new Browser::Launcher(this, details, this->show_devtools, &this->internal_pages, this->data_dir); this->windows.push_back(w); this->windows_lock.unlock(); } @@ -229,16 +123,8 @@ CefRefPtr Browser::Client::GetResourceRequestHandler( const CefString& request_initiator, bool& disable_default_handling ) { - // Some of the dreaded phrases that I don't want to show up in a grep/search - constexpr char provider[] = {106, 97, 103, 101, 120, 0}; - constexpr char tar_xz_inner_path[] = { - 46, 47, 117, 115, 114, 47, 115, 104, 97, 114, 101, 47, 103, 97, 109, 101, - 115, 47, 114, 117, 110, 101, 115, 99, 97, 112, 101, 45, 108, 97, 117, - 110, 99, 104, 101, 114, 47, 114, 117, 110, 101, 115, 99,97, 112, 101 - }; - // Find the Window responsible for this request, if any - this->windows_lock.lock(); + std::lock_guard _(this->windows_lock); auto it = std::find_if( this->windows.begin(), this->windows.end(), @@ -247,388 +133,7 @@ CefRefPtr Browser::Client::GetResourceRequestHandler( if (it == this->windows.end()) { // Request came from no window? return nullptr; + } else { + return (*it)->GetResourceRequestHandler(browser, frame, request, is_navigation, is_download, request_initiator, disable_default_handling); } - const CefRefPtr window = *it; - this->windows_lock.unlock(); - - if (window->IsApp()) { - // TODO: make Browser::Window a valid request handler for sandboxing purposes - return nullptr; - } - - if (window->IsLauncher()) { - // Parse URL - const std::string request_url = request->GetURL().ToString(); - const std::string::size_type colon = request_url.find_first_of(':'); - if (colon == std::string::npos) { - return nullptr; - } - const std::string_view schema(request_url.begin(), request_url.begin() + colon); - if (schema == provider) { - // parser for custom schema thing - bool has_code = false; - std::string_view code; - bool has_state = false; - std::string_view state; - std::string::size_type cursor = colon + 1; - while (true) { - std::string::size_type next_eq = request_url.find_first_of('=', cursor); - std::string::size_type next_comma = request_url.find_first_of(',', cursor); - if (next_eq == std::string::npos || next_comma < next_eq) return nullptr; - const bool last_pair = next_comma == std::string::npos; - std::string_view key(request_url.begin() + cursor, request_url.begin() + next_eq); - std::string_view value(request_url.begin() + next_eq + 1, last_pair ? request_url.end() : request_url.begin() + next_comma); - if (key == "code") { - code = value; - has_code = true; - } - if (key == "state") { - state = value; - has_state = true; - } - if (last_pair) { - break; - } else { - cursor = next_comma + 1; - } - } - if (has_code && has_state) { - disable_default_handling = true; - const char* data = "Moved\n"; - CefString location = CefString(this->internal_url + "oauth.html?code=" + std::string(code) + "&state=" + std::string(state)); - return new ResourceHandler(reinterpret_cast(data), strlen(data), 302, "text/plain", location); - } else { - return nullptr; - } - } - - if ((schema != "http" && schema != "https") || request_url.size() < colon + 4 - || request_url.at(colon + 1) != '/' || request_url.at(colon + 2) != '/') - { - return nullptr; - } - - // parse all the important parts of the URL as string_views - const std::string::size_type next_hash = request_url.find_first_of('#', colon + 3); - const auto url_end = next_hash == std::string::npos ? request_url.end() : request_url.begin() + next_hash; - const std::string::size_type next_sep = request_url.find_first_of('/', colon + 3); - const std::string::size_type next_question = request_url.find_first_of('?', colon + 3); - const auto domain_end = next_sep == std::string::npos && next_question == std::string::npos ? url_end : request_url.begin() + std::min(next_sep, next_question); - const std::string_view domain(request_url.begin() + colon + 3, domain_end); - const std::string_view path(domain_end, next_question == std::string::npos ? url_end : request_url.begin() + next_question); - const std::string_view query(request_url.begin() + next_question + 1, next_question == std::string::npos ? request_url.begin() + next_question + 1 : url_end); - const std::string_view comment(next_hash == std::string::npos ? request_url.end() : request_url.begin() + next_hash + 1, request_url.end()); - - // handler for another custom request thing, but this one uses localhost, for whatever reason - if (domain == "localhost" && path == "/" && comment.starts_with("code=")) { - disable_default_handling = true; - const char* data = "Moved\n"; - CefString location = CefString(this->internal_url + "game_auth.html?" + std::string(comment)); - return new ResourceHandler(reinterpret_cast(data), strlen(data), 302, "text/plain", location); - } - - // TODO: everything below this is massively unmaintainable and needs splitting up probably into CefResourceRequestHandler impls - - // internal pages - if (domain == "bolt-internal") { - disable_default_handling = true; - -#if defined(__linux__) - // instruction to launch RS3 .deb - if (path == "/launch-deb") { - CefRefPtr post_data = request->GetPostData(); - std::filesystem::path path = this->data_dir; - path.append("rs3linux"); - - auto cursor = 0; - bool has_hash = false; - bool should_set_access_token = false; - bool should_set_refresh_token = false; - bool should_set_session_id = false; - bool should_set_character_id = false; - bool should_set_display_name = false; - std::string hash; - std::string env_access_token; - std::string env_refresh_token; - std::string env_session_id; - std::string env_character_id; - std::string env_display_name; - - while (true) { - const std::string::size_type next_eq = query.find_first_of('=', cursor); - const std::string::size_type next_amp = query.find_first_of('&', cursor); - if (next_eq == std::string::npos) break; - if (next_eq >= next_amp) { - cursor = next_amp + 1; - continue; - } - std::string_view key(query.begin() + cursor, query.begin() + next_eq); - if (key == "hash") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - hash = CefURIDecode(value, true, UU_SPACES).ToString(); - has_hash = true; - } - if (key == "jx_access_token") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - env_access_token = std::string("JX_ACCESS_TOKEN=") + CefURIDecode(value, true, UU_SPACES).ToString(); - should_set_access_token = true; - } - if (key == "jx_refresh_token") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - env_refresh_token = std::string("JX_REFRESH_TOKEN=") + CefURIDecode(value, true, UU_SPACES).ToString(); - should_set_refresh_token = true; - } - if (key == "jx_session_id") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - env_session_id = std::string("JX_SESSION_ID=") + CefURIDecode(value, true, UU_SPACES).ToString(); - should_set_session_id = true; - } - if (key == "jx_character_id") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - env_character_id = std::string("JX_CHARACTER_ID=") + CefURIDecode(value, true, UU_SPACES).ToString(); - should_set_character_id = true; - } - if (key == "jx_display_name") { - auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; - CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); - env_display_name = std::string("JX_DISPLAY_NAME=") + CefURIDecode(value, true, UU_SPACES).ToString(); - should_set_display_name = true; - } - - if (next_amp == std::string::npos) break; - cursor = next_amp + 1; - } - - if (has_hash) { - CefRefPtr post_data = request->GetPostData(); - if (post_data == nullptr || post_data->GetElementCount() != 1) { - const char* data = "Bad Request"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); - } - CefPostData::ElementVector vec; - post_data->GetElements(vec); - size_t deb_size = vec[0]->GetBytesCount(); - unsigned char* deb = new unsigned char[deb_size]; - vec[0]->GetBytes(deb_size, deb); - struct archive* ar = archive_read_new(); - archive_read_support_format_ar(ar); - archive_read_open_memory(ar, deb, deb_size); - bool entry_found = false; - struct archive_entry* entry; - while (true) { - int r = archive_read_next_header(ar, &entry); - if (r == ARCHIVE_EOF) break; - if (r != ARCHIVE_OK) { - archive_read_close(ar); - archive_read_free(ar); - delete[] deb; - const char* data = "Malformed .deb file\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); - } - if (strcmp(archive_entry_pathname(entry), "data.tar.xz") == 0) { - entry_found = true; - break; - } - } - if (!entry_found) { - archive_read_close(ar); - archive_read_free(ar); - delete[] deb; - const char* data = "No data in .deb file\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); - } - - const long entry_size = archive_entry_size(entry); - unsigned char* tar_xz = new unsigned char[entry_size]; - long written = 0; - while (written < entry_size) { - written += archive_read_data(ar, tar_xz + written, entry_size - written); - } - archive_read_close(ar); - archive_read_free(ar); - delete[] deb; - - struct archive* xz = archive_read_new(); - archive_read_support_format_tar(xz); - archive_read_support_filter_xz(xz); - archive_read_open_memory(xz, tar_xz, entry_size); - entry_found = false; - while (true) { - int r = archive_read_next_header(xz, &entry); - if (r == ARCHIVE_EOF) break; - if (r != ARCHIVE_OK) { - archive_read_close(xz); - archive_read_free(xz); - delete[] tar_xz; - const char* data = "Malformed .tar.xz file\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); - } - if (strcmp(archive_entry_pathname(entry), tar_xz_inner_path) == 0) { - entry_found = true; - break; - } - } - if (!entry_found) { - archive_read_close(xz); - archive_read_free(xz); - delete[] tar_xz; - const char* data = "No target executable in .tar.xz file\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); - } - - const long game_size = archive_entry_size(entry); - char* game = new char[game_size]; - written = 0; - while (written < game_size) { - written += archive_read_data(ar, game + written, game_size - written); - } - archive_read_close(xz); - archive_read_free(xz); - delete[] tar_xz; - - written = 0; - int file = open(path.c_str(), O_WRONLY | O_CREAT, 0755); - if (file == -1) { - delete[] game; - const char* data = "Failed to save executable; if the game is already running, close it and try again\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); - } - while (written < game_size) { - written += write(file, game + written, game_size - written); - } - close(file); - delete[] game; - } - - posix_spawn_file_actions_t file_actions; - posix_spawnattr_t attributes; - pid_t pid; - posix_spawn_file_actions_init(&file_actions); - posix_spawnattr_init(&attributes); - posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSID); - std::string path_str(path.c_str()); - char* argv[2]; - argv[0] = path_str.data(); - argv[1] = nullptr; - bool should_set_home = true; - std::string env_home = this->env_key_home + this->data_dir.c_str(); - char** env = new char*[this->env_count + 7]; - size_t i; - for (i = 0; i < this->env_count; i += 1) { - if (should_set_access_token && strncmp(environ[i], this->env_key_access_token.c_str(), this->env_key_access_token.size()) == 0) { - should_set_access_token = false; - env[i] = env_access_token.data(); - } else if (should_set_refresh_token && strncmp(environ[i], this->env_key_refresh_token.c_str(), this->env_key_refresh_token.size()) == 0) { - should_set_refresh_token = false; - env[i] = env_refresh_token.data(); - } else if (should_set_session_id && strncmp(environ[i], this->env_key_session_id.c_str(), this->env_key_session_id.size()) == 0) { - should_set_session_id = false; - env[i] = env_session_id.data(); - } else if (should_set_character_id && strncmp(environ[i], this->env_key_character_id.c_str(), this->env_key_character_id.size()) == 0) { - should_set_character_id = false; - env[i] = env_character_id.data(); - } else if (should_set_display_name && strncmp(environ[i], this->env_key_display_name.c_str(), this->env_key_display_name.size()) == 0) { - should_set_display_name = false; - env[i] = env_display_name.data(); - } else if (strncmp(environ[i], this->env_key_home.c_str(), this->env_key_home.size()) == 0) { - should_set_home = false; - env[i] = env_home.data(); - } else { - env[i] = environ[i]; - } - } - if (should_set_access_token) { - env[i] = env_access_token.data(); - i += 1; - } - if (should_set_refresh_token) { - env[i] = env_refresh_token.data(); - i += 1; - } - if (should_set_session_id) { - env[i] = env_session_id.data(); - i += 1; - } - if (should_set_character_id) { - env[i] = env_character_id.data(); - i += 1; - } - if (should_set_display_name) { - env[i] = env_display_name.data(); - i += 1; - } - if (should_set_home) { - env[i] = env_home.data(); - i += 1; - } - env[i] = nullptr; - - int r = posix_spawn(&pid, path_str.c_str(), &file_actions, &attributes, argv, env); - - posix_spawnattr_destroy(&attributes); - posix_spawn_file_actions_destroy(&file_actions); - delete[] env; - - if (r == 0) { - fmt::print("[B] Successfully spawned game process with pid {}\n", pid); - - if (has_hash) { - size_t written = 0; - std::filesystem::path hash_path(this->data_dir); - hash_path.append("rs3linux.sha256"); - int file = open(hash_path.c_str(), O_WRONLY | O_CREAT, 0644); - if (file == -1) { - const char* data = "OK, but unable to save hash file\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); - } - while (written < hash.size()) { - written += write(file, hash.c_str() + written, hash.size() - written); - } - close(file); - } - - const char* data = "OK\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); - } else { - fmt::print("[B] Error from posix_spawn: {}\n", r); - const char* data = "Error spawning process\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); - } - } -#endif - - // respond using internal hashmap of filenames - auto it = std::find_if( - this->internal_pages.begin(), - this->internal_pages.end(), - [&path](const auto& e) { return e.first == path; } - ); - if (it != this->internal_pages.end()) { - if (it->second.success) { - return new ResourceHandler( - it->second.data.data(), - it->second.data.size(), - 200, - it->second.mime_type - ); - } else { - // this file failed to load during startup for some reason - 500 - const char* data = "Internal Server Error\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); - } - } else { - // no matching internal page - 404 - const char* data = "Not Found\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 404, "text/plain"); - } - } - } - - // route the request normally, to a website or whatever - return nullptr; } diff --git a/src/browser/client.hxx b/src/browser/client.hxx index 5a5d236..709c8b2 100644 --- a/src/browser/client.hxx +++ b/src/browser/client.hxx @@ -71,15 +71,7 @@ namespace Browser { bool show_devtools; std::filesystem::path data_dir; - size_t env_count; - std::string env_key_home = "HOME="; - std::string env_key_access_token = "JX_ACCESS_TOKEN="; - std::string env_key_refresh_token = "JX_REFRESH_TOKEN="; - std::string env_key_session_id = "JX_SESSION_ID="; - std::string env_key_character_id = "JX_CHARACTER_ID="; - std::string env_key_display_name = "JX_DISPLAY_NAME="; - std::string internal_url = "https://bolt-internal/"; std::map internal_pages; #if defined(__linux__) std::string launcher_uri = "index.html?platform=linux"; diff --git a/src/browser/resource_handler.cxx b/src/browser/resource_handler.cxx new file mode 100644 index 0000000..50b7d63 --- /dev/null +++ b/src/browser/resource_handler.cxx @@ -0,0 +1,56 @@ +#include "resource_handler.hxx" + +bool Browser::ResourceHandler::Open(CefRefPtr, bool& handle_request, CefRefPtr) { + handle_request = true; + return true; +} + +void Browser::ResourceHandler::GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) { + response->SetStatus(this->status); + response->SetMimeType(this->mime); + if (this->has_location) { + response->SetHeaderByName("Location", this->location, false); + } + response_length = this->data_len; +} + +bool Browser::ResourceHandler::Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr) { + if (this->cursor == this->data_len) { + // "To indicate response completion set |bytes_read| to 0 and return false." + bytes_read = 0; + return false; + } + if (this->cursor + bytes_to_read <= this->data_len) { + // requested read is entirely in bounds + bytes_read = bytes_to_read; + memcpy(data_out, this->data + this->cursor, bytes_read); + this->cursor += bytes_to_read; + } else { + // read only to end of string + bytes_read = this->data_len - this->cursor; + memcpy(data_out, this->data + this->cursor, bytes_read); + this->cursor = this->data_len; + } + return true; +} + +bool Browser::ResourceHandler::Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr) { + if (this->cursor + bytes_to_skip <= this->data_len) { + // skip in bounds + bytes_skipped = bytes_to_skip; + this->cursor += bytes_to_skip; + } else { + // skip to end of string + bytes_skipped = this->data_len - this->cursor; + this->cursor = this->data_len; + } + return true; +} + +void Browser::ResourceHandler::Cancel() { + this->cursor = this->data_len; +} + +CefRefPtr Browser::ResourceHandler::GetResourceHandler(CefRefPtr, CefRefPtr, CefRefPtr) { + return this; +} diff --git a/src/browser/resource_handler.hxx b/src/browser/resource_handler.hxx new file mode 100644 index 0000000..4680fa6 --- /dev/null +++ b/src/browser/resource_handler.hxx @@ -0,0 +1,37 @@ +#ifndef _BOLT_RESOURCE_HANDLER_HXX_ +#define _BOLT_RESOURCE_HANDLER_HXX_ + +#include "include/cef_base.h" +#include "include/cef_request_handler.h" + +namespace Browser { + /// Struct for sending some bytes from memory as an HTTP response. Store individual instances on the heap. + /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_request_handler.h + /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_handler.h + struct ResourceHandler: public CefResourceRequestHandler, CefResourceHandler { + ResourceHandler(const unsigned char* data, size_t len, int status, CefString mime): + data(data), data_len(len), status(status), mime(mime), has_location(false), cursor(0) { } + ResourceHandler(const unsigned char* data, size_t len, int status, CefString mime, CefString location): + data(data), data_len(len), status(status), mime(mime), location(location), has_location(true), cursor(0) { } + + bool Open(CefRefPtr, bool&, CefRefPtr) override; + void GetResponseHeaders(CefRefPtr, int64&, CefString&) override; + bool Read(void*, int, int&, CefRefPtr) override; + bool Skip(int64, int64&, CefRefPtr) override; + void Cancel() override; + CefRefPtr GetResourceHandler(CefRefPtr, CefRefPtr, CefRefPtr) override; + + private: + const unsigned char* data; + size_t data_len; + int status; + CefString mime; + CefString location; + bool has_location; + size_t cursor; + IMPLEMENT_REFCOUNTING(ResourceHandler); + DISALLOW_COPY_AND_ASSIGN(ResourceHandler); + }; + + #endif +} diff --git a/src/browser/window_launcher.cxx b/src/browser/window_launcher.cxx new file mode 100644 index 0000000..519a3b8 --- /dev/null +++ b/src/browser/window_launcher.cxx @@ -0,0 +1 @@ +#include "window_launcher.hxx" diff --git a/src/browser/window_launcher.hxx b/src/browser/window_launcher.hxx new file mode 100644 index 0000000..f136fe0 --- /dev/null +++ b/src/browser/window_launcher.hxx @@ -0,0 +1,39 @@ +#ifndef _BOLT_WINDOW_LAUNCHER_HXX_ +#define _BOLT_WINDOW_LAUNCHER_HXX_ + +#include "../browser.hxx" + +#include + +namespace Browser { + struct Launcher: public Window { + Launcher(CefRefPtr, Details, bool, const std::map* const, std::filesystem::path); + + CefRefPtr GetResourceRequestHandler( + CefRefPtr, + CefRefPtr, + CefRefPtr, + bool, + bool, + const CefString&, + bool& + ) override; + + private: + std::string env_key_home = "HOME="; + std::string env_key_access_token = "JX_ACCESS_TOKEN="; + std::string env_key_refresh_token = "JX_REFRESH_TOKEN="; + std::string env_key_session_id = "JX_SESSION_ID="; + std::string env_key_character_id = "JX_CHARACTER_ID="; + std::string env_key_display_name = "JX_DISPLAY_NAME="; + std::string internal_url = "https://bolt-internal/"; + const std::map* internal_pages; + std::filesystem::path data_dir; + std::filesystem::path hash_path; + std::filesystem::path rs3_path; + std::filesystem::path rs3_hash_path; + size_t env_count; + }; +} + +#endif diff --git a/src/browser/window_launcher_linux.cxx b/src/browser/window_launcher_linux.cxx new file mode 100644 index 0000000..a474467 --- /dev/null +++ b/src/browser/window_launcher_linux.cxx @@ -0,0 +1,432 @@ +#include "window_launcher.hxx" +#include "resource_handler.hxx" + +#include "include/cef_parser.h" + +#include +#include +#include +#include +#include +#include + +extern char **environ; + +Browser::Launcher::Launcher( + CefRefPtr client, + Details details, + bool show_devtools, + const std::map* const internal_pages, + std::filesystem::path data_dir +): Window(details, show_devtools), data_dir(data_dir), internal_pages(internal_pages) { + std::string url = this->internal_url + std::string("index.html?platform=linux"); + + this->rs3_path = data_dir; + this->rs3_path.append("rs3linux"); + + this->rs3_hash_path = data_dir; + this->rs3_hash_path.append("rs3linux.sha256"); + int file = open(hash_path.c_str(), O_RDONLY); + if (file != -1) { + char buf[64]; + ssize_t r = read(file, buf, 64); + if (r == 64) { + url += "&rs3_linux_installed_hash="; + url.append(buf, 64); + } + } + close(file); + + this->env_count = 0; + char** env = environ; + while (*env != nullptr) { + this->env_count += 1; + env += 1; + } + + this->Init(client, details, url, show_devtools); +} + +CefRefPtr Browser::Launcher::GetResourceRequestHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling +) { + // Some of the dreaded phrases that I don't want to show up in a grep/search + constexpr char provider[] = {106, 97, 103, 101, 120, 0}; + constexpr char tar_xz_inner_path[] = { + 46, 47, 117, 115, 114, 47, 115, 104, 97, 114, 101, 47, 103, 97, 109, 101, + 115, 47, 114, 117, 110, 101, 115, 99, 97, 112, 101, 45, 108, 97, 117, + 110, 99, 104, 101, 114, 47, 114, 117, 110, 101, 115, 99,97, 112, 101 + }; + + // Parse URL + const std::string request_url = request->GetURL().ToString(); + const std::string::size_type colon = request_url.find_first_of(':'); + if (colon == std::string::npos) { + return nullptr; + } + const std::string_view schema(request_url.begin(), request_url.begin() + colon); + if (schema == provider) { + // parser for custom schema thing + bool has_code = false; + std::string_view code; + bool has_state = false; + std::string_view state; + std::string::size_type cursor = colon + 1; + while (true) { + std::string::size_type next_eq = request_url.find_first_of('=', cursor); + std::string::size_type next_comma = request_url.find_first_of(',', cursor); + if (next_eq == std::string::npos || next_comma < next_eq) return nullptr; + const bool last_pair = next_comma == std::string::npos; + std::string_view key(request_url.begin() + cursor, request_url.begin() + next_eq); + std::string_view value(request_url.begin() + next_eq + 1, last_pair ? request_url.end() : request_url.begin() + next_comma); + if (key == "code") { + code = value; + has_code = true; + } + if (key == "state") { + state = value; + has_state = true; + } + if (last_pair) { + break; + } else { + cursor = next_comma + 1; + } + } + if (has_code && has_state) { + disable_default_handling = true; + const char* data = "Moved\n"; + CefString location = CefString(this->internal_url + "oauth.html?code=" + std::string(code) + "&state=" + std::string(state)); + return new ResourceHandler(reinterpret_cast(data), strlen(data), 302, "text/plain", location); + } else { + return nullptr; + } + } + + if ((schema != "http" && schema != "https") || request_url.size() < colon + 4 + || request_url.at(colon + 1) != '/' || request_url.at(colon + 2) != '/') + { + return nullptr; + } + + // parse all the important parts of the URL as string_views + const std::string::size_type next_hash = request_url.find_first_of('#', colon + 3); + const auto url_end = next_hash == std::string::npos ? request_url.end() : request_url.begin() + next_hash; + const std::string::size_type next_sep = request_url.find_first_of('/', colon + 3); + const std::string::size_type next_question = request_url.find_first_of('?', colon + 3); + const auto domain_end = next_sep == std::string::npos && next_question == std::string::npos ? url_end : request_url.begin() + std::min(next_sep, next_question); + const std::string_view domain(request_url.begin() + colon + 3, domain_end); + const std::string_view path(domain_end, next_question == std::string::npos ? url_end : request_url.begin() + next_question); + const std::string_view query(request_url.begin() + next_question + 1, next_question == std::string::npos ? request_url.begin() + next_question + 1 : url_end); + const std::string_view comment(next_hash == std::string::npos ? request_url.end() : request_url.begin() + next_hash + 1, request_url.end()); + + // handler for another custom request thing, but this one uses localhost, for whatever reason + if (domain == "localhost" && path == "/" && comment.starts_with("code=")) { + disable_default_handling = true; + const char* data = "Moved\n"; + CefString location = CefString(this->internal_url + "game_auth.html?" + std::string(comment)); + return new ResourceHandler(reinterpret_cast(data), strlen(data), 302, "text/plain", location); + } + + // internal pages + if (domain == "bolt-internal") { + disable_default_handling = true; + + // instruction to launch RS3 .deb + if (path == "/launch-deb") { + CefRefPtr post_data = request->GetPostData(); + auto cursor = 0; + bool has_hash = false; + bool should_set_access_token = false; + bool should_set_refresh_token = false; + bool should_set_session_id = false; + bool should_set_character_id = false; + bool should_set_display_name = false; + std::string hash; + std::string env_access_token; + std::string env_refresh_token; + std::string env_session_id; + std::string env_character_id; + std::string env_display_name; + + while (true) { + const std::string::size_type next_eq = query.find_first_of('=', cursor); + const std::string::size_type next_amp = query.find_first_of('&', cursor); + if (next_eq == std::string::npos) break; + if (next_eq >= next_amp) { + cursor = next_amp + 1; + continue; + } + std::string_view key(query.begin() + cursor, query.begin() + next_eq); + if (key == "hash") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + hash = CefURIDecode(value, true, UU_SPACES).ToString(); + has_hash = true; + } + if (key == "jx_access_token") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + env_access_token = std::string("JX_ACCESS_TOKEN=") + CefURIDecode(value, true, UU_SPACES).ToString(); + should_set_access_token = true; + } + if (key == "jx_refresh_token") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + env_refresh_token = std::string("JX_REFRESH_TOKEN=") + CefURIDecode(value, true, UU_SPACES).ToString(); + should_set_refresh_token = true; + } + if (key == "jx_session_id") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + env_session_id = std::string("JX_SESSION_ID=") + CefURIDecode(value, true, UU_SPACES).ToString(); + should_set_session_id = true; + } + if (key == "jx_character_id") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + env_character_id = std::string("JX_CHARACTER_ID=") + CefURIDecode(value, true, UU_SPACES).ToString(); + should_set_character_id = true; + } + if (key == "jx_display_name") { + auto value_end = next_amp == std::string::npos ? query.end() : query.begin() + next_amp; + CefString value = std::string(std::string_view(query.begin() + next_eq + 1, value_end)); + env_display_name = std::string("JX_DISPLAY_NAME=") + CefURIDecode(value, true, UU_SPACES).ToString(); + should_set_display_name = true; + } + + if (next_amp == std::string::npos) break; + cursor = next_amp + 1; + } + + if (has_hash) { + CefRefPtr post_data = request->GetPostData(); + if (post_data == nullptr || post_data->GetElementCount() != 1) { + const char* data = "Bad Request"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + CefPostData::ElementVector vec; + post_data->GetElements(vec); + size_t deb_size = vec[0]->GetBytesCount(); + unsigned char* deb = new unsigned char[deb_size]; + vec[0]->GetBytes(deb_size, deb); + struct archive* ar = archive_read_new(); + archive_read_support_format_ar(ar); + archive_read_open_memory(ar, deb, deb_size); + bool entry_found = false; + struct archive_entry* entry; + while (true) { + int r = archive_read_next_header(ar, &entry); + if (r == ARCHIVE_EOF) break; + if (r != ARCHIVE_OK) { + archive_read_close(ar); + archive_read_free(ar); + delete[] deb; + const char* data = "Malformed .deb file\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + if (strcmp(archive_entry_pathname(entry), "data.tar.xz") == 0) { + entry_found = true; + break; + } + } + if (!entry_found) { + archive_read_close(ar); + archive_read_free(ar); + delete[] deb; + const char* data = "No data in .deb file\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + + const long entry_size = archive_entry_size(entry); + unsigned char* tar_xz = new unsigned char[entry_size]; + long written = 0; + while (written < entry_size) { + written += archive_read_data(ar, tar_xz + written, entry_size - written); + } + archive_read_close(ar); + archive_read_free(ar); + delete[] deb; + + struct archive* xz = archive_read_new(); + archive_read_support_format_tar(xz); + archive_read_support_filter_xz(xz); + archive_read_open_memory(xz, tar_xz, entry_size); + entry_found = false; + while (true) { + int r = archive_read_next_header(xz, &entry); + if (r == ARCHIVE_EOF) break; + if (r != ARCHIVE_OK) { + archive_read_close(xz); + archive_read_free(xz); + delete[] tar_xz; + const char* data = "Malformed .tar.xz file\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + if (strcmp(archive_entry_pathname(entry), tar_xz_inner_path) == 0) { + entry_found = true; + break; + } + } + if (!entry_found) { + archive_read_close(xz); + archive_read_free(xz); + delete[] tar_xz; + const char* data = "No target executable in .tar.xz file\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + + const long game_size = archive_entry_size(entry); + char* game = new char[game_size]; + written = 0; + while (written < game_size) { + written += archive_read_data(ar, game + written, game_size - written); + } + archive_read_close(xz); + archive_read_free(xz); + delete[] tar_xz; + + written = 0; + int file = open(this->rs3_path.c_str(), O_WRONLY | O_CREAT, 0755); + if (file == -1) { + delete[] game; + const char* data = "Failed to save executable; if the game is already running, close it and try again\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); + } + while (written < game_size) { + written += write(file, game + written, game_size - written); + } + close(file); + delete[] game; + } + + posix_spawn_file_actions_t file_actions; + posix_spawnattr_t attributes; + pid_t pid; + posix_spawn_file_actions_init(&file_actions); + posix_spawnattr_init(&attributes); + posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSID); + std::string path_str(this->rs3_path.c_str()); + char* argv[2]; + argv[0] = path_str.data(); + argv[1] = nullptr; + bool should_set_home = true; + std::string env_home = this->env_key_home + this->data_dir.c_str(); + char** env = new char*[this->env_count + 7]; + size_t i; + for (i = 0; i < this->env_count; i += 1) { + if (should_set_access_token && strncmp(environ[i], this->env_key_access_token.c_str(), this->env_key_access_token.size()) == 0) { + should_set_access_token = false; + env[i] = env_access_token.data(); + } else if (should_set_refresh_token && strncmp(environ[i], this->env_key_refresh_token.c_str(), this->env_key_refresh_token.size()) == 0) { + should_set_refresh_token = false; + env[i] = env_refresh_token.data(); + } else if (should_set_session_id && strncmp(environ[i], this->env_key_session_id.c_str(), this->env_key_session_id.size()) == 0) { + should_set_session_id = false; + env[i] = env_session_id.data(); + } else if (should_set_character_id && strncmp(environ[i], this->env_key_character_id.c_str(), this->env_key_character_id.size()) == 0) { + should_set_character_id = false; + env[i] = env_character_id.data(); + } else if (should_set_display_name && strncmp(environ[i], this->env_key_display_name.c_str(), this->env_key_display_name.size()) == 0) { + should_set_display_name = false; + env[i] = env_display_name.data(); + } else if (strncmp(environ[i], this->env_key_home.c_str(), this->env_key_home.size()) == 0) { + should_set_home = false; + env[i] = env_home.data(); + } else { + env[i] = environ[i]; + } + } + if (should_set_access_token) { + env[i] = env_access_token.data(); + i += 1; + } + if (should_set_refresh_token) { + env[i] = env_refresh_token.data(); + i += 1; + } + if (should_set_session_id) { + env[i] = env_session_id.data(); + i += 1; + } + if (should_set_character_id) { + env[i] = env_character_id.data(); + i += 1; + } + if (should_set_display_name) { + env[i] = env_display_name.data(); + i += 1; + } + if (should_set_home) { + env[i] = env_home.data(); + i += 1; + } + env[i] = nullptr; + + int r = posix_spawn(&pid, path_str.c_str(), &file_actions, &attributes, argv, env); + + posix_spawnattr_destroy(&attributes); + posix_spawn_file_actions_destroy(&file_actions); + delete[] env; + + if (r == 0) { + fmt::print("[B] Successfully spawned game process with pid {}\n", pid); + + if (has_hash) { + size_t written = 0; + std::filesystem::path hash_path(this->data_dir); + hash_path.append("rs3linux.sha256"); + int file = open(hash_path.c_str(), O_WRONLY | O_CREAT, 0644); + if (file == -1) { + const char* data = "OK, but unable to save hash file\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); + } + while (written < hash.size()) { + written += write(file, hash.c_str() + written, hash.size() - written); + } + close(file); + } + + const char* data = "OK\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); + } else { + fmt::print("[B] Error from posix_spawn: {}\n", r); + const char* data = "Error spawning process\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); + } + } + + // respond using internal hashmap of filenames + auto it = std::find_if( + this->internal_pages->begin(), + this->internal_pages->end(), + [&path](const auto& e) { return e.first == path; } + ); + if (it != this->internal_pages->end()) { + if (it->second.success) { + return new ResourceHandler( + it->second.data.data(), + it->second.data.size(), + 200, + it->second.mime_type + ); + } else { + // this file failed to load during startup for some reason - 500 + const char* data = "Internal Server Error\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); + } + } else { + // no matching internal page - 404 + const char* data = "Not Found\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 404, "text/plain"); + } + } + + return nullptr; +}