mirror of
https://github.com/Adamcake/Bolt.git
synced 2026-04-23 10:26:53 -04:00
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.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -5,10 +5,20 @@
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
Browser::Window::Window(Kind kind, CefRefPtr<CefClient> 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<CefClient> 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<uintptr_t>(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<uintptr_t>(this));
|
||||
}
|
||||
|
||||
void Browser::Window::Init(CefRefPtr<CefClient> 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<CefClient> 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<uintptr_t>(this));
|
||||
}
|
||||
|
||||
void Browser::Window::OnWindowCreated(CefRefPtr<CefWindow> window) {
|
||||
@@ -113,7 +116,7 @@ CefRefPtr<CefBrowserViewDelegate> 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<CefBrowser> browser) const {
|
||||
if (this->browser->IsSame(browser)) {
|
||||
return true;
|
||||
|
||||
@@ -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<CefClient> client, Details, CefString, bool);
|
||||
Window(Kind, Details, bool);
|
||||
/// Calls this->Init internally
|
||||
Window(CefRefPtr<CefClient> 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<CefClient> client, Details, CefString, bool);
|
||||
|
||||
/// Returns true if the given browser is this window or one of its children, otherwise false
|
||||
bool HasBrowser(CefRefPtr<CefBrowser>) const;
|
||||
@@ -79,7 +72,6 @@ namespace Browser {
|
||||
) override;
|
||||
|
||||
private:
|
||||
Kind kind;
|
||||
bool show_devtools;
|
||||
Details details;
|
||||
CefRefPtr<CefWindow> window;
|
||||
|
||||
@@ -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 <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#endif
|
||||
#include "include/cef_life_span_handler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <spawn.h>
|
||||
|
||||
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<CefRequest>, bool& handle_request, CefRefPtr<CefCallback>) override {
|
||||
handle_request = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetResponseHeaders(CefRefPtr<CefResponse> 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<CefResourceReadCallback>) 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<CefResourceSkipCallback>) 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<CefResourceHandler> GetResourceHandler(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, CefRefPtr<CefRequest>) 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<Browser::App> 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<Browser::App> 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<CefLifeSpanHandler> 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<Browser::Window> w = new Browser::Window(Browser::Kind::Launcher, this, details, url, this->show_devtools);
|
||||
CefRefPtr<Browser::Window> 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<CefResourceRequestHandler> 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<std::mutex> _(this->windows_lock);
|
||||
auto it = std::find_if(
|
||||
this->windows.begin(),
|
||||
this->windows.end(),
|
||||
@@ -247,388 +133,7 @@ CefRefPtr<CefResourceRequestHandler> 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<Browser::Window> 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<const unsigned char*>(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<const unsigned char*>(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<CefPostData> 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<CefPostData> post_data = request->GetPostData();
|
||||
if (post_data == nullptr || post_data->GetElementCount() != 1) {
|
||||
const char* data = "Bad Request";
|
||||
return new ResourceHandler(reinterpret_cast<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(data), strlen(data), 500, "text/plain");
|
||||
}
|
||||
} else {
|
||||
// no matching internal page - 404
|
||||
const char* data = "Not Found\n";
|
||||
return new ResourceHandler(reinterpret_cast<const unsigned char*>(data), strlen(data), 404, "text/plain");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// route the request normally, to a website or whatever
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -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<std::string, InternalFile> internal_pages;
|
||||
#if defined(__linux__)
|
||||
std::string launcher_uri = "index.html?platform=linux";
|
||||
|
||||
56
src/browser/resource_handler.cxx
Normal file
56
src/browser/resource_handler.cxx
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "resource_handler.hxx"
|
||||
|
||||
bool Browser::ResourceHandler::Open(CefRefPtr<CefRequest>, bool& handle_request, CefRefPtr<CefCallback>) {
|
||||
handle_request = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Browser::ResourceHandler::GetResponseHeaders(CefRefPtr<CefResponse> 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<CefResourceReadCallback>) {
|
||||
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<CefResourceSkipCallback>) {
|
||||
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<CefResourceHandler> Browser::ResourceHandler::GetResourceHandler(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, CefRefPtr<CefRequest>) {
|
||||
return this;
|
||||
}
|
||||
37
src/browser/resource_handler.hxx
Normal file
37
src/browser/resource_handler.hxx
Normal file
@@ -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<CefRequest>, bool&, CefRefPtr<CefCallback>) override;
|
||||
void GetResponseHeaders(CefRefPtr<CefResponse>, int64&, CefString&) override;
|
||||
bool Read(void*, int, int&, CefRefPtr<CefResourceReadCallback>) override;
|
||||
bool Skip(int64, int64&, CefRefPtr<CefResourceSkipCallback>) override;
|
||||
void Cancel() override;
|
||||
CefRefPtr<CefResourceHandler> GetResourceHandler(CefRefPtr<CefBrowser>, CefRefPtr<CefFrame>, CefRefPtr<CefRequest>) 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
|
||||
}
|
||||
1
src/browser/window_launcher.cxx
Normal file
1
src/browser/window_launcher.cxx
Normal file
@@ -0,0 +1 @@
|
||||
#include "window_launcher.hxx"
|
||||
39
src/browser/window_launcher.hxx
Normal file
39
src/browser/window_launcher.hxx
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef _BOLT_WINDOW_LAUNCHER_HXX_
|
||||
#define _BOLT_WINDOW_LAUNCHER_HXX_
|
||||
|
||||
#include "../browser.hxx"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Browser {
|
||||
struct Launcher: public Window {
|
||||
Launcher(CefRefPtr<CefClient>, Details, bool, const std::map<std::string, InternalFile>* const, std::filesystem::path);
|
||||
|
||||
CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler(
|
||||
CefRefPtr<CefBrowser>,
|
||||
CefRefPtr<CefFrame>,
|
||||
CefRefPtr<CefRequest>,
|
||||
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<std::string, InternalFile>* 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
|
||||
432
src/browser/window_launcher_linux.cxx
Normal file
432
src/browser/window_launcher_linux.cxx
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "window_launcher.hxx"
|
||||
#include "resource_handler.hxx"
|
||||
|
||||
#include "include/cef_parser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <spawn.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
Browser::Launcher::Launcher(
|
||||
CefRefPtr<CefClient> client,
|
||||
Details details,
|
||||
bool show_devtools,
|
||||
const std::map<std::string, InternalFile>* 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<CefResourceRequestHandler> Browser::Launcher::GetResourceRequestHandler(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefFrame> frame,
|
||||
CefRefPtr<CefRequest> 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<const unsigned char*>(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<const unsigned char*>(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<CefPostData> 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<CefPostData> post_data = request->GetPostData();
|
||||
if (post_data == nullptr || post_data->GetElementCount() != 1) {
|
||||
const char* data = "Bad Request";
|
||||
return new ResourceHandler(reinterpret_cast<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(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<const unsigned char*>(data), strlen(data), 500, "text/plain");
|
||||
}
|
||||
} else {
|
||||
// no matching internal page - 404
|
||||
const char* data = "Not Found\n";
|
||||
return new ResourceHandler(reinterpret_cast<const unsigned char*>(data), strlen(data), 404, "text/plain");
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user