mirror of https://github.com/mamba-org/mamba.git
[Unix] Fix slashes usage in file urls (#3871)
This commit is contained in:
parent
dd30a5c287
commit
1b3b9e1b25
|
@ -29,6 +29,10 @@ namespace mamba::specs
|
|||
|
||||
inline static constexpr std::string_view token_prefix = "/t/";
|
||||
|
||||
/** Parse a string url.
|
||||
* The url must be percent encoded beforehand.
|
||||
* cf. https://en.wikipedia.org/wiki/Percent-encoding
|
||||
*/
|
||||
[[nodiscard]] static auto parse(std::string_view url) -> expected_parse_t<CondaURL>;
|
||||
|
||||
/** Create a local URL. */
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace mamba::util
|
|||
template <typename... Args>
|
||||
[[nodiscard]] auto url_concat(const Args&... args) -> std::string;
|
||||
|
||||
[[nodiscard]] auto make_curl_compatible(std::string url) -> std::string;
|
||||
|
||||
/**
|
||||
* Convert UNC2 file URI to UNC4.
|
||||
*
|
||||
|
|
|
@ -185,7 +185,10 @@ namespace mamba::util
|
|||
{
|
||||
return tl::make_unexpected(ParseError{ "Empty URL" });
|
||||
}
|
||||
return CurlUrl::parse(file_uri_unc2_to_unc4(url), CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME)
|
||||
return CurlUrl::parse(
|
||||
make_curl_compatible(file_uri_unc2_to_unc4(url)),
|
||||
CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME
|
||||
)
|
||||
.transform(
|
||||
[&](CurlUrl&& handle) -> URL
|
||||
{
|
||||
|
|
|
@ -85,18 +85,44 @@ namespace mamba::util
|
|||
return path_to_url(path);
|
||||
}
|
||||
|
||||
auto file_uri_unc2_to_unc4(std::string_view uri) -> std::string
|
||||
auto check_file_scheme_and_slashes(std::string_view uri)
|
||||
-> std::tuple<bool, std::string_view, std::string_view>
|
||||
{
|
||||
static constexpr std::string_view file_scheme = "file:";
|
||||
|
||||
// Not "file:" scheme
|
||||
if (!util::starts_with(uri, file_scheme))
|
||||
{
|
||||
return { false, {}, {} };
|
||||
}
|
||||
|
||||
auto [slashes, rest] = util::lstrip_parts(util::remove_prefix(uri, file_scheme), '/');
|
||||
return { true, slashes, rest };
|
||||
}
|
||||
|
||||
auto make_curl_compatible(std::string uri) -> std::string
|
||||
{
|
||||
// Convert `file://` and `file:///` to `file:////`
|
||||
// when followed with a drive letter
|
||||
// to make it compatible with libcurl on unix
|
||||
auto [is_file_scheme, slashes, rest] = check_file_scheme_and_slashes(uri);
|
||||
if (!on_win && is_file_scheme && path_has_drive_letter(rest)
|
||||
&& ((slashes.size() == 2) || (slashes.size() == 3)))
|
||||
{
|
||||
return util::concat("file:////", rest);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
auto file_uri_unc2_to_unc4(std::string_view uri) -> std::string
|
||||
{
|
||||
auto [is_file_scheme, slashes, rest] = check_file_scheme_and_slashes(uri);
|
||||
if (!is_file_scheme)
|
||||
{
|
||||
return std::string(uri);
|
||||
}
|
||||
|
||||
// No hostname set in "file://hostname/path/to/data.xml"
|
||||
auto [slashes, rest] = util::lstrip_parts(util::remove_prefix(uri, file_scheme), '/');
|
||||
if (slashes.size() != 2)
|
||||
{
|
||||
return std::string(uri);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/build.hpp"
|
||||
|
||||
using namespace mamba::specs;
|
||||
|
||||
|
@ -488,4 +489,96 @@ namespace
|
|||
REQUIRE(url.pretty_str() == "https://user@email.com:*****@mamba.org/some /path$/");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CondaURL::parse")
|
||||
{
|
||||
SECTION("File URL with 4 slashes, a drive letter, and percent encoded space")
|
||||
{
|
||||
// The URL passed to `CondaURL::parse` must be percent encoded
|
||||
auto url = CondaURL::parse("file:////D:/a/_temp/popen-gw0/some_other_parts%20spaces").value();
|
||||
REQUIRE(url.path() == "//D:/a/_temp/popen-gw0/some_other_parts spaces");
|
||||
REQUIRE(
|
||||
url.path(CondaURL::Decode::no) == "//D:/a/_temp/popen-gw0/some_other_parts%20spaces"
|
||||
);
|
||||
REQUIRE(url.str() == "file:////D:/a/_temp/popen-gw0/some_other_parts%20spaces");
|
||||
REQUIRE(url.pretty_str() == "file:////D:/a/_temp/popen-gw0/some_other_parts spaces");
|
||||
}
|
||||
|
||||
SECTION("File URL with 4 slashes, a drive letter, and non-encoded space")
|
||||
{
|
||||
// The URL passed to `CondaURL::parse` must be percent encoded
|
||||
REQUIRE_FALSE(
|
||||
CondaURL::parse("file:////D:/a/_temp/popen-gw0/some_other_parts spaces").has_value()
|
||||
);
|
||||
}
|
||||
|
||||
SECTION("File URL with 4 slashes")
|
||||
{
|
||||
auto url = CondaURL::parse("file:////ab/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.path() == "//ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:////ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:////ab/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
|
||||
SECTION("File URL with 3 slashes and drive letter")
|
||||
{
|
||||
auto url = CondaURL::parse("file:///D:/a/_temp/popen-gw0/some_other_parts").value();
|
||||
if (mamba::util::on_win)
|
||||
{
|
||||
REQUIRE(url.path() == "/D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:///D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:///D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(url.path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:////D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:////D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("File URL with 3 slashes")
|
||||
{
|
||||
auto url = CondaURL::parse("file:///ab/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.path() == "/ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:///ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:///ab/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
|
||||
SECTION("File URL with 2 slashes and drive letter")
|
||||
{
|
||||
auto url = CondaURL::parse("file://D:/a/_temp/popen-gw0/some_other_parts").value();
|
||||
if (mamba::util::on_win)
|
||||
{
|
||||
REQUIRE(url.path() == "/D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:///D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:///D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(url.path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:////D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:////D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("File URL with 2 slashes")
|
||||
{
|
||||
auto url = CondaURL::parse("file://ab/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.path() == "//ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:////ab/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:////ab/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
|
||||
// NOTE This is not valid on any platform:
|
||||
// "file://\\D:/a/_temp/popen-gw0/some_other_parts"
|
||||
|
||||
SECTION("file://\\abcd/_temp/popen-gw0/some_other_parts")
|
||||
{
|
||||
auto url = CondaURL::parse("file://\\abcd/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.path() == "//\\abcd/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.str() == "file:////\\abcd/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_str() == "file:////\\abcd/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("UTL parse")
|
||||
TEST_CASE("URL parse")
|
||||
{
|
||||
SECTION("Empty")
|
||||
{
|
||||
|
@ -311,19 +311,25 @@ namespace
|
|||
|
||||
SECTION("file://C:/Users/wolfv/test/document.json")
|
||||
{
|
||||
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json").value();
|
||||
REQUIRE(url.scheme() == "file");
|
||||
REQUIRE(url.host() == "");
|
||||
REQUIRE(url.user() == "");
|
||||
REQUIRE(url.password() == "");
|
||||
REQUIRE(url.port() == "");
|
||||
REQUIRE(url.query() == "");
|
||||
REQUIRE(url.fragment() == "");
|
||||
if (on_win)
|
||||
{
|
||||
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json").value();
|
||||
REQUIRE(url.scheme() == "file");
|
||||
REQUIRE(url.host() == "");
|
||||
REQUIRE(url.path() == "/C:/Users/wolfv/test/document.json");
|
||||
REQUIRE(url.path(URL::Decode::no) == "/C:/Users/wolfv/test/document.json");
|
||||
REQUIRE(url.pretty_path() == "C:/Users/wolfv/test/document.json");
|
||||
REQUIRE(url.user() == "");
|
||||
REQUIRE(url.password() == "");
|
||||
REQUIRE(url.port() == "");
|
||||
REQUIRE(url.query() == "");
|
||||
REQUIRE(url.fragment() == "");
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(url.path() == "//C:/Users/wolfv/test/document.json");
|
||||
REQUIRE(url.path(URL::Decode::no) == "//C:/Users/wolfv/test/document.json");
|
||||
REQUIRE(url.pretty_path() == "//C:/Users/wolfv/test/document.json");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,6 +347,42 @@ namespace
|
|||
REQUIRE(url.fragment() == "");
|
||||
}
|
||||
|
||||
SECTION("file:///D:/a/_temp/popen-gw0/some_other_parts")
|
||||
{
|
||||
const URL url = URL::parse("file:///D:/a/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.scheme() == "file");
|
||||
REQUIRE(url.host() == "");
|
||||
REQUIRE(url.user() == "");
|
||||
REQUIRE(url.password() == "");
|
||||
REQUIRE(url.port() == "");
|
||||
REQUIRE(url.query() == "");
|
||||
REQUIRE(url.fragment() == "");
|
||||
if (on_win)
|
||||
{
|
||||
REQUIRE(url.path() == "/D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_path() == "D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(url.path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("file:////D:/a/_temp/popen-gw0/some_other_parts")
|
||||
{
|
||||
const URL url = URL::parse("file:////D:/a/_temp/popen-gw0/some_other_parts").value();
|
||||
REQUIRE(url.scheme() == "file");
|
||||
REQUIRE(url.host() == "");
|
||||
REQUIRE(url.path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.pretty_path() == "//D:/a/_temp/popen-gw0/some_other_parts");
|
||||
REQUIRE(url.user() == "");
|
||||
REQUIRE(url.password() == "");
|
||||
REQUIRE(url.port() == "");
|
||||
REQUIRE(url.query() == "");
|
||||
REQUIRE(url.fragment() == "");
|
||||
}
|
||||
|
||||
SECTION("file:///home/great:doc.json")
|
||||
{
|
||||
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
|
||||
|
|
|
@ -154,6 +154,46 @@ namespace
|
|||
);
|
||||
}
|
||||
|
||||
TEST_CASE("make_curl_compatible")
|
||||
{
|
||||
for (const std::string uri : {
|
||||
"http://example.com/test",
|
||||
R"(file:////C:/Program\ (x74)/Users/hello\ world)",
|
||||
"file:////server/share",
|
||||
"file:///server/share",
|
||||
"file://absolute/path",
|
||||
R"(file://\\D:/server/share)",
|
||||
R"(file://\\server\path)",
|
||||
})
|
||||
{
|
||||
CAPTURE(uri);
|
||||
REQUIRE(make_curl_compatible(uri) == uri);
|
||||
}
|
||||
|
||||
if (on_win)
|
||||
{
|
||||
REQUIRE(
|
||||
make_curl_compatible(R"(file://C:/Program\ (x74)/Users/hello\ world)")
|
||||
== R"(file://C:/Program\ (x74)/Users/hello\ world)"
|
||||
);
|
||||
REQUIRE(
|
||||
make_curl_compatible(R"(file:///C:/Program\ (x74)/Users/hello\ world)")
|
||||
== R"(file:///C:/Program\ (x74)/Users/hello\ world)"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
REQUIRE(
|
||||
make_curl_compatible(R"(file://C:/Program\ (x74)/Users/hello\ world)")
|
||||
== R"(file:////C:/Program\ (x74)/Users/hello\ world)"
|
||||
);
|
||||
REQUIRE(
|
||||
make_curl_compatible(R"(file:///C:/Program\ (x74)/Users/hello\ world)")
|
||||
== R"(file:////C:/Program\ (x74)/Users/hello\ world)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("file_uri_unc2_to_unc4")
|
||||
{
|
||||
for (const std::string uri : {
|
||||
|
|
Loading…
Reference in New Issue