mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-08-10 00:52:16 +00:00
fix(confighttp): do not return 200 on errors (#3385)
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
Some checks failed
CI / GitHub Env Debug (push) Has been cancelled
CI / Setup Release (push) Has been cancelled
CI / Setup Flatpak Matrix (push) Has been cancelled
CI Docker / Check Dockerfiles (push) Has been cancelled
CodeQL / Get language matrix (push) Has been cancelled
localize / Update Localization (push) Has been cancelled
Build GH-Pages / prep (push) Has been cancelled
CI / Linux Flatpak (push) Has been cancelled
CI / Linux ${{ matrix.type }} (--appimage-build, 22.04, AppImage) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 13) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (macos, 14) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest) (push) Has been cancelled
CI / Homebrew (${{ matrix.os_name }}-${{ matrix.os_version }}${{ matrix.release == true && ' (Release)' || '' }}) (ubuntu, latest, true) (push) Has been cancelled
CI / Windows (push) Has been cancelled
CI Docker / Setup Release (push) Has been cancelled
CI Docker / Docker${{ matrix.tag }} (push) Has been cancelled
CodeQL / Analyze (${{ matrix.name }}) (push) Has been cancelled
Build GH-Pages / call-jekyll-build (push) Has been cancelled
Co-authored-by: Lukas Senionis <22381748+FrogTheFrog@users.noreply.github.com>
This commit is contained in:
@@ -62,4 +62,8 @@ INPUT = ../README.md \
|
||||
HTML_EXTRA_STYLESHEET += doc-styles.css
|
||||
|
||||
# extra js
|
||||
HTML_EXTRA_FILES += api.js
|
||||
HTML_EXTRA_FILES += configuration.js
|
||||
|
||||
# custom aliases
|
||||
ALIASES += api_examples{3|}="@htmlonly<script>(function() { let examples = generateExamples('\1', '\2', \3); document.write(createTabs(examples)); })();</script>@endhtmlonly"
|
||||
|
||||
130
docs/api.js
Normal file
130
docs/api.js
Normal file
@@ -0,0 +1,130 @@
|
||||
function generateExamples(endpoint, method, body = null) {
|
||||
let curlBodyString = '';
|
||||
let psBodyString = '';
|
||||
|
||||
if (body) {
|
||||
const curlJsonString = JSON.stringify(body).replace(/"/g, '\\"');
|
||||
curlBodyString = ` -d "${curlJsonString}"`;
|
||||
psBodyString = `-Body (ConvertTo-Json ${JSON.stringify(body)})`;
|
||||
}
|
||||
|
||||
return {
|
||||
cURL: `curl -u user:pass -X ${method.trim()} -k https://localhost:47990${endpoint.trim()}${curlBodyString}`,
|
||||
Python: `import json
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
requests.${method.trim().toLowerCase()}(
|
||||
auth=HTTPBasicAuth('user', 'pass'),
|
||||
url='https://localhost:47990${endpoint.trim()}',
|
||||
verify=False,${body ? `\n json=${JSON.stringify(body)},` : ''}
|
||||
).json()`,
|
||||
JavaScript: `fetch('https://localhost:47990${endpoint.trim()}', {
|
||||
method: '${method.trim()}',
|
||||
headers: {
|
||||
'Authorization': 'Basic ' + btoa('user:pass'),
|
||||
'Content-Type': 'application/json',
|
||||
}${body ? `,\n body: JSON.stringify(${JSON.stringify(body)}),` : ''}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data));`,
|
||||
PowerShell: `Invoke-RestMethod \`
|
||||
-SkipCertificateCheck \`
|
||||
-Uri 'https://localhost:47990${endpoint.trim()}' \`
|
||||
-Method ${method.trim()} \`
|
||||
-Headers @{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('user:pass'))}
|
||||
${psBodyString}`
|
||||
};
|
||||
}
|
||||
|
||||
function hashString(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function createTabs(examples) {
|
||||
const languages = Object.keys(examples);
|
||||
let tabs = '<div class="tabs-overview-container"><div class="tabs-overview">';
|
||||
let content = '<div class="tab-content">';
|
||||
|
||||
languages.forEach((lang, index) => {
|
||||
const hash = hashString(examples[lang]);
|
||||
tabs += `<button class="tab-button ${index === 0 ? 'active' : ''}" onclick="openTab(event, '${lang}')"><b class="tab-title" title=" ${lang} "> ${lang} </b></button>`;
|
||||
content += `<div id="${lang}" class="tabcontent" style="display: ${index === 0 ? 'block' : 'none'};">
|
||||
<div class="doxygen-awesome-fragment-wrapper">
|
||||
<div class="fragment">
|
||||
${examples[lang].split('\n').map(line => `<div class="line">${line}</div>`).join('')}
|
||||
</div>
|
||||
<doxygen-awesome-fragment-copy-button id="copy-button-${lang}-${hash}" title="Copy to clipboard">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path>
|
||||
</svg>
|
||||
</doxygen-awesome-fragment-copy-button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
tabs += '</div></div>';
|
||||
content += '</div>';
|
||||
|
||||
setTimeout(() => {
|
||||
languages.forEach((lang, index) => {
|
||||
const hash = hashString(examples[lang]);
|
||||
const copyButton = document.getElementById(`copy-button-${lang}-${hash}`);
|
||||
copyButton.addEventListener('click', copyContent);
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return tabs + content;
|
||||
}
|
||||
|
||||
function copyContent() {
|
||||
const content = this.previousElementSibling.cloneNode(true);
|
||||
if (content instanceof Element) {
|
||||
// filter out line number from file listings
|
||||
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
|
||||
node.remove();
|
||||
});
|
||||
let textContent = Array.from(content.querySelectorAll('.line'))
|
||||
.map(line => line.innerText)
|
||||
.join('\n')
|
||||
.trim(); // Join lines with newline characters and trim leading/trailing whitespace
|
||||
navigator.clipboard.writeText(textContent);
|
||||
this.classList.add("success");
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`;
|
||||
window.setTimeout(() => {
|
||||
this.classList.remove("success");
|
||||
this.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
|
||||
}, 980);
|
||||
} else {
|
||||
console.error('Failed to copy: content is not a DOM element');
|
||||
}
|
||||
}
|
||||
|
||||
function openTab(evt, lang) {
|
||||
const tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (const content of tabcontent) {
|
||||
content.style.display = "none";
|
||||
}
|
||||
|
||||
const tablinks = document.getElementsByClassName("tab-button");
|
||||
for (const link of tablinks) {
|
||||
link.className = link.className.replace(" active", "");
|
||||
}
|
||||
|
||||
const selectedTabs = document.querySelectorAll(`#${lang}`);
|
||||
for (const tab of selectedTabs) {
|
||||
tab.style.display = "block";
|
||||
}
|
||||
|
||||
const selectedButtons = document.querySelectorAll(`.tab-button[onclick*="${lang}"]`);
|
||||
for (const button of selectedButtons) {
|
||||
button.className += " active";
|
||||
}
|
||||
}
|
||||
11
docs/api.md
11
docs/api.md
@@ -5,6 +5,10 @@ Sunshine has a RESTful API which can be used to interact with the service.
|
||||
Unless otherwise specified, authentication is required for all API calls. You can authenticate using
|
||||
basic authentication with the admin username and password.
|
||||
|
||||
@htmlonly
|
||||
<script src="api.js"></script>
|
||||
@endhtmlonly
|
||||
|
||||
## GET /api/apps
|
||||
@copydoc confighttp::getApps()
|
||||
|
||||
@@ -14,7 +18,7 @@ basic authentication with the admin username and password.
|
||||
## POST /api/apps
|
||||
@copydoc confighttp::saveApp()
|
||||
|
||||
## DELETE /api/apps{index}
|
||||
## DELETE /api/apps/{index}
|
||||
@copydoc confighttp::deleteApp()
|
||||
|
||||
## POST /api/covers/upload
|
||||
@@ -32,6 +36,9 @@ basic authentication with the admin username and password.
|
||||
## POST /api/restart
|
||||
@copydoc confighttp::restart()
|
||||
|
||||
## POST /api/reset-display-device-persistence
|
||||
@copydoc confighttp::resetDisplayDevicePersistence()
|
||||
|
||||
## POST /api/password
|
||||
@copydoc confighttp::savePassword()
|
||||
|
||||
@@ -47,7 +54,7 @@ basic authentication with the admin username and password.
|
||||
## GET /api/clients/list
|
||||
@copydoc confighttp::listClients()
|
||||
|
||||
## GET /api/apps/close
|
||||
## POST /api/apps/close
|
||||
@copydoc confighttp::closeApp()
|
||||
|
||||
<div class="section_buttons">
|
||||
|
||||
@@ -58,6 +58,10 @@ namespace confighttp {
|
||||
REMOVE ///< Remove client
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Log the request details.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
print_req(const req_https_t &request) {
|
||||
BOOST_LOG(debug) << "METHOD :: "sv << request->method;
|
||||
@@ -76,6 +80,23 @@ namespace confighttp {
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a response.
|
||||
* @param response The HTTP response object.
|
||||
* @param output_tree The JSON tree to send.
|
||||
*/
|
||||
void
|
||||
send_response(resp_https_t response, const pt::ptree &output_tree) {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, output_tree);
|
||||
response->write(data.str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a 401 Unauthorized response.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
send_unauthorized(resp_https_t response, req_https_t request) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
@@ -86,6 +107,12 @@ namespace confighttp {
|
||||
response->write(SimpleWeb::StatusCode::client_error_unauthorized, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a redirect response.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @param path The path to redirect to.
|
||||
*/
|
||||
void
|
||||
send_redirect(resp_https_t response, req_https_t request, const char *path) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
@@ -96,6 +123,12 @@ namespace confighttp {
|
||||
response->write(SimpleWeb::StatusCode::redirection_temporary_redirect, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Authenticate the user.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @return True if the user is authenticated, false otherwise.
|
||||
*/
|
||||
bool
|
||||
authenticate(resp_https_t response, req_https_t request) {
|
||||
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
|
||||
@@ -142,21 +175,56 @@ namespace confighttp {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a 404 Not Found response.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
not_found(resp_https_t response, req_https_t request) {
|
||||
not_found(resp_https_t response, [[maybe_unused]] req_https_t request) {
|
||||
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_not_found;
|
||||
|
||||
pt::ptree tree;
|
||||
tree.put("root.<xmlattr>.status_code", 404);
|
||||
tree.put("status_code", static_cast<int>(code));
|
||||
tree.put("error", "Not Found");
|
||||
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, tree);
|
||||
|
||||
pt::write_xml(data, tree);
|
||||
response->write(data.str());
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
|
||||
*response << "HTTP/1.1 404 NOT FOUND\r\n"
|
||||
<< data.str();
|
||||
response->write(code, data.str(), headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a 400 Bad Request response.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @param error_message The error message to include in the response.
|
||||
*/
|
||||
void
|
||||
bad_request(resp_https_t response, [[maybe_unused]] req_https_t request, const std::string &error_message = "Bad Request") {
|
||||
constexpr SimpleWeb::StatusCode code = SimpleWeb::StatusCode::client_error_bad_request;
|
||||
|
||||
pt::ptree tree;
|
||||
tree.put("status_code", static_cast<int>(code));
|
||||
tree.put("status", false);
|
||||
tree.put("error", error_message);
|
||||
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, tree);
|
||||
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "application/json");
|
||||
|
||||
response->write(code, data.str(), headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the index page.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps"
|
||||
*/
|
||||
void
|
||||
@@ -171,6 +239,11 @@ namespace confighttp {
|
||||
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;
|
||||
@@ -183,6 +256,11 @@ namespace confighttp {
|
||||
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;
|
||||
@@ -196,6 +274,11 @@ namespace confighttp {
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the clients page.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*/
|
||||
void
|
||||
getClientsPage(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
@@ -208,6 +291,11 @@ namespace confighttp {
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the configuration page.
|
||||
* @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;
|
||||
@@ -220,6 +308,11 @@ namespace confighttp {
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)) return;
|
||||
@@ -232,6 +325,11 @@ namespace confighttp {
|
||||
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);
|
||||
@@ -245,6 +343,11 @@ namespace confighttp {
|
||||
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)) return;
|
||||
@@ -258,6 +361,9 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@@ -272,6 +378,9 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@@ -285,12 +394,23 @@ namespace confighttp {
|
||||
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) {
|
||||
print_req(request);
|
||||
@@ -303,32 +423,37 @@ namespace confighttp {
|
||||
// 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";
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Bad Request");
|
||||
bad_request(response, request);
|
||||
return;
|
||||
}
|
||||
else if (!fs::exists(filePath)) {
|
||||
response->write(SimpleWeb::StatusCode::client_error_not_found);
|
||||
if (!fs::exists(filePath)) {
|
||||
not_found(response, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
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()) {
|
||||
// if it is, set the content type to the mime type
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", mimeType->second);
|
||||
std::ifstream in(filePath.string(), std::ios::binary);
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
// do not return any file if the type is not in the map
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// if it is, set the content type to the mime type
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", mimeType->second);
|
||||
std::ifstream in(filePath.string(), std::ios::binary);
|
||||
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the list of available applications.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/apps| GET| null}
|
||||
*/
|
||||
void
|
||||
getApps(resp_https_t response, req_https_t request) {
|
||||
@@ -346,6 +471,8 @@ namespace confighttp {
|
||||
* @brief Get the logs from the log file.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/logs| GET| null}
|
||||
*/
|
||||
void
|
||||
getLogs(resp_https_t response, req_https_t request) {
|
||||
@@ -360,7 +487,7 @@ namespace confighttp {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Save an application. If the application already exists, it will be updated, otherwise it will be added.
|
||||
* @brief Save an application. To save a new application the index must be `-1`. To update an existing application, you must provide the current index of the application.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
* The body for the post request should be JSON serialized in the following format:
|
||||
@@ -385,9 +512,11 @@ namespace confighttp {
|
||||
* "detached": [
|
||||
* "Detached command"
|
||||
* ],
|
||||
* "image-path": "Full path to the application image. Must be a png file.",
|
||||
* "image-path": "Full path to the application image. Must be a png file."
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/apps| POST| {"name":"Hello, World!","index":-1}}
|
||||
*/
|
||||
void
|
||||
saveApp(resp_https_t response, req_https_t request) {
|
||||
@@ -398,19 +527,12 @@ namespace confighttp {
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
pt::ptree inputTree, fileTree;
|
||||
|
||||
BOOST_LOG(info) << config::stream.file_apps;
|
||||
try {
|
||||
// TODO: Input Validation
|
||||
pt::ptree fileTree;
|
||||
pt::ptree inputTree;
|
||||
pt::ptree outputTree;
|
||||
pt::read_json(ss, inputTree);
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
|
||||
@@ -464,23 +586,23 @@ namespace confighttp {
|
||||
fileTree.add_child("apps", sorted_apps);
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
proc::refresh(config::stream.file_apps);
|
||||
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
|
||||
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Input JSON");
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
|
||||
outputTree.put("status", "true");
|
||||
proc::refresh(config::stream.file_apps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delete an application.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/apps/9999| DELETE| null}
|
||||
*/
|
||||
void
|
||||
deleteApp(resp_https_t response, req_https_t request) {
|
||||
@@ -489,46 +611,46 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
pt::ptree fileTree;
|
||||
try {
|
||||
pt::ptree fileTree;
|
||||
pt::ptree newApps;
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = stoi(request->path_match[1]);
|
||||
|
||||
if (index < 0) {
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Index");
|
||||
if (index < 0 || index >= static_cast<int>(apps_node.size())) {
|
||||
std::string error;
|
||||
if (const int max_index = static_cast<int>(apps_node.size()) - 1; max_index < 0) {
|
||||
error = "No applications to delete";
|
||||
}
|
||||
else {
|
||||
error = "'index' out of range, max index is "s + std::to_string(max_index);
|
||||
}
|
||||
bad_request(response, request, error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &[k, v] : apps_node) {
|
||||
if (i++ != index) {
|
||||
newApps.push_back(std::make_pair("", v));
|
||||
}
|
||||
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
int i = 0;
|
||||
for (const auto &[k, v] : apps_node) {
|
||||
if (i++ != index) {
|
||||
newApps.push_back(std::make_pair("", v));
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
proc::refresh(config::stream.file_apps);
|
||||
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("result", "application "s + std::to_string(index) + " deleted");
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid File JSON");
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
|
||||
outputTree.put("status", "true");
|
||||
proc::refresh(config::stream.file_apps);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,9 +661,11 @@ namespace confighttp {
|
||||
* @code{.json}
|
||||
* {
|
||||
* "key": "igdb_<game_id>",
|
||||
* "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/<slug>.png",
|
||||
* "url": "https://images.igdb.com/igdb/image/upload/t_cover_big_2x/<slug>.png"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/covers/upload| POST| {"key":"igdb_1234","url":"https://images.igdb.com/igdb/image/upload/t_cover_big_2x/abc123.png"}}
|
||||
*/
|
||||
void
|
||||
uploadCover(resp_https_t response, req_https_t request) {
|
||||
@@ -551,31 +675,19 @@ namespace confighttp {
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
SimpleWeb::StatusCode code = SimpleWeb::StatusCode::success_ok;
|
||||
if (outputTree.get_child_optional("error").has_value()) {
|
||||
code = SimpleWeb::StatusCode::client_error_bad_request;
|
||||
}
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(code, data.str());
|
||||
});
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
pt::read_json(ss, inputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "UploadCover: "sv << e.what();
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", e.what());
|
||||
bad_request(response, request, e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = inputTree.get("key", "");
|
||||
if (key.empty()) {
|
||||
outputTree.put("error", "Cover key is required");
|
||||
bad_request(response, request, "Cover key is required");
|
||||
return;
|
||||
}
|
||||
auto url = inputTree.get("url", "");
|
||||
@@ -586,11 +698,11 @@ namespace confighttp {
|
||||
std::basic_string path = coverdir + http::url_escape(key) + ".png";
|
||||
if (!url.empty()) {
|
||||
if (http::url_get_host(url) != "images.igdb.com") {
|
||||
outputTree.put("error", "Only images.igdb.com is allowed");
|
||||
bad_request(response, request, "Only images.igdb.com is allowed");
|
||||
return;
|
||||
}
|
||||
if (!http::download_file(url, path)) {
|
||||
outputTree.put("error", "Failed to download cover");
|
||||
bad_request(response, request, "Failed to download cover");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -600,13 +712,17 @@ namespace confighttp {
|
||||
std::ofstream imgfile(path);
|
||||
imgfile.write(data.data(), (int) data.size());
|
||||
}
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("path", path);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the configuration settings.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/config| GET| null}
|
||||
*/
|
||||
void
|
||||
getConfig(resp_https_t response, req_https_t request) {
|
||||
@@ -615,14 +731,7 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.put("status", "true");
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("platform", SUNSHINE_PLATFORM);
|
||||
outputTree.put("version", PROJECT_VER);
|
||||
|
||||
@@ -631,12 +740,16 @@ namespace confighttp {
|
||||
for (auto &[name, value] : vars) {
|
||||
outputTree.put(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the locale setting. This endpoint does not require authentication.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/configLocale| GET| null}
|
||||
*/
|
||||
void
|
||||
getLocale(resp_https_t response, req_https_t request) {
|
||||
@@ -645,15 +758,9 @@ namespace confighttp {
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.put("status", "true");
|
||||
outputTree.put("status", true);
|
||||
outputTree.put("locale", config::sunshine.locale);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -668,6 +775,8 @@ namespace confighttp {
|
||||
* @endcode
|
||||
*
|
||||
* @attention{It is recommended to ONLY save the config settings that differ from the default behavior.}
|
||||
*
|
||||
* @api_examples{/api/config| POST| {"key":"value"}}
|
||||
*/
|
||||
void
|
||||
saveConfig(resp_https_t response, req_https_t request) {
|
||||
@@ -678,16 +787,10 @@ namespace confighttp {
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
pt::ptree inputTree;
|
||||
try {
|
||||
// TODO: Input Validation
|
||||
pt::ptree inputTree;
|
||||
pt::ptree outputTree;
|
||||
pt::read_json(ss, inputTree);
|
||||
for (const auto &[k, v] : inputTree) {
|
||||
std::string value = inputTree.get<std::string>(k);
|
||||
@@ -696,12 +799,12 @@ namespace confighttp {
|
||||
configStream << k << " = " << value << std::endl;
|
||||
}
|
||||
file_handler::write_file(config::sunshine.config_file.c_str(), configStream.str());
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,6 +812,8 @@ namespace confighttp {
|
||||
* @brief Restart Sunshine.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/restart| POST| null}
|
||||
*/
|
||||
void
|
||||
restart(resp_https_t response, req_https_t request) {
|
||||
@@ -720,6 +825,24 @@ namespace confighttp {
|
||||
platf::restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the display device persistence.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/reset-display-device-persistence| POST| null}
|
||||
*/
|
||||
void
|
||||
resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
outputTree.put("status", display_device::reset_persistence());
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update existing credentials.
|
||||
* @param response The HTTP response object.
|
||||
@@ -734,43 +857,24 @@ namespace confighttp {
|
||||
* "confirmNewPassword": "Confirm New Password"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/password| POST| {"currentUsername":"admin","currentPassword":"admin","newUsername":"admin","newPassword":"admin","confirmNewPassword":"admin"}}
|
||||
*/
|
||||
void
|
||||
resetDisplayDevicePersistence(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&outputTree, &response]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.put("status", display_device::reset_persistence());
|
||||
}
|
||||
|
||||
void
|
||||
savePassword(resp_https_t response, req_https_t request) {
|
||||
if (!config::sunshine.username.empty() && !authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::vector<std::string> errors = {};
|
||||
std::stringstream ss;
|
||||
std::stringstream configStream;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree inputTree, outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
try {
|
||||
// TODO: Input Validation
|
||||
pt::ptree inputTree;
|
||||
pt::ptree outputTree;
|
||||
pt::read_json(ss, inputTree);
|
||||
auto username = inputTree.count("currentUsername") > 0 ? inputTree.get<std::string>("currentUsername") : "";
|
||||
auto newUsername = inputTree.get<std::string>("newUsername");
|
||||
@@ -779,15 +883,13 @@ namespace confighttp {
|
||||
auto confirmPassword = inputTree.count("confirmNewPassword") > 0 ? inputTree.get<std::string>("confirmNewPassword") : "";
|
||||
if (newUsername.length() == 0) newUsername = username;
|
||||
if (newUsername.length() == 0) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Username");
|
||||
errors.emplace_back("Invalid Username");
|
||||
}
|
||||
else {
|
||||
auto hash = util::hex(crypto::hash(password + config::sunshine.salt)).to_string();
|
||||
if (config::sunshine.username.empty() || (boost::iequals(username, config::sunshine.username) && hash == config::sunshine.password)) {
|
||||
if (newPassword.empty() || newPassword != confirmPassword) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Password Mismatch");
|
||||
errors.emplace_back("Password Mismatch");
|
||||
}
|
||||
else {
|
||||
http::save_user_creds(config::sunshine.credentials_file, newUsername, newPassword);
|
||||
@@ -796,16 +898,25 @@ namespace confighttp {
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Invalid Current Credentials");
|
||||
errors.emplace_back("Invalid Current Credentials");
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.empty()) {
|
||||
// join the errors array
|
||||
std::string error = std::accumulate(errors.begin(), errors.end(), std::string(),
|
||||
[](const std::string &a, const std::string &b) {
|
||||
return a.empty() ? b : a + ", " + b;
|
||||
});
|
||||
bad_request(response, request, error);
|
||||
return;
|
||||
}
|
||||
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,6 +931,8 @@ namespace confighttp {
|
||||
* "name": "Friendly Client Name"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/pin| POST| {"pin":"1234","name":"My PC"}}
|
||||
*/
|
||||
void
|
||||
savePin(resp_https_t response, req_https_t request) {
|
||||
@@ -830,26 +943,19 @@ namespace confighttp {
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree inputTree, outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
try {
|
||||
// TODO: Input Validation
|
||||
pt::ptree inputTree;
|
||||
pt::ptree outputTree;
|
||||
pt::read_json(ss, inputTree);
|
||||
std::string pin = inputTree.get<std::string>("pin");
|
||||
std::string name = inputTree.get<std::string>("name");
|
||||
outputTree.put("status", nvhttp::pin(pin, name));
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePin: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,6 +963,8 @@ namespace confighttp {
|
||||
* @brief Unpair all clients.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/clients/unpair-all| POST| null}
|
||||
*/
|
||||
void
|
||||
unpairAll(resp_https_t response, req_https_t request) {
|
||||
@@ -864,16 +972,12 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
nvhttp::erase_all_clients();
|
||||
proc::proc.terminate();
|
||||
|
||||
pt::ptree outputTree;
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -886,6 +990,8 @@ namespace confighttp {
|
||||
* "uuid": "<uuid>"
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @api_examples{/api/unpair| POST| {"uuid":"1234"}}
|
||||
*/
|
||||
void
|
||||
unpair(resp_https_t response, req_https_t request) {
|
||||
@@ -896,25 +1002,18 @@ namespace confighttp {
|
||||
std::stringstream ss;
|
||||
ss << request->content.rdbuf();
|
||||
|
||||
pt::ptree inputTree, outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
try {
|
||||
// TODO: Input Validation
|
||||
pt::ptree inputTree;
|
||||
pt::ptree outputTree;
|
||||
pt::read_json(ss, inputTree);
|
||||
std::string uuid = inputTree.get<std::string>("uuid");
|
||||
outputTree.put("status", nvhttp::unpair_client(uuid));
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Unpair: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
bad_request(response, request, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,6 +1021,8 @@ namespace confighttp {
|
||||
* @brief Get the list of paired clients.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/clients/list| GET| null}
|
||||
*/
|
||||
void
|
||||
listClients(resp_https_t response, req_https_t request) {
|
||||
@@ -929,26 +1030,21 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree named_certs = nvhttp::get_all_clients();
|
||||
const pt::ptree named_certs = nvhttp::get_all_clients();
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
outputTree.put("status", false);
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.add_child("named_certs", named_certs);
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Close the currently running application.
|
||||
* @param response The HTTP response object.
|
||||
* @param request The HTTP request object.
|
||||
*
|
||||
* @api_examples{/api/apps/close| POST| null}
|
||||
*/
|
||||
void
|
||||
closeApp(resp_https_t response, req_https_t request) {
|
||||
@@ -956,16 +1052,11 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
proc::proc.terminate();
|
||||
|
||||
pt::ptree outputTree;
|
||||
outputTree.put("status", true);
|
||||
send_response(response, outputTree);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -976,6 +1067,18 @@ namespace confighttp {
|
||||
auto address_family = net::af_from_enum_string(config::sunshine.address_family);
|
||||
|
||||
https_server_t server { config::nvhttp.cert, config::nvhttp.pkey };
|
||||
server.default_resource["DELETE"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
server.default_resource["PATCH"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
server.default_resource["POST"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
server.default_resource["PUT"] = [](resp_https_t response, req_https_t request) {
|
||||
bad_request(response, request);
|
||||
};
|
||||
server.default_resource["GET"] = not_found;
|
||||
server.resource["^/$"]["GET"] = getIndexPage;
|
||||
server.resource["^/pin/?$"]["GET"] = getPinPage;
|
||||
|
||||
Reference in New Issue
Block a user