diff --git a/libmamba/CMakeLists.txt b/libmamba/CMakeLists.txt index 934b1a0eb..252a389bd 100644 --- a/libmamba/CMakeLists.txt +++ b/libmamba/CMakeLists.txt @@ -138,6 +138,7 @@ set(LIBMAMBA_SOURCES ${LIBMAMBA_SOURCE_DIR}/core/compression.cpp # Implementation of version and matching specs ${LIBMAMBA_SOURCE_DIR}/specs/archive.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/authentication_info.cpp ${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp ${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp ${LIBMAMBA_SOURCE_DIR}/specs/version.cpp @@ -227,10 +228,11 @@ set(LIBMAMBA_PUBLIC_HEADERS ${LIBMAMBA_INCLUDE_DIR}/mamba/util/iterator.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/util/string.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/util/path_manip.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/tuple_hash.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url_manip.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url.hpp # Implementation of version and matching specs - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentification_info.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentication_info.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/archive.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/platform.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp diff --git a/libmamba/include/mamba/core/channel.hpp b/libmamba/include/mamba/core/channel.hpp index 2fa5e3f5f..85aa09bf9 100644 --- a/libmamba/include/mamba/core/channel.hpp +++ b/libmamba/include/mamba/core/channel.hpp @@ -8,8 +8,6 @@ #define MAMBA_CORE_CHANNEL_HPP #include -#include -#include #include #include #include @@ -44,21 +42,14 @@ namespace mamba ~Channel(); - std::string_view scheme() const; const std::string& location() const; const std::string& name() const; const std::string& canonical_name() const; const util::flat_set& platforms() const; - std::optional auth() const; - std::optional user() const; - std::optional password() const; - std::optional token() const; - std::optional package_filename() const; - const validation::RepoChecker& - repo_checker(Context& context, MultiPackageCache& caches) const; + const specs::CondaURL& url() const; std::string base_url() const; - std::string platform_url(std::string platform, bool with_credential = true) const; + std::string platform_url(std::string_view platform, bool with_credential = true) const; // The pairs consist of (platform,url) util::flat_set> platform_urls(bool with_credential = true) const; @@ -66,18 +57,6 @@ namespace mamba private: - Channel( - std::string_view scheme, - std::string location, - std::string name, - std::string canonical_name, - std::string_view user = {}, - std::string_view password = {}, - std::string_view token = {}, - std::string_view package_filename = {}, - util::flat_set platforms = {} - ); - Channel( specs::CondaURL url, std::string location, @@ -86,17 +65,12 @@ namespace mamba util::flat_set platforms = {} ); - const specs::CondaURL& url() const; - specs::CondaURL m_url; std::string m_location; std::string m_name; std::string m_canonical_name; util::flat_set m_platforms; - // This is used to make sure that there is a unique repo for every channel - mutable std::unique_ptr p_repo_checker; - // Note: as long as Channel is not a regular value-type and we want each // instance only possible to create through ChannelContext, we need // to have Channel's constructor only available to ChannelContext, @@ -106,10 +80,6 @@ namespace mamba friend class ChannelContext; }; - bool operator==(const Channel& lhs, const Channel& rhs); - bool operator!=(const Channel& lhs, const Channel& rhs); - - using ChannelCache = std::map; class ChannelContext @@ -160,8 +130,10 @@ namespace mamba Channel from_any_path(specs::ChannelSpec&& spec); Channel from_package_path(specs::ChannelSpec&& spec); Channel from_path(specs::ChannelSpec&& spec); + Channel from_any_url(specs::ChannelSpec&& spec); + Channel from_package_url(specs::ChannelSpec&& spec); Channel from_url(specs::ChannelSpec&& spec); - Channel from_name(const std::string& name); + Channel from_name(specs::ChannelSpec&& spec); Channel from_value(const std::string& value); }; diff --git a/libmamba/include/mamba/core/context.hpp b/libmamba/include/mamba/core/context.hpp index e6e83829c..611857e55 100644 --- a/libmamba/include/mamba/core/context.hpp +++ b/libmamba/include/mamba/core/context.hpp @@ -16,7 +16,7 @@ #include "mamba/core/palette.hpp" #include "mamba/core/tasksync.hpp" #include "mamba/fs/filesystem.hpp" -#include "mamba/specs/authentification_info.hpp" +#include "mamba/specs/authentication_info.hpp" #include "mamba/specs/platform.hpp" #include "mamba/version.hpp" @@ -214,9 +214,8 @@ namespace mamba }; std::string channel_alias = "https://conda.anaconda.org"; - using authentication_info_map_t = std::map; - authentication_info_map_t& authentication_info(); - const authentication_info_map_t& authentication_info() const; + specs::AuthenticationDataBase& authentication_info(); + const specs::AuthenticationDataBase& authentication_info() const; std::vector token_locations{ "~/.continuum/anaconda-client/tokens" }; bool override_channels_enabled = true; @@ -253,7 +252,7 @@ namespace mamba bool on_ci = false; void load_authentication_info(); - std::map m_authentication_info; + specs::AuthenticationDataBase m_authentication_info; bool m_authentication_infos_loaded = false; std::shared_ptr logger; diff --git a/libmamba/include/mamba/core/query.hpp b/libmamba/include/mamba/core/query.hpp index a2e506491..ba933aacd 100644 --- a/libmamba/include/mamba/core/query.hpp +++ b/libmamba/include/mamba/core/query.hpp @@ -17,6 +17,8 @@ #include "mamba/core/pool.hpp" #include "mamba/util/graph.hpp" +typedef struct s_Solvable Solvable; + namespace mamba { using GraphicsParams = Context::GraphicsParams; diff --git a/libmamba/include/mamba/core/util.hpp b/libmamba/include/mamba/core/util.hpp index 2427636bf..040df167b 100644 --- a/libmamba/include/mamba/core/util.hpp +++ b/libmamba/include/mamba/core/util.hpp @@ -331,13 +331,6 @@ namespace mamba void split_package_extension(const std::string& file, std::string& name, std::string& extension); fs::u8path strip_package_extension(const std::string& file); - template - inline bool vector_is_prefix(const std::vector& prefix, const std::vector& vec) - { - return vec.size() >= prefix.size() - && prefix.end() == std::mismatch(prefix.begin(), prefix.end(), vec.begin()).first; - } - tl::expected encode_base64(std::string_view input); tl::expected decode_base64(std::string_view input); diff --git a/libmamba/include/mamba/specs/authentication_info.hpp b/libmamba/include/mamba/specs/authentication_info.hpp new file mode 100644 index 000000000..c80d67a3d --- /dev/null +++ b/libmamba/include/mamba/specs/authentication_info.hpp @@ -0,0 +1,106 @@ +// Copyright (c) 2023, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#ifndef MAMBA_SPECS_AUTHENTICATION_INFO_HPP +#define MAMBA_SPECS_AUTHENTICATION_INFO_HPP + +#include +#include +#include + +namespace mamba::specs +{ + class CondaURL; + + /** User and password authetification set in the URL. */ + struct BasicHTTPAuthentication + { + std::string user; + std::string password; + }; + + /** HTTP Bearer token set in the request headers. */ + struct BearerToken + { + std::string token; + }; + + /** A Conda token set in the URL path. */ + struct CondaToken + { + std::string token; + }; + + using AuthenticationInfo = std::variant; + + /** + * A class that holds the authentication info stored by users. + * + * Essentially a map, except that some keys can match mutliple queries. + * For instance "mamba.org/private" should be matched by queries "mamba.org/private", + * "mamba.org/private/channel", but not "mamba.org/public". + * + * A best effort is made to satifiy this with `xxx_compatible`. + * + * Future development of this class should aim to replace the map and keys with a + * `AuthenticationSpec`, that can decide whether or not a URL should benefit from such + * its authentication. + * Possibly, a string reprensentation such as "*.mamba.org/private/channel*" could be added + * to parse users intentions, rather than relying on the assumptions made here. + */ + class AuthenticationDataBase : private std::unordered_map + { + public: + + using Base = std::unordered_map; + + using typename Base::key_type; + using typename Base::mapped_type; + using typename Base::value_type; + using typename Base::size_type; + using typename Base::iterator; + using typename Base::const_iterator; + + using Base::Base; + + using Base::begin; + using Base::end; + using Base::cbegin; + using Base::cend; + + using Base::empty; + using Base::size; + using Base::max_size; + + using Base::clear; + using Base::insert; + using Base::insert_or_assign; + using Base::emplace; + using Base::emplace_hint; + using Base::try_emplace; + using Base::erase; + using Base::swap; + using Base::extract; + using Base::merge; + + using Base::reserve; + + using Base::at; + + [[nodiscard]] auto at_compatible(const key_type& key) -> mapped_type&; + [[nodiscard]] auto at_compatible(const key_type& key) const -> const mapped_type&; + + using Base::find; + + auto find_compatible(const key_type& key) -> iterator; + auto find_compatible(const key_type& key) const -> const_iterator; + + [[nodiscard]] auto contains(const key_type& key) const -> bool; + + [[nodiscard]] auto contains_compatible(const key_type& key) const -> bool; + }; +} +#endif diff --git a/libmamba/include/mamba/specs/authentification_info.hpp b/libmamba/include/mamba/specs/authentification_info.hpp deleted file mode 100644 index 67b78dad5..000000000 --- a/libmamba/include/mamba/specs/authentification_info.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2023, QuantStack and Mamba Contributors -// -// Distributed under the terms of the BSD 3-Clause License. -// -// The full license is in the file LICENSE, distributed with this software. - -#ifndef MAMBA_SPECS_AUTHENTIFICATION_INFO_HPP -#define MAMBA_SPECS_AUTHENTIFICATION_INFO_HPP - -#include -#include - -namespace mamba::specs -{ - /** User and password authetification set in the URL. */ - struct BasicHTTPAuthentication - { - std::string user; - std::string password; - }; - - /** HTTP Bearer token set in the request headers. */ - struct BearerToken - { - std::string token; - }; - - /** A Conda token set in the URL path. */ - struct CondaToken - { - std::string token; - }; - - using AuthenticationInfo = std::variant; -} -#endif diff --git a/libmamba/include/mamba/specs/conda_url.hpp b/libmamba/include/mamba/specs/conda_url.hpp index 938d8ef4f..bd4459469 100644 --- a/libmamba/include/mamba/specs/conda_url.hpp +++ b/libmamba/include/mamba/specs/conda_url.hpp @@ -7,6 +7,7 @@ #ifndef MAMBA_SPECS_CONDA_URL_HPP #define MAMBA_SPECS_CONDA_URL_HPP +#include #include #include "mamba/specs/platform.hpp" @@ -34,13 +35,17 @@ namespace mamba::specs explicit CondaURL(util::URL&& url); explicit CondaURL(const util::URL& url); + auto base() const -> const util::URL&; + using Base::scheme_is_defaulted; using Base::scheme; using Base::set_scheme; using Base::clear_scheme; + using Base::has_user; using Base::user; using Base::set_user; using Base::clear_user; + using Base::has_password; using Base::password; using Base::set_password; using Base::clear_password; @@ -99,6 +104,9 @@ namespace mamba::specs */ void append_path(std::string_view path, Encode::no_type); + /** Return wether a token is set. */ + [[nodiscard]] auto has_token() const -> bool; + /** Return the Conda token, as delimited with "/t/", or empty if there isn't any. */ [[nodiscard]] auto token() const -> std::string_view; @@ -226,7 +234,7 @@ namespace mamba::specs friend auto operator==(const CondaURL&, const CondaURL&) -> bool; }; - /** Compare two CondaURL. */ + /** Tuple-like equality of all observable members */ auto operator==(const CondaURL& a, const CondaURL& b) -> bool; auto operator!=(const CondaURL& a, const CondaURL& b) -> bool; @@ -234,4 +242,11 @@ namespace mamba::specs auto operator/(const CondaURL& url, std::string_view subpath) -> CondaURL; auto operator/(CondaURL&& url, std::string_view subpath) -> CondaURL; } + +template <> +struct std::hash +{ + auto operator()(const mamba::specs::CondaURL& p) const -> std::size_t; +}; + #endif diff --git a/libmamba/include/mamba/util/tuple_hash.hpp b/libmamba/include/mamba/util/tuple_hash.hpp new file mode 100644 index 000000000..bde581004 --- /dev/null +++ b/libmamba/include/mamba/util/tuple_hash.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2019, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#ifndef MAMBA_UTIL_TUPLE_HASH_HPP +#define MAMBA_UTIL_TUPLE_HASH_HPP + +#include +#include + +namespace mamba::util +{ + constexpr void hash_combine(std::size_t& seed, std::size_t other) + { + const auto boost_magic_num = 0x9e3779b9; + seed ^= other + boost_magic_num + (seed << 6) + (seed >> 2); + } + + template > + constexpr void hash_combine_val(std::size_t& seed, const T& val, const Hasher& hasher = {}) + { + hash_combine(seed, hasher(val)); + } + + template + constexpr auto hash_vals(const T&... vals) -> std::size_t + { + std::size_t seed = 0; + (hash_combine_val(seed, vals), ...); + return seed; + } + + template + constexpr auto hash_tuple(const std::tuple& t) -> std::size_t + { + return std::apply([](const auto&... vals) { return hash_vals(vals...); }, t); + } + + template + struct Tuplehasher + { + constexpr auto operator()(const std::tuple& t) const -> std::size_t + { + return hash_tuple(t); + } + }; +} +#endif diff --git a/libmamba/include/mamba/util/url.hpp b/libmamba/include/mamba/util/url.hpp index b9cc9080d..99cb7229d 100644 --- a/libmamba/include/mamba/util/url.hpp +++ b/libmamba/include/mamba/util/url.hpp @@ -8,6 +8,7 @@ #define MAMBA_UTIL_URL_HPP #include +#include #include #include @@ -98,6 +99,9 @@ namespace mamba::util /** Clear the scheme back to a defaulted value and return the old value. */ auto clear_scheme() -> std::string; + /** Return wether the user is empty. */ + [[nodiscard]] auto has_user() const -> bool; + /** Return the encoded user, or empty if none. */ [[nodiscard]] auto user(Decode::no_type) const -> const std::string&; @@ -113,6 +117,9 @@ namespace mamba::util /** Clear and return the encoded user. */ auto clear_user() -> std::string; + /** Return wether the password is empty. */ + [[nodiscard]] auto has_password() const -> bool; + /** Return the encoded password, or empty if none. */ [[nodiscard]] auto password(Decode::no_type) const -> const std::string&; @@ -270,6 +277,7 @@ namespace mamba::util std::string m_fragment = {}; }; + /** Tuple-like equality of all observable members */ auto operator==(URL const& a, URL const& b) -> bool; auto operator!=(URL const& a, URL const& b) -> bool; @@ -277,4 +285,10 @@ namespace mamba::util auto operator/(URL const& url, std::string_view subpath) -> URL; auto operator/(URL&& url, std::string_view subpath) -> URL; } + +template <> +struct std::hash +{ + auto operator()(const mamba::util::URL& p) const -> std::size_t; +}; #endif diff --git a/libmamba/include/mamba/util/url_manip.hpp b/libmamba/include/mamba/util/url_manip.hpp index 11fa1d43a..401506797 100644 --- a/libmamba/include/mamba/util/url_manip.hpp +++ b/libmamba/include/mamba/util/url_manip.hpp @@ -7,7 +7,6 @@ #ifndef MAMBA_UTIL_URL_MANIP_HPP #define MAMBA_UTIL_URL_MANIP_HPP -#include #include #include #include @@ -34,15 +33,6 @@ namespace mamba::util */ [[nodiscard]] auto url_decode(std::string_view url) -> std::string; - std::string concat_scheme_url(const std::string& scheme, const std::string& location); - - std::string build_url( - const std::optional& auth, - const std::string& scheme, - const std::string& base, - bool with_credential - ); - void split_platform( const std::vector& known_platforms, const std::string& url, diff --git a/libmamba/src/api/channel_loader.cpp b/libmamba/src/api/channel_loader.cpp index 8af4f8ea5..2027ed699 100644 --- a/libmamba/src/api/channel_loader.cpp +++ b/libmamba/src/api/channel_loader.cpp @@ -89,10 +89,10 @@ namespace mamba else { // Consider 'flexible' and 'strict' the same way - if (channel->name() != prev_channel_name) + if (channel->canonical_name() != prev_channel_name) { max_prio--; - prev_channel_name = channel->name(); + prev_channel_name = channel->canonical_name(); } priorities.push_back(std::make_pair(max_prio, 0)); } diff --git a/libmamba/src/api/list.cpp b/libmamba/src/api/list.cpp index b34519d7b..9076a863e 100644 --- a/libmamba/src/api/list.cpp +++ b/libmamba/src/api/list.cpp @@ -77,7 +77,7 @@ namespace mamba obj["base_url"] = channel.base_url(); obj["build_number"] = pkg_info.build_number; obj["build_string"] = pkg_info.build_string; - obj["channel"] = channel.name(); + obj["channel"] = channel.canonical_name(); obj["dist_name"] = pkg_info.str(); obj["name"] = pkg_info.name; obj["platform"] = pkg_info.subdir; @@ -112,7 +112,7 @@ namespace mamba else { const Channel& channel = channel_context.make_channel(package.second.url); - formatted_pkgs.channel = channel.name(); + formatted_pkgs.channel = channel.canonical_name(); } packages.push_back(formatted_pkgs); } diff --git a/libmamba/src/core/channel.cpp b/libmamba/src/core/channel.cpp index d0faf5e3d..f4bcd45c0 100644 --- a/libmamba/src/core/channel.cpp +++ b/libmamba/src/core/channel.cpp @@ -38,19 +38,6 @@ namespace mamba const char LOCAL_CHANNELS_NAME[] = "local"; const char DEFAULT_CHANNELS_NAME[] = "defaults"; - - std::optional nonempty_str(std::string&& s) - { - return s.empty() ? std::optional() : std::make_optional(s); - } - - auto channel_alias_location(specs::CondaURL url) -> std::string - { - url.clear_user(); - url.clear_password(); - url.clear_token(); - return url.pretty_str(specs::CondaURL::StripScheme::yes, '/'); - } } std::vector get_known_platforms() @@ -63,48 +50,6 @@ namespace mamba * Channel implementation * **************************/ - Channel::Channel( - std::string_view scheme, - std::string location, - std::string name, - std::string canonical_name, - std::string_view user, - std::string_view password, - std::string_view token, - std::string_view package_filename, - util::flat_set platforms - ) - : m_url() - , m_location(std::move(location)) - , m_name(std::move(name)) - , m_canonical_name(std::move(canonical_name)) - , m_platforms(std::move(platforms)) - { - if (m_name != UNKNOWN_CHANNEL) - { - if (scheme == "file") - { - m_url.set_path(m_location); - } - else - { - m_url = specs::CondaURL::parse(m_location); - if (!token.empty()) - { - m_url.set_token(token); - } - m_url.set_user(user); - m_url.set_password(password); - } - m_url.set_scheme(scheme); - m_url.append_path(m_name); - if (!package_filename.empty()) - { - m_url.set_package(package_filename); - } - } - } - Channel::Channel( specs::CondaURL url, std::string location, @@ -127,11 +72,6 @@ namespace mamba return m_url; } - std::string_view Channel::scheme() const - { - return m_url.scheme(); - } - const std::string& Channel::location() const { return m_location; @@ -147,51 +87,6 @@ namespace mamba return m_platforms; } - std::optional Channel::auth() const - { - return nonempty_str(m_url.authentication()); - } - - std::optional Channel::user() const - { - return nonempty_str(m_url.user()); - } - - std::optional Channel::password() const - { - return nonempty_str(m_url.password()); - } - - std::optional Channel::token() const - { - return nonempty_str(std::string(m_url.token())); - } - - std::optional Channel::package_filename() const - { - return nonempty_str(m_url.package()); - } - - const validation::RepoChecker& - Channel::repo_checker(Context& context, MultiPackageCache& caches) const - { - if (p_repo_checker == nullptr) - { - p_repo_checker = std::make_unique( - context, - util::rsplit(base_url(), "/", 1).front(), - context.prefix_params.root_prefix / "etc" / "trusted-repos" - / util::cache_name_from_url(base_url()), - caches.first_writable_path() / "cache" / util::cache_name_from_url(base_url()) - ); - - fs::create_directories(p_repo_checker->cache_path()); - p_repo_checker->generate_index_checker(); - } - - return *p_repo_checker; - } - const std::string& Channel::canonical_name() const { return m_canonical_name; @@ -199,36 +94,17 @@ namespace mamba std::string Channel::base_url() const { - if (name() == UNKNOWN_CHANNEL) - { - return ""; - } - else - { - return util::concat_scheme_url(std::string(scheme()), util::join_url(location(), name())); - } + return url().str(specs::CondaURL::Credentials::Remove); } util::flat_set Channel::urls(bool with_credential) const { - if (auto fn = package_filename()) + if (!url().package().empty()) { - std::string base = {}; - if (with_credential && token()) - { - base = util::join_url(location(), "t", *token()); - } - else - { - base = location(); - } - - return { { util::build_url( - auth(), - std::string(scheme()), - util::join_url(base, name(), std::move(fn).value()), - with_credential - ) } }; + return { url().str( + with_credential ? specs::CondaURL::Credentials::Show + : specs::CondaURL::Credentials::Remove + ) }; } auto out = util::flat_set{}; @@ -242,51 +118,29 @@ namespace mamba util::flat_set> Channel::platform_urls(bool with_credential) const { - std::string base = location(); - if (with_credential && token()) + if (!url().package().empty()) { - base = util::join_url(base, "t", *token()); + return {}; } auto out = util::flat_set>{}; for (const auto& platform : platforms()) { - out.insert({ - platform, - util::build_url( - auth(), - std::string(scheme()), - util::join_url(base, name(), platform), - with_credential - ), - }); + out.insert({ platform, platform_url(platform, with_credential) }); } return out; } - std::string Channel::platform_url(std::string platform, bool with_credential) const + std::string Channel::platform_url(std::string_view platform, bool with_credential) const { - std::string base = location(); - if (with_credential && token()) + auto cred = with_credential ? specs::CondaURL::Credentials::Show + : specs::CondaURL::Credentials::Remove; + + if (!url().package().empty()) { - base = util::join_url(base, "t", *token()); + return url().str(cred); } - return util::build_url( - auth(), - std::string(scheme()), - util::join_url(base, name(), platform), - with_credential - ); - } - - bool operator==(const Channel& lhs, const Channel& rhs) - { - return lhs.location() == rhs.location() && lhs.name() == rhs.name(); - } - - bool operator!=(const Channel& lhs, const Channel& rhs) - { - return !(lhs == rhs); + return (url() / platform).str(cred); } /********************************* @@ -302,34 +156,36 @@ namespace mamba { if (!util::url_has_scheme(channel_url)) { - auto ca_location = channel_alias_location(channel_alias); + const auto& alias = get_channel_alias(); + auto url = alias; + auto name = std::string(util::strip(channel_name.empty() ? channel_url : channel_name, '/') + ); + url.append_path(channel_url); return Channel( - /* scheme= */ channel_alias.scheme(), - /* location= */ std::move(ca_location), - /* name= */ std::string(util::strip(channel_name.empty() ? channel_url : channel_name, '/')), - /* canonical_name= */ channel_canonical_name, - /* user= */ channel_alias.user(), - /* password= */ channel_alias.password(), - /* token= */ channel_alias.token(), - /* package_filename= */ {} + /* url= */ std::move(url), + /* location= */ alias.pretty_str(specs::CondaURL::StripScheme::yes, '/', specs::CondaURL::Credentials::Remove), + /* name= */ std::move(name), + /* canonical_name= */ channel_canonical_name ); } - auto url = specs::CondaURL::parse(channel_url); - std::string token = std::string(url.token()); - url.clear_token(); - std::string user = url.user(); // % encoded - url.clear_user(); - std::string password = url.password(); // % encoded - url.clear_password(); - std::string location = url.pretty_str(specs::CondaURL::StripScheme::yes, '/'); - + std::string location = url.pretty_str( + specs::CondaURL::StripScheme::yes, + '/', + specs::CondaURL::Credentials::Remove + ); std::string name(channel_name); - if (name.empty()) + + if (channel_name.empty()) { - if (auto ca_location = channel_alias_location(channel_alias); - util::starts_with(location, ca_location)) + auto ca_location = channel_alias.pretty_str( + specs::CondaURL::StripScheme::yes, + '/', + specs::CondaURL::Credentials::Remove + ); + + if (util::starts_with(location, ca_location)) { name = std::string(util::strip(util::remove_prefix(location, ca_location), '/')); location = std::move(ca_location); @@ -342,20 +198,21 @@ namespace mamba } else { - location = util::concat(url.host(), url.port().empty() ? "" : ":", url.port()); - name = url.path(); + location = url.authority(specs::CondaURL::Credentials::Remove); + name = url.path_without_token(); } } + else + { + url.append_path(channel_name); + } + name = util::strip(name.empty() ? channel_url : name, '/'); return Channel( - /* scheme= */ url.scheme(), - /* location= */ location, - /* name= */ name, - /* canonical_name= */ channel_canonical_name, - /* user= */ user, - /* password= */ password, - /* token= */ token, - /* package_filename= */ {} + /* url = */ std::move(url), + /* location= */ std::move(location), + /* name= */ std::move(name), + /* canonical_name= */ channel_canonical_name ); } @@ -380,6 +237,50 @@ namespace mamba ); } + void + set_fallback_credential_from_auth(specs::CondaURL& url, const specs::AuthenticationInfo& auth) + { + std::visit( + [&](const auto& info) + { + using Info = std::decay_t; + if constexpr (std::is_same_v) + { + if (!url.has_user() && !url.has_password()) + { + url.set_user(info.user, specs::CondaURL::Encode::yes); + url.set_password(info.password, specs::CondaURL::Encode::yes); + } + } + else if constexpr (std::is_same_v) + { + if (!url.has_token()) + { + url.set_token(info.token); + } + } + }, + auth + ); + } + + void + set_fallback_credential_from_db(specs::CondaURL& url, const specs::AuthenticationDataBase& db) + { + if (!url.has_token() || !url.has_user() || !url.has_password()) + { + const auto key = url.pretty_str( + specs::CondaURL::StripScheme::yes, + '/', + specs::CondaURL::Credentials::Remove + ); + if (auto it = db.find_compatible(key); it != db.end()) + { + set_fallback_credential_from_auth(url, it->second); + } + } + } + auto rsplit_once(std::string_view str, char sep) { auto [head, tail] = util::rstrip_if_parts(str, [sep](char c) { return c != sep; }); @@ -458,159 +359,102 @@ namespace mamba return chan; } - namespace + Channel ChannelContext::from_any_url(specs::ChannelSpec&& spec) { - // Channel configuration - struct channel_configuration + assert(util::url_has_scheme(spec.location())); + auto url = specs::CondaURL::parse(spec.location()); + assert(url.scheme() != "file"); + + using StripScheme = typename specs::CondaURL::StripScheme; + using Credentials = typename specs::CondaURL::Credentials; + + std::string default_location = url.pretty_str(StripScheme::yes, '/', Credentials::Remove); + + for (const auto& [canonical_name, chan] : get_custom_channels()) { - specs::CondaURL url; - std::string location; - std::string name; - std::string canonical_name; - }; - - channel_configuration - read_channel_configuration(ChannelContext& channel_context, specs::CondaURL url) - { - assert(url.scheme() != "file"); - - url.clear_package(); - url.clear_token(); - url.clear_password(); - url.clear_user(); - - std::string default_location = url.pretty_str( - specs::CondaURL::StripScheme::yes, - '/', - specs::CondaURL::Credentials::Remove - ); - - // Case 4: custom_channels matches - for (const auto& [canonical_name, chan] : channel_context.get_custom_channels()) + if (url_match(chan.url(), url)) { - std::string test_url = util::join_url(chan.location(), chan.name()); - if (vector_is_prefix(util::split(test_url, "/"), util::split(default_location, "/"))) - { - auto subname = std::string( - util::strip(default_location.replace(0u, test_url.size(), ""), '/') - ); - - auto location = chan.location(); - auto name = util::join_url(chan.name(), subname); - auto l_url = specs::CondaURL::parse(chan.base_url()); - l_url.append_path(subname); - l_url.set_scheme(url.scheme()); - l_url.set_user(chan.user().value_or("")); - l_url.set_password(chan.password().value_or("")); - if (auto token = chan.token().value_or(""); !token.empty()) - { - l_url.set_token(std::move(token)); - } - return channel_configuration{ - /* .url= */ std::move(l_url), - /* .location= */ std::move(location), - /* .name= */ std::move(name), - /* .canonical_name= */ std::move(canonical_name), - }; - } - } - - // Case 5: channel_alias match - const auto& ca = channel_context.get_channel_alias(); - if (auto ca_location = channel_alias_location(ca); - util::starts_with(default_location, ca_location)) - { - auto name = std::string( - util::strip(util::remove_prefix(default_location, ca_location), '/') + std::string location = chan.location(); + // TODO cannot change all the locations at once since they are used in from_name + // std::string location = chan.url().pretty_str(StripScheme::yes, '/', + // Credentials::Remove); + std::string name = std::string( + util::strip(util::remove_prefix(default_location, location), '/') + ); + return Channel( + /* url= */ std::move(url), + /* location= */ std::move(location), + /* name= */ std::move(name), + /* canonical_name= */ std::string(canonical_name) ); - auto l_url = specs::CondaURL::parse(util::join_url(ca_location, name)); - l_url.set_scheme(url.scheme()); - l_url.set_user(ca.user()); - l_url.set_password(ca.password()); - if (auto token = ca.token(); !token.empty()) - { - l_url.set_token(std::move(token)); - } - return channel_configuration{ - /*. .url= */ std::move(l_url), - /* .location= */ std::move(ca_location), - /* .name= */ name, - /* .canonical_name= */ name, - }; } - - // Case 2: migrated_custom_channels not implemented yet - // Case 3: migrated_channel_aliases not implemented yet - - // Case 1: No path given, channel name is "" - // Case 7: fallback, channel_location = host:port and channel_name = path - auto name = std::string(util::strip(url.path_without_token(), '/')); - auto location = url.authority(specs::CondaURL::Credentials::Remove); - auto canonical_name = url.pretty_str( - util::URL::StripScheme::no, - /* rstrip_path/ */ '/', - specs::CondaURL::Credentials::Remove - ); - return channel_configuration{ - /* .url= */ std::move(url), - /* .location= */ std::move(location), - /* .name= */ std::move(name), - /* .canonical_name= */ std::move(canonical_name), - }; } + + if (const auto& ca = get_channel_alias(); url_match(ca, url)) + { + auto location = ca.pretty_str(StripScheme::yes, '/', Credentials::Remove); + // Overridding url scheme since chan_url could have been defaulted + auto name = std::string(util::strip(util::remove_prefix(default_location, location), '/')); + return Channel( + /*..url= */ std::move(url), + /* location= */ std::move(location), + /* name= */ name, + /* canonical_name= */ name + ); + } + + auto name = std::string(util::strip(url.path_without_token(), '/')); + auto location = url.authority(Credentials::Remove); + auto canonical_name = url.pretty_str(StripScheme::no, '/', Credentials::Remove); + return Channel( + /* url= */ std::move(url), + /* location= */ std::move(location), + /* name= */ std::move(name), + /* canonical_name= */ std::move(canonical_name) + ); + } + + Channel ChannelContext::from_package_url(specs::ChannelSpec&& spec) + { + auto chan = from_any_url(std ::move(spec)); + set_fallback_credential_from_db(chan.m_url, m_context.authentication_info()); + return chan; } Channel ChannelContext::from_url(specs::ChannelSpec&& spec) { - assert(util::url_has_scheme(spec.location())); - auto url = specs::CondaURL::parse(spec.location()); - - auto config = read_channel_configuration(*this, url); - - using Decode = typename specs::CondaURL::Decode; - using Encode = typename specs::CondaURL::Encode; - if (url.user(Decode::no).empty() && !config.url.user(Decode::no).empty()) - { - url.set_user(config.url.clear_user(), Encode::no); - } - if (url.password(Decode::no).empty() && !config.url.password(Decode::no).empty()) - { - url.set_password(config.url.clear_password(), Encode::no); - } - if (url.token().empty() && !config.url.token().empty()) - { - url.set_token(config.url.token()); - } - return Channel( - /* url= */ std::move(url), - /* location= */ std::move(config.location), - /* name= */ std::move(config.name), - /* canonical_name= */ std::move(config.canonical_name) - ); + auto platforms = make_platforms(spec.clear_platform_filters(), m_context.platforms()); + auto chan = from_any_url(std::move(spec)); + chan.m_platforms = std::move(platforms); + set_fallback_credential_from_db(chan.m_url, m_context.authentication_info()); + return chan; } - Channel ChannelContext::from_name(const std::string& name) + Channel ChannelContext::from_name(specs::ChannelSpec&& spec) { - std::string tmp_stripped = name; + std::string name = spec.clear_location(); const auto& custom_channels = get_custom_channels(); const auto it_end = custom_channels.end(); - auto it = custom_channels.find(tmp_stripped); - while (it == it_end) + auto it = custom_channels.find(name); { - const auto pos = tmp_stripped.rfind("/"); - if (pos == std::string::npos) + std::string considered_name = name; + while (it == it_end) { - break; - } - else - { - tmp_stripped = tmp_stripped.substr(0, pos); - it = custom_channels.find(tmp_stripped); + if (const auto pos = considered_name.rfind("/"); pos != std::string::npos) + { + considered_name = considered_name.substr(0, pos); + it = custom_channels.find(considered_name); + } + else + { + break; + } } } if (it != it_end) { + auto url = it->second.url(); // we can have a channel like // testchannel: https://server.com/private/testchannel // where `name == private/testchannel` and we need to join the remaining label part @@ -624,6 +468,7 @@ namespace mamba // Combine names properly if (common_str.empty()) { + url.append_path(name); combined_name += "/" + name; } else @@ -632,33 +477,31 @@ namespace mamba // beginning of `name` and at the end of `combined_name` (I don't know about // other use cases for now) combined_name += name.substr(common_str.size()); + url.append_path(name.substr(common_str.size())); } } + set_fallback_credential_from_db(url, m_context.authentication_info()); return Channel( - /* scheme= */ it->second.scheme(), - /* location= */ it->second.location(), - /* name= */ combined_name, - /* canonical_name= */ name, - /* user= */ it->second.user().value_or(""), - /* password= */ it->second.password().value_or(""), - /* token= */ it->second.token().value_or(""), - /* package_filename= */ it->second.package_filename().value_or("") - ); - } - else - { - const auto& alias = get_channel_alias(); - return Channel( - /* scheme= */ alias.scheme(), - /* location= */ channel_alias_location(alias), - /* name= */ name, - /* canonical_name= */ name, - /* user= */ alias.user(), - /* password= */ alias.password(), - /* token= */ alias.token() + /* url= */ std::move(url), + /* location= */ it->second.location(), + /* name= */ std::move(combined_name), + /* canonical_name= */ std::move(name), + /* platforms= */ make_platforms(spec.clear_platform_filters(), m_context.platforms()) ); } + + const auto& alias = get_channel_alias(); + auto url = alias; + url.append_path(name); + set_fallback_credential_from_db(url, m_context.authentication_info()); + return Channel( + /* url= */ std::move(url), + /* location= */ alias.pretty_str(specs::CondaURL::StripScheme::yes, '/', specs::CondaURL::Credentials::Remove), + /* name= */ name, + /* canonical_name= */ name, + /* platforms= */ make_platforms(spec.clear_platform_filters(), m_context.platforms()) + ); } Channel ChannelContext::from_value(const std::string& in_value) @@ -666,98 +509,51 @@ namespace mamba if (INVALID_CHANNELS.count(in_value) > 0) { return Channel( - /* scheme= */ "", - /* location= */ "", - /* name= */ UNKNOWN_CHANNEL, - /* canonical_name= */ "" + /* url= */ specs::CondaURL{}, + /* location= */ "", + /* name= */ UNKNOWN_CHANNEL, + /* canonical_name= */ UNKNOWN_CHANNEL ); } auto spec = specs::ChannelSpec::parse(in_value); - auto get_platforms = [&]() + switch (spec.type()) { - auto out = spec.platform_filters(); - - if (out.empty()) + case specs::ChannelSpec::Type::PackagePath: { - for (const auto& plat : m_context.platforms()) - { - out.insert(plat); - } + return from_package_path(std::move(spec)); } - return out; - }; - - return [&](specs::ChannelSpec&& l_spec) -> Channel - { - switch (l_spec.type()) + case specs::ChannelSpec::Type::Path: { - case specs::ChannelSpec::Type::PackagePath: - { - return from_package_path(std::move(l_spec)); - } - case specs::ChannelSpec::Type::Path: - { - return from_path(std::move(l_spec)); - } - case specs::ChannelSpec::Type::PackageURL: - { - return from_url(std::move(l_spec)); - } - case specs::ChannelSpec::Type::URL: - { - auto plats = get_platforms(); - auto chan = from_url(std::move(l_spec)); - chan.m_platforms = std::move(plats); - return chan; - } - case specs::ChannelSpec::Type::Name: - { - auto chan = from_name(l_spec.location()); - chan.m_platforms = get_platforms(); - return chan; - } + return from_path(std::move(spec)); } - throw std::invalid_argument("Invalid ChannelSpec::Type"); - }(std::move(spec)); + case specs::ChannelSpec::Type::PackageURL: + { + return from_package_url(std::move(spec)); + } + case specs::ChannelSpec::Type::URL: + { + return from_url(std::move(spec)); + } + case specs::ChannelSpec::Type::Name: + { + return from_name(std::move(spec)); + } + } + throw std::invalid_argument("Invalid ChannelSpec::Type"); } const Channel& ChannelContext::make_channel(const std::string& value) { - auto res = m_channel_cache.find(value); - if (res == m_channel_cache.end()) + if (const auto it = m_channel_cache.find(value); it != m_channel_cache.end()) { - auto chan = from_value(value); - if (!chan.token()) - { - const auto& with_channel = util::join_url( - chan.location(), - chan.name() == UNKNOWN_CHANNEL ? "" : chan.name() - ); - const auto& without_channel = chan.location(); - for (const auto& auth : { with_channel, without_channel }) - { - const auto& authentication_info = m_context.authentication_info(); - auto it = authentication_info.find(auth); - if (it != authentication_info.end() - && std::holds_alternative(it->second)) - { - chan.m_url.set_token(std::get(it->second).token); - break; - } - else if (it != authentication_info.end() && std::holds_alternative(it->second)) - { - const auto& l_auth = std::get(it->second); - chan.m_url.set_user(l_auth.user); - chan.m_url.set_password(l_auth.password); - break; - } - } - } - res = m_channel_cache.insert(std::make_pair(value, std::move(chan))).first; + return it->second; } - return res->second; + + auto [it, inserted] = m_channel_cache.emplace(value, from_value(value)); + assert(inserted); + return it->second; } std::vector @@ -877,31 +673,24 @@ namespace mamba m_custom_multichannels.emplace(LOCAL_CHANNELS_NAME, std::move(local_names)); const auto& context_custom_channels = m_context.custom_channels; - for (const auto& [n, p] : context_custom_channels) + for (const auto& [name, location] : context_custom_channels) { - std::string url = p; - if (!util::starts_with(url, "http")) - { - url = util::path_or_url_to_url(url); - } - - auto channel = make_simple_channel(m_channel_alias, url, n, n); - m_custom_channels.emplace(n, std::move(channel)); + auto channel = from_value(location); + channel.m_canonical_name = name; + m_custom_channels.emplace(name, std::move(channel)); } - auto& multichannels = m_context.custom_multichannels; - for (auto& [multichannelname, urllist] : multichannels) + for (const auto& [multi_name, location_list] : m_context.custom_multichannels) { - std::vector names(urllist.size()); - auto name_iter = names.begin(); - for (auto& url : urllist) + std::vector names = {}; + names.reserve(location_list.size()); + for (auto& location : location_list) { - auto channel = make_simple_channel(m_channel_alias, url, "", multichannelname); - std::string name = channel.name(); - m_custom_channels.emplace(std::move(name), std::move(channel)); - *name_iter++ = url; + auto channel = from_value(location); + // No cannonical name give to mulit_channels + names.push_back(location); } - m_custom_multichannels.emplace(multichannelname, std::move(names)); + m_custom_multichannels.emplace(multi_name, std::move(names)); } /******************* diff --git a/libmamba/src/core/context.cpp b/libmamba/src/core/context.cpp index e7c419729..cf694211e 100644 --- a/libmamba/src/core/context.cpp +++ b/libmamba/src/core/context.cpp @@ -179,7 +179,7 @@ namespace mamba return { platform, "noarch" }; } - Context::authentication_info_map_t& Context::authentication_info() + specs::AuthenticationDataBase& Context::authentication_info() { if (!m_authentication_infos_loaded) { @@ -188,7 +188,7 @@ namespace mamba return m_authentication_info; } - const Context::authentication_info_map_t& Context::authentication_info() const + const specs::AuthenticationDataBase& Context::authentication_info() const { return const_cast(this)->authentication_info(); } @@ -223,9 +223,11 @@ namespace mamba // cut ".token" ending token_url = token_url.substr(0, token_url.size() - 6); - m_authentication_info[token_url] = specs::CondaToken{ read_contents(entry.path() - ) }; LOG_INFO << "Found token for " << token_url << " at " << entry.path(); + m_authentication_info.emplace( + std::move(token_url), + specs::CondaToken{ read_contents(entry.path()) } + ); } } } @@ -241,7 +243,7 @@ namespace mamba infile >> j; for (auto& [key, el] : j.items()) { - std::string const host = key; + const std::string host = key; const auto type = el["type"].get(); specs::AuthenticationInfo info; if (type == "CondaToken") @@ -277,7 +279,7 @@ namespace mamba LOG_INFO << "Found bearer token for host " << host << " in ~/.mamba/auth/authentication.json"; } - m_authentication_info[host] = info; + m_authentication_info.emplace(std::move(host), std::move(info)); } } } diff --git a/libmamba/src/core/download.cpp b/libmamba/src/core/download.cpp index 2e4ae5cff..3887d3328 100644 --- a/libmamba/src/core/download.cpp +++ b/libmamba/src/core/download.cpp @@ -317,10 +317,10 @@ namespace mamba host += ":" + port; } - if (context.authentication_info().count(host)) + const auto& auth_info = context.authentication_info(); + if (auto it = auth_info.find_compatible(host); it != auth_info.end()) { - const auto& auth = context.authentication_info().at(host); - if (std::holds_alternative(auth)) + if (const auto& auth = it->second; std::holds_alternative(auth)) { m_handle.add_header( fmt::format("Authorization: Bearer {}", std::get(auth).token) diff --git a/libmamba/src/core/match_spec.cpp b/libmamba/src/core/match_spec.cpp index 747237e90..f13ff67fb 100644 --- a/libmamba/src/core/match_spec.cpp +++ b/libmamba/src/core/match_spec.cpp @@ -90,11 +90,12 @@ namespace mamba LOG_INFO << "need to expand path!"; spec_str = util::path_or_url_to_url(fs::absolute(env::expand_user(spec_str)).string()); } - auto& parsed_channel = channel_context.make_channel(spec_str); - if (parsed_channel.package_filename()) + const auto& parsed_channel = channel_context.make_channel(spec_str); + + if (auto pkg = parsed_channel.url().package(); !pkg.empty()) { - auto dist = parse_legacy_dist(*parsed_channel.package_filename()); + auto dist = parse_legacy_dist(pkg); name = dist[0]; version = dist[1]; @@ -106,7 +107,7 @@ namespace mamba { subdir = plats.front(); } - fn = *parsed_channel.package_filename(); + fn = std::move(pkg); url = spec_str; is_file = true; } diff --git a/libmamba/src/core/pool.cpp b/libmamba/src/core/pool.cpp index 63e146516..c3b485bcf 100644 --- a/libmamba/src/core/pool.cpp +++ b/libmamba/src/core/pool.cpp @@ -143,7 +143,7 @@ namespace mamba bool channel_match(ChannelContext& channel_context, const Channel& chan, const Channel& needle) { - if ((chan) == needle) + if (chan.base_url() == needle.base_url()) { return true; } @@ -155,7 +155,7 @@ namespace mamba for (auto el : (x->second)) { const Channel& inner = channel_context.make_channel(el); - if ((chan) == inner) + if (chan.base_url() == inner.base_url()) { return true; } diff --git a/libmamba/src/core/solver.cpp b/libmamba/src/core/solver.cpp index d4db23703..c40ea3603 100644 --- a/libmamba/src/core/solver.cpp +++ b/libmamba/src/core/solver.cpp @@ -108,7 +108,7 @@ namespace mamba MatchSpec modified_spec(ms); const Channel& chan = m_pool.channel_context().make_channel(std::string(solvable->channel())); - modified_spec.channel = chan.name(); + modified_spec.channel = chan.canonical_name(); modified_spec.version = solvable->version(); modified_spec.build_string = solvable->build_string(); diff --git a/libmamba/src/core/subdirdata.cpp b/libmamba/src/core/subdirdata.cpp index b22be7463..5aef9e1f7 100644 --- a/libmamba/src/core/subdirdata.cpp +++ b/libmamba/src/core/subdirdata.cpp @@ -380,7 +380,7 @@ namespace mamba { for (const auto& c : channel_context.context().repodata_has_zst) { - if (channel_context.make_channel(c) == channel) + if (channel_context.make_channel(c).base_url() == channel.base_url()) { return true; } diff --git a/libmamba/src/core/transaction.cpp b/libmamba/src/core/transaction.cpp index 9108cdce5..6bbc69849 100644 --- a/libmamba/src/core/transaction.cpp +++ b/libmamba/src/core/transaction.cpp @@ -16,7 +16,6 @@ #include #include -#include "mamba/core/validate.hpp" extern "C" // Incomplete header { #include @@ -34,7 +33,6 @@ extern "C" // Incomplete header #include "mamba/core/pool.hpp" #include "mamba/core/thread_utils.hpp" #include "mamba/core/transaction.hpp" -#include "mamba/core/validate.hpp" #include "mamba/util/flat_set.hpp" #include "mamba/util/string.hpp" #include "solv-cpp/pool.hpp" @@ -849,17 +847,46 @@ namespace mamba solution.actions, [&](const auto& pkg) { - if (ctx.experimental && ctx.validation_params.verify_artifacts) - { - const auto& repo_checker = channel_context.make_channel(pkg.channel) - .repo_checker(ctx, multi_cache); - repo_checker.verify_package( - pkg.json_signable(), - nlohmann::json::parse(pkg.signatures) - ); + // The following was used for the The Update Framework (TUF) / package signing + // proof of concept. + // + // Due to uncertainties on how TUF would be implemented, this was left commented + // out as in was getting in the way of the Channel refactoring. - LOG_DEBUG << "'" << pkg.name << "' trusted from '" << pkg.channel << "'"; - } + // In channel.cpp, repo-checkers were instanciated with the folowing: + // const validation::RepoChecker& + // Channel::repo_checker(Context& context, MultiPackageCache& caches) const + // { + // if (p_repo_checker == nullptr) + // { + // p_repo_checker = std::make_unique( + // context, + // util::rsplit(base_url(), "/", 1).front(), + // context.prefix_params.root_prefix / "etc" / "trusted-repos" + // / util::cache_name_from_url(base_url()), + // caches.first_writable_path() / "cache" / + // util::cache_name_from_url(base_url()) + // ); + // + // fs::create_directories(p_repo_checker->cache_path()); + // p_repo_checker->generate_index_checker(); + // } + // + // return *p_repo_checker; + // } + + // Here, the repo-checker would be fetched the following way: + // if (ctx.experimental && ctx.validation_params.verify_artifacts) + // { + // const auto& repo_checker = channel_context.make_channel(pkg.channel) + // .repo_checker(ctx, multi_cache); + // repo_checker.verify_package( + // pkg.json_signable(), + // nlohmann::json::parse(pkg.signatures) + // ); + // + // LOG_DEBUG << "'" << pkg.name << "' trusted from '" << pkg.channel << "'"; + // } fetchers.emplace_back(pkg, channel_context, multi_cache); } ); diff --git a/libmamba/src/specs/authentication_info.cpp b/libmamba/src/specs/authentication_info.cpp new file mode 100644 index 000000000..3ebcd2b05 --- /dev/null +++ b/libmamba/src/specs/authentication_info.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2019, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include + +#include + +#include "mamba/specs/authentication_info.hpp" +#include "mamba/util/string.hpp" + +namespace mamba::specs +{ + + auto AuthenticationDataBase::at_compatible(const key_type& key) -> mapped_type& + { + if (auto it = find_compatible(key); it != end()) + { + return it->second; + } + throw std::out_of_range(fmt::format(R"(No entry for key "{}")", key)); + } + + auto AuthenticationDataBase::at_compatible(const key_type& key) const -> const mapped_type& + { + return const_cast(this)->at_compatible(key); + } + + auto AuthenticationDataBase::find_compatible(const key_type& key) -> iterator + { + // First we start by adding a final '/' if absent, so that "mamba.org/" can + // be find by query "mamba.org". + std::string maybe_key = {}; + if (!util::ends_with(key, '/')) + { + maybe_key = util::concat(key, '/'); + } + else + { + maybe_key = key; + } + + // We look for successsive parent dirs as queries with and without trailing '/'; + // "mamba.org/channel/" > "mamba.org/channel" > "mamba.org/" > "mamba.org" + auto it = Base::find(maybe_key); + while (it == Base::end()) + { + const auto pos = maybe_key.rfind('/'); + if (maybe_key.empty() || (pos == std::string::npos)) + { + // Nothing else to try + break; + } + else if ((pos + 1) == maybe_key.size()) + { + // Try again without final '/' + maybe_key.erase(pos); + it = Base::find(maybe_key); + } + else + { + // Try again without final element + maybe_key.erase(pos + 1); + it = Base::find(maybe_key); + } + } + return it; + } + + auto AuthenticationDataBase::find_compatible(const key_type& key) const -> const_iterator + { + return static_cast( + const_cast(this)->find_compatible(key) + ); + } + + auto AuthenticationDataBase::contains(const key_type& key) const -> bool + { + return find(key) != end(); + } + + auto AuthenticationDataBase::contains_compatible(const key_type& key) const -> bool + { + return find_compatible(key) != end(); + } +} diff --git a/libmamba/src/specs/conda_url.cpp b/libmamba/src/specs/conda_url.cpp index 99c7b9ad7..e505f88b5 100644 --- a/libmamba/src/specs/conda_url.cpp +++ b/libmamba/src/specs/conda_url.cpp @@ -113,6 +113,11 @@ namespace mamba::specs { } + auto CondaURL::base() const -> const util::URL& + { + return static_cast(*this); + } + auto CondaURL::parse(std::string_view url) -> CondaURL { return CondaURL(URL::parse(url)); @@ -142,6 +147,16 @@ namespace mamba::specs ensure_path_without_token_leading_slash(); } + auto CondaURL::has_token() const -> bool + { + const auto& p = path(Decode::no); + return + // Fast return for easy cases + (p.size() > token_prefix.size()) + // The actual check + && util::starts_with(p, token_prefix); + } + auto CondaURL::token() const -> std::string_view { static constexpr auto npos = std::string_view::npos; @@ -491,7 +506,7 @@ namespace mamba::specs auto operator==(const CondaURL& a, const CondaURL& b) -> bool { - return static_cast(a) == static_cast(b); + return a.base() == b.base(); } auto operator!=(const CondaURL& a, const CondaURL& b) -> bool @@ -510,3 +525,9 @@ namespace mamba::specs return std::move(url); } } + +auto +std::hash::operator()(const mamba::specs::CondaURL& u) const -> std::size_t +{ + return std::hash()(u.base()); +} diff --git a/libmamba/src/util/url.cpp b/libmamba/src/util/url.cpp index a33d040c1..1ffb60550 100644 --- a/libmamba/src/util/url.cpp +++ b/libmamba/src/util/url.cpp @@ -18,6 +18,7 @@ #include "mamba/util/build.hpp" #include "mamba/util/path_manip.hpp" #include "mamba/util/string.hpp" +#include "mamba/util/tuple_hash.hpp" #include "mamba/util/url.hpp" #include "mamba/util/url_manip.hpp" @@ -185,8 +186,8 @@ namespace mamba::util out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or("")); out.set_user(handle.get_part(CURLUPART_USER).value_or(""), Encode::no); out.set_password(handle.get_part(CURLUPART_PASSWORD).value_or(""), Encode::no); - out.set_host(handle.get_part(CURLUPART_HOST).value_or("")); - out.set_path(handle.get_part(CURLUPART_PATH).value_or("/")); + out.set_host(handle.get_part(CURLUPART_HOST).value_or(""), Encode::no); + out.set_path(handle.get_part(CURLUPART_PATH).value_or("/"), Encode::no); out.set_port(handle.get_part(CURLUPART_PORT).value_or("")); out.set_query(handle.get_part(CURLUPART_QUERY).value_or("")); out.set_fragment(handle.get_part(CURLUPART_FRAGMENT).value_or("")); @@ -222,6 +223,11 @@ namespace mamba::util return std::exchange(m_scheme, ""); } + auto URL::has_user() const -> bool + { + return !m_user.empty(); + } + auto URL::user(Decode::no_type) const -> const std::string& { return m_user; @@ -247,6 +253,11 @@ namespace mamba::util return std::exchange(m_user, ""); } + auto URL::has_password() const -> bool + { + return !m_password.empty(); + } + auto URL::password(Decode::no_type) const -> const std::string& { return m_password; @@ -458,27 +469,19 @@ namespace mamba::util if (on_win && (scheme() == "file")) { auto [slashes, no_slash_path] = lstrip_parts(path, '/'); - if (slashes.empty()) - { - slashes = "/"; - } if ((no_slash_path.size() >= 2) && path_has_drive_letter(no_slash_path)) { - m_path = concat( - slashes, - no_slash_path.substr(0, 2), - url_encode(no_slash_path.substr(2), '/') + return set_path( + concat( + slashes.empty() ? "/" : slashes, + no_slash_path.substr(0, 2), + url_encode(no_slash_path.substr(2), '/') + ), + Encode::no ); } - else - { - m_path = concat(slashes, url_encode(no_slash_path, '/')); - } - } - else - { - return set_path(url_encode(path, '/'), Encode::no); } + return set_path(url_encode(path, '/'), Encode::no); } void URL::set_path(std::string path, Encode::no_type) @@ -512,9 +515,9 @@ namespace mamba::util void URL::append_path(std::string_view subpath, Encode::yes_type) { - if (path(Decode::no) == "/") + if (util::lstrip(path(Decode::no), '/').empty()) { - // Allow hanldling of Windows drive letter encoding + // Allow handling of Windows drive letter encoding return set_path(std::string(subpath), Encode::yes); } return append_path(url_encode(subpath, '/'), Encode::no); @@ -625,15 +628,34 @@ namespace mamba::util ); } + namespace + { + auto attrs(URL const& url) + { + return std::tuple< + std::string_view, + const std::string&, + const std::string&, + std::string_view, + const std::string&, + const std::string&, + const std::string&, + const std::string&>{ + url.scheme(), + url.user(URL::Decode::no), + url.password(URL::Decode::no), + url.host(URL::Decode::no), + url.port(), + url.path(URL::Decode::no), + url.query(), + url.fragment(), + }; + } + } + auto operator==(URL const& a, URL const& b) -> bool { - return (a.scheme() == b.scheme()) - && (a.user() == b.user()) - // omitting password, is that desirable? - && (a.host() == b.host()) - // Would it be desirable to account for default ports? - && (a.port() == b.port()) && (a.path() == b.path()) && (a.query() == b.query()) - && (a.fragment() == b.fragment()); + return attrs(a) == attrs(b); } auto operator!=(URL const& a, URL const& b) -> bool @@ -653,3 +675,9 @@ namespace mamba::util } } // namespace mamba + +auto +std::hash::operator()(const mamba::util::URL& u) const -> std::size_t +{ + return mamba::util::hash_tuple(mamba::util::attrs(u)); +} diff --git a/libmamba/src/util/url_manip.cpp b/libmamba/src/util/url_manip.cpp index b1f201a7f..119ac093f 100644 --- a/libmamba/src/util/url_manip.cpp +++ b/libmamba/src/util/url_manip.cpp @@ -126,37 +126,6 @@ namespace mamba::util return out; } - // proper file scheme on Windows is `file:///C:/blabla` - // https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/ - std::string concat_scheme_url(const std::string& scheme, const std::string& location) - { - if (scheme == "file" && location.size() > 1 && location[1] == ':') - { - return util::concat("file:///", location); - } - else - { - return util::concat(scheme, "://", location); - } - } - - std::string build_url( - const std::optional& auth, - const std::string& scheme, - const std::string& base, - bool with_credential - ) - { - if (with_credential && auth) - { - return concat_scheme_url(scheme, util::concat(*auth, "@", base)); - } - else - { - return concat_scheme_url(scheme, base); - } - } - void split_platform( const std::vector& known_platforms, const std::string& url, diff --git a/libmamba/tests/CMakeLists.txt b/libmamba/tests/CMakeLists.txt index 0514cc90b..296e25377 100644 --- a/libmamba/tests/CMakeLists.txt +++ b/libmamba/tests/CMakeLists.txt @@ -37,9 +37,11 @@ set(LIBMAMBA_TEST_SRCS src/util/test_graph.cpp src/util/test_iterator.cpp src/util/test_path_manip.cpp + src/util/test_tuple_hash.cpp src/util/test_url_manip.cpp src/util/test_url.cpp # Implementation of version and matching specs + src/specs/test_authentication_info.cpp src/specs/test_archive.cpp src/specs/test_platform.cpp src/specs/test_conda_url.cpp diff --git a/libmamba/tests/src/core/test_channel.cpp b/libmamba/tests/src/core/test_channel.cpp index 124803e88..775394a93 100644 --- a/libmamba/tests/src/core/test_channel.cpp +++ b/libmamba/tests/src/core/test_channel.cpp @@ -12,6 +12,11 @@ #include "mamba/core/context.hpp" #include "mamba/core/environment.hpp" #include "mamba/util/flat_set.hpp" +#include "mamba/util/string.hpp" +#include "mamba/util/url_manip.hpp" + +#include "doctest-printer/conda_url.hpp" +#include "doctest-printer/flat_set.hpp" #include "mambatests.hpp" @@ -21,10 +26,12 @@ namespace mamba static const std::string platform = std::string(specs::build_platform_name()); using PlatformSet = typename util::flat_set; using UrlSet = typename util::flat_set; + using CondaURL = specs::CondaURL; static_assert(std::is_move_constructible_v); static_assert(std::is_move_assignable_v); + TEST_SUITE("ChannelContext") { TEST_CASE("init") @@ -38,21 +45,18 @@ namespace mamba const auto& custom = channel_context.get_custom_channels(); auto it = custom.find("pkgs/main"); - CHECK_NE(it, custom.end()); - CHECK_EQ(it->second.name(), "pkgs/main"); - CHECK_EQ(it->second.location(), "repo.anaconda.com"); + REQUIRE_NE(it, custom.end()); + CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); CHECK_EQ(it->second.canonical_name(), "defaults"); it = custom.find("pkgs/pro"); - CHECK_NE(it, custom.end()); - CHECK_EQ(it->second.name(), "pkgs/pro"); - CHECK_EQ(it->second.location(), "repo.anaconda.com"); + REQUIRE_NE(it, custom.end()); + CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/pro")); CHECK_EQ(it->second.canonical_name(), "pkgs/pro"); it = custom.find("pkgs/r"); - CHECK_NE(it, custom.end()); - CHECK_EQ(it->second.name(), "pkgs/r"); - CHECK_EQ(it->second.location(), "repo.anaconda.com"); + REQUIRE_NE(it, custom.end()); + CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r")); CHECK_EQ(it->second.canonical_name(), "defaults"); } @@ -71,18 +75,14 @@ namespace mamba const auto& custom = channel_context.get_custom_channels(); auto it = custom.find("pkgs/main"); - CHECK_NE(it, custom.end()); - CHECK_EQ(it->second.name(), "pkgs/main"); - CHECK_EQ(it->second.location(), "repo.anaconda.com"); + REQUIRE_NE(it, custom.end()); + CHECK_EQ(it->second.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); CHECK_EQ(it->second.canonical_name(), "defaults"); std::string value = "conda-forge"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "mydomain.com/channels"); - CHECK_EQ(c.name(), "conda-forge"); + CHECK_EQ(c.url(), CondaURL::parse("https://mydomain.com/channels/conda-forge")); CHECK_EQ(c.canonical_name(), "conda-forge"); - // CHECK_EQ(c.url(), "conda-forge"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); ctx.channel_alias = "https://conda.anaconda.org"; @@ -125,9 +125,7 @@ namespace mamba { std::string value = "test_channel"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "file"); - CHECK_EQ(c.location(), "/tmp"); - CHECK_EQ(c.name(), "test_channel"); + CHECK_EQ(c.url(), CondaURL::parse("file:///tmp/test_channel")); CHECK_EQ(c.canonical_name(), "test_channel"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); const UrlSet exp_urls({ @@ -140,9 +138,7 @@ namespace mamba { std::string value = "some_channel"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "conda.mydomain.xyz"); - CHECK_EQ(c.name(), "some_channel"); + CHECK_EQ(c.url(), CondaURL::parse("https://conda.mydomain.xyz/some_channel")); CHECK_EQ(c.canonical_name(), "some_channel"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); const UrlSet exp_urls({ @@ -261,23 +257,20 @@ namespace mamba const Channel* c1 = x[0]; const Channel* c2 = x[1]; - CHECK_EQ(c1->name(), "pkgs/main"); + CHECK_EQ(c1->url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); const UrlSet exp_urls({ std::string("https://repo.anaconda.com/pkgs/main/") + platform, "https://repo.anaconda.com/pkgs/main/noarch", }); CHECK_EQ(c1->urls(), exp_urls); - CHECK_EQ(c2->name(), "pkgs/r"); + CHECK_EQ(c2->url(), CondaURL::parse("https://repo.anaconda.com/pkgs/r")); const UrlSet exp_urls2({ std::string("https://repo.anaconda.com/pkgs/r/") + platform, "https://repo.anaconda.com/pkgs/r/noarch", }); CHECK_EQ(c2->urls(), exp_urls2); - CHECK_EQ(c1->location(), "repo.anaconda.com"); - CHECK_EQ(c1->scheme(), "https"); - #endif ctx.custom_channels.clear(); } @@ -295,22 +288,20 @@ namespace mamba const Channel* c1 = x[0]; const Channel* c2 = x[1]; - CHECK_EQ(c1->name(), "test/channel"); + CHECK_EQ(c1->url(), CondaURL::parse("https://mamba.com/test/channel")); const UrlSet exp_urls({ std::string("https://mamba.com/test/channel/") + platform, "https://mamba.com/test/channel/noarch", }); CHECK_EQ(c1->urls(), exp_urls); + + CHECK_EQ(c2->url(), CondaURL::parse("https://mamba.com/stable/channel")); const UrlSet exp_urls2({ std::string("https://mamba.com/stable/channel/") + platform, "https://mamba.com/stable/channel/noarch", }); CHECK_EQ(c2->urls(), exp_urls2); - CHECK_EQ(c2->name(), "stable/channel"); - CHECK_EQ(c2->location(), "mamba.com"); - CHECK_EQ(c2->scheme(), "https"); - ctx.custom_channels.clear(); } @@ -329,11 +320,9 @@ namespace mamba CHECK_EQ(custom.size(), 4); auto it = custom.find("conda-bld"); - CHECK_NE(it, custom.end()); - CHECK_EQ(it->second.name(), "conda-bld"); - CHECK_EQ(it->second.location(), env::home_directory()); + REQUIRE_NE(it, custom.end()); + CHECK_EQ(it->second.url(), CondaURL::parse(util::path_to_url(conda_bld_dir.string()))); CHECK_EQ(it->second.canonical_name(), "local"); - CHECK_EQ(it->second.scheme(), "file"); auto local_channels = channel_context.get_channels({ "local" }); CHECK_EQ(local_channels.size(), 1); @@ -358,9 +347,7 @@ namespace mamba { std::string value = "test_channel"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "server.com/private/channels"); - CHECK_EQ(c.name(), "test_channel"); + CHECK_EQ(c.url(), CondaURL::parse("https://server.com/private/channels/test_channel")); CHECK_EQ(c.canonical_name(), "test_channel"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); const UrlSet exp_urls({ @@ -373,9 +360,10 @@ namespace mamba { std::string value = "test_channel/mylabel/xyz"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "server.com/private/channels"); - CHECK_EQ(c.name(), "test_channel/mylabel/xyz"); + CHECK_EQ( + c.url(), + CondaURL::parse("https://server.com/private/channels/test_channel/mylabel/xyz") + ); CHECK_EQ(c.canonical_name(), "test_channel/mylabel/xyz"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); const UrlSet exp_urls({ @@ -387,11 +375,13 @@ namespace mamba } { + // https://github.com/mamba-org/mamba/issues/2553 std::string value = "random/test_channel/pkg"; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "server.com/random/channels"); - CHECK_EQ(c.name(), "random/test_channel/pkg"); + CHECK_EQ( + c.url(), + CondaURL::parse("https://server.com/random/channels/random/test_channel/pkg") + ); CHECK_EQ(c.canonical_name(), "random/test_channel/pkg"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); const UrlSet exp_urls({ @@ -414,9 +404,7 @@ namespace mamba std::string value = "https://repo.mamba.pm/conda-forge"; ChannelContext channel_context{ mambatests::context() }; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "repo.mamba.pm"); - CHECK_EQ(c.name(), "conda-forge"); + CHECK_EQ(c.url(), CondaURL::parse("https://repo.mamba.pm/conda-forge")); CHECK_EQ(c.canonical_name(), "https://repo.mamba.pm/conda-forge"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); } @@ -426,53 +414,33 @@ namespace mamba std::string value = "conda-forge"; ChannelContext channel_context{ mambatests::context() }; const Channel& c = channel_context.make_channel(value); - CHECK_EQ(c.scheme(), "https"); - CHECK_EQ(c.location(), "conda.anaconda.org"); - CHECK_EQ(c.name(), "conda-forge"); + CHECK_EQ(c.url(), CondaURL::parse("https://conda.anaconda.org/conda-forge")); CHECK_EQ(c.canonical_name(), "conda-forge"); CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" })); std::string value2 = "https://repo.anaconda.com/pkgs/main[" + platform + "]"; const Channel& c2 = channel_context.make_channel(value2); - CHECK_EQ(c2.scheme(), "https"); - CHECK_EQ(c2.location(), "repo.anaconda.com"); - CHECK_EQ(c2.name(), "pkgs/main"); + CHECK_EQ(c2.url(), CondaURL::parse("https://repo.anaconda.com/pkgs/main")); CHECK_EQ(c2.canonical_name(), "https://repo.anaconda.com/pkgs/main"); CHECK_EQ(c2.platforms(), PlatformSet({ platform })); std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]"; const Channel& c3 = channel_context.make_channel(value3); - CHECK_EQ(c3.scheme(), c.scheme()); - CHECK_EQ(c3.location(), c.location()); - CHECK_EQ(c3.name(), c.name()); + CHECK_EQ(c3.url(), c.url()); CHECK_EQ(c3.canonical_name(), c.canonical_name()); CHECK_EQ(c3.platforms(), PlatformSet({ platform })); std::string value4 = "/home/mamba/test/channel_b"; const Channel& c4 = channel_context.make_channel(value4); - CHECK_EQ(c4.scheme(), "file"); -#ifdef _WIN32 - std::string driveletter = fs::absolute(fs::u8path("/")).string().substr(0, 1); - CHECK_EQ(c4.location(), driveletter + ":/home/mamba/test"); - CHECK_EQ(c4.canonical_name(), "file:///" + driveletter + ":/home/mamba/test/channel_b"); -#else - CHECK_EQ(c4.location(), "/home/mamba/test"); - CHECK_EQ(c4.canonical_name(), "file:///home/mamba/test/channel_b"); -#endif - CHECK_EQ(c4.name(), "channel_b"); + CHECK_EQ(c4.url(), CondaURL::parse(util::path_to_url(value4))); + CHECK_EQ(c4.canonical_name(), c4.url().pretty_str()); CHECK_EQ(c4.platforms(), PlatformSet({ platform, "noarch" })); - std::string value5 = "/home/mamba/test/channel_b[" + platform + "]"; + std::string path5 = "/home/mamba/test/channel_b"; + std::string value5 = util::concat(path5, '[', platform, ']'); const Channel& c5 = channel_context.make_channel(value5); - CHECK_EQ(c5.scheme(), "file"); -#ifdef _WIN32 - CHECK_EQ(c5.location(), driveletter + ":/home/mamba/test"); - CHECK_EQ(c5.canonical_name(), "file:///" + driveletter + ":/home/mamba/test/channel_b"); -#else - CHECK_EQ(c5.location(), "/home/mamba/test"); - CHECK_EQ(c5.canonical_name(), "file:///home/mamba/test/channel_b"); -#endif - CHECK_EQ(c5.name(), "channel_b"); + CHECK_EQ(c5.url(), CondaURL::parse(util::path_to_url(path5))); + CHECK_EQ(c5.canonical_name(), c5.url().pretty_str()); CHECK_EQ(c5.platforms(), PlatformSet({ platform })); std::string value6a = "http://localhost:8000/conda-forge[noarch]"; @@ -518,12 +486,15 @@ namespace mamba TEST_CASE("add_token") { auto& ctx = mambatests::context(); - ctx.authentication_info()["conda.anaconda.org"] = specs::CondaToken{ "my-12345-token" }; + ctx.authentication_info().emplace( + "conda.anaconda.org", + specs::CondaToken{ "my-12345-token" } + ); ChannelContext channel_context{ ctx }; const auto& chan = channel_context.make_channel("conda-forge[noarch]"); - CHECK_EQ(chan.token(), "my-12345-token"); + CHECK_EQ(chan.url().token(), "my-12345-token"); CHECK_EQ( chan.urls(true), UrlSet({ "https://conda.anaconda.org/t/my-12345-token/conda-forge/noarch" }) @@ -534,15 +505,16 @@ namespace mamba TEST_CASE("add_multiple_tokens") { auto& ctx = mambatests::context(); - ctx.authentication_info()["conda.anaconda.org"] = specs::CondaToken{ "base-token" }; - ctx.authentication_info()["conda.anaconda.org/conda-forge"] = specs::CondaToken{ - "channel-token" - }; + ctx.authentication_info().emplace("conda.anaconda.org", specs::CondaToken{ "base-token" }); + ctx.authentication_info().emplace( + "conda.anaconda.org/conda-forge", + specs::CondaToken{ "channel-token" } + ); ChannelContext channel_context{ ctx }; const auto& chan = channel_context.make_channel("conda-forge[noarch]"); - CHECK_EQ(chan.token(), "channel-token"); + CHECK_EQ(chan.url().token(), "channel-token"); } TEST_CASE("fix_win_file_path") @@ -572,7 +544,7 @@ namespace mamba ChannelContext channel_context{ mambatests::context() }; const Channel& c = channel_context.make_channel("http://localhost:8000/"); CHECK_EQ(c.platform_url("win-64", false), "http://localhost:8000/win-64"); - CHECK_EQ(c.base_url(), "http://localhost:8000"); + CHECK_EQ(c.base_url(), "http://localhost:8000/"); const UrlSet expected_urls({ std::string("http://localhost:8000/") + platform, "http://localhost:8000/noarch" }); CHECK_EQ(c.urls(true), expected_urls); diff --git a/libmamba/tests/src/specs/test_authentication_info.cpp b/libmamba/tests/src/specs/test_authentication_info.cpp new file mode 100644 index 000000000..b01e8972a --- /dev/null +++ b/libmamba/tests/src/specs/test_authentication_info.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2019, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include + +#include "mamba/specs/authentication_info.hpp" + +using namespace mamba::specs; + +TEST_SUITE("specs::AuthticationDataBase") +{ + TEST_CASE("mamba.org") + { + auto db = AuthenticationDataBase{ { "mamba.org", BearerToken{ "mytoken" } } }; + + CHECK(db.contains("mamba.org")); + CHECK_FALSE(db.contains("mamba.org/")); + + CHECK(db.contains_compatible("mamba.org")); + CHECK(db.contains_compatible("mamba.org/")); + CHECK(db.contains_compatible("mamba.org/channel")); + CHECK_FALSE(db.contains_compatible("repo.mamba.org")); + CHECK_FALSE(db.contains_compatible("/folder")); + } + + TEST_CASE("mamba.org/") + { + auto db = AuthenticationDataBase{ { "mamba.org/", BearerToken{ "mytoken" } } }; + + CHECK(db.contains("mamba.org/")); + CHECK_FALSE(db.contains("mamba.org")); + + CHECK(db.contains_compatible("mamba.org")); + CHECK(db.contains_compatible("mamba.org/")); + CHECK(db.contains_compatible("mamba.org/channel")); + CHECK_FALSE(db.contains_compatible("repo.mamba.org/")); + CHECK_FALSE(db.contains_compatible("/folder")); + } + + TEST_CASE("mamba.org/channel") + { + auto db = AuthenticationDataBase{ { "mamba.org/channel", BearerToken{ "mytoken" } } }; + + CHECK(db.contains("mamba.org/channel")); + CHECK_FALSE(db.contains("mamba.org")); + + CHECK_FALSE(db.contains_compatible("mamba.org")); + CHECK_FALSE(db.contains_compatible("mamba.org/")); + CHECK(db.contains_compatible("mamba.org/channel")); + CHECK_FALSE(db.contains_compatible("repo.mamba.org/")); + CHECK_FALSE(db.contains_compatible("/folder")); + } +} diff --git a/libmamba/tests/src/specs/test_conda_url.cpp b/libmamba/tests/src/specs/test_conda_url.cpp index 2120d809e..05c74fce0 100644 --- a/libmamba/tests/src/specs/test_conda_url.cpp +++ b/libmamba/tests/src/specs/test_conda_url.cpp @@ -22,15 +22,18 @@ TEST_SUITE("specs::CondaURL") SUBCASE("https://repo.mamba.pm/folder/file.txt") { url.set_path("/folder/file.txt"); + CHECK_FALSE(url.has_token()); CHECK_EQ(url.token(), ""); CHECK_EQ(url.path_without_token(), "/folder/file.txt"); url.set_token("mytoken"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "mytoken"); CHECK_EQ(url.path_without_token(), "/folder/file.txt"); CHECK_EQ(url.path(), "/t/mytoken/folder/file.txt"); CHECK(url.clear_token()); + CHECK_FALSE(url.has_token()); CHECK_EQ(url.path_without_token(), "/folder/file.txt"); CHECK_EQ(url.path(), "/folder/file.txt"); } @@ -38,6 +41,7 @@ TEST_SUITE("specs::CondaURL") SUBCASE("https://repo.mamba.pm/t/xy-12345678-1234/conda-forge/linux-64") { url.set_path("/t/xy-12345678-1234/conda-forge/linux-64"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "xy-12345678-1234"); CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64"); @@ -45,6 +49,7 @@ TEST_SUITE("specs::CondaURL") { CHECK_THROWS_AS(url.set_token(""), std::invalid_argument); CHECK_THROWS_AS(url.set_token("?fds:g"), std::invalid_argument); + CHECK(url.has_token()); CHECK_EQ(url.token(), "xy-12345678-1234"); CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64"); CHECK_EQ(url.path(), "/t/xy-12345678-1234/conda-forge/linux-64"); @@ -53,6 +58,7 @@ TEST_SUITE("specs::CondaURL") SUBCASE("Clear token") { CHECK(url.clear_token()); + CHECK_FALSE(url.has_token()); CHECK_EQ(url.token(), ""); CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64"); CHECK_EQ(url.path(), "/conda-forge/linux-64"); @@ -61,6 +67,7 @@ TEST_SUITE("specs::CondaURL") SUBCASE("Set token") { url.set_token("abcd"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "abcd"); CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64"); CHECK_EQ(url.path(), "/t/abcd/conda-forge/linux-64"); @@ -70,14 +77,17 @@ TEST_SUITE("specs::CondaURL") SUBCASE("https://repo.mamba.pm/t/xy-12345678-1234-1234-1234-123456789012") { url.set_path("/t/xy-12345678-1234-1234-1234-123456789012"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012"); url.set_token("abcd"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "abcd"); CHECK_EQ(url.path_without_token(), "/"); CHECK_EQ(url.path(), "/t/abcd/"); CHECK(url.clear_token()); + CHECK_FALSE(url.has_token()); CHECK_EQ(url.token(), ""); CHECK_EQ(url.path_without_token(), "/"); CHECK_EQ(url.path(), "/"); @@ -86,9 +96,11 @@ TEST_SUITE("specs::CondaURL") SUBCASE("https://repo.mamba.pm/bar/t/xy-12345678-1234-1234-1234-123456789012/") { url.set_path("/bar/t/xy-12345678-1234-1234-1234-123456789012/"); + CHECK_FALSE(url.has_token()); CHECK_EQ(url.token(), ""); // Not at begining of path url.set_token("abcd"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "abcd"); CHECK_EQ(url.path_without_token(), "/bar/t/xy-12345678-1234-1234-1234-123456789012/"); CHECK_EQ(url.path(), "/t/abcd/bar/t/xy-12345678-1234-1234-1234-123456789012/"); @@ -118,6 +130,7 @@ TEST_SUITE("specs::CondaURL") SUBCASE("Parse") { url = CondaURL::parse("mamba.org/t/xy-12345678-1234-1234-1234-123456789012"); + CHECK(url.has_token()); CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012"); CHECK_EQ(url.path_without_token(), "/"); CHECK_EQ(url.path(), "/t/xy-12345678-1234-1234-1234-123456789012/"); @@ -365,7 +378,7 @@ TEST_SUITE("specs::CondaURL") CHECK_EQ(url.pretty_str(CondaURL::StripScheme::no, '/'), "https://mamba.org/page"); } - SUBCASE("Credentail option") + SUBCASE("Credential option") { CondaURL url = {}; diff --git a/libmamba/tests/src/util/test_tuple_hash.cpp b/libmamba/tests/src/util/test_tuple_hash.cpp new file mode 100644 index 000000000..d87115182 --- /dev/null +++ b/libmamba/tests/src/util/test_tuple_hash.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2023, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include +#include + +#include + +#include "mamba/util/tuple_hash.hpp" + +using namespace mamba::util; + + +TEST_SUITE("util::tuple_hash") +{ + TEST_CASE("hash_tuple") + { + const auto t1 = std::tuple{ 33, std::string("hello") }; + CHECK_NE(hash_tuple(t1), 0); + + // Hash colision are hard to predict, but this is so trivial it is likely a bug if it fails. + const auto t2 = std::tuple{ 0, std::string("hello") }; + CHECK_NE(hash_tuple(t1), hash_tuple(t2)); + + const auto t3 = std::tuple{ std::string("hello"), 33 }; + CHECK_NE(hash_tuple(t1), hash_tuple(t3)); + } +} diff --git a/libmamba/tests/src/util/test_url.cpp b/libmamba/tests/src/util/test_url.cpp index 3eaca7eb9..6e25e5d8b 100644 --- a/libmamba/tests/src/util/test_url.cpp +++ b/libmamba/tests/src/util/test_url.cpp @@ -22,7 +22,9 @@ TEST_SUITE("util::URL") { URL url{}; CHECK_EQ(url.scheme(), URL::https); + CHECK_FALSE(url.has_user()); CHECK_EQ(url.user(), ""); + CHECK_FALSE(url.has_password()); CHECK_EQ(url.password(), ""); CHECK_EQ(url.port(), ""); CHECK_EQ(url.host(), URL::localhost); @@ -60,7 +62,9 @@ TEST_SUITE("util::URL") CHECK_EQ(url.scheme(), "https"); CHECK_EQ(url.host(), "mamba.org"); + CHECK(url.has_user()); CHECK_EQ(url.user(), "user"); + CHECK(url.has_password()); CHECK_EQ(url.password(), "pass:word"); CHECK_EQ(url.port(), "8080"); CHECK_EQ(url.path(), "/folder/file.html"); @@ -110,10 +114,12 @@ TEST_SUITE("util::URL") CHECK_EQ(url.path(), "/C:/folder/file.txt"); if (on_win) { + CHECK_EQ(url.path(URL::Decode::no), "/C:/folder/file.txt"); CHECK_EQ(url.pretty_path(), "C:/folder/file.txt"); } else { + CHECK_EQ(url.path(URL::Decode::no), "/C%3A/folder/file.txt"); CHECK_EQ(url.pretty_path(), "/C:/folder/file.txt"); } } @@ -292,16 +298,10 @@ TEST_SUITE("util::URL") SUBCASE("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333") { + // Not a valid IETF RFC 3986+ URL, but Curl parses it anyways. + // Undefined behavior, no assumptions are made const URL url = URL::parse("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333"); - CHECK_EQ(url.scheme(), "https"); - CHECK_EQ(url.host(), "mamba🆒🔬.org"); - CHECK_EQ(url.path(), "/this/is/a/path/"); - CHECK_EQ(url.pretty_path(), "/this/is/a/path/"); - CHECK_EQ(url.user(), ""); - CHECK_EQ(url.password(), ""); - CHECK_EQ(url.port(), ""); - CHECK_EQ(url.query(), "query=123&xyz=3333"); - CHECK_EQ(url.fragment(), ""); + CHECK_NE(url.host(URL::Decode::no), "mamba%f0%9f%86%92%f0%9f%94%ac.org"); } SUBCASE("file://C:/Users/wolfv/test/document.json") @@ -312,14 +312,8 @@ TEST_SUITE("util::URL") CHECK_EQ(url.scheme(), "file"); CHECK_EQ(url.host(), ""); CHECK_EQ(url.path(), "/C:/Users/wolfv/test/document.json"); - if (on_win) - { - CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json"); - } - else - { - CHECK_EQ(url.pretty_path(), "/C:/Users/wolfv/test/document.json"); - } + CHECK_EQ(url.path(URL::Decode::no), "/C:/Users/wolfv/test/document.json"); + CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json"); CHECK_EQ(url.user(), ""); CHECK_EQ(url.password(), ""); CHECK_EQ(url.port(), ""); @@ -330,19 +324,39 @@ TEST_SUITE("util::URL") SUBCASE("file:///home/wolfv/test/document.json") { - if (!on_win) - { - const URL url = URL::parse("file:///home/wolfv/test/document.json"); - CHECK_EQ(url.scheme(), "file"); - CHECK_EQ(url.host(), ""); - CHECK_EQ(url.path(), "/home/wolfv/test/document.json"); - CHECK_EQ(url.pretty_path(), "/home/wolfv/test/document.json"); - CHECK_EQ(url.user(), ""); - CHECK_EQ(url.password(), ""); - CHECK_EQ(url.port(), ""); - CHECK_EQ(url.query(), ""); - CHECK_EQ(url.fragment(), ""); - } + const URL url = URL::parse("file:///home/wolfv/test/document.json"); + CHECK_EQ(url.scheme(), "file"); + CHECK_EQ(url.host(), ""); + CHECK_EQ(url.path(), "/home/wolfv/test/document.json"); + CHECK_EQ(url.pretty_path(), "/home/wolfv/test/document.json"); + CHECK_EQ(url.user(), ""); + CHECK_EQ(url.password(), ""); + CHECK_EQ(url.port(), ""); + CHECK_EQ(url.query(), ""); + CHECK_EQ(url.fragment(), ""); + } + + SUBCASE("file:///home/great:doc.json") + { + // Not a valid IETF RFC 3986+ URL, but Curl parses it anyways. + // Undefined behavior, no assumptions are made + const URL url = URL::parse("file:///home/great:doc.json"); + CHECK_NE(url.path(URL::Decode::no), "/home/great%3Adoc.json"); + } + + SUBCASE("file:///home/great%3Adoc.json") + { + const URL url = URL::parse("file:///home/great%3Adoc.json"); + CHECK_EQ(url.scheme(), "file"); + CHECK_EQ(url.host(), ""); + CHECK_EQ(url.path(), "/home/great:doc.json"); + CHECK_EQ(url.path(URL::Decode::no), "/home/great%3Adoc.json"); + CHECK_EQ(url.pretty_path(), "/home/great:doc.json"); + CHECK_EQ(url.user(), ""); + CHECK_EQ(url.password(), ""); + CHECK_EQ(url.port(), ""); + CHECK_EQ(url.query(), ""); + CHECK_EQ(url.fragment(), ""); } SUBCASE("https://169.254.0.0/page") @@ -679,4 +693,54 @@ TEST_SUITE("util::URL") } } } + + TEST_CASE("Comparison") + { + URL url{}; + url.set_scheme("https"); + url.set_user("user"); + url.set_password("password"); + url.set_host("mamba.org"); + url.set_port("33"); + url.set_path("/folder/file.html"); + auto other = url; + + CHECK_EQ(url, other); + + SUBCASE("Different scheme") + { + other.set_scheme("ftp"); + CHECK_NE(url, other); + } + + SUBCASE("Different hosts") + { + other.clear_host(); + CHECK_NE(url, other); + } + + SUBCASE("Different users") + { + other.clear_user(); + CHECK_NE(url, other); + } + + SUBCASE("Different passwords") + { + other.clear_password(); + CHECK_NE(url, other); + } + + SUBCASE("Different ports") + { + other.clear_port(); + CHECK_NE(url, other); + } + + SUBCASE("Different path") + { + other.clear_path(); + CHECK_NE(url, other); + } + } } diff --git a/libmamba/tests/src/util/test_url_manip.cpp b/libmamba/tests/src/util/test_url_manip.cpp index 79f60ce35..1a220de74 100644 --- a/libmamba/tests/src/util/test_url_manip.cpp +++ b/libmamba/tests/src/util/test_url_manip.cpp @@ -48,36 +48,6 @@ TEST_SUITE("util::url_manip") ); } - TEST_CASE("concat_scheme_url") - { - auto url = concat_scheme_url("https", "mamba.com"); - CHECK_EQ(url, "https://mamba.com"); - - url = concat_scheme_url("file", "C:/some_folder"); - CHECK_EQ(url, "file:///C:/some_folder"); - - url = concat_scheme_url("file", "some_folder"); - CHECK_EQ(url, "file://some_folder"); - } - - TEST_CASE("build_url") - { - auto url = build_url(std::nullopt, "https", "mamba.com", true); - CHECK_EQ(url, "https://mamba.com"); - - url = build_url(std::nullopt, "https", "mamba.com", false); - CHECK_EQ(url, "https://mamba.com"); - - url = build_url(std::optional("auth"), "https", "mamba.com", false); - CHECK_EQ(url, "https://mamba.com"); - - url = build_url(std::optional("auth"), "https", "mamba.com", true); - CHECK_EQ(url, "https://auth@mamba.com"); - - url = build_url(std::optional(""), "https", "mamba.com", true); - CHECK_EQ(url, "https://@mamba.com"); - } - TEST_CASE("split_platform") { std::string platform_found, cleaned_url; diff --git a/libmambapy/libmambapy/__init__.pyi b/libmambapy/libmambapy/__init__.pyi index 61c04dfc5..c91fa442e 100644 --- a/libmambapy/libmambapy/__init__.pyi +++ b/libmambapy/libmambapy/__init__.pyi @@ -136,11 +136,6 @@ class Channel: ) -> typing.Set[typing.Tuple[str, str]]: ... def urls(self, with_credentials: bool = True) -> typing.Set[str]: ... @property - def auth(self) -> typing.Optional[str]: - """ - :type: typing.Optional[str] - """ - @property def canonical_name(self) -> str: """ :type: str @@ -156,25 +151,10 @@ class Channel: :type: str """ @property - def package_filename(self) -> typing.Optional[str]: - """ - :type: typing.Optional[str] - """ - @property def platforms(self) -> typing.Set[str]: """ :type: typing.Set[str] """ - @property - def scheme(self) -> str: - """ - :type: str - """ - @property - def token(self) -> typing.Optional[str]: - """ - :type: typing.Optional[str] - """ pass class ChannelPriority: diff --git a/libmambapy/src/main.cpp b/libmambapy/src/main.cpp index f7b5fd11a..f2536c75a 100644 --- a/libmambapy/src/main.cpp +++ b/libmambapy/src/main.cpp @@ -1147,12 +1147,8 @@ PYBIND11_MODULE(bindings, m) ); } )) - .def_property_readonly("scheme", &Channel::scheme) .def_property_readonly("location", &Channel::location) .def_property_readonly("name", &Channel::name) - .def_property_readonly("auth", &Channel::auth) - .def_property_readonly("token", &Channel::token) - .def_property_readonly("package_filename", &Channel::package_filename) .def_property_readonly("platforms", &Channel::platforms) .def_property_readonly("canonical_name", &Channel::canonical_name) .def("urls", &Channel::urls, py::arg("with_credentials") = true) diff --git a/mamba/mamba/api.py b/mamba/mamba/api.py index ed650b2d8..9bf2608b7 100644 --- a/mamba/mamba/api.py +++ b/mamba/mamba/api.py @@ -106,7 +106,7 @@ class MambaSolver: pkg_cache_path = self.context.pkgs_dirs package_cache = libmambapy.MultiPackageCache(pkg_cache_path) - return libmambapy.Transaction(api_solver, package_cache) + return libmambapy.Transaction(self.pool, api_solver, package_cache) def install( diff --git a/mamba/mamba/utils.py b/mamba/mamba/utils.py index 3ee01ea9a..e6589f6d0 100644 --- a/mamba/mamba/utils.py +++ b/mamba/mamba/utils.py @@ -263,19 +263,8 @@ def init_api_context(use_mamba_experimental=False): def to_conda_channel(channel, platform): - if channel.scheme == "file": - return CondaChannel.from_value( - channel.platform_url(platform, with_credentials=False) - ) - - return CondaChannel( - channel.scheme, - channel.auth, - channel.location, - channel.token, - channel.name, - platform, - channel.package_filename, + return CondaChannel.from_value( + channel.platform_url(platform, with_credentials=False) ) diff --git a/micromamba/src/env.cpp b/micromamba/src/env.cpp index 18fc07440..fe7df2eb7 100644 --- a/micromamba/src/env.cpp +++ b/micromamba/src/env.cpp @@ -186,7 +186,7 @@ set_env_command(CLI::App* com, Configuration& config) dependencies << "- "; if (channel_subdir) { - dependencies << channel.name() << "/" << v.subdir << "::"; + dependencies << channel.canonical_name() << "/" << v.subdir << "::"; } dependencies << v.name << "=" << v.version; if (!no_build) @@ -200,7 +200,7 @@ set_env_command(CLI::App* com, Configuration& config) dependencies << "\n"; } - channels.insert(channel.name()); + channels.insert(channel.canonical_name()); } for (const auto& c : channels)