Files
SpineParticlesWeb/connector/native/config.cpp
T
2026-09-05 23:35:45 +08:00

121 lines
4.3 KiB
C++

#include "config.hpp"
#include "json.hpp"
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <regex>
#include <stdexcept>
namespace connector {
namespace {
std::string environment(const char* name) {
const char* value = std::getenv(name);
return value ? value : "";
}
std::filesystem::path home_directory() {
#ifdef _WIN32
std::string home = environment("USERPROFILE");
if (home.empty()) home = environment("HOMEDRIVE") + environment("HOMEPATH");
#else
std::string home = environment("HOME");
#endif
return home.empty() ? std::filesystem::current_path() : std::filesystem::u8path(home);
}
bool local_origin(const std::string& origin) {
static const std::vector<std::string> origins = {
"http://127.0.0.1:5173", "http://localhost:5173",
"http://127.0.0.1:4173", "http://localhost:4173"};
return std::find(origins.begin(), origins.end(), origin) != origins.end();
}
} // namespace
std::filesystem::path config_path() {
const std::string override_path = environment("SPINE_CONNECTOR_CONFIG");
if (!override_path.empty()) return std::filesystem::u8path(override_path);
#ifdef _WIN32
std::string app_data = environment("APPDATA");
const auto root = app_data.empty() ? home_directory() : std::filesystem::u8path(app_data);
return root / "SpineParticleConnector" / "config.json";
#elif defined(__APPLE__)
return home_directory() / "Library" / "Application Support" / "SpineParticleConnector" / "config.json";
#else
std::string xdg = environment("XDG_CONFIG_HOME");
const auto root = xdg.empty() ? home_directory() / ".config" : std::filesystem::u8path(xdg);
return root / "spine-particle-connector" / "config.json";
#endif
}
ConnectorConfig load_config() {
ConnectorConfig result;
try {
std::ifstream input(config_path(), std::ios::binary);
if (!input) return result;
const std::string text((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
const Json root = Json::parse(text);
if (const Json* origins = root.find("allowedOrigins"); origins && origins->is_array()) {
for (const Json& item : origins->array()) if (item.is_string()) result.allowed_origins.push_back(item.string());
}
if (const Json* path = root.find("spineExecutable"); path && path->is_string()) {
result.spine_executable = path->string();
}
} catch (...) {
// A damaged preference file must not prevent the connector from starting.
}
return result;
}
void save_config(const ConnectorConfig& config) {
Json::Array origins;
for (const std::string& origin : config.allowed_origins) origins.emplace_back(origin);
Json::Object root;
root.emplace("allowedOrigins", Json(std::move(origins)));
root.emplace("spineExecutable", Json(config.spine_executable));
const auto path = config_path();
std::filesystem::create_directories(path.parent_path());
const auto temporary = path.string() + ".tmp";
{
std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
if (!output) throw std::runtime_error("无法写入连接器配置");
output << Json(std::move(root)).dump(2) << '\n';
}
std::error_code remove_error;
std::filesystem::remove(path, remove_error);
std::filesystem::rename(temporary, path);
}
bool origin_allowed(const std::string& origin) {
if (origin.empty() || local_origin(origin)) return true;
const std::string environment_origins = environment("SPINE_CONNECTOR_ORIGINS");
std::size_t start = 0;
while (start <= environment_origins.size()) {
const std::size_t end = environment_origins.find(',', start);
const std::string item = environment_origins.substr(start, end - start);
if (item == origin) return true;
if (end == std::string::npos) break;
start = end + 1;
}
const auto config = load_config();
return std::find(config.allowed_origins.begin(), config.allowed_origins.end(), origin) != config.allowed_origins.end();
}
void allow_origin(const std::string& origin) {
ConnectorConfig config = load_config();
if (std::find(config.allowed_origins.begin(), config.allowed_origins.end(), origin) == config.allowed_origins.end()) {
config.allowed_origins.push_back(origin);
save_config(config);
}
}
bool valid_web_origin(const std::string& origin) {
static const std::regex pattern(R"(^https?://(?:\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9.-]+)(?::[0-9]{1,5})?$)");
return std::regex_match(origin, pattern);
}
} // namespace connector