From c73dca05857d171447625d1f2826055f965e75af Mon Sep 17 00:00:00 2001 From: Joseph Birkner Date: Fri, 5 Jul 2024 16:57:45 +0200 Subject: [PATCH] Support new flexible YAML settings file format. --- libs/httpcl/include/httpcl/http-settings.hpp | 10 +- libs/httpcl/src/http-settings.cpp | 98 ++++++++++++++++---- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/libs/httpcl/include/httpcl/http-settings.hpp b/libs/httpcl/include/httpcl/http-settings.hpp index f62a0e5..125a3d6 100644 --- a/libs/httpcl/include/httpcl/http-settings.hpp +++ b/libs/httpcl/include/httpcl/http-settings.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "yaml-cpp/yaml.h" namespace httpcl @@ -41,6 +42,10 @@ struct Config std::string keychain; }; + std::optional scope; + std::regex urlPattern; + std::string urlPatternString; + std::map cookies; std::optional auth; std::optional proxy; @@ -67,7 +72,7 @@ struct Config }; /** - * Loads settings from HTTP_SETTINGS_FILE. + * Loads/stores settings from/to HTTP_SETTINGS_FILE. * Allows returning config for a specific URL. */ struct Settings @@ -85,7 +90,8 @@ struct Settings /** * Map from URL pattern to some config values. */ - std::map settings; + std::vector settings; + YAML::Node document; }; struct secret diff --git a/libs/httpcl/src/http-settings.cpp b/libs/httpcl/src/http-settings.cpp index 71dfc97..4540ddd 100644 --- a/libs/httpcl/src/http-settings.cpp +++ b/libs/httpcl/src/http-settings.cpp @@ -112,9 +112,51 @@ struct convert } namespace { -YAML::Node configToNode(Config const& config, std::string const& url=".*") { + +std::string convertToRegex(const std::string& scope) { + std::string regexPattern = "^"; + for (char c : scope) { + switch (c) { + case '*': + regexPattern += ".*"; + break; + case '.': + regexPattern += "\\."; + break; + case '\\': + regexPattern += "\\\\"; + break; + case '^': + case '$': + case '|': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '?': + case '+': + case '-': + case '!': + regexPattern += '\\'; + regexPattern += c; + break; + default: + regexPattern += c; + break; + } + } + regexPattern += ".*$"; + return regexPattern; +} + +YAML::Node configToNode(Config const& config) { YAML::Node result; - result["url"] = url; + if (config.scope) + result["scope"] = *config.scope; + else + result["url"] = config.urlPatternString; if (!config.cookies.empty()) result["cookies"] = config.cookies; @@ -139,16 +181,21 @@ YAML::Node configToNode(Config const& config, std::string const& url=".*") { return result; } -std::pair configFromNode(YAML::Node const& node) +Config configFromNode(YAML::Node const& node) { - std::string urlPattern; Config conf; - if (auto entryParam = node["url"]) - urlPattern = entryParam.as(); - else - throw std::runtime_error( - "HTTP Settings: Missing 'url' field in: " + YAML::Dump(node)); + if (auto entryParam = node["url"]) { + conf.urlPattern = conf.urlPatternString = entryParam.as(); + } + else { + if (auto entryParamScope = node["scope"]) + conf.scope = entryParamScope.as(); + else + conf.scope = "*"; + conf.urlPatternString = convertToRegex(*conf.scope); + conf.urlPattern = conf.urlPatternString; + } if (auto cookies = node["cookies"]) conf.cookies = cookies.as>(); @@ -172,7 +219,7 @@ std::pair configFromNode(YAML::Node const& node) if (auto apiKey = node["api-key"]) conf.apiKey = apiKey.as(); - return {std::move(conf), std::move(urlPattern)}; + return conf; } } @@ -302,12 +349,20 @@ void Settings::load() try { log().debug("Loading HTTP settings from '{}'...", cookieJar); - auto node = YAML::LoadFile(cookieJar); + document = YAML::LoadFile(cookieJar); + YAML::Node httpSettingsNode; uint32_t idx = 0; - for (auto const& entry : node.as>()) { - auto [conf, urlPattern] = configFromNode(entry); - settings[urlPattern] = std::move(conf); + if (document.IsMap()) { + httpSettingsNode = document["http-settings"]; + } + else { + // Keep supporting the old format, where the root structure is a settings array. + httpSettingsNode = document; + } + + for (auto const& entry : httpSettingsNode.as>()) { + settings.emplace_back(configFromNode(entry)); ++idx; } @@ -330,9 +385,16 @@ void Settings::store() try { auto node = YAML::Node(); - for (const auto& [key, config] : settings) + for (const auto& config : settings) node.push_back(configToNode(config)); + if (document && document.IsMap()) { + document["http-settings"] = node; + } + else { + document = node; + } + log().debug("Saving HTTP settings to '{}'...", cookieJar); std::ofstream os(cookieJar); os << node; @@ -345,16 +407,16 @@ void Settings::store() Config::Config(const std::string& yamlConf) { YAML::Node parsedYaml = YAML::Load(yamlConf); - *this = configFromNode(parsedYaml).first; + *this = configFromNode(parsedYaml); } Config Settings::operator[] (const std::string &url) const { Config result; - for (auto const& [pattern, config] : settings) + for (auto const& config : settings) { - if (!std::regex_match(url, std::regex(pattern))) + if (!std::regex_match(url, config.urlPattern)) continue; result |= config; }