Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"scripts": {
"build": "vite build --debug",
"build-clean": "vite build --debug --emptyOutDir",
"dev": "vite build --watch",
"dev": "vite",
"serve": "serve ./tests/fixtures/http --no-port-switching"
},
"dependencies": {
"@lizardbyte/shared-web": "2025.326.11214",
"@popperjs/core": "^2.11.8",
"vue": "3.5.13",
"vue-i18n": "11.1.3"
"vue-i18n": "11.1.3",
"vue-router": "^4.5.1"
Comment thread
TheElixZammuto marked this conversation as resolved.
Outdated
},
"devDependencies": {
"@codecov/vite-plugin": "1.9.0",
Expand Down
204 changes: 45 additions & 159 deletions src/confighttp.cpp
Comment thread
TheElixZammuto marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,19 @@ namespace confighttp {

// If credentials are shown, redirect the user to a /welcome page
if (config::sunshine.username.empty()) {
if (request->path == "/welcome") {
return true;
}
send_redirect(response, request, "/welcome");
return false;
}

// Redirect after /welcome to /
if (request->path == "/welcome") {
send_redirect(response, request, "/");
return false;
}

auto fg = util::fail_guard([&]() {
send_unauthorized(response, request);
});
Expand Down Expand Up @@ -225,191 +234,76 @@ namespace confighttp {
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "index.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}

/**
* @brief Get the PIN page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getPinPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "pin.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}

/**
* @brief Get the apps page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getAppsPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
if (request->path.starts_with("/api")) {
return not_found(response, request);
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "apps.html");
std::string content = file_handler::read_file(WEB_DIR "index.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
response->write(content, headers);
}

/**
* @brief Get the clients page.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @brief Check if a path is a child of another path.
* @param base The base path.
* @param query The path to check.
* @return True if the path is a child of the base path, false otherwise.
*/
void getClientsPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "clients.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
bool isChildPath(fs::path const &base, fs::path const &query) {
auto relPath = fs::relative(base, query);
return *(relPath.begin()) != fs::path("..");
}

/**
* @brief Get the configuration page.
* @brief Get an asset from the node_modules directory.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getConfigPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
return;
}

void getNodeModules(resp_https_t response, req_https_t request) {
print_req(request);
fs::path webDirPath(WEB_DIR);
fs::path nodeModulesPath(webDirPath / "assets");

std::string content = file_handler::read_file(WEB_DIR "config.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}
// .relative_path is needed to shed any leading slash that might exist in the request path
auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path());

/**
* @brief Get the password page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getPasswordPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
// Don't do anything if file does not exist or is outside the assets directory
if (!isChildPath(filePath, nodeModulesPath)) {
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the assets folder";
bad_request(response, request);
return;
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "password.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}

/**
* @brief Get the welcome page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getWelcomePage(resp_https_t response, req_https_t request) {
print_req(request);
if (!config::sunshine.username.empty()) {
send_redirect(response, request, "/");
if (!fs::exists(filePath)) {
not_found(response, request);
return;
}
std::string content = file_handler::read_file(WEB_DIR "welcome.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}

/**
* @brief Get the troubleshooting page.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getTroubleshootingPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
auto relPath = fs::relative(filePath, webDirPath);
// get the mime type from the file extension mime_types map
// remove the leading period from the extension
auto mimeType = mime_types.find(relPath.extension().string().substr(1));
// check if the extension is in the map at the x position
if (mimeType == mime_types.end()) {
bad_request(response, request);
return;
}

print_req(request);

std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
response->write(content, headers);
}

/**
* @brief Get the favicon image.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @todo combine function with getSunshineLogoImage and possibly getNodeModules
* @todo use mime_types map
*/
void getFaviconImage(resp_https_t response, req_https_t request) {
print_req(request);

std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary);
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "image/x-icon");
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}

/**
* @brief Get the Sunshine logo image.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @todo combine function with getFaviconImage and possibly getNodeModules
* @todo use mime_types map
*/
void getSunshineLogoImage(resp_https_t response, req_https_t request) {
print_req(request);

std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary);
// if it is, set the content type to the mime type
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "image/png");
headers.emplace("Content-Type", mimeType->second);
std::ifstream in(filePath.string(), std::ios::binary);
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}

/**
* @brief Check if a path is a child of another path.
* @param base The base path.
* @param query The path to check.
* @return True if the path is a child of the base path, false otherwise.
*/
bool isChildPath(fs::path const &base, fs::path const &query) {
auto relPath = fs::relative(base, query);
return *(relPath.begin()) != fs::path("..");
}

/**
* @brief Get an asset from the node_modules directory.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
void getNodeModules(resp_https_t response, req_https_t request) {
void getImages(resp_https_t response, req_https_t request) {
print_req(request);
fs::path webDirPath(WEB_DIR);
fs::path nodeModulesPath(webDirPath / "assets");
fs::path nodeModulesPath(webDirPath / "images");

// .relative_path is needed to shed any leading slash that might exist in the request path
auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path());
Expand Down Expand Up @@ -1092,15 +986,8 @@ namespace confighttp {
server.default_resource["PUT"] = [](resp_https_t response, req_https_t request) {
bad_request(response, request);
};
server.default_resource["GET"] = not_found;
server.default_resource["GET"] = getIndexPage;
server.resource["^/$"]["GET"] = getIndexPage;
server.resource["^/pin/?$"]["GET"] = getPinPage;
server.resource["^/apps/?$"]["GET"] = getAppsPage;
server.resource["^/clients/?$"]["GET"] = getClientsPage;
server.resource["^/config/?$"]["GET"] = getConfigPage;
server.resource["^/password/?$"]["GET"] = getPasswordPage;
server.resource["^/welcome/?$"]["GET"] = getWelcomePage;
server.resource["^/troubleshooting/?$"]["GET"] = getTroubleshootingPage;
server.resource["^/api/pin$"]["POST"] = savePin;
server.resource["^/api/apps$"]["GET"] = getApps;
server.resource["^/api/logs$"]["GET"] = getLogs;
Expand All @@ -1117,9 +1004,8 @@ namespace confighttp {
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
server.resource["^/api/apps/close$"]["POST"] = closeApp;
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
server.resource["^/assets\\/.+$"]["GET"] = getNodeModules;
server.resource["^/images\\/.+$"]["GET"] = getImages;
server.config.reuse_address = true;
server.config.address = net::af_to_any_address_string(address_family);
server.config.port = port_https;
Expand Down
Loading