Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only create fallback mirrors for targets when there is no mirror existing for it. #153

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ set(LIBPOWERLOADER_SRCS
${POWERLOADER_SOURCE_DIR}/target.cpp
${POWERLOADER_SOURCE_DIR}/url.cpp
${POWERLOADER_SOURCE_DIR}/utils.cpp
${POWERLOADER_SOURCE_DIR}/target.hpp
${POWERLOADER_SOURCE_DIR}/curl_internal.hpp
)
if (WITH_ZCHUNK)
list(APPEND LIBPOWERLOADER_SRCS ${POWERLOADER_SOURCE_DIR}/zck.cpp
Expand Down
19 changes: 17 additions & 2 deletions include/powerloader/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@

#include <powerloader/export.hpp>
#include <powerloader/mirrorid.hpp>
#include <powerloader/curl.hpp>

namespace powerloader
{
namespace fs = std::filesystem;

class Context;
struct Mirror;
class Mirror;

using mirror_set
= std::vector<std::shared_ptr<Mirror>>; // TODO: replace by std::flat_set once available.
Expand Down Expand Up @@ -86,6 +87,15 @@ namespace powerloader
void reset(mirror_map_base new_values = {});
};

using proxy_map_type = std::map<std::string, std::string>;

// Options provided when starting a powerloader context.
struct ContextOptions
{
// If set, specifies which SSL backend to use with CURL.
std::optional<ssl_backend_t> ssl_backend;
};

class POWERLOADER_API Context
{
public:
Expand Down Expand Up @@ -122,19 +132,24 @@ namespace powerloader
std::chrono::steady_clock::duration retry_default_timeout = std::chrono::seconds(2);

mirror_map_type mirror_map;
proxy_map_type proxy_map;

std::vector<std::string> additional_httpheaders;

void set_verbosity(int v);

// Throws if another instance already exists: there can only be one at any time!
Context();
Context(ContextOptions options = {});
~Context();

Context(const Context&) = delete;
Context& operator=(const Context&) = delete;
Context(Context&&) = delete;
Context& operator=(Context&&) = delete;

private:
struct Impl;
std::unique_ptr<Impl> impl; // Private implementation details
};

}
Expand Down
28 changes: 26 additions & 2 deletions include/powerloader/curl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#include <nlohmann/json.hpp>
#include <tl/expected.hpp>

extern "C"
{
#include <curl/curl.h>
}

#include <powerloader/export.hpp>
#include <powerloader/utils.hpp>
#include <powerloader/enums.hpp>
Expand All @@ -21,8 +26,25 @@ namespace powerloader
{
class Context;
class CURLHandle;
using proxy_map_type = std::map<std::string, std::string>;

#include <curl/curl.h>
enum class ssl_backend_t
{
none = CURLSSLBACKEND_NONE,
openssl = CURLSSLBACKEND_OPENSSL,
gnutls = CURLSSLBACKEND_GNUTLS,
nss = CURLSSLBACKEND_NSS,
gskit = CURLSSLBACKEND_GSKIT,
// polarssl = CURLSSLBACKEND_POLARSSL /* deprecated by curl */,
wolfssl = CURLSSLBACKEND_WOLFSSL,
schannel = CURLSSLBACKEND_SCHANNEL,
securetransport = CURLSSLBACKEND_SECURETRANSPORT,
// axtls = CURLSSLBACKEND_AXTLS, /* deprecated by curl */
mbedtls = CURLSSLBACKEND_MBEDTLS,
// mesalink = CURLSSLBACKEND_MESALINK, /* deprecated by curl */
bearssl = CURLSSLBACKEND_BEARSSL,
rustls = CURLSSLBACKEND_RUSTLS,
};

class POWERLOADER_API curl_error : public std::runtime_error
{
Expand Down Expand Up @@ -66,7 +88,7 @@ namespace powerloader
CURLHandle(const Context& ctx, const std::string& url);
~CURLHandle();

CURLHandle& url(const std::string& url);
CURLHandle& url(const std::string& url, const proxy_map_type& proxies);
CURLHandle& accept_encoding();
CURLHandle& user_agent(const std::string& user_agent);

Expand Down Expand Up @@ -134,6 +156,8 @@ namespace powerloader
}
return *this;
}

std::optional<std::string> proxy_match(const proxy_map_type& ctx, const std::string& url);
}

#endif
48 changes: 44 additions & 4 deletions include/powerloader/mirror.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,29 @@ namespace powerloader
}
};

inline std::string strip_trailing_slash(const std::string& s)
{
if (s.size() > 0 && s.back() == '/' && s != "file://")
{
return s.substr(0, s.size() - 1);
}
return s;
}

// mirrors should be dict -> urls mapping
struct POWERLOADER_API Mirror
class POWERLOADER_API Mirror
{
Mirror(MirrorID id, const Context& ctx, const std::string& url);
Mirror(const Context& ctx, const std::string& url);
public:
Mirror(const MirrorID& id, const Context& ctx, const std::string& url)
: m_id(id)
, m_url(strip_trailing_slash(url))
{
if (ctx.max_downloads_per_mirror > 0)
{
m_stats.allowed_parallel_connections = ctx.max_downloads_per_mirror;
}
}

virtual ~Mirror();

Mirror(const Mirror&) = delete;
Expand Down Expand Up @@ -156,8 +174,8 @@ namespace powerloader
}

private:
std::string m_url;
const MirrorID m_id;
const std::string m_url;

Protocol m_protocol = Protocol::kHTTP;
MirrorState m_state = MirrorState::READY;
Expand All @@ -180,6 +198,28 @@ namespace powerloader
std::size_t m_retry_counter = 0;
};

class POWERLOADER_API HTTPMirror : public Mirror
{
public:
HTTPMirror(const Context& ctx, const std::string& url)
: Mirror(HTTPMirror::id(url), ctx, url)
{
}

static MirrorID id(const std::string& url)
{
return MirrorID{ fmt::format("HTTPMirror[{}]", url) };
}

void set_auth(const std::string& user, const std::string& password);

bool authenticate(CURLHandle& handle, const std::string& path) override;

private:
std::string m_auth_user;
std::string m_auth_password;
};

bool sort_mirrors(std::vector<std::shared_ptr<Mirror>>& mirrors,
const std::shared_ptr<Mirror>& mirror,
bool success,
Expand Down
3 changes: 3 additions & 0 deletions include/powerloader/mirrors/oci.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ namespace powerloader
std::string m_password;
split_function_type m_split_func;

// we copy over the proxy map from the context, otherwise we can't set new
// proxy options for each curl handle
proxy_map_type m_proxy_map;

std::pair<std::string, std::string> split_path_tag(const std::string& path) const;

Expand Down
2 changes: 1 addition & 1 deletion src/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ parse_mirrors(const Context& ctx, const YAML::Node& node)
else if (kof == KindOf::kHTTP)
{
spdlog::info("Adding HTTP mirror: {} -> {}", mirror_name, creds.url.url());
result.create_unique_mirror<Mirror>(mirror_name, ctx, creds.url.url());
result.create_unique_mirror<HTTPMirror>(mirror_name, ctx, creds.url.url());
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion src/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,31 @@ extern "C"

#include <powerloader/mirror.hpp>

#include "./curl_internal.hpp"


namespace powerloader
{
struct Context::Impl
{
std::optional<details::CURLSetup> curl_setup;
};

static std::atomic<bool> is_context_alive{ false };

Context::Context()
Context::Context(ContextOptions options)
: impl(new Impl)
{
bool expected = false;
if (!is_context_alive.compare_exchange_strong(expected, true))
throw std::runtime_error(
"powerloader::Context created more than once - instance must be unique");

if (options.ssl_backend)
{
impl->curl_setup = details::CURLSetup{ options.ssl_backend.value() };
}

cache_dir = fs::absolute(fs::path(".pdcache"));
if (!fs::exists(cache_dir))
{
Expand Down
76 changes: 66 additions & 10 deletions src/curl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <powerloader/curl.hpp>
#include <powerloader/utils.hpp>
#include <powerloader/context.hpp>
#include <powerloader/url.hpp>

namespace powerloader
{
Expand All @@ -24,6 +25,7 @@ namespace powerloader
return m_serious;
}


/**************
* CURLHandle*
**************/
Expand Down Expand Up @@ -54,28 +56,35 @@ namespace powerloader

if (ctx.disable_ssl)
{
spdlog::warn("SSL verification is disabled");
setopt(CURLOPT_SSL_VERIFYHOST, 0);
setopt(CURLOPT_SSL_VERIFYPEER, 0);

// also disable proxy SSL verification
setopt(CURLOPT_PROXY_SSL_VERIFYPEER, 0L);
setopt(CURLOPT_PROXY_SSL_VERIFYHOST, 0L);
}
else
{
spdlog::warn("SSL verification is ENABLED");

setopt(CURLOPT_SSL_VERIFYHOST, 2);
setopt(CURLOPT_SSL_VERIFYPEER, 1);

// Windows SSL backend doesn't support this
CURLcode verifystatus = curl_easy_setopt(m_handle, CURLOPT_SSL_VERIFYSTATUS, 0);
if (verifystatus != CURLE_OK && verifystatus != CURLE_NOT_BUILT_IN)
throw curl_error("Could not initialize CURL handle");
}

if (!ctx.ssl_ca_info.empty())
{
setopt(CURLOPT_CAINFO, ctx.ssl_ca_info.c_str());
}
if (!ctx.ssl_ca_info.empty())
{
setopt(CURLOPT_CAINFO, ctx.ssl_ca_info.c_str());
}

if (ctx.ssl_no_revoke)
{
setopt(CURLOPT_SSL_OPTIONS, ctx.ssl_no_revoke);
if (ctx.ssl_no_revoke)
{
setopt(CURLOPT_SSL_OPTIONS, ctx.ssl_no_revoke);
}
}

setopt(CURLOPT_FTP_USE_EPSV, (long) ctx.ftp_use_seepsv);
Expand All @@ -88,7 +97,7 @@ namespace powerloader
CURLHandle::CURLHandle(const Context& ctx, const std::string& url)
: CURLHandle(ctx)
{
this->url(url);
this->url(url, ctx.proxy_map);
}

CURLHandle::~CURLHandle()
Expand Down Expand Up @@ -123,9 +132,18 @@ namespace powerloader
return *this;
}

CURLHandle& CURLHandle::url(const std::string& url)
CURLHandle& CURLHandle::url(const std::string& url, const proxy_map_type& proxies)
{
setopt(CURLOPT_URL, url.c_str());
const auto match = proxy_match(proxies, url);
if (match)
{
setopt(CURLOPT_PROXY, match.value().c_str());
}
else
{
setopt(CURLOPT_PROXY, nullptr);
}
return *this;
}

Expand Down Expand Up @@ -373,4 +391,42 @@ namespace powerloader
downloaded_size
= handle.getinfo<decltype(downloaded_size)>(CURLINFO_SIZE_DOWNLOAD_T).value();
}

std::optional<std::string> proxy_match(const proxy_map_type& proxies, const std::string& url)
{
// This is a reimplementation of requests.utils.select_proxy()
// of the python requests library used by conda
if (proxies.empty())
{
return std::nullopt;
}

auto handler = URLHandler(url);
auto scheme = handler.scheme();
auto host = handler.host();
std::vector<std::string> options;

if (host.empty())
{
options = {
scheme,
"all",
};
}
else
{
options = { scheme + "://" + host, scheme, "all://" + host, "all" };
}

for (auto& option : options)
{
auto proxy = proxies.find(option);
if (proxy != proxies.end())
{
return proxy->second;
}
}

return std::nullopt;
}
}
Loading