连接器客户端
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user