#include "config.hpp" #include "json.hpp" #include "platform.hpp" #include #include #include #include #include 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() : to_path(home); } bool local_origin(const std::string& origin) { static const std::vector 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 to_path(override_path); #ifdef _WIN32 std::string app_data = environment("APPDATA"); const auto root = app_data.empty() ? home_directory() : to_path(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" : to_path(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(input)), std::istreambuf_iterator()); 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