Resolve ChannelSpec into a Channel (#2899)

* Change URL custom_channel matcher

* Cannot change location at once

* Simpler channel_alias_location

* Change URL channel_alias matcher

* Refactor ChannelContext::from_url

* Add Windows URL path encoding test

* Simplify URL::set_path

* Fix URL encoding parsing

* Refactor platforms urls

* Fix missing forward declaration

* Fix Win file URL encoding

* Split Channel url functions

* Remove no-URL ctor from ChannelContext::from_name

* Remove no-URL ctor from ChannelContext::make_simple_channel

* Remove old Channel ctor

* Remove channel_alias_location

* Refactor Channel::base_url

* Try not to use location mamba.py

* Remove Channel comparison

* Try Channel::platform_url

* Refactor Channel::platform_urls to use platform_url

* Remove build_url

* Remove concat_scheme_url

* Use spec in from_name

* Tuple equality for URL

* Add tuple_hash

* Add std::hash<mamba::util::URL>

* Make CondaURL hashable

* Refactor make_channel

* New algorithm for authetification map

* Add specs::AutheticationDataBase

* Fix typo authentification > authentication

* Use AuthenticationDataBase

* Fix AuthenticationDataBase

* use AuthenticationDataBase::find_compatible

* Use canonical_name

* Add Credential check to URL

* Simplify crendential check in channel

* Refactor AuthDB credential setters

* No Channel::location in tests

* Remove Channel user scheme password

* Document AuthenticationInfo

* Remove Channel token

* Remove Channel::auth

* Remove Channel::package_name

* More replacement name > canonical_name

* Remove unused function

* No credential from URLs

* Remove unneeded header

* Disable Channel::repo_checker

This is disbled as TUF implementation is not well defined and this
function is not used except in experimental conditions.

* Apply auth info as needed

* Simplify custom_channels instanciation

* Simplify custom_multi_channels instanciation

* Review improvements
This commit is contained in:
Antoine Prouvost 2023-10-25 19:06:46 +02:00 committed by GitHub
parent f23e078f2c
commit c7aba975b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 916 additions and 811 deletions

View File

@ -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

View File

@ -8,8 +8,6 @@
#define MAMBA_CORE_CHANNEL_HPP
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
@ -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<std::string>& platforms() const;
std::optional<std::string> auth() const;
std::optional<std::string> user() const;
std::optional<std::string> password() const;
std::optional<std::string> token() const;
std::optional<std::string> 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<std::pair<std::string, std::string>>
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<std::string> platforms = {}
);
Channel(
specs::CondaURL url,
std::string location,
@ -86,17 +65,12 @@ namespace mamba
util::flat_set<std::string> 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<std::string> m_platforms;
// This is used to make sure that there is a unique repo for every channel
mutable std::unique_ptr<validation::RepoChecker> 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<std::string, Channel>;
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);
};

View File

@ -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<std::string, specs::AuthenticationInfo>;
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<fs::u8path> 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<std::string, specs::AuthenticationInfo> m_authentication_info;
specs::AuthenticationDataBase m_authentication_info;
bool m_authentication_infos_loaded = false;
std::shared_ptr<Logger> logger;

View File

@ -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;

View File

@ -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 <class T>
inline bool vector_is_prefix(const std::vector<T>& prefix, const std::vector<T>& vec)
{
return vec.size() >= prefix.size()
&& prefix.end() == std::mismatch(prefix.begin(), prefix.end(), vec.begin()).first;
}
tl::expected<std::string, mamba_error> encode_base64(std::string_view input);
tl::expected<std::string, mamba_error> decode_base64(std::string_view input);

View File

@ -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 <string>
#include <unordered_map>
#include <variant>
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<BasicHTTPAuthentication, BearerToken, CondaToken>;
/**
* 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<std::string, AuthenticationInfo>
{
public:
using Base = std::unordered_map<std::string, AuthenticationInfo>;
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

View File

@ -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 <string>
#include <variant>
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<BasicHTTPAuthentication, BearerToken, CondaToken>;
}
#endif

View File

@ -7,6 +7,7 @@
#ifndef MAMBA_SPECS_CONDA_URL_HPP
#define MAMBA_SPECS_CONDA_URL_HPP
#include <functional>
#include <string_view>
#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<mamba::specs::CondaURL>
{
auto operator()(const mamba::specs::CondaURL& p) const -> std::size_t;
};
#endif

View File

@ -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 <functional>
#include <tuple>
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 <class T, typename Hasher = std::hash<T>>
constexpr void hash_combine_val(std::size_t& seed, const T& val, const Hasher& hasher = {})
{
hash_combine(seed, hasher(val));
}
template <typename... T>
constexpr auto hash_vals(const T&... vals) -> std::size_t
{
std::size_t seed = 0;
(hash_combine_val(seed, vals), ...);
return seed;
}
template <typename... T>
constexpr auto hash_tuple(const std::tuple<T...>& t) -> std::size_t
{
return std::apply([](const auto&... vals) { return hash_vals(vals...); }, t);
}
template <typename... T>
struct Tuplehasher
{
constexpr auto operator()(const std::tuple<T...>& t) const -> std::size_t
{
return hash_tuple(t);
}
};
}
#endif

View File

@ -8,6 +8,7 @@
#define MAMBA_UTIL_URL_HPP
#include <array>
#include <functional>
#include <string>
#include <string_view>
@ -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<mamba::util::URL>
{
auto operator()(const mamba::util::URL& p) const -> std::size_t;
};
#endif

View File

@ -7,7 +7,6 @@
#ifndef MAMBA_UTIL_URL_MANIP_HPP
#define MAMBA_UTIL_URL_MANIP_HPP
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@ -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<std::string>& auth,
const std::string& scheme,
const std::string& base,
bool with_credential
);
void split_platform(
const std::vector<std::string>& known_platforms,
const std::string& url,

View File

@ -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));
}

View File

@ -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);
}

View File

@ -38,19 +38,6 @@ namespace mamba
const char LOCAL_CHANNELS_NAME[] = "local";
const char DEFAULT_CHANNELS_NAME[] = "defaults";
std::optional<std::string> nonempty_str(std::string&& s)
{
return s.empty() ? std::optional<std::string>() : 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<std::string> 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<std::string> 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<std::string> Channel::auth() const
{
return nonempty_str(m_url.authentication());
}
std::optional<std::string> Channel::user() const
{
return nonempty_str(m_url.user());
}
std::optional<std::string> Channel::password() const
{
return nonempty_str(m_url.password());
}
std::optional<std::string> Channel::token() const
{
return nonempty_str(std::string(m_url.token()));
}
std::optional<std::string> 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<validation::RepoChecker>(
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<std::string> 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<std::string>{};
@ -242,51 +118,29 @@ namespace mamba
util::flat_set<std::pair<std::string, std::string>>
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<std::pair<std::string, std::string>>{};
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<decltype(info)>;
if constexpr (std::is_same_v<Info, specs::BasicHTTPAuthentication>)
{
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<Info, specs::CondaToken>)
{
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<specs::CondaToken>(it->second))
{
chan.m_url.set_token(std::get<specs::CondaToken>(it->second).token);
break;
}
else if (it != authentication_info.end() && std::holds_alternative<specs::BasicHTTPAuthentication>(it->second))
{
const auto& l_auth = std::get<specs::BasicHTTPAuthentication>(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<const Channel*>
@ -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<std::string> names(urllist.size());
auto name_iter = names.begin();
for (auto& url : urllist)
std::vector<std::string> 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));
}
/*******************

View File

@ -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<Context*>(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<std::string_view>();
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));
}
}
}

View File

@ -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<specs::BearerToken>(auth))
if (const auto& auth = it->second; std::holds_alternative<specs::BearerToken>(auth))
{
m_handle.add_header(
fmt::format("Authorization: Bearer {}", std::get<specs::BearerToken>(auth).token)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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;
}

View File

@ -16,7 +16,6 @@
#include <fmt/ostream.h>
#include <solv/selection.h>
#include "mamba/core/validate.hpp"
extern "C" // Incomplete header
{
#include <solv/conda.h>
@ -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<validation::RepoChecker>(
// 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);
}
);

View File

@ -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 <stdexcept>
#include <fmt/format.h>
#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<AuthenticationDataBase*>(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_iterator>(
const_cast<AuthenticationDataBase*>(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();
}
}

View File

@ -113,6 +113,11 @@ namespace mamba::specs
{
}
auto CondaURL::base() const -> const util::URL&
{
return static_cast<const util::URL&>(*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<const util::URL&>(a) == static_cast<const util::URL&>(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<mamba::specs::CondaURL>::operator()(const mamba::specs::CondaURL& u) const -> std::size_t
{
return std::hash<mamba::util::URL>()(u.base());
}

View File

@ -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<mamba::util::URL>::operator()(const mamba::util::URL& u) const -> std::size_t
{
return mamba::util::hash_tuple(mamba::util::attrs(u));
}

View File

@ -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<std::string>& 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<std::string>& known_platforms,
const std::string& url,

View File

@ -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

View File

@ -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<std::string>;
using UrlSet = typename util::flat_set<std::string>;
using CondaURL = specs::CondaURL;
static_assert(std::is_move_constructible_v<mamba::Channel>);
static_assert(std::is_move_assignable_v<mamba::Channel>);
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);

View File

@ -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 <doctest/doctest.h>
#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"));
}
}

View File

@ -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 = {};

View File

@ -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 <string>
#include <tuple>
#include <doctest/doctest.h>
#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));
}
}

View File

@ -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);
}
}
}

View File

@ -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<std::string>("auth"), "https", "mamba.com", false);
CHECK_EQ(url, "https://mamba.com");
url = build_url(std::optional<std::string>("auth"), "https", "mamba.com", true);
CHECK_EQ(url, "https://auth@mamba.com");
url = build_url(std::optional<std::string>(""), "https", "mamba.com", true);
CHECK_EQ(url, "https://@mamba.com");
}
TEST_CASE("split_platform")
{
std::string platform_found, cleaned_url;

View File

@ -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:

View File

@ -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)

View File

@ -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(

View File

@ -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)
)

View File

@ -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)