mirror of https://github.com/mamba-org/mamba.git
Add CondaURL (#2805)
* No build pattern in util::URL * URL move string for already encoded operations * Add exclusion characters to util::url_encode * Add encoding of URL path * Simplify URL file empty host handling * Add raw URL::str * Add path_get_drive_letter * Url encode with char exclude * Handle Windows drive encoding * Add URL::clear_xxx * Add CondaURL * Add specs::known_platforms * Fix CondaURL::token * Add CondaURL::platform * Fix pybind tests * Add CondaURL::package * Rename specs/url.hpp > specs/conda_url.hpp
This commit is contained in:
parent
69a2cd30e2
commit
a527511c14
|
@ -70,6 +70,7 @@ QualifierAlignment: Custom # Experimental
|
|||
QualifierOrder: [inline, static, constexpr, const, volatile, type]
|
||||
ReflowComments: 'true'
|
||||
SortIncludes: CaseInsensitive
|
||||
SortUsingDeclarations: Never
|
||||
SpaceAfterCStyleCast: 'true'
|
||||
SpaceAfterTemplateKeyword: 'true'
|
||||
SpaceBeforeAssignmentOperators: 'true'
|
||||
|
|
|
@ -76,6 +76,7 @@ jobs:
|
|||
cache-environment: true
|
||||
create-args: >-
|
||||
conda-build
|
||||
pre-commit
|
||||
python=${{ matrix.python_version }}
|
||||
- uses: hendrikmuhs/ccache-action@main
|
||||
with:
|
||||
|
|
|
@ -137,6 +137,7 @@ set(LIBMAMBA_SOURCES
|
|||
# Implementation of version and matching specs
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/archive.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp
|
||||
|
@ -226,6 +227,7 @@ set(LIBMAMBA_PUBLIC_HEADERS
|
|||
# Implementation of version and matching specs
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/archive.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/platform.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version_spec.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/repo_data.hpp
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
// 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_CONDA_URL_HPP
|
||||
#define MAMBA_SPECS_CONDA_URL_HPP
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "mamba/specs/platform.hpp"
|
||||
#include "mamba/util/url.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
class CondaURL : private util::URL
|
||||
{
|
||||
using Base = typename util::URL;
|
||||
|
||||
public:
|
||||
|
||||
using Base::StripScheme;
|
||||
using Base::HidePassword;
|
||||
using Base::Encode;
|
||||
using Base::Decode;
|
||||
|
||||
using Base::https;
|
||||
using Base::localhost;
|
||||
inline static constexpr std::string_view token_prefix = "/t/";
|
||||
|
||||
[[nodiscard]] static auto parse(std::string_view url) -> CondaURL;
|
||||
|
||||
/** Create a local URL. */
|
||||
CondaURL() = default;
|
||||
|
||||
using Base::scheme;
|
||||
using Base::set_scheme;
|
||||
using Base::user;
|
||||
using Base::set_user;
|
||||
using Base::clear_user;
|
||||
using Base::password;
|
||||
using Base::set_password;
|
||||
using Base::clear_password;
|
||||
using Base::authentication;
|
||||
using Base::host;
|
||||
using Base::set_host;
|
||||
using Base::clear_host;
|
||||
using Base::port;
|
||||
using Base::set_port;
|
||||
using Base::clear_port;
|
||||
using Base::authority;
|
||||
using Base::path;
|
||||
using Base::set_path;
|
||||
using Base::clear_path;
|
||||
using Base::append_path;
|
||||
using Base::query;
|
||||
using Base::set_query;
|
||||
using Base::clear_query;
|
||||
using Base::fragment;
|
||||
using Base::set_fragment;
|
||||
using Base::clear_fragment;
|
||||
|
||||
/** Return the Conda token, as delimited with "/t/", or empty if there isn't any. */
|
||||
[[nodiscard]] auto token() const -> std::string_view;
|
||||
|
||||
/** Set a token if the URL already contains one, or throw an error. */
|
||||
void set_token(std::string_view token);
|
||||
|
||||
/** Clear the token and return ``true`` if it exists, otherwise return ``false``. */
|
||||
auto clear_token() -> bool;
|
||||
|
||||
/** Return the platform if part of the URL path. */
|
||||
[[nodiscard]] auto platform() const -> std::optional<Platform>;
|
||||
|
||||
/**
|
||||
* Return the platform if part of the URL path, or empty.
|
||||
*
|
||||
* If a platform is found, it is returned as a view onto the path without normalization
|
||||
* (for instance the capitalization isn't changed).
|
||||
*/
|
||||
[[nodiscard]] auto platform_name() const -> std::string_view;
|
||||
|
||||
/** Set the platform if the URL already contains one, or throw an error. */
|
||||
void set_platform(Platform platform);
|
||||
|
||||
/**
|
||||
* Set the platform if the URL already contains one, or throw an error.
|
||||
*
|
||||
* If the input @p platform is a valid platform, it is inserted as it is into the path
|
||||
* (for instance the capitalization isn't changed).
|
||||
*/
|
||||
void set_platform(std::string_view platform);
|
||||
|
||||
/** Clear the token and return true if it exists, otherwise return ``false``. */
|
||||
auto clear_platform() -> bool;
|
||||
|
||||
/**
|
||||
* Return the encoded package name, or empty otherwise.
|
||||
*
|
||||
* Package name are at the end of the path and end with a archive extension.
|
||||
*
|
||||
* @see has_archive_extension
|
||||
*/
|
||||
[[nodiscard]] auto package(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/**
|
||||
* Return the decoded package name, or empty otherwise.
|
||||
*
|
||||
* Package name are at the end of the path and end with a archive extension.
|
||||
*
|
||||
* @see has_archive_extension
|
||||
*/
|
||||
[[nodiscard]] auto package(Decode::no_type) const -> std::string_view;
|
||||
|
||||
/**
|
||||
* Change the package file name with a not encoded value.
|
||||
*
|
||||
* If a package file name is present, replace it, otherwise add it at the end
|
||||
* of the path.
|
||||
*/
|
||||
void set_package(std::string_view pkg, Encode::yes_type = Encode::yes);
|
||||
|
||||
/**
|
||||
* Change the package file name with already encoded value.
|
||||
*
|
||||
* If a package file name is present, replace it, otherwise add it at the end
|
||||
* of the path.
|
||||
*/
|
||||
void set_package(std::string_view pkg, Encode::no_type);
|
||||
|
||||
/** Clear the package and return true if it exists, otherwise return ``false``. */
|
||||
auto clear_package() -> bool;
|
||||
|
||||
private:
|
||||
|
||||
explicit CondaURL(URL&& url);
|
||||
void set_platform_no_check_input(std::string_view platform);
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -8,6 +8,7 @@
|
|||
#ifndef MAMBA_SPECS_PLATFORM_HPP
|
||||
#define MAMBA_SPECS_PLATFORM_HPP
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -15,16 +16,22 @@
|
|||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* All platforms known to Conda.
|
||||
*
|
||||
* When one platform name is the substring of another, the longest appears first so that
|
||||
* it makes it easier to use in a parser.
|
||||
*/
|
||||
enum class Platform
|
||||
{
|
||||
noarch,
|
||||
noarch = 0,
|
||||
linux_32,
|
||||
linux_64,
|
||||
linux_armv6l,
|
||||
linux_armv7l,
|
||||
linux_aarch64,
|
||||
linux_ppc64,
|
||||
linux_ppc64le,
|
||||
linux_ppc64,
|
||||
linux_s390x,
|
||||
linux_riscv32,
|
||||
linux_riscv64,
|
||||
|
@ -33,12 +40,24 @@ namespace mamba::specs
|
|||
win_32,
|
||||
win_64,
|
||||
win_arm64,
|
||||
|
||||
// For reflexion purposes only
|
||||
count_,
|
||||
};
|
||||
|
||||
constexpr auto known_platforms_count() -> std::size_t
|
||||
{
|
||||
return static_cast<std::size_t>(Platform::count_);
|
||||
}
|
||||
|
||||
constexpr auto known_platforms() -> std::array<Platform, known_platforms_count()>;
|
||||
|
||||
constexpr auto known_platform_names() -> std::array<std::string_view, known_platforms_count()>;
|
||||
|
||||
/**
|
||||
* Convert the enumeration to its conda string.
|
||||
*/
|
||||
auto platform_name(Platform p) -> std::string_view;
|
||||
constexpr auto platform_name(Platform p) -> std::string_view;
|
||||
|
||||
/**
|
||||
* Return the enum matching the platform name.
|
||||
|
@ -61,5 +80,71 @@ namespace mamba::specs
|
|||
*/
|
||||
void from_json(const nlohmann::json& j, Platform& p);
|
||||
|
||||
/********************
|
||||
* Implementation *
|
||||
********************/
|
||||
|
||||
constexpr auto platform_name(Platform p) -> std::string_view
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case Platform::noarch:
|
||||
return "noarch";
|
||||
case Platform::linux_32:
|
||||
return "linux-32";
|
||||
case Platform::linux_64:
|
||||
return "linux-64";
|
||||
case Platform::linux_armv6l:
|
||||
return "linux-armv6l";
|
||||
case Platform::linux_armv7l:
|
||||
return "linux-armv7l";
|
||||
case Platform::linux_aarch64:
|
||||
return "linux-aarch64";
|
||||
case Platform::linux_ppc64:
|
||||
return "linux-ppc64";
|
||||
case Platform::linux_ppc64le:
|
||||
return "linux-ppc64le";
|
||||
case Platform::linux_s390x:
|
||||
return "linux-s390x";
|
||||
case Platform::linux_riscv32:
|
||||
return "linux-riscv32";
|
||||
case Platform::linux_riscv64:
|
||||
return "linux-riscv64";
|
||||
case Platform::osx_64:
|
||||
return "osx-64";
|
||||
case Platform::osx_arm64:
|
||||
return "osx-arm64";
|
||||
case Platform::win_32:
|
||||
return "win-32";
|
||||
case Platform::win_64:
|
||||
return "win-64";
|
||||
case Platform::win_arm64:
|
||||
return "win-arm64";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr auto known_platforms() -> std::array<Platform, known_platforms_count()>
|
||||
{
|
||||
auto out = std::array<Platform, known_platforms_count()>{};
|
||||
for (std::size_t idx = 0; idx < out.size(); ++idx)
|
||||
{
|
||||
out[idx] = static_cast<Platform>(idx);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr auto known_platform_names() -> std::array<std::string_view, known_platforms_count()>
|
||||
{
|
||||
auto out = std::array<std::string_view, known_platforms_count()>{};
|
||||
auto iter = out.begin();
|
||||
for (auto p : known_platforms())
|
||||
{
|
||||
*(iter++) = platform_name(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef MAMBA_UTIL_PATH_MANIP_HPP
|
||||
#define MAMBA_UTIL_PATH_MANIP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -25,6 +26,11 @@ namespace mamba::util
|
|||
*/
|
||||
[[nodiscard]] auto is_explicit_path(std::string_view input) -> bool;
|
||||
|
||||
/**
|
||||
* Return the path drive letter, if any, or none.
|
||||
*/
|
||||
[[nodiscard]] auto path_get_drive_letter(std::string_view path) -> std::optional<char>;
|
||||
|
||||
/**
|
||||
* Check if a Windows path (not URL) starts with a drive letter.
|
||||
*/
|
||||
|
|
|
@ -82,6 +82,8 @@ namespace mamba::util
|
|||
bool ends_with(std::string_view str, std::string_view::value_type c);
|
||||
|
||||
bool contains(std::string_view str, std::string_view sub_str);
|
||||
bool contains(std::string_view str, char c);
|
||||
bool contains(char c1, char c2);
|
||||
|
||||
/**
|
||||
* Check if any of the strings starts with the prefix.
|
||||
|
|
|
@ -24,7 +24,11 @@ namespace mamba::util
|
|||
// clang-format off
|
||||
enum class StripScheme : bool { no, yes };
|
||||
enum class HidePassword : bool { no, yes };
|
||||
enum class Encode : bool { no, yes };
|
||||
struct Encode
|
||||
{
|
||||
inline static constexpr struct yes_type {} yes = {};
|
||||
inline static constexpr struct no_type {} no = {};
|
||||
};
|
||||
struct Decode
|
||||
{
|
||||
inline static constexpr struct yes_type {} yes = {};
|
||||
|
@ -57,7 +61,7 @@ namespace mamba::util
|
|||
[[nodiscard]] auto scheme() const -> const std::string&;
|
||||
|
||||
/** Set a non-empty scheme. */
|
||||
auto set_scheme(std::string_view scheme) -> URL&;
|
||||
void set_scheme(std::string_view scheme);
|
||||
|
||||
/** Return the encoded user, or empty if none. */
|
||||
[[nodiscard]] auto user(Decode::no_type) const -> const std::string&;
|
||||
|
@ -65,8 +69,14 @@ namespace mamba::util
|
|||
/** Retrun the decoded user, or empty if none. */
|
||||
[[nodiscard]] auto user(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/** Set or clear the user. */
|
||||
auto set_user(std::string_view user, Encode encode = Encode::yes) -> URL&;
|
||||
/** Set the user from a not encoded value. */
|
||||
void set_user(std::string_view user, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Set the user from an already encoded value. */
|
||||
void set_user(std::string user, Encode::no_type);
|
||||
|
||||
/** Clear and return the encoded user. */
|
||||
auto clear_user() -> std::string;
|
||||
|
||||
/** Return the encoded password, or empty if none. */
|
||||
[[nodiscard]] auto password(Decode::no_type) const -> const std::string&;
|
||||
|
@ -74,82 +84,133 @@ namespace mamba::util
|
|||
/** Return the decoded password, or empty if none. */
|
||||
[[nodiscard]] auto password(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/** Set or clear the password. */
|
||||
auto set_password(std::string_view password, Encode encode = Encode::yes) -> URL&;
|
||||
/** Set the password from a not encoded value. */
|
||||
void set_password(std::string_view password, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Set the password from an already encoded value. */
|
||||
void set_password(std::string password, Encode::no_type);
|
||||
|
||||
/** Clear and return the encoded password. */
|
||||
auto clear_password() -> std::string;
|
||||
|
||||
/** Return the encoded basic authentication string. */
|
||||
[[nodiscard]] auto authentication() const -> std::string;
|
||||
|
||||
/** Return the encoded host, always non-empty. */
|
||||
[[nodiscard]] auto host(Decode::no_type) const -> const std::string&;
|
||||
/** Return the encoded host, always non-empty except for file scheme. */
|
||||
[[nodiscard]] auto host(Decode::no_type) const -> std::string_view;
|
||||
|
||||
/** Return the decoded host, always non-empty. */
|
||||
/** Return the decoded host, always non-empty except for file scheme. */
|
||||
[[nodiscard]] auto host(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/** Set a non-empty host. */
|
||||
auto set_host(std::string_view host, Encode encode = Encode::yes) -> URL&;
|
||||
/** Set the host from a not encoded value. */
|
||||
void set_host(std::string_view host, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Set the host from an already encoded value. */
|
||||
void set_host(std::string host, Encode::no_type);
|
||||
|
||||
/** Clear and return the encoded hostname. */
|
||||
auto clear_host() -> std::string;
|
||||
|
||||
/** Return the port, or empty if none. */
|
||||
[[nodiscard]] auto port() const -> const std::string&;
|
||||
|
||||
/** Set or clear the port. */
|
||||
auto set_port(std::string_view port) -> URL&;
|
||||
void set_port(std::string_view port);
|
||||
|
||||
/** Clear and return the port number. */
|
||||
auto clear_port() -> std::string;
|
||||
|
||||
/** Return the encoded autority part of the URL. */
|
||||
[[nodiscard]] auto authority() const -> std::string;
|
||||
|
||||
/** Return the path, always starts with a '/'. */
|
||||
[[nodiscard]] auto path() const -> const std::string&;
|
||||
/** Return the encoded path, always starts with a '/'. */
|
||||
[[nodiscard]] auto path(Decode::no_type) const -> const std::string&;
|
||||
|
||||
/** Return the decoded path, always starts with a '/'. */
|
||||
[[nodiscard]] auto path(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/**
|
||||
* Return the path.
|
||||
* Set the path from a not encoded value.
|
||||
*
|
||||
* All '/' are not encoded but interpreted as separators.
|
||||
* On windows with a file scheme, the colon after the drive letter is not encoded.
|
||||
* A leading '/' is added if abscent.
|
||||
*/
|
||||
void set_path(std::string_view path, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Set the path from an already encoded value, a leading '/' is added if abscent. */
|
||||
void set_path(std::string path, Encode::no_type);
|
||||
|
||||
/** Clear the path and return the encoded path, always starts with a '/'. */
|
||||
auto clear_path() -> std::string;
|
||||
|
||||
/**
|
||||
* Return the decoded path.
|
||||
*
|
||||
* For a "file" scheme, with a Windows path containing a drive, the leading '/' is
|
||||
* stripped.
|
||||
*/
|
||||
[[nodiscard]] auto pretty_path() const -> std::string_view;
|
||||
|
||||
/** Set the path, a leading '/' is added if abscent. */
|
||||
auto set_path(std::string_view path) -> URL&;
|
||||
[[nodiscard]] auto pretty_path() const -> std::string;
|
||||
|
||||
/**
|
||||
* Append a sub path to the current path.
|
||||
* Append a not encoded sub path to the current path.
|
||||
*
|
||||
* Contrary to `std::filesystem::path::append`, this always append and never replace
|
||||
* the current path, even if @p subpath starts with a '/'.
|
||||
* All '/' are not encoded but interpreted as separators.
|
||||
*/
|
||||
void append_path(std::string_view path, Encode::yes_type = Encode::yes);
|
||||
|
||||
/**
|
||||
* Append a already encoded sub path to the current path.
|
||||
*
|
||||
* Contrary to `std::filesystem::path::append`, this always append and never replace
|
||||
* the current path, even if @p subpath starts with a '/'.
|
||||
*/
|
||||
auto append_path(std::string_view subpath) -> URL&;
|
||||
void append_path(std::string_view path, Encode::no_type);
|
||||
|
||||
/** Return the query, or empty if none. */
|
||||
[[nodiscard]] auto query() const -> const std::string&;
|
||||
|
||||
/** Set or clear the query. */
|
||||
auto set_query(std::string_view query) -> URL&;
|
||||
void set_query(std::string_view query);
|
||||
|
||||
/** Clear and return the query. */
|
||||
auto clear_query() -> std::string;
|
||||
|
||||
/** Return the fragment, or empty if none. */
|
||||
[[nodiscard]] auto fragment() const -> const std::string&;
|
||||
|
||||
/** Set or clear the fragment. */
|
||||
auto set_fragment(std::string_view fragment) -> URL&;
|
||||
void set_fragment(std::string_view fragment);
|
||||
|
||||
/** Clear and return the fragment. */
|
||||
auto clear_fragment() -> std::string;
|
||||
|
||||
/** Return the full, exact, encoded URL. */
|
||||
[[nodiscard]] auto str() -> std::string;
|
||||
|
||||
/**
|
||||
* Return the full encoded url.
|
||||
* Return the full decoded url.
|
||||
*
|
||||
* Due to decoding, the outcome may not be understood by parser and usable to reach an
|
||||
* asset.
|
||||
* @param strip_scheme If true, remove the scheme and "localhost" on file URI.
|
||||
* @param rstrip_path If non-null, remove the given charaters at the end of the path.
|
||||
* @param hide_password If true, hide password in the decoded string.
|
||||
*/
|
||||
[[nodiscard]] auto
|
||||
str(StripScheme strip_scheme = StripScheme::no,
|
||||
[[nodiscard]] auto pretty_str(
|
||||
StripScheme strip_scheme = StripScheme::no,
|
||||
char rstrip_path = 0,
|
||||
HidePassword hide_password = HidePassword::no) const -> std::string;
|
||||
HidePassword hide_password = HidePassword::no
|
||||
) const -> std::string;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_scheme = std::string(https);
|
||||
std::string m_user = {};
|
||||
std::string m_password = {};
|
||||
std::string m_host = std::string(localhost);
|
||||
std::string m_host = {};
|
||||
std::string m_path = "/";
|
||||
std::string m_port = {};
|
||||
std::string m_query = {};
|
||||
|
|
|
@ -26,11 +26,15 @@ namespace mamba::util
|
|||
/**
|
||||
* Escape reserved URL reserved characters with '%' encoding.
|
||||
*
|
||||
* Does not parse URL in any way so '/' in "http://mamba.org/page" get encoded.
|
||||
* The secons argument can be used to specify characters to exclude from encoding,
|
||||
* so that for instance path can be encoded without splitting them (if they have no '/' other
|
||||
* than separators).
|
||||
*
|
||||
* @see url_decode
|
||||
*/
|
||||
[[nodiscard]] auto url_encode(std::string_view url) -> std::string;
|
||||
[[nodiscard]] auto url_encode(std::string_view url, std::string_view exclude) -> std::string;
|
||||
[[nodiscard]] auto url_encode(std::string_view url, char exclude) -> std::string;
|
||||
|
||||
/**
|
||||
* Unescape percent encoded string to their URL reserved characters.
|
||||
|
|
|
@ -114,22 +114,24 @@ namespace mamba
|
|||
)
|
||||
{
|
||||
auto spath = std::string(util::rstrip(path, '/'));
|
||||
std::string url = util::URL() //
|
||||
.set_scheme(scheme)
|
||||
.set_host(host)
|
||||
.set_port(port)
|
||||
.set_path(spath)
|
||||
.str(util::URL::StripScheme::yes);
|
||||
std::string url = [&]()
|
||||
{
|
||||
auto parsed_url = util::URL();
|
||||
parsed_url.set_scheme(scheme);
|
||||
parsed_url.set_host(host);
|
||||
parsed_url.set_port(port);
|
||||
parsed_url.set_path(spath);
|
||||
return parsed_url.pretty_str(util::URL::StripScheme::yes);
|
||||
}();
|
||||
|
||||
// Case 1: No path given, channel name is ""
|
||||
if (spath.empty())
|
||||
{
|
||||
auto l_url = util::URL().set_host(host).set_port(port).str(
|
||||
util::URL::StripScheme::yes,
|
||||
/* rstrip_path= */ '/'
|
||||
);
|
||||
auto l_url = util::URL();
|
||||
l_url.set_host(host);
|
||||
l_url.set_port(port);
|
||||
return channel_configuration{
|
||||
/* location= */ std::move(l_url),
|
||||
/* location= */ l_url.pretty_str(util::URL::StripScheme::yes, /* rstrip_path= */ '/'),
|
||||
/* name= */ "",
|
||||
/* scheme= */ scheme,
|
||||
/* auth= */ "",
|
||||
|
@ -189,12 +191,11 @@ namespace mamba
|
|||
|
||||
// Case 7: fallback, channel_location = host:port and channel_name = path
|
||||
spath = util::lstrip(spath, '/');
|
||||
std::string location = util::URL().set_host(host).set_port(port).str(
|
||||
util::URL::StripScheme::yes,
|
||||
/* rstrip_path= */ '/'
|
||||
);
|
||||
auto location = util::URL();
|
||||
location.set_host(host);
|
||||
location.set_port(port);
|
||||
return channel_configuration{
|
||||
/* location= */ std::move(location),
|
||||
/* location= */ location.pretty_str(util::URL::StripScheme::yes, /* rstrip_path= */ '/'),
|
||||
/* name= */ spath,
|
||||
/* scheme= */ scheme,
|
||||
/* auth= */ "",
|
||||
|
@ -452,10 +453,10 @@ namespace mamba
|
|||
{
|
||||
std::string full_url = util::concat_scheme_url(scheme, location);
|
||||
const auto parser = util::URL::parse(full_url);
|
||||
location = util::URL()
|
||||
.set_host(parser.host())
|
||||
.set_port(parser.port())
|
||||
.str(util::URL::StripScheme::yes, /* rstrip_path= */ '/');
|
||||
auto url = util::URL();
|
||||
url.set_host(parser.host());
|
||||
url.set_port(parser.port());
|
||||
location = url.pretty_str(util::URL::StripScheme::yes, /* rstrip_path= */ '/');
|
||||
name = util::lstrip(parser.pretty_path(), '/');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1449,7 +1449,7 @@ namespace mamba::validation
|
|||
auto dl_target = std::make_unique<mamba::DownloadTarget>(
|
||||
context,
|
||||
"key_mgr.json",
|
||||
url.str(),
|
||||
url.pretty_str(),
|
||||
tmp_metadata_path.string()
|
||||
);
|
||||
|
||||
|
@ -1614,7 +1614,7 @@ namespace mamba::validation
|
|||
auto dl_target = std::make_unique<mamba::DownloadTarget>(
|
||||
context,
|
||||
"pkg_mgr.json",
|
||||
url.str(),
|
||||
url.pretty_str(),
|
||||
tmp_metadata_path.string()
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
// 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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/archive.hpp"
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/util/url_manip.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] auto is_token_char(char c) -> bool
|
||||
{
|
||||
return util::is_alphanum(c) || (c == '-');
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_token_first_char(char c) -> bool
|
||||
{
|
||||
return is_token_char(c) || (c == '_');
|
||||
}
|
||||
|
||||
[[nodiscard]] auto is_token(std::string_view str) -> bool
|
||||
{
|
||||
// usernames on anaconda.org can have a underscore, which influences the first two
|
||||
// characters
|
||||
static constexpr std::size_t token_start_size = 2;
|
||||
if (str.size() < token_start_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto token_first = str.substr(0, token_start_size);
|
||||
const auto token_rest = str.substr(token_start_size);
|
||||
return std::all_of(token_first.cbegin(), token_first.cend(), &is_token_first_char)
|
||||
&& std::all_of(token_rest.cbegin(), token_rest.cend(), &is_token_char);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto find_token_and_prefix(std::string_view path)
|
||||
-> std::pair<std::size_t, std::size_t>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto prefix_pos = path.find(CondaURL::token_prefix);
|
||||
if (prefix_pos == npos)
|
||||
{
|
||||
return std::pair{ std::string_view::npos, 0ul };
|
||||
}
|
||||
|
||||
const auto token_pos = prefix_pos + CondaURL::token_prefix.size();
|
||||
const auto token_end_pos = path.find('/', token_pos);
|
||||
assert(token_pos < token_end_pos);
|
||||
const auto token_len = (token_end_pos == npos) ? npos : token_end_pos - token_pos;
|
||||
if (is_token(path.substr(token_pos, token_len)))
|
||||
{
|
||||
const auto token_and_prefix_len = (token_end_pos == npos)
|
||||
? npos
|
||||
: token_end_pos - token_pos
|
||||
+ CondaURL::token_prefix.size();
|
||||
return std::pair{ prefix_pos, token_and_prefix_len };
|
||||
}
|
||||
|
||||
return std::pair{ std::string_view::npos, 0ul };
|
||||
}
|
||||
}
|
||||
|
||||
CondaURL::CondaURL(URL&& url)
|
||||
: Base(std::move(url))
|
||||
{
|
||||
}
|
||||
|
||||
auto CondaURL::parse(std::string_view url) -> CondaURL
|
||||
{
|
||||
return CondaURL(URL::parse(url));
|
||||
}
|
||||
|
||||
auto CondaURL::token() const -> std::string_view
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto& l_path = path(Decode::no);
|
||||
const auto [pos, len] = find_token_and_prefix(l_path);
|
||||
if ((pos == npos) || (len == 0))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
assert(token_prefix.size() < len);
|
||||
const auto token_len = (len != npos) ? len - token_prefix.size() : npos;
|
||||
return std::string_view(l_path).substr(pos + token_prefix.size(), token_len);
|
||||
}
|
||||
|
||||
void CondaURL::set_token(std::string_view token)
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
if (!is_token(token))
|
||||
{
|
||||
throw std::invalid_argument(fmt::format(R"(Invalid CondaURL token "{}")", token));
|
||||
}
|
||||
const auto [pos, len] = find_token_and_prefix(path(Decode::no));
|
||||
if ((pos == npos) || (len == 0))
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
fmt::format(R"(No token template in orignial path "{}")", path(Decode::no))
|
||||
);
|
||||
}
|
||||
assert(token_prefix.size() < len);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
const auto token_len = (len != npos) ? len - token_prefix.size() : npos;
|
||||
l_path.replace(pos + token_prefix.size(), token_len, token);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
|
||||
auto CondaURL::clear_token() -> bool
|
||||
{
|
||||
const auto [pos, len] = find_token_and_prefix(path(Decode::no));
|
||||
if ((pos == std::string::npos) || (len == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(token_prefix.size() < len);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
l_path.erase(pos, len);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] auto find_slash_and_platform(std::string_view path)
|
||||
-> std::tuple<std::size_t, std::size_t, std::optional<Platform>>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
assert(!path.empty() && (path.front() == '/'));
|
||||
auto start = std::size_t(0);
|
||||
auto end = path.find('/', start + 1);
|
||||
while (start != npos)
|
||||
{
|
||||
assert(start < end);
|
||||
const auto count = (end == npos) ? npos : end - start;
|
||||
const auto count_minus_1 = (end == npos) ? npos : end - start - 1;
|
||||
if (auto plat = platform_parse(path.substr(start + 1, count_minus_1)))
|
||||
{
|
||||
return { start, count, plat };
|
||||
}
|
||||
start = end;
|
||||
end = path.find('/', start + 1);
|
||||
}
|
||||
return { npos, 0, std::nullopt };
|
||||
}
|
||||
}
|
||||
|
||||
auto CondaURL::platform() const -> std::optional<Platform>
|
||||
{
|
||||
const auto& l_path = path(Decode::no);
|
||||
const auto [pos, count, plat] = find_slash_and_platform(l_path);
|
||||
return plat;
|
||||
}
|
||||
|
||||
auto CondaURL::platform_name() const -> std::string_view
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto& l_path = path(Decode::no);
|
||||
const auto [pos, len, plat] = find_slash_and_platform(l_path);
|
||||
if (!plat.has_value())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
assert(1 < len);
|
||||
const auto plat_len = (len != npos) ? len - 1 : npos;
|
||||
return std::string_view(l_path).substr(pos + 1, plat_len);
|
||||
}
|
||||
|
||||
void CondaURL::set_platform_no_check_input(std::string_view platform)
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto [pos, len, plat] = find_slash_and_platform(path(Decode::no));
|
||||
if (!plat.has_value())
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
fmt::format(R"(No platform in orignial path "{}")", path(Decode::no))
|
||||
);
|
||||
}
|
||||
assert(1 < len);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
const auto plat_len = (len != npos) ? len - 1 : npos;
|
||||
l_path.replace(pos + 1, plat_len, platform);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
|
||||
void CondaURL::set_platform(std::string_view platform)
|
||||
{
|
||||
if (!platform_parse(platform).has_value())
|
||||
{
|
||||
throw std::invalid_argument(fmt::format(R"(Invalid CondaURL platform "{}")", platform));
|
||||
}
|
||||
return set_platform_no_check_input(platform);
|
||||
}
|
||||
|
||||
void CondaURL::set_platform(Platform platform)
|
||||
{
|
||||
return set_platform_no_check_input(specs::platform_name(platform));
|
||||
}
|
||||
|
||||
auto CondaURL::clear_platform() -> bool
|
||||
{
|
||||
const auto [pos, count, plat] = find_slash_and_platform(path(Decode::no));
|
||||
if (!plat.has_value())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(1 < count);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
l_path.erase(pos, count);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CondaURL::package(Decode::yes_type) const -> std::string
|
||||
{
|
||||
return util::url_decode(package(Decode::no));
|
||||
}
|
||||
|
||||
auto CondaURL::package(Decode::no_type) const -> std::string_view
|
||||
{
|
||||
// Must not decode to find the meaningful '/' spearators
|
||||
const auto& l_path = path(Decode::no);
|
||||
if (has_archive_extension(l_path))
|
||||
{
|
||||
auto [head, pkg] = util::rstrip_if_parts(l_path, [](char c) { return c != '/'; });
|
||||
return pkg;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void CondaURL::set_package(std::string_view pkg, Encode::yes_type)
|
||||
{
|
||||
return set_package(util::url_encode(pkg), Encode::no);
|
||||
}
|
||||
|
||||
void CondaURL::set_package(std::string_view pkg, Encode::no_type)
|
||||
{
|
||||
if (!has_archive_extension(pkg))
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
fmt::format(R"(Invalid CondaURL package "{}", use path_append instead)", pkg)
|
||||
);
|
||||
}
|
||||
// Must not decode to find the meaningful '/' spearators
|
||||
if (has_archive_extension(path(Decode::no)))
|
||||
{
|
||||
auto l_path = clear_path();
|
||||
const auto pos = std::min(std::min(l_path.rfind('/'), l_path.size()) + 1ul, l_path.size());
|
||||
l_path.replace(pos, std::string::npos, pkg);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
else
|
||||
{
|
||||
append_path(pkg, Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
auto CondaURL::clear_package() -> bool
|
||||
{
|
||||
// Must not decode to find the meaningful '/' spearators
|
||||
if (has_archive_extension(path(Decode::no)))
|
||||
{
|
||||
auto l_path = clear_path();
|
||||
l_path.erase(l_path.rfind('/'));
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -16,73 +16,10 @@
|
|||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* Convert the enumeration to its conda string.
|
||||
*/
|
||||
auto platform_name(Platform p) -> std::string_view
|
||||
{
|
||||
switch (p)
|
||||
{
|
||||
case Platform::noarch:
|
||||
return "noarch";
|
||||
case Platform::linux_32:
|
||||
return "linux-32";
|
||||
case Platform::linux_64:
|
||||
return "linux-64";
|
||||
case Platform::linux_armv6l:
|
||||
return "linux-armv6l";
|
||||
case Platform::linux_armv7l:
|
||||
return "linux-armv7l";
|
||||
case Platform::linux_aarch64:
|
||||
return "linux-aarch64";
|
||||
case Platform::linux_ppc64:
|
||||
return "linux-ppc64";
|
||||
case Platform::linux_ppc64le:
|
||||
return "linux-ppc64le";
|
||||
case Platform::linux_s390x:
|
||||
return "linux-s390x";
|
||||
case Platform::linux_riscv32:
|
||||
return "linux-riscv32";
|
||||
case Platform::linux_riscv64:
|
||||
return "linux-riscv64";
|
||||
case Platform::osx_64:
|
||||
return "osx-64";
|
||||
case Platform::osx_arm64:
|
||||
return "osx-arm64";
|
||||
case Platform::win_32:
|
||||
return "win-32";
|
||||
case Platform::win_64:
|
||||
return "win-64";
|
||||
case Platform::win_arm64:
|
||||
return "win-arm64";
|
||||
default:
|
||||
// All enum cases must be handled
|
||||
assert(false);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
auto platform_parse(std::string_view str) -> std::optional<Platform>
|
||||
{
|
||||
std::string const str_clean = util::to_lower(util::strip(str));
|
||||
for (const auto p : {
|
||||
Platform::noarch,
|
||||
Platform::linux_32,
|
||||
Platform::linux_64,
|
||||
Platform::linux_armv6l,
|
||||
Platform::linux_armv7l,
|
||||
Platform::linux_aarch64,
|
||||
Platform::linux_ppc64,
|
||||
Platform::linux_ppc64le,
|
||||
Platform::linux_s390x,
|
||||
Platform::linux_riscv32,
|
||||
Platform::linux_riscv64,
|
||||
Platform::osx_64,
|
||||
Platform::osx_arm64,
|
||||
Platform::win_32,
|
||||
Platform::win_64,
|
||||
Platform::win_arm64,
|
||||
})
|
||||
for (const auto p : known_platforms())
|
||||
{
|
||||
if (str_clean == platform_name(p))
|
||||
{
|
||||
|
|
|
@ -38,13 +38,19 @@ namespace mamba::util
|
|||
return false;
|
||||
}
|
||||
|
||||
auto path_get_drive_letter(std::string_view path) -> std::optional<char>
|
||||
{
|
||||
if (path_has_drive_letter(path))
|
||||
{
|
||||
return { path.front() };
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto path_has_drive_letter(std::string_view path) -> bool
|
||||
{
|
||||
static constexpr auto is_drive_char = [](char c) -> bool { return is_alphanum(c); };
|
||||
|
||||
auto [drive, rest] = lstrip_if_parts(path, is_drive_char);
|
||||
return !drive.empty() && (rest.size() >= 2) && (rest[0] == ':')
|
||||
&& ((rest[1] == '/') || (rest[1] == '\\'));
|
||||
return (path.size() >= 3) && is_alpha(path[0]) && (path[1] == ':')
|
||||
&& ((path[2] == '/') || (path[2] == '\\'));
|
||||
}
|
||||
|
||||
auto path_win_to_posix(std::string path) -> std::string
|
||||
|
|
|
@ -236,6 +236,17 @@ namespace mamba::util
|
|||
return str.find(sub_str) != std::string::npos;
|
||||
}
|
||||
|
||||
// TODO(C++20) This is a method of string_view
|
||||
bool contains(std::string_view str, char c)
|
||||
{
|
||||
return str.find(c) != std::string::npos;
|
||||
}
|
||||
|
||||
bool contains(char c1, char c2)
|
||||
{
|
||||
return c1 == c2;
|
||||
}
|
||||
|
||||
// TODO(C++20) This is a method of string_view
|
||||
bool ends_with(std::string_view str, std::string_view suffix)
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <curl/urlapi.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/util/url.hpp"
|
||||
|
@ -179,14 +180,14 @@ namespace mamba::util
|
|||
file_uri_unc2_to_unc4(url),
|
||||
CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME,
|
||||
};
|
||||
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(std::string(URL::https)))
|
||||
.set_user(handle.get_part(CURLUPART_USER).value_or(""), Encode::no)
|
||||
.set_password(handle.get_part(CURLUPART_PASSWORD).value_or(""), Encode::no)
|
||||
.set_host(handle.get_part(CURLUPART_HOST).value_or(std::string(URL::localhost)))
|
||||
.set_path(handle.get_part(CURLUPART_PATH).value_or("/"))
|
||||
.set_port(handle.get_part(CURLUPART_PORT).value_or(""))
|
||||
.set_query(handle.get_part(CURLUPART_QUERY).value_or(""))
|
||||
.set_fragment(handle.get_part(CURLUPART_FRAGMENT).value_or(""));
|
||||
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(std::string(URL::https)));
|
||||
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_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(""));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -196,14 +197,13 @@ namespace mamba::util
|
|||
return m_scheme;
|
||||
}
|
||||
|
||||
auto URL::set_scheme(std::string_view scheme) -> URL&
|
||||
void URL::set_scheme(std::string_view scheme)
|
||||
{
|
||||
if (scheme.empty())
|
||||
{
|
||||
throw std::invalid_argument("Cannot set empty scheme");
|
||||
}
|
||||
m_scheme = util::to_lower(util::rstrip(scheme));
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto URL::user(Decode::no_type) const -> const std::string&
|
||||
|
@ -213,20 +213,22 @@ namespace mamba::util
|
|||
|
||||
auto URL::user(Decode::yes_type) const -> std::string
|
||||
{
|
||||
return url_decode(m_user);
|
||||
return url_decode(user(Decode::no));
|
||||
}
|
||||
|
||||
auto URL::set_user(std::string_view user, Encode encode) -> URL&
|
||||
void URL::set_user(std::string_view user, Encode::yes_type)
|
||||
{
|
||||
if (encode == Encode::yes)
|
||||
{
|
||||
m_user = url_encode(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_user = user;
|
||||
}
|
||||
return *this;
|
||||
return set_user(url_encode(user), Encode::no);
|
||||
}
|
||||
|
||||
void URL::set_user(std::string user, Encode::no_type)
|
||||
{
|
||||
m_user = std::move(user);
|
||||
}
|
||||
|
||||
auto URL::clear_user() -> std::string
|
||||
{
|
||||
return std::exchange(m_user, "");
|
||||
}
|
||||
|
||||
auto URL::password(Decode::no_type) const -> const std::string&
|
||||
|
@ -236,20 +238,22 @@ namespace mamba::util
|
|||
|
||||
auto URL::password(Decode::yes_type) const -> std::string
|
||||
{
|
||||
return url_decode(m_password);
|
||||
return url_decode(password(Decode::no));
|
||||
}
|
||||
|
||||
auto URL::set_password(std::string_view password, Encode encode) -> URL&
|
||||
void URL::set_password(std::string_view password, Encode::yes_type)
|
||||
{
|
||||
if (encode == Encode::yes)
|
||||
{
|
||||
m_password = url_encode(password);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_password = password;
|
||||
}
|
||||
return *this;
|
||||
return set_password(url_encode(password), Encode::no);
|
||||
}
|
||||
|
||||
void URL::set_password(std::string password, Encode::no_type)
|
||||
{
|
||||
m_password = std::move(password);
|
||||
}
|
||||
|
||||
auto URL::clear_password() -> std::string
|
||||
{
|
||||
return std::exchange(m_password, "");
|
||||
}
|
||||
|
||||
auto URL::authentication() const -> std::string
|
||||
|
@ -259,33 +263,46 @@ namespace mamba::util
|
|||
return p.empty() ? u : util::concat(u, ':', p);
|
||||
}
|
||||
|
||||
auto URL::host(Decode::no_type) const -> const std::string&
|
||||
auto URL::host(Decode::no_type) const -> std::string_view
|
||||
{
|
||||
if ((m_scheme != "file") && m_host.empty())
|
||||
{
|
||||
return localhost;
|
||||
}
|
||||
return m_host;
|
||||
}
|
||||
|
||||
auto URL::host(Decode::yes_type) const -> std::string
|
||||
{
|
||||
return url_decode(m_host);
|
||||
return url_decode(host(Decode::no));
|
||||
}
|
||||
|
||||
auto URL::set_host(std::string_view host, Encode encode) -> URL&
|
||||
void URL::set_host(std::string_view host, Encode::yes_type)
|
||||
{
|
||||
std::string new_host = {};
|
||||
if (encode == Encode::yes)
|
||||
return set_host(url_encode(host), Encode::no);
|
||||
}
|
||||
|
||||
void URL::set_host(std::string host, Encode::no_type)
|
||||
{
|
||||
std::transform(
|
||||
host.cbegin(),
|
||||
host.cend(),
|
||||
host.begin(),
|
||||
[](char c) { return util::to_lower(c); }
|
||||
);
|
||||
m_host = std::move(host);
|
||||
}
|
||||
|
||||
auto URL::clear_host() -> std::string
|
||||
{
|
||||
// Cheap == comparison that works because of class invariant
|
||||
if (auto l_host = host(Decode::no); l_host.data() != m_host.data())
|
||||
{
|
||||
new_host = url_encode(host);
|
||||
auto out = std::string(l_host);
|
||||
set_host("", Encode::no);
|
||||
return out;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_host = util::strip(host); // spaces are illegal if not encoded
|
||||
}
|
||||
if (new_host.empty())
|
||||
{
|
||||
throw std::invalid_argument("Cannot set empty host");
|
||||
}
|
||||
m_host = util::to_lower(new_host);
|
||||
return *this;
|
||||
return std::exchange(m_host, "");
|
||||
}
|
||||
|
||||
auto URL::port() const -> const std::string&
|
||||
|
@ -293,14 +310,18 @@ namespace mamba::util
|
|||
return m_port;
|
||||
}
|
||||
|
||||
auto URL::set_port(std::string_view port) -> URL&
|
||||
void URL::set_port(std::string_view port)
|
||||
{
|
||||
if (!std::all_of(port.cbegin(), port.cend(), [](char c) { return util::is_digit(c); }))
|
||||
{
|
||||
throw std::invalid_argument(fmt::format(R"(Port must be a number, got "{}")", port));
|
||||
}
|
||||
m_port = port;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto URL::clear_port() -> std::string
|
||||
{
|
||||
return std::exchange(m_port, "");
|
||||
}
|
||||
|
||||
auto URL::authority() const -> std::string
|
||||
|
@ -319,44 +340,86 @@ namespace mamba::util
|
|||
);
|
||||
}
|
||||
|
||||
auto URL::path() const -> const std::string&
|
||||
auto URL::path(Decode::no_type) const -> const std::string&
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
auto URL::pretty_path() const -> std::string_view
|
||||
auto URL::path(Decode::yes_type) const -> std::string
|
||||
{
|
||||
// All paths start with a '/' except those like "file://C:/folder/file.txt"
|
||||
return url_decode(path(Decode::no));
|
||||
}
|
||||
|
||||
void URL::set_path(std::string_view path, Encode::yes_type)
|
||||
{
|
||||
// Drive colon must not be encoded
|
||||
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), '/')
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path = concat(slashes, url_encode(no_slash_path, '/'));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return set_path(url_encode(path, '/'), Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
void URL::set_path(std::string path, Encode::no_type)
|
||||
{
|
||||
if (!util::starts_with(path, '/'))
|
||||
{
|
||||
path.insert(0, 1, '/');
|
||||
}
|
||||
m_path = path;
|
||||
}
|
||||
|
||||
auto URL::clear_path() -> std::string
|
||||
{
|
||||
return std::exchange(m_path, "/");
|
||||
}
|
||||
|
||||
auto URL::pretty_path() const -> std::string
|
||||
{
|
||||
// All paths start with a '/' except those like "file:///C:/folder/file.txt"
|
||||
if (m_scheme == "file")
|
||||
{
|
||||
assert(util::starts_with(m_path, '/'));
|
||||
auto path_no_slash = std::string_view(m_path).substr(1);
|
||||
auto path_no_slash = url_decode(std::string_view(m_path).substr(1));
|
||||
if (path_has_drive_letter(path_no_slash))
|
||||
{
|
||||
return path_no_slash;
|
||||
}
|
||||
}
|
||||
return m_path;
|
||||
return url_decode(m_path);
|
||||
}
|
||||
|
||||
auto URL::set_path(std::string_view path) -> URL&
|
||||
void URL::append_path(std::string_view subpath, Encode::yes_type)
|
||||
{
|
||||
if (!util::starts_with(path, '/'))
|
||||
if (path(Decode::no) == "/")
|
||||
{
|
||||
m_path.reserve(path.size() + 1);
|
||||
m_path = '/';
|
||||
m_path += path;
|
||||
// Allow hanldling of Windows drive letter encoding
|
||||
return set_path(std::string(subpath), Encode::yes);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path = path;
|
||||
}
|
||||
return *this;
|
||||
return append_path(url_encode(subpath, '/'), Encode::no);
|
||||
}
|
||||
|
||||
auto URL::append_path(std::string_view subpath) -> URL&
|
||||
void URL::append_path(std::string_view subpath, Encode::no_type)
|
||||
{
|
||||
subpath = util::strip(subpath);
|
||||
m_path.reserve(m_path.size() + 1 + subpath.size());
|
||||
const bool trailing = util::ends_with(m_path, '/');
|
||||
const bool leading = util::starts_with(subpath, '/');
|
||||
|
@ -369,7 +432,6 @@ namespace mamba::util
|
|||
m_path.pop_back();
|
||||
}
|
||||
m_path += subpath;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto URL::query() const -> const std::string&
|
||||
|
@ -377,10 +439,14 @@ namespace mamba::util
|
|||
return m_query;
|
||||
}
|
||||
|
||||
auto URL::set_query(std::string_view query) -> URL&
|
||||
void URL::set_query(std::string_view query)
|
||||
{
|
||||
m_query = query;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto URL::clear_query() -> std::string
|
||||
{
|
||||
return std::exchange(m_query, "");
|
||||
}
|
||||
|
||||
auto URL::fragment() const -> const std::string&
|
||||
|
@ -388,36 +454,59 @@ namespace mamba::util
|
|||
return m_fragment;
|
||||
}
|
||||
|
||||
auto URL::set_fragment(std::string_view fragment) -> URL&
|
||||
void URL::set_fragment(std::string_view fragment)
|
||||
{
|
||||
m_fragment = fragment;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto URL::str(StripScheme strip_scheme, char rstrip_path, HidePassword hide_password) const
|
||||
auto URL::clear_fragment() -> std::string
|
||||
{
|
||||
return std::exchange(m_fragment, "");
|
||||
}
|
||||
|
||||
auto URL::str() -> std::string
|
||||
{
|
||||
return util::concat(
|
||||
scheme(),
|
||||
"://",
|
||||
user(Decode::no),
|
||||
m_password.empty() ? "" : ":",
|
||||
password(Decode::no),
|
||||
m_user.empty() ? "" : "@",
|
||||
host(Decode::no),
|
||||
m_port.empty() ? "" : ":",
|
||||
port(),
|
||||
path(Decode::no),
|
||||
m_query.empty() ? "" : "?",
|
||||
m_query,
|
||||
m_fragment.empty() ? "" : "#",
|
||||
m_fragment
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::pretty_str(StripScheme strip_scheme, char rstrip_path, HidePassword hide_password) const
|
||||
-> std::string
|
||||
{
|
||||
// Not showing "localhost" on file URI
|
||||
std::string_view computed_host = m_host;
|
||||
if ((m_scheme == "file") && (m_host == localhost))
|
||||
{
|
||||
computed_host = "";
|
||||
}
|
||||
std::string computed_path = {};
|
||||
// When stripping file scheme, not showing leading '/' for Windows path with drive
|
||||
std::string_view computed_path = m_path;
|
||||
if ((m_scheme == "file") && (strip_scheme == StripScheme::yes) && computed_host.empty())
|
||||
if ((m_scheme == "file") && (strip_scheme == StripScheme::yes) && host(Decode::no).empty())
|
||||
{
|
||||
computed_path = pretty_path();
|
||||
}
|
||||
else
|
||||
{
|
||||
computed_path = path(Decode::yes);
|
||||
}
|
||||
computed_path = util::rstrip(computed_path, rstrip_path);
|
||||
|
||||
return util::concat(
|
||||
(strip_scheme == StripScheme::no) ? m_scheme : "",
|
||||
(strip_scheme == StripScheme::no) ? "://" : "",
|
||||
m_user,
|
||||
user(Decode::yes),
|
||||
m_password.empty() ? "" : ":",
|
||||
(hide_password == HidePassword::no) ? m_password : "*****",
|
||||
(hide_password == HidePassword::no) ? password(Decode::yes) : "*****",
|
||||
m_user.empty() ? "" : "@",
|
||||
computed_host,
|
||||
host(Decode::yes),
|
||||
m_port.empty() ? "" : ":",
|
||||
m_port,
|
||||
computed_path,
|
||||
|
|
|
@ -68,25 +68,40 @@ namespace mamba::util
|
|||
return static_cast<char>((hex_offset[idx10] << 4) | hex_offset[idx1]);
|
||||
}
|
||||
|
||||
template <typename Str>
|
||||
auto url_encode_impl(std::string_view url, Str exclude) -> std::string
|
||||
{
|
||||
std::string out = {};
|
||||
out.reserve(url.size());
|
||||
for (char c : url)
|
||||
{
|
||||
if (url_is_unreserved_char(c) || contains(exclude, c))
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto encoding = url_encode_char(c);
|
||||
out += std::string_view(encoding.data(), encoding.size());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
auto url_encode(std::string_view url) -> std::string
|
||||
{
|
||||
std::string out = {};
|
||||
out.reserve(url.size());
|
||||
for (char c : url)
|
||||
{
|
||||
if (url_is_unreserved_char(c))
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto encoding = url_encode_char(c);
|
||||
out += std::string_view(encoding.data(), encoding.size());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return url_encode_impl(url, 'a'); // Already not encoded
|
||||
}
|
||||
|
||||
auto url_encode(std::string_view url, std::string_view exclude) -> std::string
|
||||
{
|
||||
return url_encode_impl(url, exclude);
|
||||
}
|
||||
|
||||
auto url_encode(std::string_view url, char exclude) -> std::string
|
||||
{
|
||||
return url_encode_impl(url, exclude);
|
||||
}
|
||||
|
||||
auto url_decode(std::string_view url) -> std::string
|
||||
|
@ -253,7 +268,7 @@ namespace mamba::util
|
|||
auth = url_parsed.authentication();
|
||||
url_parsed.set_user("");
|
||||
url_parsed.set_password("");
|
||||
remaining_url = util::rstrip(url_parsed.str(URL::StripScheme::yes), '/');
|
||||
remaining_url = util::rstrip(url_parsed.pretty_str(URL::StripScheme::yes), '/');
|
||||
}
|
||||
|
||||
bool compare_cleaned_url(const std::string& url1, const std::string& url2)
|
||||
|
|
|
@ -37,6 +37,7 @@ set(LIBMAMBA_TEST_SRCS
|
|||
# Implementation of version and matching specs
|
||||
src/specs/test_archive.cpp
|
||||
src/specs/test_platform.cpp
|
||||
src/specs/test_conda_url.cpp
|
||||
src/specs/test_version.cpp
|
||||
src/specs/test_version_spec.cpp
|
||||
src/specs/test_repo_data.cpp
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
// 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 <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("specs::CondaURL")
|
||||
{
|
||||
TEST_CASE("Token")
|
||||
{
|
||||
CondaURL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_host("repo.mamba.pm");
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/folder/file.txt")
|
||||
{
|
||||
url.set_path("/folder/file.txt");
|
||||
CHECK_EQ(url.token(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_token("token"), std::invalid_argument);
|
||||
CHECK_EQ(url.path(), "/folder/file.txt");
|
||||
|
||||
CHECK_FALSE(url.clear_token());
|
||||
CHECK_EQ(url.path(), "/folder/file.txt");
|
||||
}
|
||||
|
||||
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_EQ(url.token(), "xy-12345678-1234");
|
||||
|
||||
SUBCASE("Cannot set invalid token")
|
||||
{
|
||||
CHECK_THROWS_AS(url.set_token(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_token("?fds:g"), std::invalid_argument);
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234");
|
||||
CHECK_EQ(url.path(), "/t/xy-12345678-1234/conda-forge/linux-64");
|
||||
}
|
||||
|
||||
SUBCASE("Clear token")
|
||||
{
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64");
|
||||
}
|
||||
|
||||
SUBCASE("Set token")
|
||||
{
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path(), "/t/abcd/conda-forge/linux-64");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/t/xy-12345678-1234-1234-1234-123456789012")
|
||||
{
|
||||
url.set_path("/t/xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path(), "/t/abcd");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.path(), "/");
|
||||
}
|
||||
|
||||
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_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path(), "/bar/t/abcd/");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.path(), "/bar/");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Platform")
|
||||
{
|
||||
CondaURL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_host("repo.mamba.pm");
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/")
|
||||
{
|
||||
CHECK_FALSE(url.platform().has_value());
|
||||
CHECK_EQ(url.platform_name(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_platform(Platform::linux_64), std::invalid_argument);
|
||||
CHECK_EQ(url.path(), "/");
|
||||
|
||||
CHECK_FALSE(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge")
|
||||
{
|
||||
url.set_path("conda-forge");
|
||||
|
||||
CHECK_FALSE(url.platform().has_value());
|
||||
CHECK_EQ(url.platform_name(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_platform(Platform::linux_64), std::invalid_argument);
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
|
||||
CHECK_FALSE(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/")
|
||||
{
|
||||
url.set_path("conda-forge/");
|
||||
|
||||
CHECK_FALSE(url.platform().has_value());
|
||||
CHECK_EQ(url.platform_name(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_platform(Platform::linux_64), std::invalid_argument);
|
||||
CHECK_EQ(url.path(), "/conda-forge/");
|
||||
|
||||
CHECK_FALSE(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/conda-forge/");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/win-64")
|
||||
{
|
||||
url.set_path("conda-forge/win-64");
|
||||
|
||||
CHECK_EQ(url.platform(), Platform::win_64);
|
||||
CHECK_EQ(url.platform_name(), "win-64");
|
||||
|
||||
url.set_platform(Platform::linux_64);
|
||||
CHECK_EQ(url.platform(), Platform::linux_64);
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64");
|
||||
|
||||
CHECK(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/OSX-64/")
|
||||
{
|
||||
url.set_path("conda-forge/OSX-64");
|
||||
|
||||
CHECK_EQ(url.platform(), Platform::osx_64);
|
||||
CHECK_EQ(url.platform_name(), "OSX-64"); // Captialization not changed
|
||||
|
||||
url.set_platform("Win-64");
|
||||
CHECK_EQ(url.platform(), Platform::win_64);
|
||||
CHECK_EQ(url.path(), "/conda-forge/Win-64"); // Captialization not changed
|
||||
|
||||
CHECK(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/linux-64/micromamba-1.5.1-0.tar.bz2")
|
||||
{
|
||||
url.set_path("/conda-forge/linux-64/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK_EQ(url.platform(), Platform::linux_64);
|
||||
CHECK_EQ(url.platform_name(), "linux-64");
|
||||
|
||||
url.set_platform("osx-64");
|
||||
CHECK_EQ(url.platform(), Platform::osx_64);
|
||||
CHECK_EQ(url.path(), "/conda-forge/osx-64/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK(url.clear_platform());
|
||||
CHECK_EQ(url.path(), "/conda-forge/micromamba-1.5.1-0.tar.bz2");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Package")
|
||||
{
|
||||
CondaURL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_host("repo.mamba.pm");
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/")
|
||||
{
|
||||
CHECK_EQ(url.package(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_package("not-package/"), std::invalid_argument);
|
||||
CHECK_EQ(url.path(), "/");
|
||||
|
||||
CHECK_FALSE(url.clear_package());
|
||||
CHECK_EQ(url.package(), "");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
|
||||
url.set_package("micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.package(), "micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.path(), "/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK(url.clear_package());
|
||||
CHECK_EQ(url.package(), "");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge")
|
||||
{
|
||||
url.set_path("conda-forge");
|
||||
|
||||
CHECK_EQ(url.package(), "");
|
||||
|
||||
url.set_package("micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.package(), "micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.path(), "/conda-forge/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK(url.clear_package());
|
||||
CHECK_EQ(url.package(), "");
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/")
|
||||
{
|
||||
url.set_path("conda-forge/");
|
||||
|
||||
CHECK_EQ(url.package(), "");
|
||||
|
||||
url.set_package("micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.package(), "micromamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.path(), "/conda-forge/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK(url.clear_package());
|
||||
CHECK_EQ(url.package(), "");
|
||||
CHECK_EQ(url.path(), "/conda-forge");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/conda-forge/linux-64/micromamba-1.5.1-0.tar.bz2")
|
||||
{
|
||||
url.set_path("/conda-forge/linux-64/micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK_EQ(url.package(), "micromamba-1.5.1-0.tar.bz2");
|
||||
|
||||
url.set_package("mamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.package(), "mamba-1.5.1-0.tar.bz2");
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64/mamba-1.5.1-0.tar.bz2");
|
||||
|
||||
CHECK(url.clear_package());
|
||||
CHECK_EQ(url.package(), "");
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("platform")
|
||||
TEST_SUITE("specs::platform")
|
||||
{
|
||||
TEST_CASE("name")
|
||||
{
|
||||
|
@ -26,4 +26,16 @@ TEST_SUITE("platform")
|
|||
CHECK_EQ(platform_parse(" OSX-64"), Platform::osx_64);
|
||||
CHECK_EQ(platform_parse("linus-46"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST_CASE("known_platform")
|
||||
{
|
||||
static constexpr decltype(known_platform_names()) expected{
|
||||
"noarch", "linux-32", "linux-64", "linux-armv6l",
|
||||
"linux-armv7l", "linux-aarch64", "linux-ppc64le", "linux-ppc64",
|
||||
"linux-s390x", "linux-riscv32", "linux-riscv64", "osx-64",
|
||||
"osx-arm64", "win-32", "win-64", "win-arm64",
|
||||
|
||||
};
|
||||
CHECK_EQ(expected, known_platform_names());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ TEST_SUITE("util::path_manip")
|
|||
TEST_CASE("path_has_drive_letter")
|
||||
{
|
||||
CHECK(path_has_drive_letter("C:/folder/file"));
|
||||
CHECK_EQ(path_get_drive_letter("C:/folder/file"), 'C');
|
||||
CHECK(path_has_drive_letter(R"(C:\folder\file)"));
|
||||
CHECK_EQ(path_get_drive_letter(R"(C:\folder\file)"), 'C');
|
||||
CHECK_FALSE(path_has_drive_letter("/folder/file"));
|
||||
CHECK_FALSE(path_has_drive_letter("folder/file"));
|
||||
CHECK_FALSE(path_has_drive_letter(R"(\folder\file)"));
|
||||
|
|
|
@ -62,9 +62,11 @@ namespace mamba::util
|
|||
|
||||
TEST_CASE("contains")
|
||||
{
|
||||
CHECK(contains('c', 'c'));
|
||||
CHECK_FALSE(contains('c', 'a'));
|
||||
CHECK(contains(":hello&", ""));
|
||||
CHECK(contains(":hello&", "&"));
|
||||
CHECK(contains(":hello&", ":"));
|
||||
CHECK(contains(":hello&", '&'));
|
||||
CHECK(contains(":hello&", ':'));
|
||||
CHECK(contains(":hello&", "ll"));
|
||||
CHECK_FALSE(contains(":hello&", "eo"));
|
||||
CHECK(contains("áäᜩgþhëb®hüghœ©®xb", "ëb®"));
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/url.hpp"
|
||||
|
||||
using namespace mamba::util;
|
||||
|
@ -22,35 +23,76 @@ TEST_SUITE("util::URL")
|
|||
{
|
||||
URL url{};
|
||||
CHECK_EQ(url.scheme(), URL::https);
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ(url.pretty_path(), "/");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ(url.pretty_path(), "/");
|
||||
CHECK_EQ(url.query(), "");
|
||||
|
||||
CHECK_EQ(url.clear_user(), "");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.clear_password(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.clear_port(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.clear_host(), URL::localhost);
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
CHECK_EQ(url.clear_path(), "/");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ(url.clear_query(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.clear_fragment(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
|
||||
SUBCASE("Complete")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https")
|
||||
.set_host("mamba.org")
|
||||
.set_user("user")
|
||||
.set_password("password")
|
||||
.set_port("8080")
|
||||
.set_path("/folder/file.html")
|
||||
.set_query("param=value")
|
||||
.set_fragment("fragment");
|
||||
url.set_scheme("https");
|
||||
url.set_host("mamba.org");
|
||||
url.set_user("user");
|
||||
url.set_password("pass:word");
|
||||
url.set_port("8080");
|
||||
url.set_path("/folder/file.html");
|
||||
url.set_query("param=value");
|
||||
url.set_fragment("fragment");
|
||||
|
||||
CHECK_EQ(url.scheme(), "https");
|
||||
CHECK_EQ(url.host(), "mamba.org");
|
||||
CHECK_EQ(url.user(), "user");
|
||||
CHECK_EQ(url.password(), "password");
|
||||
CHECK_EQ(url.password(), "pass:word");
|
||||
CHECK_EQ(url.port(), "8080");
|
||||
CHECK_EQ(url.path(), "/folder/file.html");
|
||||
CHECK_EQ(url.pretty_path(), "/folder/file.html");
|
||||
CHECK_EQ(url.query(), "param=value");
|
||||
CHECK_EQ(url.fragment(), "fragment");
|
||||
|
||||
CHECK_EQ(url.clear_user(), "user");
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.clear_password(), "pass%3Aword");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.clear_port(), "8080");
|
||||
CHECK_EQ(url.port(), "");
|
||||
CHECK_EQ(url.clear_host(), "mamba.org");
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
CHECK_EQ(url.clear_path(), "/folder/file.html");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ(url.clear_query(), "param=value");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.clear_fragment(), "fragment");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
|
||||
SUBCASE("File")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("file");
|
||||
url.set_path("/folder/file.txt");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/folder/file.txt");
|
||||
}
|
||||
|
||||
SUBCASE("Path")
|
||||
|
@ -64,7 +106,8 @@ TEST_SUITE("util::URL")
|
|||
SUBCASE("Windows path")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("file").set_path("C:/folder/file.txt");
|
||||
url.set_scheme("file");
|
||||
url.set_path("C:/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/C:/folder/file.txt");
|
||||
CHECK_EQ(url.pretty_path(), "C:/folder/file.txt");
|
||||
}
|
||||
|
@ -72,7 +115,8 @@ TEST_SUITE("util::URL")
|
|||
SUBCASE("Case")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("FtP").set_host("sOme_Host.COM");
|
||||
url.set_scheme("FtP");
|
||||
url.set_host("sOme_Host.COM");
|
||||
CHECK_EQ(url.scheme(), "ftp");
|
||||
CHECK_EQ(url.host(), "some_host.com");
|
||||
}
|
||||
|
@ -81,7 +125,6 @@ TEST_SUITE("util::URL")
|
|||
{
|
||||
URL url{};
|
||||
CHECK_THROWS_AS(url.set_scheme(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_host(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_port("not-a-number"), std::invalid_argument);
|
||||
}
|
||||
|
||||
|
@ -212,34 +255,36 @@ TEST_SUITE("util::URL")
|
|||
|
||||
SUBCASE("file://C:/Users/wolfv/test/document.json")
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
CHECK_EQ(url.path(), "/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(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
#endif
|
||||
if (on_win)
|
||||
{
|
||||
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/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(), "");
|
||||
CHECK_EQ(url.query(), "");
|
||||
CHECK_EQ(url.fragment(), "");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("file:///home/wolfv/test/document.json")
|
||||
{
|
||||
#ifndef _WIN32
|
||||
const URL url = URL::parse("file:///home/wolfv/test/document.json");
|
||||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), URL::localhost);
|
||||
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(), "");
|
||||
#endif
|
||||
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(), "");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("https://169.254.0.0/page")
|
||||
|
@ -286,7 +331,7 @@ TEST_SUITE("util::URL")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("str")
|
||||
TEST_CASE("pretty_str options")
|
||||
{
|
||||
SUBCASE("scheme option")
|
||||
{
|
||||
|
@ -295,15 +340,15 @@ TEST_SUITE("util::URL")
|
|||
|
||||
SUBCASE("defaut scheme")
|
||||
{
|
||||
CHECK_EQ(url.str(URL::StripScheme::no), "https://mamba.org/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no), "https://mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "mamba.org/");
|
||||
}
|
||||
|
||||
SUBCASE("ftp scheme")
|
||||
{
|
||||
url.set_scheme("ftp");
|
||||
CHECK_EQ(url.str(URL::StripScheme::no), "ftp://mamba.org/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no), "ftp://mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "mamba.org/");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,83 +356,120 @@ TEST_SUITE("util::URL")
|
|||
{
|
||||
URL url = {};
|
||||
url.set_host("mamba.org");
|
||||
CHECK_EQ(url.str(URL::StripScheme::no, 0), "https://mamba.org/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::no, '/'), "https://mamba.org");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no, 0), "https://mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no, '/'), "https://mamba.org");
|
||||
url.set_path("/page/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::no, ':'), "https://mamba.org/page/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::no, '/'), "https://mamba.org/page");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no, ':'), "https://mamba.org/page/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::no, '/'), "https://mamba.org/page");
|
||||
}
|
||||
|
||||
SUBCASE("Hide password option")
|
||||
{
|
||||
URL url = {};
|
||||
url.set_user("user").set_password("pass");
|
||||
url.set_user("user");
|
||||
url.set_password("pass");
|
||||
CHECK_EQ(
|
||||
url.str(URL::StripScheme::no, 0, URL::HidePassword::no),
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::HidePassword::no),
|
||||
"https://user:pass@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.str(URL::StripScheme::no, 0, URL::HidePassword::yes),
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::HidePassword::yes),
|
||||
"https://user:*****@localhost/"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("str and pretty_str")
|
||||
{
|
||||
SUBCASE("https://user:password@mamba.org:8080/folder/file.html?param=value#fragment")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https")
|
||||
.set_host("mamba.org")
|
||||
.set_user("user")
|
||||
.set_password("password")
|
||||
.set_port("8080")
|
||||
.set_path("/folder/file.html")
|
||||
.set_query("param=value")
|
||||
.set_fragment("fragment");
|
||||
url.set_scheme("https");
|
||||
url.set_host("mamba.org");
|
||||
url.set_user("user");
|
||||
url.set_password("password");
|
||||
url.set_port("8080");
|
||||
url.set_path("/folder/file.html");
|
||||
url.set_query("param=value");
|
||||
url.set_fragment("fragment");
|
||||
|
||||
CHECK_EQ(
|
||||
url.str(),
|
||||
"https://user:password@mamba.org:8080/folder/file.html?param=value#fragment"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(),
|
||||
"https://user:password@mamba.org:8080/folder/file.html?param=value#fragment"
|
||||
);
|
||||
}
|
||||
|
||||
SUBCASE("user@mamba.org")
|
||||
{
|
||||
URL url{};
|
||||
url.set_host("mamba.org").set_user("user");
|
||||
url.set_host("mamba.org");
|
||||
url.set_user("user");
|
||||
CHECK_EQ(url.pretty_str(), "https://user@mamba.org/");
|
||||
CHECK_EQ(url.str(), "https://user@mamba.org/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "user@mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "user@mamba.org/");
|
||||
}
|
||||
|
||||
SUBCASE("https://mamba.org")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https").set_host("mamba.org");
|
||||
url.set_scheme("https");
|
||||
url.set_host("mamba.org");
|
||||
CHECK_EQ(url.str(), "https://mamba.org/");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(), "https://mamba.org/");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "mamba.org/");
|
||||
}
|
||||
|
||||
SUBCASE("file:////folder/file.txt")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("file").set_path("//folder/file.txt");
|
||||
url.set_scheme("file");
|
||||
url.set_path("//folder/file.txt");
|
||||
CHECK_EQ(url.str(), "file:////folder/file.txt");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "//folder/file.txt");
|
||||
CHECK_EQ(url.pretty_str(), "file:////folder/file.txt");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "//folder/file.txt");
|
||||
}
|
||||
|
||||
SUBCASE("file:///folder/file.txt")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("file").set_path("/folder/file.txt");
|
||||
url.set_scheme("file");
|
||||
url.set_path("/folder/file.txt");
|
||||
CHECK_EQ(url.str(), "file:///folder/file.txt");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "/folder/file.txt");
|
||||
CHECK_EQ(url.pretty_str(), "file:///folder/file.txt");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "/folder/file.txt");
|
||||
}
|
||||
|
||||
SUBCASE("file:///C:/folder/file.txt")
|
||||
SUBCASE("file:///C:/folder&/file.txt")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("file").set_path("C:/folder/file.txt");
|
||||
CHECK_EQ(url.str(), "file:///C:/folder/file.txt");
|
||||
CHECK_EQ(url.str(URL::StripScheme::yes), "C:/folder/file.txt");
|
||||
url.set_scheme("file");
|
||||
url.set_path("C:/folder&/file.txt");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.str(), "file:///C:/folder%26/file.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.str(), "file:///C%3A/folder%26/file.txt");
|
||||
}
|
||||
CHECK_EQ(url.pretty_str(), "file:///C:/folder&/file.txt");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "C:/folder&/file.txt");
|
||||
}
|
||||
|
||||
SUBCASE("https://user@email.com:pw%rd@mamba.org/some /path$/")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_host("mamba.org");
|
||||
url.set_user("user@email.com");
|
||||
url.set_password("pw%rd");
|
||||
url.set_path("/some /path$/");
|
||||
CHECK_EQ(url.str(), "https://user%40email.com:pw%25rd@mamba.org/some%20/path%24/");
|
||||
CHECK_EQ(url.pretty_str(), "https://user@email.com:pw%rd@mamba.org/some /path$/");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,11 +486,11 @@ TEST_SUITE("util::URL")
|
|||
TEST_CASE("authority")
|
||||
{
|
||||
URL url{};
|
||||
url.set_scheme("https")
|
||||
.set_host("mamba.org")
|
||||
.set_path("/folder/file.html")
|
||||
.set_query("param=value")
|
||||
.set_fragment("fragment");
|
||||
url.set_scheme("https");
|
||||
url.set_host("mamba.org");
|
||||
url.set_path("/folder/file.html");
|
||||
url.set_query("param=value");
|
||||
url.set_fragment("fragment");
|
||||
CHECK_EQ(url.authority(), "mamba.org");
|
||||
url.set_port("8000");
|
||||
CHECK_EQ(url.authority(), "mamba.org:8000");
|
||||
|
@ -437,20 +519,37 @@ TEST_SUITE("util::URL")
|
|||
{
|
||||
auto url = URL();
|
||||
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ((url / "").path(), "/");
|
||||
CHECK_EQ((url / " ").path(), "/");
|
||||
CHECK_EQ((url / "/").path(), "/");
|
||||
CHECK_EQ((url / "page").path(), "/page");
|
||||
CHECK_EQ((url / "/page").path(), "/page");
|
||||
CHECK_EQ((url / " /page").path(), "/page");
|
||||
CHECK_EQ(url.path(), "/"); // unchanged
|
||||
SUBCASE("Add components")
|
||||
{
|
||||
CHECK_EQ(url.path(), "/");
|
||||
CHECK_EQ((url / "").path(), "/");
|
||||
CHECK_EQ((url / " ").path(), "/ ");
|
||||
CHECK_EQ((url / "/").path(), "/");
|
||||
CHECK_EQ((url / "page").path(), "/page");
|
||||
CHECK_EQ((url / "/page").path(), "/page");
|
||||
CHECK_EQ((url / " /page").path(), "/ /page");
|
||||
CHECK_EQ(url.path(), "/"); // unchanged
|
||||
|
||||
url.append_path("folder");
|
||||
CHECK_EQ(url.path(), "/folder");
|
||||
CHECK_EQ((url / "").path(), "/folder");
|
||||
CHECK_EQ((url / "/").path(), "/folder/");
|
||||
CHECK_EQ((url / "page").path(), "/folder/page");
|
||||
CHECK_EQ((url / "/page").path(), "/folder/page");
|
||||
url.append_path("folder");
|
||||
CHECK_EQ(url.path(), "/folder");
|
||||
CHECK_EQ((url / "").path(), "/folder");
|
||||
CHECK_EQ((url / "/").path(), "/folder/");
|
||||
CHECK_EQ((url / "page").path(), "/folder/page");
|
||||
CHECK_EQ((url / "/page").path(), "/folder/page");
|
||||
}
|
||||
|
||||
SUBCASE("Absolute paths")
|
||||
{
|
||||
url.set_scheme("file");
|
||||
url.append_path("C:/folder/file.txt");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.str(), "file:///C:/folder/file.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.str(), "file:///C%3A/folder/file.txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,13 @@ TEST_SUITE("util::url_manip")
|
|||
// Does NOT parse URL
|
||||
CHECK_EQ(url_encode("https://foo/"), "https%3A%2F%2Ffoo%2F");
|
||||
|
||||
// Exclude characters
|
||||
CHECK_EQ(url_encode(" /word%", '/'), "%20/word%25");
|
||||
|
||||
CHECK_EQ(url_decode(""), "");
|
||||
CHECK_EQ(url_decode("page"), "page");
|
||||
CHECK_EQ(url_decode("%20%2Fword%25"), " /word%");
|
||||
CHECK_EQ(url_decode(" /word%25"), " /word%");
|
||||
CHECK_EQ(url_decode("user%40email.com"), "user@email.com");
|
||||
CHECK_EQ(url_decode("https%3A%2F%2Ffoo%2F"), "https://foo/");
|
||||
CHECK_EQ(
|
||||
|
|
Loading…
Reference in New Issue