blob: d5a437a9c481031e6edf249b966f8a7d845716a5 [file] [log] [blame]
// Copyright (c) 2017 Cloudflare, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "url.h"
#include <kj/debug.h>
#include <kj/test.h>
namespace kj {
namespace {
Url parseAndCheck(kj::StringPtr originalText, kj::StringPtr expectedRestringified = nullptr,
Url::Options options = {}) {
if (expectedRestringified == nullptr) expectedRestringified = originalText;
auto url = Url::parse(originalText, Url::REMOTE_HREF, options);
KJ_EXPECT(kj::str(url) == expectedRestringified, url, originalText, expectedRestringified);
// Make sure clones also restringify to the expected string.
auto clone = url.clone();
KJ_EXPECT(kj::str(clone) == expectedRestringified, clone, originalText, expectedRestringified);
return url;
}
static constexpr Url::Options NO_DECODE {
false, // percentDecode
false, // allowEmpty
};
static constexpr Url::Options ALLOW_EMPTY {
true, // percentDecode
true, // allowEmpty
};
KJ_TEST("parse / stringify URL") {
{
auto url = parseAndCheck("https://capnproto.org");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org:80");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org:80");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org/");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar/");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux&corge#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux&corge=#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=&corge=grault#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == "grault");
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar/?baz=qux&corge=grault#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == "grault");
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo?bar%20baz=qux+quux",
"https://capnproto.org/foo?bar+baz=qux+quux");
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "bar baz");
KJ_EXPECT(url.query[0].value == "qux quux");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar/#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://[email protected]");
KJ_EXPECT(url.scheme == "https");
auto& user = KJ_ASSERT_NONNULL(url.userInfo);
KJ_EXPECT(user.username == "foo");
KJ_EXPECT(user.password == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://$foo&:12+,[email protected]");
KJ_EXPECT(url.scheme == "https");
auto& user = KJ_ASSERT_NONNULL(url.userInfo);
KJ_EXPECT(user.username == "$foo&");
KJ_EXPECT(KJ_ASSERT_NONNULL(user.password) == "12+,34");
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://[2001:db8::1234]:80/foo");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "[2001:db8::1234]:80");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
auto url = parseAndCheck("https://capnproto.org/foo%2Fbar/baz");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo/bar", "baz"}));
}
parseAndCheck("https://capnproto.org/foo/bar?", "https://capnproto.org/foo/bar");
parseAndCheck("https://capnproto.org/foo/bar?#", "https://capnproto.org/foo/bar#");
parseAndCheck("https://capnproto.org/foo/bar#");
// Scheme and host are forced to lower-case.
parseAndCheck("hTtP://capNprotO.org/fOo/bAr", "http://capnproto.org/fOo/bAr");
// URLs with underscores in their hostnames are allowed, but you probably shouldn't use them. They
// are not valid domain names.
parseAndCheck("https://bad_domain.capnproto.org/");
// Make sure URLs with %-encoded '%' signs in their userinfo, path, query, and fragment components
// get correctly re-encoded.
parseAndCheck("https://foo%25bar:baz%[email protected]/");
parseAndCheck("https://capnproto.org/foo%25bar");
parseAndCheck("https://capnproto.org/?foo%25bar=baz%25qux");
parseAndCheck("https://capnproto.org/#foo%25bar");
// Make sure redundant /'s and &'s aren't collapsed when options.removeEmpty is false.
parseAndCheck("https://capnproto.org/foo//bar///test//?foo=bar&&baz=qux&", nullptr, ALLOW_EMPTY);
// "." and ".." are still processed, though.
parseAndCheck("https://capnproto.org/foo//../bar/.",
"https://capnproto.org/foo/bar/", ALLOW_EMPTY);
{
auto url = parseAndCheck("https://foo/", nullptr, ALLOW_EMPTY);
KJ_EXPECT(url.path.size() == 0);
KJ_EXPECT(url.hasTrailingSlash);
}
{
auto url = parseAndCheck("https://foo/bar/", nullptr, ALLOW_EMPTY);
KJ_EXPECT(url.path.size() == 1);
KJ_EXPECT(url.hasTrailingSlash);
}
}
KJ_TEST("URL percent encoding") {
parseAndCheck(
"https://b%6fb:%61bcd@capnpr%6fto.org/f%6fo?b%61r=b%61z#q%75x",
"https://bob:[email protected]/foo?bar=baz#qux");
parseAndCheck(
"https://b\001b:\[email protected]/f\001o?b\001r=b\001z#q\001x",
"https://b%01b:%[email protected]/f%01o?b%01r=b%01z#q%01x");
parseAndCheck(
"https://b b: [email protected]/f o?b r=b z#q x",
"https://b%20b:%[email protected]/f%20o?b+r=b+z#q%20x");
parseAndCheck(
"https://capnproto.org/foo?bar=baz#@?#^[\\]{|}",
"https://capnproto.org/foo?bar=baz#@?#^[\\]{|}");
// All permissible non-alphanumeric, non-separator path characters.
parseAndCheck(
"https://capnproto.org/!$&'()*+,-.:;=@[]^_|~",
"https://capnproto.org/!$&'()*+,-.:;=@[]^_|~");
}
KJ_TEST("parse / stringify URL w/o decoding") {
{
auto url = parseAndCheck("https://capnproto.org/foo%2Fbar/baz", nullptr, NO_DECODE);
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo%2Fbar", "baz"}));
}
{
// This case would throw an exception without NO_DECODE.
Url url = parseAndCheck("https://capnproto.org/R%20%26%20S?%foo=%QQ", nullptr, NO_DECODE);
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"R%20%26%20S"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "%foo");
KJ_EXPECT(url.query[0].value == "%QQ");
}
}
KJ_TEST("URL relative paths") {
parseAndCheck(
"https://capnproto.org/foo//bar",
"https://capnproto.org/foo/bar");
parseAndCheck(
"https://capnproto.org/foo/./bar",
"https://capnproto.org/foo/bar");
parseAndCheck(
"https://capnproto.org/foo/bar//",
"https://capnproto.org/foo/bar/");
parseAndCheck(
"https://capnproto.org/foo/bar/.",
"https://capnproto.org/foo/bar/");
parseAndCheck(
"https://capnproto.org/foo/baz/../bar",
"https://capnproto.org/foo/bar");
parseAndCheck(
"https://capnproto.org/foo/bar/baz/..",
"https://capnproto.org/foo/bar/");
parseAndCheck(
"https://capnproto.org/..",
"https://capnproto.org/");
parseAndCheck(
"https://capnproto.org/foo/../..",
"https://capnproto.org/");
}
KJ_TEST("URL for HTTP request") {
{
Url url = Url::parse("https://bob:[email protected]/foo/bar?baz=qux#corge");
KJ_EXPECT(url.toString(Url::REMOTE_HREF) ==
"https://bob:[email protected]/foo/bar?baz=qux#corge");
KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/foo/bar?baz=qux");
KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/foo/bar?baz=qux");
}
{
Url url = Url::parse("https://capnproto.org");
KJ_EXPECT(url.toString(Url::REMOTE_HREF) == "https://capnproto.org");
KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org");
KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/");
}
{
Url url = Url::parse("/foo/bar?baz=qux&corge", Url::HTTP_REQUEST);
KJ_EXPECT(url.scheme == nullptr);
KJ_EXPECT(url.host == nullptr);
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == nullptr);
}
{
Url url = Url::parse("https://capnproto.org/foo/bar?baz=qux&corge", Url::HTTP_PROXY_REQUEST);
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == nullptr);
}
{
// '#' is allowed in path components in the HTTP_REQUEST context.
Url url = Url::parse("/foo#bar", Url::HTTP_REQUEST);
KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/foo%23bar");
KJ_EXPECT(url.scheme == nullptr);
KJ_EXPECT(url.host == nullptr);
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>{"foo#bar"});
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
// '#' is allowed in path components in the HTTP_PROXY_REQUEST context.
Url url = Url::parse("https://capnproto.org/foo#bar", Url::HTTP_PROXY_REQUEST);
KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/foo%23bar");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>{"foo#bar"});
KJ_EXPECT(!url.hasTrailingSlash);
KJ_EXPECT(url.query == nullptr);
KJ_EXPECT(url.fragment == nullptr);
}
{
// '#' is allowed in query components in the HTTP_REQUEST context.
Url url = Url::parse("/?foo=bar#123", Url::HTTP_REQUEST);
KJ_EXPECT(url.toString(Url::HTTP_REQUEST) == "/?foo=bar%23123");
KJ_EXPECT(url.scheme == nullptr);
KJ_EXPECT(url.host == nullptr);
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "foo");
KJ_EXPECT(url.query[0].value == "bar#123");
KJ_EXPECT(url.fragment == nullptr);
}
{
// '#' is allowed in query components in the HTTP_PROXY_REQUEST context.
Url url = Url::parse("https://capnproto.org/?foo=bar#123", Url::HTTP_PROXY_REQUEST);
KJ_EXPECT(url.toString(Url::HTTP_PROXY_REQUEST) == "https://capnproto.org/?foo=bar%23123");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path == nullptr);
KJ_EXPECT(url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 1);
KJ_EXPECT(url.query[0].name == "foo");
KJ_EXPECT(url.query[0].value == "bar#123");
KJ_EXPECT(url.fragment == nullptr);
}
}
KJ_TEST("parse URL failure") {
KJ_EXPECT(Url::tryParse("ht/tps://capnproto.org") == nullptr);
KJ_EXPECT(Url::tryParse("capnproto.org") == nullptr);
KJ_EXPECT(Url::tryParse("https:foo") == nullptr);
// percent-decode errors
KJ_EXPECT(Url::tryParse("https://capnproto.org/f%nno") == nullptr);
KJ_EXPECT(Url::tryParse("https://capnproto.org/foo?b%nnr=baz") == nullptr);
// components not valid in context
KJ_EXPECT(Url::tryParse("https://capnproto.org/foo", Url::HTTP_REQUEST) == nullptr);
KJ_EXPECT(Url::tryParse("https://bob:[email protected]/foo", Url::HTTP_PROXY_REQUEST) == nullptr);
KJ_EXPECT(Url::tryParse("https://capnproto.org#/foo", Url::HTTP_PROXY_REQUEST) == nullptr);
}
void parseAndCheckRelative(kj::StringPtr base, kj::StringPtr relative, kj::StringPtr expected,
Url::Options options = {}) {
auto parsed = Url::parse(base, Url::REMOTE_HREF, options).parseRelative(relative);
KJ_EXPECT(kj::str(parsed) == expected, parsed, expected);
auto clone = parsed.clone();
KJ_EXPECT(kj::str(clone) == expected, clone, expected);
}
KJ_TEST("parse relative URL") {
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"#grault",
"https://capnproto.org/foo/bar?baz=qux#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz#corge",
"#grault",
"https://capnproto.org/foo/bar?baz#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=#corge",
"#grault",
"https://capnproto.org/foo/bar?baz=#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault",
"https://capnproto.org/foo/bar?grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault=",
"https://capnproto.org/foo/bar?grault=");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault+garply=waldo",
"https://capnproto.org/foo/bar?grault+garply=waldo");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"grault",
"https://capnproto.org/foo/grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"/grault",
"https://capnproto.org/grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"//grault",
"https://grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"//grault/garply",
"https://grault/garply");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"http:/grault",
"http://capnproto.org/grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"/http:/grault",
"https://capnproto.org/http:/grault");
parseAndCheckRelative("https://capnproto.org/",
"/foo/../bar",
"https://capnproto.org/bar");
}
KJ_TEST("parse relative URL w/o decoding") {
// This case would throw an exception without NO_DECODE.
parseAndCheckRelative("https://capnproto.org/R%20%26%20S?%foo=%QQ",
"%ANOTH%ERBAD%URL",
"https://capnproto.org/%ANOTH%ERBAD%URL", NO_DECODE);
}
KJ_TEST("parse relative URL failure") {
auto base = Url::parse("https://example.com/");
KJ_EXPECT(base.tryParseRelative("https://[not a host]") == nullptr);
}
} // namespace
} // namespace kj