diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_1.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_1.txt new file mode 100644 index 000000000..861b5bb35 --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_1.txt @@ -0,0 +1 @@ +ssh://::1 \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_2.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_2.txt new file mode 100644 index 000000000..952d5a55d --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_2.txt @@ -0,0 +1 @@ +ssh://userinfo@fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_3.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_3.txt new file mode 100644 index 000000000..d1f17adac --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_3.txt @@ -0,0 +1 @@ +ssh://userinfo@fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/store-reference.cc b/src/libstore-tests/store-reference.cc index d9f040ab6..7b42b45a2 100644 --- a/src/libstore-tests/store-reference.cc +++ b/src/libstore-tests/store-reference.cc @@ -148,4 +148,39 @@ URI_TEST( .params = {}, })) +static StoreReference sshLoopbackIPv6{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "[::1]", + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_1, sshLoopbackIPv6) + +static StoreReference sshIPv6AuthorityWithUserinfo{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e]", + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_2, sshIPv6AuthorityWithUserinfo) + +static StoreReference sshIPv6AuthorityWithUserinfoAndParams{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e]", + }, + .params = + { + {"a", "b"}, + {"c", "d"}, + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_3, sshIPv6AuthorityWithUserinfoAndParams) + } // namespace nix diff --git a/src/libstore/meson.build b/src/libstore/meson.build index 253152772..7aeacbab7 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -105,6 +105,7 @@ boost = dependency( 'container', # Shouldn't list, because can header-only, and Meson currently looks for libs #'regex', + 'url', ], include_type : 'system', ) diff --git a/src/libstore/store-reference.cc b/src/libstore/store-reference.cc index 2c54e497e..96ee829d0 100644 --- a/src/libstore/store-reference.cc +++ b/src/libstore/store-reference.cc @@ -1,11 +1,12 @@ -#include - #include "nix/util/error.hh" +#include "nix/util/split.hh" #include "nix/util/url.hh" #include "nix/store/store-reference.hh" #include "nix/util/file-system.hh" #include "nix/util/util.hh" +#include + namespace nix { static bool isNonUriPath(const std::string & spec) @@ -43,6 +44,29 @@ std::string StoreReference::render(bool withParams) const return res; } +namespace { + +struct SchemeAndAuthorityWithPath +{ + std::string_view scheme; + std::string_view authority; +}; + +} // namespace + +/** + * Return the 'scheme' and remove the '://' or ':' separator. + */ +static std::optional splitSchemePrefixTo(std::string_view string) +{ + auto scheme = splitPrefixTo(string, ':'); + if (!scheme) + return std::nullopt; + + splitPrefix(string, "//"); + return SchemeAndAuthorityWithPath{.scheme = *scheme, .authority = string}; +} + StoreReference StoreReference::parse(const std::string & uri, const StoreReference::Params & extraParams) { auto params = extraParams; @@ -90,6 +114,32 @@ StoreReference StoreReference::parse(const std::string & uri, const StoreReferen }, .params = std::move(params), }; + } else if (auto schemeAndAuthority = splitSchemePrefixTo(baseURI)) { + /* Back-compatibility shim to accept unbracketed IPv6 addresses after the scheme. + * Old versions of nix allowed that. Note that this is ambiguous and does not allow + * specifying the port number. For that the address must be bracketed, otherwise it's + * greedily assumed to be the part of the host address. */ + auto authorityString = schemeAndAuthority->authority; + auto userinfo = splitPrefixTo(authorityString, '@'); + auto maybeIpv6 = boost::urls::parse_ipv6_address(authorityString); + if (maybeIpv6) { + std::string fixedAuthority; + if (userinfo) { + fixedAuthority += *userinfo; + fixedAuthority += '@'; + } + fixedAuthority += '['; + fixedAuthority += authorityString; + fixedAuthority += ']'; + return { + .variant = + Specified{ + .scheme = std::string(schemeAndAuthority->scheme), + .authority = fixedAuthority, + }, + .params = std::move(params), + }; + } } }