srctree

Robin Linden parent 258c08fd 419f165e
uri: Support parsing failure

This is both needed to fix a problem found in fuzz-testing, and bringsthe API more in line with how the new url parsing works.

inlinesplit
browser/gui/app.cpp added: 114, removed: 87, total 27
@@ -467,8 +467,13 @@ void App::navigate() {
return uri::Uri::parse(url_buf_);
}();
 
browse_history_.push(uri);
maybe_page_ = engine_.navigate(std::move(uri));
if (!uri) {
spdlog::error("Unable to parse '{}'", url_buf_);
return;
}
 
browse_history_.push(*uri);
maybe_page_ = engine_.navigate(*std::move(uri));
 
// Make sure the displayed url is still correct if we followed any redirects.
if (maybe_page_) {
@@ -558,10 +563,15 @@ void App::on_page_loaded() {
}
 
auto uri = uri::Uri::parse(link->attributes.at("href"), page().uri);
auto icon = engine_.load(uri).response;
if (!uri) {
spdlog::warn("Unable to parse favicon uri '{}'", link->attributes.at("href"));
continue;
}
 
auto icon = engine_.load(*uri).response;
sf::Image favicon;
if (icon.err != protocol::Error::Ok || !favicon.loadFromMemory(icon.body.data(), icon.body.size())) {
spdlog::warn("Error loading favicon from '{}': {}", uri.uri, to_string(icon.err));
spdlog::warn("Error loading favicon from '{}': {}", uri->uri, to_string(icon.err));
continue;
}
 
 
browser/tui/tui.cpp added: 114, removed: 87, total 27
@@ -45,12 +45,17 @@ int main(int argc, char **argv) {
auto uri_str = argc > 1 ? std::string{argv[1]} : kDefaultUri;
ensure_has_scheme(uri_str);
auto uri = uri::Uri::parse(uri_str);
if (!uri) {
spdlog::error(R"(Invalid URI "{}")", uri_str);
return 1;
}
 
// Latest Firefox ESR user agent (on Windows). This matches what the Tor browser does.
auto user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"s;
engine::Engine engine{protocol::HandlerFactory::create(std::move(user_agent))};
auto maybe_page = engine.navigate(uri);
auto maybe_page = engine.navigate(*uri);
if (!maybe_page) {
spdlog::error(R"(Error loading "{}": {})", uri.uri, to_string(maybe_page.error().response.err));
spdlog::error(R"(Error loading "{}": {})", uri->uri, to_string(maybe_page.error().response.err));
return 1;
}
 
@@ -60,7 +65,7 @@ int main(int argc, char **argv) {
spdlog::info("Building TUI");
 
if (!page->layout.has_value()) {
spdlog::error("Unable to create a layout of {}", uri.uri);
spdlog::error("Unable to create a layout of {}", uri->uri);
return 1;
}
 
 
engine/engine.cpp added: 114, removed: 87, total 27
@@ -70,23 +70,27 @@ tl::expected<std::unique_ptr<PageState>, NavigationError> Engine::navigate(uri::
future_new_rules.push_back(std::async(std::launch::async, [=, this, &state]() -> css::StyleSheet {
auto const &href = link->attributes.at("href");
auto stylesheet_url = uri::Uri::parse(href, state->uri);
if (!stylesheet_url) {
spdlog::warn("Failed to parse href '{}', skipping stylesheet", href);
return {};
}
 
spdlog::info("Downloading stylesheet from {}", stylesheet_url.uri);
auto res = load(stylesheet_url);
spdlog::info("Downloading stylesheet from {}", stylesheet_url->uri);
auto res = load(*stylesheet_url);
auto &style_data = res.response;
stylesheet_url = std::move(res.uri_after_redirects);
 
if (style_data.err != protocol::Error::Ok) {
spdlog::warn("Error {} downloading {}", static_cast<int>(style_data.err), stylesheet_url.uri);
spdlog::warn("Error {} downloading {}", static_cast<int>(style_data.err), stylesheet_url->uri);
return {};
}
 
if ((stylesheet_url.scheme == "http" || stylesheet_url.scheme == "https")
if ((stylesheet_url->scheme == "http" || stylesheet_url->scheme == "https")
&& style_data.status_line.status_code != 200) {
spdlog::warn("Error {}: {} downloading {}",
style_data.status_line.status_code,
style_data.status_line.reason,
stylesheet_url.uri);
stylesheet_url->uri);
return {};
}
 
@@ -99,7 +103,7 @@ tl::expected<std::unique_ptr<PageState>, NavigationError> Engine::navigate(uri::
auto const &err = decoded.error();
spdlog::error("Failed {}-decoding of '{}': '{}: {}'",
*encoding,
stylesheet_url.uri,
stylesheet_url->uri,
err.code,
err.message);
return {};
@@ -107,7 +111,7 @@ tl::expected<std::unique_ptr<PageState>, NavigationError> Engine::navigate(uri::
 
style_data.body = *std::move(decoded);
} else if (encoding) {
spdlog::warn("Got unsupported encoding '{}', skipping stylesheet '{}'", *encoding, stylesheet_url.uri);
spdlog::warn("Got unsupported encoding '{}', skipping stylesheet '{}'", *encoding, stylesheet_url->uri);
return {};
}
 
@@ -152,7 +156,13 @@ Engine::LoadResult Engine::load(uri::Uri uri) {
}
 
spdlog::info("Following {} redirect from {} to {}", response.status_line.status_code, uri.uri, *location);
uri = uri::Uri::parse(std::string(*location), uri);
auto new_uri = uri::Uri::parse(std::string(*location), uri);
if (!new_uri) {
response.err = protocol::Error::InvalidResponse;
return {std::move(response), std::move(uri)};
}
 
uri = *std::move(new_uri);
response = protocol_handler_->handle(uri);
if (redirect_count > kMaxRedirects) {
response.err = protocol::Error::RedirectLimit;
 
engine/engine_test.cpp added: 114, removed: 87, total 27
@@ -59,7 +59,7 @@ int main() {
},
}};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com"));
auto page = e.navigate(uri::Uri::parse("hax://example.com").value());
expect_eq(page.value()->stylesheet.rules.back(),
css::Rule{
.selectors{"p"},
@@ -72,7 +72,7 @@ int main() {
std::pair{"hax://example.com"s, Response{.err = Error::Unresolved}},
})};
 
auto page = e.navigate(uri::Uri::parse("hax://example.com"));
auto page = e.navigate(uri::Uri::parse("hax://example.com").value());
expect_eq(page.has_value(), false);
});
 
@@ -81,7 +81,7 @@ int main() {
std::pair{"hax://example.com"s, Response{.err = Error::Ok}},
})};
 
auto page = e.navigate(uri::Uri::parse("hax://example.com"));
auto page = e.navigate(uri::Uri::parse("hax://example.com").value());
expect(page.has_value());
});
 
@@ -91,7 +91,7 @@ int main() {
})};
e.set_layout_width(123);
 
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect_eq(page->layout_width, 123);
 
e.relayout(*page, 100);
@@ -108,7 +108,7 @@ int main() {
},
}};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
// Our default CSS gives <html> the property display: block.
require(page->layout.has_value());
expect_eq(page->layout->get_property<css::PropertyId::Display>(), style::DisplayValue::Block);
@@ -123,7 +123,7 @@ int main() {
}};
 
e = engine::Engine{std::make_unique<FakeProtocolHandler>(std::move(responses))};
page = e.navigate(uri::Uri::parse("hax://example.com")).value();
page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
 
// The CSS declared in the page should have a higher priority and give
// <html> the property display: inline.
@@ -147,7 +147,7 @@ int main() {
},
}};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
require(page->layout.has_value());
auto const *a = dom::nodes_by_xpath(*page->layout, "//a"sv).at(0);
expect_eq(a->get_property<css::PropertyId::Color>(), gfx::Color::from_css_name("red"));
@@ -176,7 +176,7 @@ int main() {
.body{"p { color: green; }"},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(contains(
page->stylesheet.rules, {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}}));
expect(contains(page->stylesheet.rules, {.selectors{"p"}, .declarations{{css::PropertyId::Color, "green"}}}));
@@ -196,7 +196,7 @@ int main() {
.body{"p { font-size: 123em; }"},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(!contains(
page->stylesheet.rules, {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}}));
});
@@ -222,7 +222,7 @@ int main() {
.body{gzipped_css},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(responses)};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -238,7 +238,7 @@ int main() {
.body{std::move(gzipped_css)},
};
e = engine::Engine{std::make_unique<FakeProtocolHandler>(responses)};
page = e.navigate(uri::Uri::parse("hax://example.com")).value();
page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -263,7 +263,7 @@ int main() {
.body{std::move(gzipped_css)},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -288,7 +288,7 @@ int main() {
.body{std::move(gzipped_css)},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -311,7 +311,7 @@ int main() {
.body{zlibbed_css},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(responses)};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -334,7 +334,7 @@ int main() {
.body{zlibbed_css},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(responses)};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect(std::ranges::find(page->stylesheet.rules,
css::Rule{
.selectors{"p"},
@@ -356,7 +356,7 @@ int main() {
.body{"<html><body>hello!</body></html>"},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect_eq(page->response.err, protocol::Error::Ok);
expect_eq(page->uri.uri, "hax://example.com/redirected");
 
@@ -371,7 +371,7 @@ int main() {
.status_line = {.status_code = 301},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
expect_eq(e.navigate(uri::Uri::parse("hax://example.com")).error().response.err,
expect_eq(e.navigate(uri::Uri::parse("hax://example.com").value()).error().response.err,
protocol::Error::InvalidResponse);
});
 
@@ -395,7 +395,7 @@ int main() {
.body{"p { color: green; }"},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
auto page = e.navigate(uri::Uri::parse("hax://example.com")).value();
auto page = e.navigate(uri::Uri::parse("hax://example.com").value()).value();
expect_eq(page->response.err, protocol::Error::Ok);
expect(contains(page->stylesheet.rules, {.selectors{"p"}, .declarations{{css::PropertyId::Color, "green"}}}));
});
@@ -408,7 +408,7 @@ int main() {
.headers = {{"Location", "hax://example.com"}},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(std::move(responses))};
expect_eq(e.navigate(uri::Uri::parse("hax://example.com")).error().response.err, //
expect_eq(e.navigate(uri::Uri::parse("hax://example.com").value()).error().response.err, //
protocol::Error::RedirectLimit);
});
 
@@ -425,7 +425,7 @@ int main() {
.body{"<html><body>hello!</body></html>"},
};
engine::Engine e{std::make_unique<FakeProtocolHandler>(responses)};
auto res = e.load(uri::Uri::parse("hax://example.com"));
auto res = e.load(uri::Uri::parse("hax://example.com").value());
expect_eq(res.uri_after_redirects, uri::Uri::parse("hax://example.com/redirected"));
expect_eq(res.response, responses.at("hax://example.com/redirected"));
});
 
protocol/file_handler_test.cpp added: 114, removed: 87, total 27
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022-2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -62,7 +62,7 @@ private:
int main() {
etest::test("uri pointing to non-existent file", [] {
protocol::FileHandler handler;
auto res = handler.handle(uri::Uri::parse("file:///this/file/does/definitely/not/exist.hastur"));
auto res = handler.handle(uri::Uri::parse("file:///this/file/does/definitely/not/exist.hastur").value());
expect_eq(res, protocol::Response{protocol::Error::Unresolved});
});
 
@@ -70,7 +70,7 @@ int main() {
auto tmp_dir = fs::temp_directory_path();
 
protocol::FileHandler handler;
auto res = handler.handle(uri::Uri::parse(fmt::format("file://{}", tmp_dir.generic_string())));
auto res = handler.handle(uri::Uri::parse(fmt::format("file://{}", tmp_dir.generic_string())).value());
expect_eq(res, protocol::Response{protocol::Error::InvalidResponse});
});
 
@@ -83,7 +83,7 @@ int main() {
require(bool{tmp_file->fstream() << "hello!" << std::flush});
 
protocol::FileHandler handler;
auto res = handler.handle(uri::Uri::parse(fmt::format("file://{}", tmp_file->path().generic_string())));
auto res = handler.handle(uri::Uri::parse(fmt::format("file://{}", tmp_file->path().generic_string())).value());
expect_eq(res, protocol::Response{protocol::Error::Ok, {}, {}, "hello!"});
});
 
 
protocol/http_test.cpp added: 114, removed: 87, total 27
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2021-2022 Mikael Larsson <c.mikael.larsson@gmail.com>
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2023-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -62,7 +62,7 @@ struct FakeSocket {
};
 
uri::Uri create_uri(std::string url = "http://example.com") {
return uri::Uri::parse(std::move(url));
return uri::Uri::parse(std::move(url)).value();
}
 
FakeSocket create_chunked_socket(std::string const &body) {
 
uri/uri.cpp added: 114, removed: 87, total 27
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2021 David Zero <zero-one@zer0-one.net>
// SPDX-FileCopyrightText: 2022-2023 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -9,7 +9,6 @@
 
#include <fmt/core.h>
 
#include <exception>
#include <functional>
#include <optional>
#include <regex>
@@ -36,29 +35,29 @@ void normalize(Uri &uri) {
void complete_from_base_if_needed(Uri &uri, Uri const &base) {
if (uri.scheme.empty() && uri.authority.host.empty() && uri.path.starts_with('/')) {
// Origin-relative.
uri = Uri::parse(fmt::format("{}://{}{}", base.scheme, base.authority.host, uri.uri));
uri = Uri::parse(fmt::format("{}://{}{}", base.scheme, base.authority.host, uri.uri)).value();
} else if (uri.scheme.empty() && uri.authority.host.empty() && !uri.path.empty()) {
// https://url.spec.whatwg.org/#path-relative-url-string
if (base.path == "/") {
uri = Uri::parse(fmt::format("{}/{}", base.uri, uri.uri));
uri = Uri::parse(fmt::format("{}/{}", base.uri, uri.uri)).value();
} else {
auto end_of_last_path_segment = base.uri.find_last_of('/');
uri = Uri::parse(fmt::format("{}/{}", base.uri.substr(0, end_of_last_path_segment), uri.uri));
uri = Uri::parse(fmt::format("{}/{}", base.uri.substr(0, end_of_last_path_segment), uri.uri)).value();
}
} else if (uri.scheme.empty() && !uri.authority.host.empty() && uri.uri.starts_with("//")) {
// Scheme-relative.
uri = Uri::parse(fmt::format("{}:{}", base.scheme, uri.uri));
uri = Uri::parse(fmt::format("{}:{}", base.scheme, uri.uri)).value();
}
}
 
} // namespace
 
Uri Uri::parse(std::string uristr, std::optional<std::reference_wrapper<Uri const>> base_uri) {
std::optional<Uri> Uri::parse(std::string uristr, std::optional<std::reference_wrapper<Uri const>> base_uri) {
// Regex taken from RFC 3986.
std::smatch match;
std::regex const uri_regex{"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"};
if (!std::regex_search(uristr, match, uri_regex)) {
std::terminate();
return std::nullopt;
}
 
Authority authority{};
 
uri/uri.h added: 114, removed: 87, total 27
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2021 David Zero <zero-one@zer0-one.net>
// SPDX-FileCopyrightText: 2022 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -24,7 +24,8 @@ struct Authority {
};
 
struct Uri {
static Uri parse(std::string uri, std::optional<std::reference_wrapper<Uri const>> base_uri = std::nullopt);
static std::optional<Uri> parse(
std::string uri, std::optional<std::reference_wrapper<Uri const>> base_uri = std::nullopt);
 
std::string uri;
std::string scheme;
 
uri/uri_test.cpp added: 114, removed: 87, total 27
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: 2021 David Zero <zero-one@zer0-one.net>
// SPDX-FileCopyrightText: 2022 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2022-2024 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -37,7 +37,8 @@ int main() {
});
 
etest::test("https: user, pass, port, path, query", [] {
auto https_uri = Uri::parse("https://zero-one:muh_password@example-domain.net:8080/muh/long/path.html?foo=bar");
auto https_uri =
Uri::parse("https://zero-one:muh_password@example-domain.net:8080/muh/long/path.html?foo=bar").value();
 
expect(https_uri.scheme == "https");
expect(https_uri.authority.user == "zero-one");
@@ -50,7 +51,8 @@ int main() {
});
 
etest::test("https: user, pass, path, query", [] {
auto https_uri = Uri::parse("https://zero-one:muh_password@example-domain.net/muh/long/path.html?foo=bar");
auto https_uri =
Uri::parse("https://zero-one:muh_password@example-domain.net/muh/long/path.html?foo=bar").value();
 
expect(https_uri.scheme == "https");
expect(https_uri.authority.user == "zero-one");
@@ -63,7 +65,7 @@ int main() {
});
 
etest::test("https: user, path, query", [] {
auto https_uri = Uri::parse("https://zero-one@example-domain.net/muh/long/path.html?foo=bar");
auto https_uri = Uri::parse("https://zero-one@example-domain.net/muh/long/path.html?foo=bar").value();
 
expect(https_uri.scheme == "https");
expect(https_uri.authority.user == "zero-one");
@@ -76,7 +78,7 @@ int main() {
});
 
etest::test("https: path, query", [] {
auto https_uri = Uri::parse("https://example-domain.net/muh/long/path.html?foo=bar");
auto https_uri = Uri::parse("https://example-domain.net/muh/long/path.html?foo=bar").value();
 
expect(https_uri.scheme == "https");
expect(https_uri.authority.user.empty());
@@ -89,7 +91,7 @@ int main() {
});
 
etest::test("https: path, fragment", [] {
auto https_uri = Uri::parse("https://example-domain.net/muh/long/path.html#About");
auto https_uri = Uri::parse("https://example-domain.net/muh/long/path.html#About").value();
 
expect(https_uri.scheme == "https");
expect(https_uri.authority.user.empty());
@@ -102,7 +104,7 @@ int main() {
});
 
etest::test("mailto: path", [] {
auto mailto_uri = Uri::parse("mailto:example@example.net");
auto mailto_uri = Uri::parse("mailto:example@example.net").value();
 
expect(mailto_uri.scheme == "mailto");
expect(mailto_uri.authority.user.empty());
@@ -115,7 +117,7 @@ int main() {
});
 
etest::test("tel: path", [] {
auto tel_uri = Uri::parse("tel:+1-830-476-5664");
auto tel_uri = Uri::parse("tel:+1-830-476-5664").value();
 
expect(tel_uri.scheme == "tel");
expect(tel_uri.authority.user.empty());
@@ -128,17 +130,17 @@ int main() {
});
 
etest::test("relative, no host", [] {
auto uri = Uri::parse("hello/there.html");
auto uri = Uri::parse("hello/there.html").value();
expect_eq(uri, Uri{.uri = "hello/there.html", .path = "hello/there.html"});
});
 
etest::test("absolute, no host", [] {
auto uri = Uri::parse("/hello/there.html");
auto uri = Uri::parse("/hello/there.html").value();
expect_eq(uri, Uri{.uri = "/hello/there.html", .path = "/hello/there.html"});
});
 
etest::test("scheme-relative", [] {
auto uri = Uri::parse("//example.com/hello/there.html");
auto uri = Uri::parse("//example.com/hello/there.html").value();
expect_eq(uri,
Uri{.uri = "//example.com/hello/there.html",
.authority = {.host = "example.com"},
@@ -146,7 +148,7 @@ int main() {
});
 
etest::test("normalization, lowercasing scheme+host", [] {
auto actual = Uri::parse("HTTPS://EXAMPLE.COM/");
auto actual = Uri::parse("HTTPS://EXAMPLE.COM/").value();
Uri expected{
.uri = "HTTPS://EXAMPLE.COM/",
.scheme = "https",
@@ -158,30 +160,30 @@ int main() {
});
 
etest::test("origin-relative completion", [] {
auto const base = uri::Uri::parse("hax://example.com");
auto const completed = uri::Uri::parse("/test", base);
expect_eq(completed, uri::Uri::parse("hax://example.com/test"));
auto const base = uri::Uri::parse("hax://example.com").value();
auto const completed = uri::Uri::parse("/test", base).value();
expect_eq(completed, uri::Uri::parse("hax://example.com/test").value());
});
 
etest::test("scheme-relative uri", [] {
auto const base = uri::Uri::parse("hax://example.com");
auto const completed = uri::Uri::parse("//example2.com/test", base);
expect_eq(completed, uri::Uri::parse("hax://example2.com/test"));
auto const base = uri::Uri::parse("hax://example.com").value();
auto const completed = uri::Uri::parse("//example2.com/test", base).value();
expect_eq(completed, uri::Uri::parse("hax://example2.com/test").value());
});
 
etest::test("path-relative uri", [] {
auto const base = uri::Uri::parse("hax://example.com");
auto completed = uri::Uri::parse("test", base);
expect_eq(completed, uri::Uri::parse("hax://example.com/test"));
auto const base = uri::Uri::parse("hax://example.com").value();
auto completed = uri::Uri::parse("test", base).value();
expect_eq(completed, uri::Uri::parse("hax://example.com/test").value());
 
completed = uri::Uri::parse("hello/", completed);
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/"));
completed = uri::Uri::parse("hello/", completed).value();
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/").value());
 
completed = uri::Uri::parse("test", completed);
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/test"));
completed = uri::Uri::parse("test", completed).value();
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/test").value());
 
completed = uri::Uri::parse("goodbye", completed);
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/goodbye"));
completed = uri::Uri::parse("goodbye", completed).value();
expect_eq(completed, uri::Uri::parse("hax://example.com/hello/goodbye").value());
});
 
return etest::run_all_tests();