Layer stacking
This commit is contained in:
@@ -34,6 +34,38 @@ std::string ToLower(std::string text)
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
return text;
|
||||
}
|
||||
|
||||
bool IsSafeUiPath(const std::filesystem::path& relativePath)
|
||||
{
|
||||
for (const std::filesystem::path& part : relativePath)
|
||||
{
|
||||
if (part == "..")
|
||||
return false;
|
||||
}
|
||||
return !relativePath.empty();
|
||||
}
|
||||
|
||||
std::string GuessContentType(const std::filesystem::path& assetPath)
|
||||
{
|
||||
const std::string extension = ToLower(assetPath.extension().string());
|
||||
if (extension == ".js" || extension == ".mjs")
|
||||
return "text/javascript";
|
||||
if (extension == ".css")
|
||||
return "text/css";
|
||||
if (extension == ".json")
|
||||
return "application/json";
|
||||
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/html";
|
||||
}
|
||||
}
|
||||
|
||||
ControlServer::ControlServer()
|
||||
@@ -204,20 +236,21 @@ bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& re
|
||||
closesocket(clientSocket);
|
||||
return true;
|
||||
}
|
||||
if (path == "/app.js" || path == "/styles.css")
|
||||
{
|
||||
std::string contentType;
|
||||
std::string body = LoadUiAsset(path.substr(1), contentType);
|
||||
SendHttpResponse(clientSocket, "200 OK", contentType, body);
|
||||
closesocket(clientSocket);
|
||||
return true;
|
||||
}
|
||||
if (path == "/api/state")
|
||||
{
|
||||
SendHttpResponse(clientSocket, "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
|
||||
closesocket(clientSocket);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string contentType;
|
||||
std::string body = LoadUiAsset(path.substr(1), contentType);
|
||||
if (!body.empty())
|
||||
{
|
||||
SendHttpResponse(clientSocket, "200 OK", contentType, body);
|
||||
closesocket(clientSocket);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (method == "POST")
|
||||
{
|
||||
@@ -234,36 +267,69 @@ bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& re
|
||||
bool success = false;
|
||||
std::string actionError;
|
||||
|
||||
if (path == "/api/select-shader")
|
||||
if (path == "/api/layers/add")
|
||||
{
|
||||
const JsonValue* shaderId = root.find("shaderId");
|
||||
success = shaderId && mCallbacks.selectShader && mCallbacks.selectShader(shaderId->asString(), actionError);
|
||||
success = shaderId && mCallbacks.addLayer && mCallbacks.addLayer(shaderId->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/update-parameter")
|
||||
else if (path == "/api/layers/remove")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
success = layerId && mCallbacks.removeLayer && mCallbacks.removeLayer(layerId->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/layers/move")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
const JsonValue* direction = root.find("direction");
|
||||
if (layerId && direction && mCallbacks.moveLayer)
|
||||
success = mCallbacks.moveLayer(layerId->asString(), static_cast<int>(direction->asNumber()), actionError);
|
||||
}
|
||||
else if (path == "/api/layers/reorder")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
const JsonValue* targetIndex = root.find("targetIndex");
|
||||
if (layerId && targetIndex && mCallbacks.moveLayerToIndex)
|
||||
success = mCallbacks.moveLayerToIndex(layerId->asString(), static_cast<std::size_t>(targetIndex->asNumber()), actionError);
|
||||
}
|
||||
else if (path == "/api/layers/set-bypass")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
const JsonValue* bypass = root.find("bypass");
|
||||
if (layerId && bypass && mCallbacks.setLayerBypass)
|
||||
success = mCallbacks.setLayerBypass(layerId->asString(), bypass->asBoolean(), actionError);
|
||||
}
|
||||
else if (path == "/api/layers/set-shader")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
const JsonValue* shaderId = root.find("shaderId");
|
||||
if (layerId && shaderId && mCallbacks.setLayerShader)
|
||||
success = mCallbacks.setLayerShader(layerId->asString(), shaderId->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/layers/update-parameter")
|
||||
{
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
const JsonValue* parameterId = root.find("parameterId");
|
||||
const JsonValue* value = root.find("value");
|
||||
if (shaderId && parameterId && value && mCallbacks.updateParameter)
|
||||
success = mCallbacks.updateParameter(shaderId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
|
||||
if (layerId && parameterId && value && mCallbacks.updateLayerParameter)
|
||||
success = mCallbacks.updateLayerParameter(layerId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
|
||||
}
|
||||
else if (path == "/api/reset-parameters")
|
||||
else if (path == "/api/layers/reset-parameters")
|
||||
{
|
||||
const JsonValue* shaderId = root.find("shaderId");
|
||||
if (shaderId && mCallbacks.resetParameters)
|
||||
success = mCallbacks.resetParameters(shaderId->asString(), actionError);
|
||||
const JsonValue* layerId = root.find("layerId");
|
||||
if (layerId && mCallbacks.resetLayerParameters)
|
||||
success = mCallbacks.resetLayerParameters(layerId->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/set-bypass")
|
||||
else if (path == "/api/stack-presets/save")
|
||||
{
|
||||
const JsonValue* bypass = root.find("bypass");
|
||||
if (bypass && mCallbacks.setBypass)
|
||||
success = mCallbacks.setBypass(bypass->asBoolean(), actionError);
|
||||
const JsonValue* presetName = root.find("presetName");
|
||||
if (presetName && mCallbacks.saveStackPreset)
|
||||
success = mCallbacks.saveStackPreset(presetName->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/set-mix")
|
||||
else if (path == "/api/stack-presets/load")
|
||||
{
|
||||
const JsonValue* mixAmount = root.find("mixAmount");
|
||||
if (mixAmount && mCallbacks.setMixAmount)
|
||||
success = mCallbacks.setMixAmount(mixAmount->asNumber(), actionError);
|
||||
const JsonValue* presetName = root.find("presetName");
|
||||
if (presetName && mCallbacks.loadStackPreset)
|
||||
success = mCallbacks.loadStackPreset(presetName->asString(), actionError);
|
||||
}
|
||||
else if (path == "/api/reload")
|
||||
{
|
||||
@@ -357,17 +423,16 @@ void ControlServer::BroadcastStateLocked()
|
||||
|
||||
std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::string& contentType) const
|
||||
{
|
||||
const std::filesystem::path assetPath = mUiRoot / relativePath;
|
||||
const std::filesystem::path sanitizedPath = std::filesystem::path(relativePath).lexically_normal();
|
||||
if (!IsSafeUiPath(sanitizedPath))
|
||||
return std::string();
|
||||
|
||||
const std::filesystem::path assetPath = mUiRoot / sanitizedPath;
|
||||
std::ifstream input(assetPath, std::ios::binary);
|
||||
if (!input)
|
||||
return "<!doctype html><title>Missing UI asset</title><p>UI asset missing.</p>";
|
||||
return std::string();
|
||||
|
||||
if (assetPath.extension() == ".js")
|
||||
contentType = "text/javascript";
|
||||
else if (assetPath.extension() == ".css")
|
||||
contentType = "text/css";
|
||||
else
|
||||
contentType = "text/html";
|
||||
contentType = GuessContentType(assetPath);
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
|
||||
Reference in New Issue
Block a user