121 lines
4.3 KiB
C++
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
|