input buffer
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
#include "HttpControlServer.h"
|
||||
|
||||
#include "../json/JsonWriter.h"
|
||||
#include "../logging/Logger.h"
|
||||
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
@@ -27,22 +23,6 @@ bool InitializeWinsock(std::string& error)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsKnownPostEndpoint(const std::string& path)
|
||||
{
|
||||
return path == "/api/layers/add"
|
||||
|| path == "/api/layers/remove"
|
||||
|| path == "/api/layers/move"
|
||||
|| path == "/api/layers/reorder"
|
||||
|| path == "/api/layers/set-bypass"
|
||||
|| path == "/api/layers/set-shader"
|
||||
|| path == "/api/layers/update-parameter"
|
||||
|| path == "/api/layers/reset-parameters"
|
||||
|| path == "/api/stack-presets/save"
|
||||
|| path == "/api/stack-presets/load"
|
||||
|| path == "/api/reload"
|
||||
|| path == "/api/screenshot";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UniqueSocket::UniqueSocket(SOCKET socket) :
|
||||
@@ -260,179 +240,6 @@ HttpControlServer::HttpResponse HttpControlServer::RouteRequest(const HttpReques
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeGet(const HttpRequest& request) const
|
||||
{
|
||||
if (request.path == "/api/state")
|
||||
return JsonResponse("200 OK", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
|
||||
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
|
||||
return ServeOpenApiSpec();
|
||||
if (request.path == "/docs" || request.path == "/docs/")
|
||||
return ServeSwaggerDocs();
|
||||
if (request.path == "/" || request.path == "/index.html")
|
||||
return ServeUiAsset("index.html");
|
||||
if (request.path.rfind("/assets/", 0) == 0)
|
||||
return ServeUiAsset(request.path.substr(1));
|
||||
if (request.path.size() > 1)
|
||||
{
|
||||
const HttpResponse asset = ServeUiAsset(request.path.substr(1));
|
||||
if (asset.status != "404 Not Found")
|
||||
return asset;
|
||||
}
|
||||
return ServeUiAsset("index.html");
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest& request) const
|
||||
{
|
||||
if (!IsKnownPostEndpoint(request.path))
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
|
||||
if (mCallbacks.executePost)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.executePost(request.path, request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
if (request.path == "/api/layers/add" && mCallbacks.addLayer)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.addLayer(request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
if (request.path == "/api/layers/remove" && mCallbacks.removeLayer)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.removeLayer(request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
return {
|
||||
"400 Bad Request",
|
||||
"application/json",
|
||||
ActionResponse(false, "Endpoint is not implemented in RenderCadenceCompositor yet.")
|
||||
};
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeOpenApiSpec() const
|
||||
{
|
||||
const std::filesystem::path path = mDocsRoot / "openapi.yaml";
|
||||
const std::string body = LoadTextFile(path);
|
||||
return body.empty()
|
||||
? TextResponse("404 Not Found", "OpenAPI spec not found")
|
||||
: HttpResponse{ "200 OK", GuessContentType(path), body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeSwaggerDocs() const
|
||||
{
|
||||
std::ostringstream html;
|
||||
html << "<!doctype html>\n"
|
||||
<< "<html><head><meta charset=\"utf-8\"><title>Video Shader Toys API Docs</title>\n"
|
||||
<< "<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\"></head>\n"
|
||||
<< "<body><div id=\"swagger-ui\"></div>\n"
|
||||
<< "<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n"
|
||||
<< "<script>SwaggerUIBundle({url:'/docs/openapi.yaml',dom_id:'#swagger-ui'});</script>\n"
|
||||
<< "</body></html>\n";
|
||||
return { "200 OK", "text/html", html.str() };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeUiAsset(const std::string& relativePath) const
|
||||
{
|
||||
if (mUiRoot.empty())
|
||||
return TextResponse("404 Not Found", "UI root is not configured");
|
||||
|
||||
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
|
||||
if (!IsSafeRelativePath(sanitizedPath))
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
|
||||
const std::filesystem::path path = mUiRoot / sanitizedPath;
|
||||
const std::string body = LoadTextFile(path);
|
||||
if (body.empty())
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
return { "200 OK", GuessContentType(path), body };
|
||||
}
|
||||
|
||||
std::string HttpControlServer::LoadTextFile(const std::filesystem::path& path) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
return std::string();
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::JsonResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "application/json", body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::TextResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "text/plain", body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::HtmlResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "text/html", body };
|
||||
}
|
||||
|
||||
std::string HttpControlServer::ActionResponse(bool ok, const std::string& error)
|
||||
{
|
||||
JsonWriter writer;
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("ok", ok);
|
||||
if (!error.empty())
|
||||
writer.KeyString("error", error);
|
||||
writer.EndObject();
|
||||
return writer.StringValue();
|
||||
}
|
||||
|
||||
std::string HttpControlServer::GuessContentType(const std::filesystem::path& path)
|
||||
{
|
||||
const std::string extension = ToLower(path.extension().string());
|
||||
if (extension == ".yaml" || extension == ".yml")
|
||||
return "application/yaml";
|
||||
if (extension == ".json")
|
||||
return "application/json";
|
||||
if (extension == ".js" || extension == ".mjs")
|
||||
return "text/javascript";
|
||||
if (extension == ".css")
|
||||
return "text/css";
|
||||
if (extension == ".html" || extension == ".htm")
|
||||
return "text/html";
|
||||
if (extension == ".svg")
|
||||
return "image/svg+xml";
|
||||
if (extension == ".png")
|
||||
return "image/png";
|
||||
if (extension == ".jpg" || extension == ".jpeg")
|
||||
return "image/jpeg";
|
||||
if (extension == ".ico")
|
||||
return "image/x-icon";
|
||||
if (extension == ".map")
|
||||
return "application/json";
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool HttpControlServer::IsSafeRelativePath(const std::filesystem::path& path)
|
||||
{
|
||||
if (path.empty() || path.is_absolute())
|
||||
return false;
|
||||
|
||||
for (const std::filesystem::path& part : path)
|
||||
{
|
||||
if (part == "..")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HttpControlServer::ToLower(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char character) {
|
||||
return static_cast<char>(std::tolower(character));
|
||||
});
|
||||
return text;
|
||||
}
|
||||
|
||||
bool HttpControlServer::ParseHttpRequest(const std::string& rawRequest, HttpRequest& request)
|
||||
{
|
||||
const std::size_t requestLineEnd = rawRequest.find("\r\n");
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
#include "HttpControlServer.h"
|
||||
|
||||
#include "../json/JsonWriter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool IsKnownPostEndpoint(const std::string& path)
|
||||
{
|
||||
return path == "/api/layers/add"
|
||||
|| path == "/api/layers/remove"
|
||||
|| path == "/api/layers/move"
|
||||
|| path == "/api/layers/reorder"
|
||||
|| path == "/api/layers/set-bypass"
|
||||
|| path == "/api/layers/set-shader"
|
||||
|| path == "/api/layers/update-parameter"
|
||||
|| path == "/api/layers/reset-parameters"
|
||||
|| path == "/api/stack-presets/save"
|
||||
|| path == "/api/stack-presets/load"
|
||||
|| path == "/api/reload"
|
||||
|| path == "/api/screenshot";
|
||||
}
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeGet(const HttpRequest& request) const
|
||||
{
|
||||
if (request.path == "/api/state")
|
||||
return JsonResponse("200 OK", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
|
||||
if (request.path == "/openapi.yaml" || request.path == "/docs/openapi.yaml")
|
||||
return ServeOpenApiSpec();
|
||||
if (request.path == "/docs" || request.path == "/docs/")
|
||||
return ServeSwaggerDocs();
|
||||
if (request.path == "/" || request.path == "/index.html")
|
||||
return ServeUiAsset("index.html");
|
||||
if (request.path.rfind("/assets/", 0) == 0)
|
||||
return ServeUiAsset(request.path.substr(1));
|
||||
if (request.path.size() > 1)
|
||||
{
|
||||
const HttpResponse asset = ServeUiAsset(request.path.substr(1));
|
||||
if (asset.status != "404 Not Found")
|
||||
return asset;
|
||||
}
|
||||
return ServeUiAsset("index.html");
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest& request) const
|
||||
{
|
||||
if (!IsKnownPostEndpoint(request.path))
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
|
||||
if (mCallbacks.executePost)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.executePost(request.path, request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
if (request.path == "/api/layers/add" && mCallbacks.addLayer)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.addLayer(request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
if (request.path == "/api/layers/remove" && mCallbacks.removeLayer)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.removeLayer(request.body);
|
||||
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||
}
|
||||
|
||||
return {
|
||||
"400 Bad Request",
|
||||
"application/json",
|
||||
ActionResponse(false, "Endpoint is not implemented in RenderCadenceCompositor yet.")
|
||||
};
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeOpenApiSpec() const
|
||||
{
|
||||
const std::filesystem::path path = mDocsRoot / "openapi.yaml";
|
||||
const std::string body = LoadTextFile(path);
|
||||
return body.empty()
|
||||
? TextResponse("404 Not Found", "OpenAPI spec not found")
|
||||
: HttpResponse{ "200 OK", GuessContentType(path), body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeSwaggerDocs() const
|
||||
{
|
||||
std::ostringstream html;
|
||||
html << "<!doctype html>\n"
|
||||
<< "<html><head><meta charset=\"utf-8\"><title>Video Shader Toys API Docs</title>\n"
|
||||
<< "<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\"></head>\n"
|
||||
<< "<body><div id=\"swagger-ui\"></div>\n"
|
||||
<< "<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\"></script>\n"
|
||||
<< "<script>SwaggerUIBundle({url:'/docs/openapi.yaml',dom_id:'#swagger-ui'});</script>\n"
|
||||
<< "</body></html>\n";
|
||||
return { "200 OK", "text/html", html.str() };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::ServeUiAsset(const std::string& relativePath) const
|
||||
{
|
||||
if (mUiRoot.empty())
|
||||
return TextResponse("404 Not Found", "UI root is not configured");
|
||||
|
||||
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
|
||||
if (!IsSafeRelativePath(sanitizedPath))
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
|
||||
const std::filesystem::path path = mUiRoot / sanitizedPath;
|
||||
const std::string body = LoadTextFile(path);
|
||||
if (body.empty())
|
||||
return TextResponse("404 Not Found", "Not Found");
|
||||
return { "200 OK", GuessContentType(path), body };
|
||||
}
|
||||
|
||||
std::string HttpControlServer::LoadTextFile(const std::filesystem::path& path) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
return std::string();
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::JsonResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "application/json", body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::TextResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "text/plain", body };
|
||||
}
|
||||
|
||||
HttpControlServer::HttpResponse HttpControlServer::HtmlResponse(const std::string& status, const std::string& body)
|
||||
{
|
||||
return { status, "text/html", body };
|
||||
}
|
||||
|
||||
std::string HttpControlServer::ActionResponse(bool ok, const std::string& error)
|
||||
{
|
||||
JsonWriter writer;
|
||||
writer.BeginObject();
|
||||
writer.KeyBool("ok", ok);
|
||||
if (!error.empty())
|
||||
writer.KeyString("error", error);
|
||||
writer.EndObject();
|
||||
return writer.StringValue();
|
||||
}
|
||||
|
||||
std::string HttpControlServer::GuessContentType(const std::filesystem::path& path)
|
||||
{
|
||||
const std::string extension = ToLower(path.extension().string());
|
||||
if (extension == ".yaml" || extension == ".yml")
|
||||
return "application/yaml";
|
||||
if (extension == ".json")
|
||||
return "application/json";
|
||||
if (extension == ".js" || extension == ".mjs")
|
||||
return "text/javascript";
|
||||
if (extension == ".css")
|
||||
return "text/css";
|
||||
if (extension == ".html" || extension == ".htm")
|
||||
return "text/html";
|
||||
if (extension == ".svg")
|
||||
return "image/svg+xml";
|
||||
if (extension == ".png")
|
||||
return "image/png";
|
||||
if (extension == ".jpg" || extension == ".jpeg")
|
||||
return "image/jpeg";
|
||||
if (extension == ".ico")
|
||||
return "image/x-icon";
|
||||
if (extension == ".map")
|
||||
return "application/json";
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool HttpControlServer::IsSafeRelativePath(const std::filesystem::path& path)
|
||||
{
|
||||
if (path.empty() || path.is_absolute())
|
||||
return false;
|
||||
|
||||
for (const std::filesystem::path& part : path)
|
||||
{
|
||||
if (part == "..")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HttpControlServer::ToLower(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char character) {
|
||||
return static_cast<char>(std::tolower(character));
|
||||
});
|
||||
return text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user