diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/other_tools/git_operations/git_config_settings.cpp | 281 | ||||
-rw-r--r-- | src/other_tools/git_operations/git_config_settings.hpp | 11 |
2 files changed, 292 insertions, 0 deletions
diff --git a/src/other_tools/git_operations/git_config_settings.cpp b/src/other_tools/git_operations/git_config_settings.cpp index dd930883..4423cfff 100644 --- a/src/other_tools/git_operations/git_config_settings.cpp +++ b/src/other_tools/git_operations/git_config_settings.cpp @@ -63,6 +63,33 @@ struct ConfigKeyMatchCompare { } }; +/// \brief Tries to parse the given proxy string as an URL using the libcurl API +/// in a more permissive way, mirroring what git and curl internally do, then +/// returns the reconstructed URL if parsing succeeded, or a nullopt ProxyInfo. +/// Returns nullopt on unexpected errors. +[[nodiscard]] auto GetProxyAsPermissiveUrl(std::string const& proxy_url) + -> std::optional<ProxyInfo> { + // parse proxy string with permissive options: + // use_non_support_scheme allows for non-standard schemes to be parsed; + // use_guess_scheme tries to figure out the scheme from the hostname if none + // is provided and defaults to http if it fails; + auto parsed_url = + CurlURLHandle::CreatePermissive(proxy_url, + true /*use_guess_scheme*/, + false /*use_default_scheme*/, + true /*use_non_support_scheme*/); + if (not parsed_url) { + // exception encountered + return std::nullopt; + } + if (not *parsed_url) { + // failure to parse + return ProxyInfo{std::nullopt}; + } + // recombine the parsed url without changing it any further + return ProxyInfo{parsed_url.value()->GetURL()}; +} + } // namespace auto GitConfigSettings::GetSSLCallback(std::shared_ptr<git_config> const& cfg, @@ -169,3 +196,257 @@ auto GitConfigSettings::GetSSLCallback(std::shared_ptr<git_config> const& cfg, return std::nullopt; } } + +auto GitConfigSettings::GetProxySettings(std::shared_ptr<git_config> const& cfg, + std::string const& url, + anon_logger_ptr const& logger) + -> std::optional<ProxyInfo> { + try { + // perform proxy checks as git does + git_buf tmp_buf{}; // temp buffer + if (cfg != nullptr) { + // parse given url + auto parsed_url = CurlURLHandle::Create(url); + if (not parsed_url) { + // unexpected error occurred + (*logger)( + "While getting proxy settings:\nfailed to parse remote URL", + true /*fatal*/); + return std::nullopt; + } + if (parsed_url.value()) { + // check for no_proxy envariable + if (const char* envar = std::getenv("no_proxy")) { + // check if there is a pattern match + auto is_matched = + parsed_url.value()->NoproxyStringMatches(envar); + if (not is_matched) { + // unexpected error occurred + (*logger)( + "While getting proxy settings:\nmatching no_proxy " + "envariable patterns failed", + true /*fatal*/); + return std::nullopt; + } + if (is_matched == true) { + return ProxyInfo{std::nullopt}; + } + } + // check for NO_PROXY envariable + if (const char* envar = std::getenv("NO_PROXY")) { + // check if there is a pattern match + auto is_matched = + parsed_url.value()->NoproxyStringMatches(envar); + if (not is_matched) { + // unexpected error occurred + (*logger)( + "While getting proxy settings:\nmatching NO_PROXY " + "envariable patterns failed", + true /*fatal*/); + return std::nullopt; + } + if (is_matched == true) { + return ProxyInfo{std::nullopt}; + } + } + // iterate over config entries of type + // "http.<url>.proxy" + git_config_iterator* iter_ptr{nullptr}; + if (git_config_iterator_glob_new( + &iter_ptr, cfg.get(), R"(http\..*\.proxy)") == 0) { + // wrap iterator + auto iter = std::unique_ptr<git_config_iterator, + decltype(&config_iter_closer)>( + iter_ptr, config_iter_closer); + // set config key parsing offsets + const std::string::difference_type start_offset{ + 5}; // len("http.") + const std::string::difference_type end_offset{ + 6}; // len(".proxy") + // define ordered container storing matches of git + // config keys + std::map<ConfigKeyMatchDegree, + std::string, + ConfigKeyMatchCompare> + matches{}; + // iterate through config keys + git_config_entry* entry{nullptr}; + while (git_config_next(&entry, iter.get()) == 0) { + // get the url part of the config key + std::string entry_name{entry->name}; + auto entry_url = + std::string(entry_name.begin() + start_offset, + entry_name.end() - end_offset); + // get match degree + auto match = + parsed_url.value()->MatchConfigKey(entry_url); + if (not match) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\nmatching " + "config key failed", + true /*fatal*/); + return std::nullopt; + } + // store in ordered list only if a match occurred + if (match->matched) { + matches.emplace(*match, std::string(entry->value)); + } + } + // look for any empty proxy value; if found, proxy is + // disabled + for (auto const& elem : matches) { + if (git_config_parse_path(&tmp_buf, + elem.second.c_str()) == 0) { + if (git_buf_contains_nul(&tmp_buf) == 0 and + tmp_buf.size == 0) { + // cleanup memory + git_buf_dispose(&tmp_buf); + return ProxyInfo{std::nullopt}; + } + // cleanup memory + git_buf_dispose(&tmp_buf); + } + } + // no_proxy checks are done, so look for actual proxy info; + // first, check the top "http.<url>.proxy" match + if (not matches.empty()) { + if (git_config_parse_path( + &tmp_buf, matches.begin()->second.c_str()) == + 0) { + if (git_buf_contains_nul(&tmp_buf) == 0) { + auto tmp_str = std::string(tmp_buf.ptr); + // cleanup memory + git_buf_dispose(&tmp_buf); + // get proxy url in standard form + auto proxy_info = + GetProxyAsPermissiveUrl(tmp_str); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy " + "settings:\npermissive parsing of " + "remote-specific proxy URL failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + } + } + // check the generic "http.proxy" gitconfig entry; + // ignore errors + if (git_config_get_string_buf( + &tmp_buf, cfg.get(), R"(http.proxy)") == 0) { + if (git_buf_contains_nul(&tmp_buf) == 0) { + auto tmp_str = std::string(tmp_buf.ptr); + // cleanup memory + git_buf_dispose(&tmp_buf); + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(tmp_str); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of http.proxy URL failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + } + // check proxy envariables depending on the scheme + auto url_scheme = parsed_url.value()->GetScheme(); + if (not url_scheme) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\nretrieving scheme " + "from parsed URL failed", + true /*fatal*/); + return std::nullopt; + } + if (url_scheme.value() == "https") { + // check https_proxy envariable + if (const char* envar = std::getenv("https_proxy")) { + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(envar); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of https_proxy envariable failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + // check HTTPS_PROXY envariable + if (const char* envar = std::getenv("HTTPS_PROXY")) { + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(envar); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of HTTPS_PROXY envariable failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + } + else if (url_scheme.value() == "http") { + // check http_proxy envariable + if (const char* envar = std::getenv("http_proxy")) { + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(envar); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of http_proxy envariable failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + } + // check all_proxy envariable + if (const char* envar = std::getenv("all_proxy")) { + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(envar); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of all_proxy envariable failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + // check ALL_PROXY envariable + if (const char* envar = std::getenv("ALL_PROXY")) { + // get proxy url in standard form + auto proxy_info = GetProxyAsPermissiveUrl(envar); + if (not proxy_info) { + // unexpected behavior + (*logger)( + "While getting proxy settings:\npermissive " + "parsing of ALL_PROXY envariable failed", + true /*fatal*/); + return std::nullopt; + } + return proxy_info.value(); + } + } + } + } + return ProxyInfo{std::nullopt}; // default to disabling proxy + } catch (std::exception const& ex) { + (*logger)( + fmt::format("Getting proxy settings failed with:\n{}", ex.what()), + true /*fatal*/); + return std::nullopt; + } +} diff --git a/src/other_tools/git_operations/git_config_settings.hpp b/src/other_tools/git_operations/git_config_settings.hpp index 8aa412e8..afe8a73b 100644 --- a/src/other_tools/git_operations/git_config_settings.hpp +++ b/src/other_tools/git_operations/git_config_settings.hpp @@ -33,6 +33,9 @@ using git_transport_certificate_check_cb = auto (*)(git_cert*, using anon_logger_t = std::function<void(std::string const&, bool)>; using anon_logger_ptr = std::shared_ptr<anon_logger_t>; +/// \brief Contains the proxy URL if proxy is set, or nullopt if proxy unset. +using ProxyInfo = std::optional<std::string>; + namespace GitConfigSettings { /// \brief Get a custom SSL certificate check callback to honor the existing @@ -44,6 +47,14 @@ namespace GitConfigSettings { anon_logger_ptr const& logger) -> std::optional<git_transport_certificate_check_cb>; +/// \brief Get the remote proxy settings from envariables and the given git +/// config snapshot. Performs same checks and honors same settings as git. +/// Returns the proxy state and information, or nullopt if errors. +[[nodiscard]] auto GetProxySettings(std::shared_ptr<git_config> const& cfg, + std::string const& url, + anon_logger_ptr const& logger) + -> std::optional<ProxyInfo>; + } // namespace GitConfigSettings #endif // INCLUDED_SRC_OTHER_TOOLS_GIT_OPERATIONS_GIT_CONFIG_SETTINGS_HPP |