srctree

David Zero parent ed425d4f 9c721146
url: Complete implementation of Origin

inlinesplit
url/url.cpp added: 244, removed: 6, total 238
@@ -154,6 +154,40 @@ static void icu_init() {
delete uts;
}
 
// https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin
std::string Origin::serialize() const {
if (opaque) {
return "null";
}
 
std::string result = scheme;
 
result += "://";
 
result += host.serialize();
 
if (port.has_value()) {
result += ":";
 
result += std::to_string(*port);
}
 
return result;
}
 
// https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-effective-domain
std::variant<std::monostate, std::string, Host> Origin::effective_domain() const {
if (opaque) {
return std::monostate{};
}
 
if (domain.has_value()) {
return *domain;
}
 
return host;
}
 
// https://w3c.github.io/FileAPI/#unicodeBlobURL
std::string blob_url_create(Origin const &origin) {
std::string result = "blob:";
@@ -180,7 +214,7 @@ std::string blob_url_create(Origin const &origin) {
}
 
if (origin.port.has_value()) {
serialized += ":" + std::to_string(origin.port.value());
serialized += ":" + std::to_string(*origin.port);
}
}
 
@@ -259,6 +293,38 @@ std::string Url::serialize(bool exclude_fragment) const {
return output;
}
 
// https://url.spec.whatwg.org/#concept-url-origin
Origin Url::origin() const {
// Return tuple origin of the path URL
if (scheme == "blob") {
// TODO(dzero): Implement checking blob URL entry, once those are implemented
UrlParser p;
 
std::optional<Url> path_url = p.parse(serialize_path());
 
if (!path_url.has_value()) {
return Origin{"", Host{}, std::nullopt, std::nullopt, true};
}
 
if (path_url->scheme != "http" && path_url->scheme != "https") {
return Origin{"", Host{}, std::nullopt, std::nullopt, true};
}
 
return path_url->origin();
}
// Return a tuple origin
else if (scheme == "ftp" || scheme == "http" || scheme == "https" || scheme == "ws" || scheme == "wss") {
// These schemes all require a host in a valid URL
assert(host.has_value());
 
return Origin{scheme, *host, port, std::nullopt};
}
// Return a new opaque origin
else {
return Origin{"", Host{}, std::nullopt, std::nullopt, true};
}
}
 
void UrlParser::validation_error(ValidationError err) const {
spdlog::debug("url: InputPos: {}, ParserState: {}, Validation Error: {} {}",
current_pos(),
 
url/url.h added: 244, removed: 6, total 238
@@ -28,6 +28,8 @@ struct Host {
std::variant<std::string, std::uint32_t, std::array<std::uint16_t, 8>> data;
 
std::string serialize() const;
 
bool operator==(Host const &) const = default;
};
 
struct Origin {
@@ -35,8 +37,46 @@ struct Origin {
Host host;
std::optional<std::uint16_t> port;
std::optional<std::string> domain;
// Need this placeholder until I figure out what "opaqueness" means for an origin in this context
bool opaque;
// If opaque, then this Origin should serialize to (null). All opaque origins are equal to each other, and not equal
// to all non-opaque origins.
bool opaque = false;
 
std::string serialize() const;
std::variant<std::monostate, std::string, Host> effective_domain() const;
 
// https://html.spec.whatwg.org/multipage/browsers.html#same-origin
bool operator==(Origin const &b) const {
if (opaque && b.opaque) {
return true;
}
 
if (!opaque && !b.opaque) {
if (scheme == b.scheme && host == b.host && port == b.port) {
return true;
}
}
 
return false;
}
 
// https://html.spec.whatwg.org/multipage/browsers.html#same-origin-domain
constexpr bool is_same_origin_domain(Origin const &b) const {
if (opaque && b.opaque) {
return true;
}
 
if (!opaque && !b.opaque) {
if (scheme == b.scheme && domain == b.domain && domain.has_value() && b.domain.has_value()) {
return true;
}
 
if (*this == b && domain == b.domain && !domain.has_value() && !b.domain.has_value()) {
return true;
}
}
 
return false;
}
};
 
/**
@@ -57,6 +97,8 @@ struct Url {
std::string serialize(bool exclude_fragment = false) const;
std::string serialize_path() const;
 
Origin origin() const;
 
constexpr bool includes_credentials() const { return !user.empty() || !passwd.empty(); }
constexpr bool has_opaque_path() const { return std::holds_alternative<std::string>(path); }
 
 
url/url_test.cpp added: 244, removed: 6, total 238
@@ -495,6 +495,136 @@ int main() {
etest::expect_eq(url->serialize(), R"(file:///C:/Users/zero-one/repos/hastur/README.md)");
});
 
etest::test("URL origin", [] {
url::UrlParser p;
 
std::optional<url::Url> url = p.parse("https://example.com:8080/index.html");
std::optional<url::Url> url2 = p.parse("https://example.com:9999/index.php");
std::optional<url::Url> url3 = p.parse("http://example.com:8080/index.html");
std::optional<url::Url> url4 = p.parse("https://example.com:8080/index.php?foo=bar");
 
etest::require(url.has_value());
etest::require(url2.has_value());
etest::require(url3.has_value());
etest::require(url4.has_value());
 
url::Origin o = url->origin();
url::Origin o2 = url2->origin();
url::Origin o3 = url3->origin();
url::Origin o4 = url4->origin();
url::Origin o5{"https", {url::HostType::DnsDomain, "example.com"}, std::uint16_t{8080}, "example.com"};
 
etest::require(o.port.has_value());
etest::require(o2.port.has_value());
etest::require(o3.port.has_value());
etest::require(o4.port.has_value());
 
etest::expect(!o.domain.has_value());
etest::expect(!o2.domain.has_value());
etest::expect(!o3.domain.has_value());
etest::expect(!o4.domain.has_value());
 
etest::expect_eq(o.scheme, "https");
etest::expect_eq(o2.scheme, "https");
etest::expect_eq(o3.scheme, "http");
etest::expect_eq(o4.scheme, "https");
 
etest::expect_eq(o.host.serialize(), "example.com");
etest::expect_eq(o2.host.serialize(), "example.com");
etest::expect_eq(o3.host.serialize(), "example.com");
etest::expect_eq(o4.host.serialize(), "example.com");
 
etest::expect_eq(*o.port, 8080);
etest::expect_eq(*o2.port, 9999);
etest::expect_eq(*o3.port, 8080);
etest::expect_eq(*o4.port, 8080);
 
etest::expect(!o.opaque);
etest::expect(!o2.opaque);
etest::expect(!o3.opaque);
etest::expect(!o4.opaque);
 
etest::expect_eq(o.serialize(), "https://example.com:8080");
etest::expect_eq(o2.serialize(), "https://example.com:9999");
etest::expect_eq(o3.serialize(), "http://example.com:8080");
etest::expect_eq(o4.serialize(), "https://example.com:8080");
 
etest::expect(o != o2);
etest::expect(o != o3);
etest::expect(o == o4);
etest::expect(o == o5);
 
etest::expect(!o.is_same_origin_domain(o2));
etest::expect(!o.is_same_origin_domain(o3));
etest::expect(o.is_same_origin_domain(o4));
etest::expect(!o.is_same_origin_domain(o5));
 
etest::expect(std::holds_alternative<url::Host>(o.effective_domain()));
etest::expect(std::holds_alternative<url::Host>(o2.effective_domain()));
etest::expect(std::holds_alternative<url::Host>(o3.effective_domain()));
etest::expect(std::holds_alternative<url::Host>(o4.effective_domain()));
etest::expect(std::holds_alternative<std::string>(o5.effective_domain()));
 
etest::expect_eq(std::get<std::string>(o5.effective_domain()), "example.com");
});
 
etest::test("URL origin: opaque origin", [] {
url::UrlParser p;
 
std::optional<url::Url> url = p.parse("file:///usr/local/bin/foo");
std::optional<url::Url> url2 = p.parse("file:///etc/passwd");
std::optional<url::Url> url3 = p.parse("http://example.com");
 
etest::require(url.has_value());
etest::require(url2.has_value());
etest::require(url3.has_value());
 
url::Origin o = url->origin();
url::Origin o2 = url2->origin();
url::Origin o3 = url3->origin();
 
etest::expect(o.opaque);
etest::expect(o2.opaque);
etest::expect(!o3.opaque);
 
etest::expect_eq(o.serialize(), "null");
etest::expect_eq(o2.serialize(), "null");
 
etest::expect(std::holds_alternative<std::monostate>(o.effective_domain()));
etest::expect(std::holds_alternative<std::monostate>(o2.effective_domain()));
 
etest::expect(o == o2);
etest::expect(o != o3);
 
etest::expect(o.is_same_origin_domain(o2));
etest::expect(!o.is_same_origin_domain(o3));
});
 
etest::test("URL origin: blob URL", [] {
url::UrlParser p;
 
std::optional<url::Url> url = p.parse("blob:https://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f");
std::optional<url::Url> url2 = p.parse("blob:ws://whatwg.org/d0360e2f-caee-469f-9a2f-87d5b0456f6f");
 
etest::require(url.has_value());
etest::require(url2.has_value());
 
url::Origin o = url->origin();
url::Origin o2 = url2->origin();
 
etest::expect(!o.opaque);
etest::expect(o2.opaque);
 
etest::expect(!o.port.has_value());
etest::expect(!o.domain.has_value());
 
etest::expect_eq(o.scheme, "https");
etest::expect_eq(o.host.serialize(), "whatwg.org");
 
etest::expect_eq(o.serialize(), "https://whatwg.org");
etest::expect_eq(o2.serialize(), "null");
});
 
int ret = etest::run_all_tests();
 
url::icu_cleanup();