#![allow(dead_code)]
use core::fmt;
use iri_string::build::Builder;
#[derive(Debug, Clone, Copy)]
pub struct TestCase<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub composed: &'a str,
pub components: Components<'a>,
pub normalized_uri: &'a str,
pub normalized_iri: &'a str,
pub normalized_uri_whatwg_like: Option<&'a str>,
pub normalized_iri_whatwg_like: Option<&'a str>,
pub different_iris: &'a [&'a str],
}
impl TestCase<'_> {
#[inline]
#[must_use]
pub fn is_uri_class(&self) -> bool {
self.composed.is_ascii()
}
#[inline]
#[must_use]
pub const fn is_iri_class(&self) -> bool {
true
}
#[inline]
#[must_use]
pub const fn is_absolute(&self) -> bool {
self.components.is_absolute()
}
#[inline]
#[must_use]
pub const fn is_absolute_without_fragment(&self) -> bool {
self.components.is_absolute_without_fragment()
}
#[inline]
#[must_use]
pub const fn is_relative(&self) -> bool {
self.components.is_relative()
}
#[inline]
#[must_use]
pub fn is_rfc3986_normalizable(&self) -> bool {
match self.normalized_iri.find('/') {
Some(pos) => !self.normalized_iri[(pos + 1)..].starts_with("./"),
None => true,
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Components<'a> {
pub scheme: Option<&'a str>,
pub user: Option<&'a str>,
pub password: Option<&'a str>,
pub host: Option<&'a str>,
pub port: Option<&'a str>,
pub path: &'a str,
pub query: Option<&'a str>,
pub fragment: Option<&'a str>,
}
impl<'a> Components<'a> {
#[inline]
#[must_use]
const fn const_default() -> Self {
Self {
scheme: None,
user: None,
password: None,
host: None,
port: None,
path: "",
query: None,
fragment: None,
}
}
pub fn feed_builder(&self, builder: &mut Builder<'a>, clean: bool) {
if let Some(scheme) = self.scheme {
builder.scheme(scheme);
} else if clean {
builder.unset_scheme();
}
if let Some(host) = self.host {
if self.user.is_some() || self.password.is_some() {
builder.userinfo((self.user.unwrap_or(""), self.password));
} else if clean {
builder.unset_userinfo();
}
builder.host(host);
if let Some(port) = self.port {
builder.port(port);
} else if clean {
builder.unset_port();
}
} else if clean {
builder.unset_authority();
}
builder.path(self.path);
if let Some(query) = self.query {
builder.query(query);
} else if clean {
builder.unset_query();
}
if let Some(fragment) = self.fragment {
builder.fragment(fragment);
} else if clean {
builder.unset_fragment();
}
}
#[inline]
#[must_use]
pub const fn is_absolute(&self) -> bool {
self.scheme.is_some()
}
#[inline]
#[must_use]
pub const fn is_absolute_without_fragment(&self) -> bool {
self.scheme.is_some() && self.fragment.is_none()
}
#[inline]
#[must_use]
pub const fn is_relative(&self) -> bool {
self.scheme.is_none()
}
}
impl fmt::Display for Components<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(scheme) = self.scheme {
write!(f, "{scheme}:")?;
}
assert!(
self.host.is_some()
|| (self.user.is_none() && self.password.is_none() && self.port.is_none()),
"`user`, `password`, and `port` requires `host` to be present"
);
if let Some(host) = self.host {
if let Some(user) = self.user {
f.write_str(user)?;
}
if let Some(password) = self.password {
write!(f, ":{password}")?;
}
if self.user.is_some() || self.password.is_some() {
write!(f, "@")?;
}
f.write_str(host)?;
if let Some(port) = self.port {
write!(f, ":{port}")?;
}
}
f.write_str(self.path)?;
if let Some(query) = self.query {
write!(f, "#{query}")?;
}
if let Some(fragment) = self.fragment {
write!(f, "#{fragment}")?;
}
Ok(())
}
}
macro_rules! components {
() => {
Components::default()
};
($($field:ident: $expr:expr),* $(,)?) => {
Components {
$( $field: components!(@field; $field: $expr) ),*,
.. Components::const_default()
}
};
(@field; path: $expr:expr) => {
$expr
};
(@field; $field:ident: None) => {
None
};
(@field; $field:ident: $expr:expr) => {
Some($expr)
};
}
macro_rules! test_case {
(@field=name; name: $value:expr, $($rest:tt)*) => {
$value
};
(@field=description; description: $value:expr, $($rest:tt)*) => {
Some($value)
};
(@field=description;) => {
None
};
(@field=composed; composed: $value:expr, $($rest:tt)*) => {
$value
};
(@field=components; components: { $($toks:tt)* }, $($rest:tt)*) => {
components! { $($toks)* }
};
(@field=normalized_uri; normalized_uri: $value:expr, $($rest:tt)*) => {
$value
};
(@field=normalized_iri; normalized_iri: $value:expr, $($rest:tt)*) => {
$value
};
(@field=normalized_uri_whatwg_like; normalized_uri_whatwg_like: $value:expr, $($rest:tt)*) => {
Some($value)
};
(@field=normalized_uri_whatwg_like;) => {
None
};
(@field=normalized_iri_whatwg_like; normalized_iri_whatwg_like: $value:expr, $($rest:tt)*) => {
Some($value)
};
(@field=normalized_iri_whatwg_like;) => {
None
};
(@field=different_iris; different_iris: $value:expr, $($rest:tt)*) => {
$value
};
(@field=different_iris;) => {
&[]
};
(@field=$name:ident; $field:ident: { $($toks:tt)* }, $($rest:tt)*) => {
test_case!(@field=$name; $($rest)*)
};
(@field=$name:ident; $field:ident: $value:expr, $($rest:tt)*) => {
test_case!(@field=$name; $($rest)*)
};
($($args:tt)*) => {
TestCase {
name: Some(test_case!(@field=name; $($args)*)),
description: test_case!(@field=description; $($args)*),
composed: test_case!(@field=composed; $($args)*),
components: test_case!(@field=components; $($args)*),
normalized_uri: test_case!(@field=normalized_uri; $($args)*),
normalized_iri: test_case!(@field=normalized_iri; $($args)*),
normalized_uri_whatwg_like: test_case!(@field=normalized_uri_whatwg_like; $($args)*),
normalized_iri_whatwg_like: test_case!(@field=normalized_iri_whatwg_like; $($args)*),
different_iris: test_case!(@field=different_iris; $($args)*),
}
};
}
macro_rules! test_cases {
($({$($toks:tt)*}),* $(,)?) => {
&[ $( test_case! { $($toks)* } ),* ]
}
}
#[allow(clippy::needless_update)] pub static TEST_CASES: &[TestCase<'static>] = test_cases![
{
name: "typical example URI",
composed: "http://example.com/",
components: {
scheme: "http",
host: "example.com",
path: "/",
},
normalized_uri: "http://example.com/",
normalized_iri: "http://example.com/",
},
{
name: "typical example URI with user and password",
composed: "http://user:password@example.com/",
components: {
scheme: "http",
user: "user",
password: "password",
host: "example.com",
path: "/",
},
normalized_uri: "http://user:password@example.com/",
normalized_iri: "http://user:password@example.com/",
},
{
name: "URI with ASCII-only hostname with capital letters",
description: "ASCII-only hostname should be normalized to lower letters",
composed: "http://usER:passWORD@eXAMPLe.CoM/",
components: {
scheme: "http",
user: "usER",
password: "passWORD",
host: "eXAMPLe.CoM",
path: "/",
},
normalized_uri: "http://usER:passWORD@example.com/",
normalized_iri: "http://usER:passWORD@example.com/",
},
{
name: "IRI with non-ASCII hostname with capital letters",
description: "hostname with non-ASCII characters should not be normalized to lower letters",
composed: "http://usER:passWORD@\u{03B1}.CoM/",
components: {
scheme: "http",
user: "usER",
password: "passWORD",
host: "\u{03B1}.CoM",
path: "/",
},
normalized_uri: "http://usER:passWORD@%CE%B1.CoM/",
normalized_iri: "http://usER:passWORD@\u{03B1}.CoM/",
},
{
name: "URI with all components set",
composed: "http://user:password@example.com:80/path/to/somewhere?query#fragment",
components: {
scheme: "http",
user: "user",
password: "password",
host: "example.com",
port: "80",
path: "/path/to/somewhere",
query: "query",
fragment: "fragment",
},
normalized_uri: "http://user:password@example.com:80/path/to/somewhere?query#fragment",
normalized_iri: "http://user:password@example.com:80/path/to/somewhere?query#fragment",
},
{
name: "URI that cannot be normalized by pure RFC 3986",
composed: "scheme:/.//not-a-host",
components: {
scheme: "scheme",
path: "/.//not-a-host",
},
normalized_uri: "scheme:/.//not-a-host",
normalized_iri: "scheme:/.//not-a-host",
},
{
name: "URI that cannot be normalized by pure RFC 3986",
composed: "scheme:..///not-a-host",
components: {
scheme: "scheme",
path: "..///not-a-host",
},
normalized_uri: "scheme:/.//not-a-host",
normalized_iri: "scheme:/.//not-a-host",
normalized_uri_whatwg_like: "scheme:..///not-a-host",
normalized_iri_whatwg_like: "scheme:..///not-a-host",
},
{
name: "Relative URI reference as a relative path `..`",
description: "Relative path without scheme and authority should not be normalized",
composed: "..",
components: {
path: "..",
},
normalized_uri: "..",
normalized_iri: "..",
},
{
name: "Relative URI reference as a relative path",
description: "Relative path without scheme and authority should not be normalized",
composed: "../foo/..",
components: {
path: "../foo/..",
},
normalized_uri: "../foo/..",
normalized_iri: "../foo/..",
},
{
name: "Relative URI reference as a relative path",
description: "Relative path without scheme and authority should not be normalized",
composed: "foo/../p%61th",
components: {
path: "foo/../p%61th",
},
normalized_uri: "foo/../path",
normalized_iri: "foo/../path",
},
{
name: "Relative path in an absolute URI",
composed: "scheme:foo/../p%61th",
components: {
scheme: "scheme",
path: "foo/../p%61th",
},
normalized_uri: "scheme:/path",
normalized_iri: "scheme:/path",
normalized_uri_whatwg_like: "scheme:foo/../path",
normalized_iri_whatwg_like: "scheme:foo/../path",
},
{
name: "Non-normalized URI",
composed: "HTTPs://EXaMPLE.COM/pA/Th?Query#Frag",
components: {
scheme: "HTTPs",
host: "EXaMPLE.COM",
path: "/pA/Th",
query: "Query",
fragment: "Frag",
},
normalized_uri: "https://example.com/pA/Th?Query#Frag",
normalized_iri: "https://example.com/pA/Th?Query#Frag",
different_iris: &[
"https://example.com/pa/th?Query#Frag",
"https://example.com/pA/Th?query#Frag",
"https://example.com/pA/Th?Query#frag",
],
},
{
name: "UUID URN",
composed: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
components: {
scheme: "urn",
path: "uuid:7f1450df-6678-465b-a881-188f9b6ec822",
},
normalized_uri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
normalized_iri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
different_iris: &[
"urn:UUID:7f1450df-6678-465b-a881-188f9b6ec822",
"urn:uuid:7F1450DF-6678-465B-A881-188F9B6EC822",
],
},
{
name: "UUID URN",
composed: "URN:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
components: {
scheme: "URN",
path: "uuid:7f1450df-6678-465b-a881-188f9b6ec822",
},
normalized_uri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
normalized_iri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
different_iris: &[
"urn:UUID:7f1450df-6678-465b-a881-188f9b6ec822",
"urn:uuid:7F1450DF-6678-465B-A881-188F9B6EC822",
],
},
{
name: "UUID URN",
composed: "URN:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
components: {
scheme: "URN",
path: "uuid:7f1450df-6678-465b-a881-188f9b6ec822",
},
normalized_uri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
normalized_iri: "urn:uuid:7f1450df-6678-465b-a881-188f9b6ec822",
different_iris: &[
"urn:UUID:7f1450df-6678-465b-a881-188f9b6ec822",
"urn:uuid:7F1450DF-6678-465B-A881-188F9B6EC822",
],
},
{
name: "IRI with percent-encoded unreserved characters and non-valid UTF-8 bytes",
composed: "http://example.com/?a=%CE%B1&b=%CE%CE%B1%B1",
components: {
scheme: "http",
host: "example.com",
path: "/",
query: "a=%CE%B1&b=%CE%CE%B1%B1",
},
normalized_uri: "http://example.com/?a=%CE%B1&b=%CE%CE%B1%B1",
normalized_iri: "http://example.com/?a=\u{03B1}&b=%CE\u{03B1}%B1",
},
{
name: "not ASCII-only host",
composed: "SCHEME://Alpha%ce%b1/",
components: {
scheme: "SCHEME",
host: "Alpha%ce%b1",
path: "/",
},
normalized_uri: "scheme://Alpha%CE%B1/",
normalized_iri: "scheme://Alpha\u{03B1}/",
},
{
name: "URI with percent-encoded unreserevd and reserved characters",
description: "Tilde character (0x7e) is unreserved and bang (0x21) is reserved",
composed: "http://example.com/%7E%41%73%63%69%69%21",
components: {
scheme: "http",
host: "example.com",
path: "/%7E%41%73%63%69%69%21",
},
normalized_uri: "http://example.com/~Ascii%21",
normalized_iri: "http://example.com/~Ascii%21",
},
{
name: "not ASCII-only host",
description: "Plus character (0x2B) is reserved (sub-delim), so it should not be decoded in host part",
composed: "SCHEME://PLUS%2bPLUS/",
components: {
scheme: "SCHEME",
host: "PLUS%2bPLUS",
path: "/",
},
normalized_uri: "scheme://plus%2Bplus/",
normalized_iri: "scheme://plus%2Bplus/",
},
{
name: "empty port",
description: "According to RFC 3986 section 3.2.3, empty port should be omitted by normalization",
composed: "https://example.com:/",
components: {
scheme: "https",
host: "example.com",
port: "",
path: "/",
},
normalized_uri: "https://example.com/",
normalized_iri: "https://example.com/",
},
{
name: "URI with a dot-dot segment",
composed: "http://example.com/a/b/c/%2e%2e/d/e",
components: {
scheme: "http",
host: "example.com",
path: "/a/b/c/%2e%2e/d/e",
},
normalized_uri: "http://example.com/a/b/d/e",
normalized_iri: "http://example.com/a/b/d/e",
},
{
name: "URI with a dot-dot segment",
composed: "http://example.com/a/b/c/%2E%2E/d/e",
components: {
scheme: "http",
host: "example.com",
path: "/a/b/c/%2E%2E/d/e",
},
normalized_uri: "http://example.com/a/b/d/e",
normalized_iri: "http://example.com/a/b/d/e",
},
{
name: "URI with a dot-dot segment",
composed: "http://example.com/a/b/c/../d/e",
components: {
scheme: "http",
host: "example.com",
path: "/a/b/c/../d/e",
},
normalized_uri: "http://example.com/a/b/d/e",
normalized_iri: "http://example.com/a/b/d/e",
},
{
name: "URI with a dot-dot segment",
composed: "http://example.com/a/b/c/.%2e/d/e",
components: {
scheme: "http",
host: "example.com",
path: "/a/b/c/.%2e/d/e",
},
normalized_uri: "http://example.com/a/b/d/e",
normalized_iri: "http://example.com/a/b/d/e",
},
{
name: "URI with dot segments",
composed: "http://example.com/a/./././././b/c/.%2e/d/e",
components: {
scheme: "http",
host: "example.com",
path: "/a/./././././b/c/.%2e/d/e",
},
normalized_uri: "http://example.com/a/b/d/e",
normalized_iri: "http://example.com/a/b/d/e",
},
{
name: "Empty relative IRI",
composed: "",
components: {
path: "",
},
normalized_uri: "",
normalized_iri: "",
},
{
name: "Combination: fragment",
composed: "#fragment",
components: {
fragment: "fragment",
},
normalized_uri: "#fragment",
normalized_iri: "#fragment",
},
{
name: "Combination: query",
composed: "?query",
components: {
query: "query",
},
normalized_uri: "?query",
normalized_iri: "?query",
},
{
name: "Combination: query+fragment",
composed: "?query#fragment",
components: {
query: "query",
fragment: "fragment",
},
normalized_uri: "?query#fragment",
normalized_iri: "?query#fragment",
},
{
name: "Combination: path",
composed: "/pa/th",
components: {
path: "/pa/th",
},
normalized_uri: "/pa/th",
normalized_iri: "/pa/th",
},
{
name: "Combination: path+fragment",
composed: "/pa/th#fragment",
components: {
path: "/pa/th",
fragment: "fragment",
},
normalized_uri: "/pa/th#fragment",
normalized_iri: "/pa/th#fragment",
},
{
name: "Combination: path+query",
composed: "/pa/th?query",
components: {
path: "/pa/th",
query: "query",
},
normalized_uri: "/pa/th?query",
normalized_iri: "/pa/th?query",
},
{
name: "Combination: path+query+fragment",
composed: "/pa/th?query#fragment",
components: {
path: "/pa/th",
query: "query",
fragment: "fragment",
},
normalized_uri: "/pa/th?query#fragment",
normalized_iri: "/pa/th?query#fragment",
},
{
name: "Combination: authority",
composed: "//authority",
components: {
host: "authority",
},
normalized_uri: "//authority",
normalized_iri: "//authority",
},
{
name: "Combination: authority+fragment",
composed: "//authority#fragment",
components: {
host: "authority",
fragment: "fragment",
},
normalized_uri: "//authority#fragment",
normalized_iri: "//authority#fragment",
},
{
name: "Combination: authority+query",
composed: "//authority?query",
components: {
host: "authority",
query: "query",
},
normalized_uri: "//authority?query",
normalized_iri: "//authority?query",
},
{
name: "Combination: authority+query+fragment",
composed: "//authority?query#fragment",
components: {
host: "authority",
query: "query",
fragment: "fragment",
},
normalized_uri: "//authority?query#fragment",
normalized_iri: "//authority?query#fragment",
},
{
name: "Combination: authority+path",
composed: "//authority/pa/th",
components: {
host: "authority",
path: "/pa/th",
},
normalized_uri: "//authority/pa/th",
normalized_iri: "//authority/pa/th",
},
{
name: "Combination: authority+path+fragment",
composed: "//authority/pa/th#fragment",
components: {
host: "authority",
path: "/pa/th",
fragment: "fragment",
},
normalized_uri: "//authority/pa/th#fragment",
normalized_iri: "//authority/pa/th#fragment",
},
{
name: "Combination: authority+path+query",
composed: "//authority/pa/th?query",
components: {
host: "authority",
path: "/pa/th",
query: "query",
},
normalized_uri: "//authority/pa/th?query",
normalized_iri: "//authority/pa/th?query",
},
{
name: "Combination: authority+path+query+fragment",
composed: "//authority/pa/th?query#fragment",
components: {
host: "authority",
path: "/pa/th",
query: "query",
fragment: "fragment",
},
normalized_uri: "//authority/pa/th?query#fragment",
normalized_iri: "//authority/pa/th?query#fragment",
},
{
name: "Combination: scheme",
composed: "scheme:",
components: {
scheme: "scheme",
},
normalized_uri: "scheme:",
normalized_iri: "scheme:",
},
{
name: "Combination: scheme+fragment",
composed: "scheme:#fragment",
components: {
scheme: "scheme",
fragment: "fragment",
},
normalized_uri: "scheme:#fragment",
normalized_iri: "scheme:#fragment",
},
{
name: "Combination: scheme+query",
composed: "scheme:?query",
components: {
scheme: "scheme",
query: "query",
},
normalized_uri: "scheme:?query",
normalized_iri: "scheme:?query",
},
{
name: "Combination: scheme+query+fragment",
composed: "scheme:?query#fragment",
components: {
scheme: "scheme",
query: "query",
fragment: "fragment",
},
normalized_uri: "scheme:?query#fragment",
normalized_iri: "scheme:?query#fragment",
},
{
name: "Combination: scheme+path",
composed: "scheme:/pa/th",
components: {
scheme: "scheme",
path: "/pa/th",
},
normalized_uri: "scheme:/pa/th",
normalized_iri: "scheme:/pa/th",
},
{
name: "Combination: scheme+path+fragment",
composed: "scheme:/pa/th#fragment",
components: {
scheme: "scheme",
path: "/pa/th",
fragment: "fragment",
},
normalized_uri: "scheme:/pa/th#fragment",
normalized_iri: "scheme:/pa/th#fragment",
},
{
name: "Combination: scheme+path+query",
composed: "scheme:/pa/th?query",
components: {
scheme: "scheme",
path: "/pa/th",
query: "query",
},
normalized_uri: "scheme:/pa/th?query",
normalized_iri: "scheme:/pa/th?query",
},
{
name: "Combination: scheme+path+query+fragment",
composed: "scheme:/pa/th?query#fragment",
components: {
scheme: "scheme",
path: "/pa/th",
query: "query",
fragment: "fragment",
},
normalized_uri: "scheme:/pa/th?query#fragment",
normalized_iri: "scheme:/pa/th?query#fragment",
},
{
name: "Combination: scheme+authority",
composed: "scheme://authority",
components: {
scheme: "scheme",
host: "authority",
},
normalized_uri: "scheme://authority",
normalized_iri: "scheme://authority",
},
{
name: "Combination: scheme+authority+fragment",
composed: "scheme://authority#fragment",
components: {
scheme: "scheme",
host: "authority",
fragment: "fragment",
},
normalized_uri: "scheme://authority#fragment",
normalized_iri: "scheme://authority#fragment",
},
{
name: "Combination: scheme+authority+query",
composed: "scheme://authority?query",
components: {
scheme: "scheme",
host: "authority",
query: "query",
},
normalized_uri: "scheme://authority?query",
normalized_iri: "scheme://authority?query",
},
{
name: "Combination: scheme+authority+query+fragment",
composed: "scheme://authority?query#fragment",
components: {
scheme: "scheme",
host: "authority",
query: "query",
fragment: "fragment",
},
normalized_uri: "scheme://authority?query#fragment",
normalized_iri: "scheme://authority?query#fragment",
},
{
name: "Combination: scheme+authority+path",
composed: "scheme://authority/pa/th",
components: {
scheme: "scheme",
host: "authority",
path: "/pa/th",
},
normalized_uri: "scheme://authority/pa/th",
normalized_iri: "scheme://authority/pa/th",
},
{
name: "Combination: scheme+authority+path+fragment",
composed: "scheme://authority/pa/th#fragment",
components: {
scheme: "scheme",
host: "authority",
path: "/pa/th",
fragment: "fragment",
},
normalized_uri: "scheme://authority/pa/th#fragment",
normalized_iri: "scheme://authority/pa/th#fragment",
},
{
name: "Combination: scheme+authority+path+query",
composed: "scheme://authority/pa/th?query",
components: {
scheme: "scheme",
host: "authority",
path: "/pa/th",
query: "query",
},
normalized_uri: "scheme://authority/pa/th?query",
normalized_iri: "scheme://authority/pa/th?query",
},
{
name: "Combination: scheme+authority+path+query+fragment",
composed: "scheme://authority/pa/th?query#fragment",
components: {
scheme: "scheme",
host: "authority",
path: "/pa/th",
query: "query",
fragment: "fragment",
},
normalized_uri: "scheme://authority/pa/th?query#fragment",
normalized_iri: "scheme://authority/pa/th?query#fragment",
},
{
name: "1 slash following to the scheme",
composed: "scheme:/",
components: {
scheme: "scheme",
path: "/",
},
normalized_uri: "scheme:/",
normalized_iri: "scheme:/",
},
{
name: "2 slashes following to the scheme",
composed: "scheme://",
components: {
scheme: "scheme",
host: "",
},
normalized_uri: "scheme://",
normalized_iri: "scheme://",
},
{
name: "3 slashes following to the scheme",
composed: "scheme:///",
components: {
scheme: "scheme",
host: "",
path: "/",
},
normalized_uri: "scheme:///",
normalized_iri: "scheme:///",
},
{
name: "4 slashes following to the scheme",
composed: "scheme:////",
components: {
scheme: "scheme",
host: "",
path: "//",
},
normalized_uri: "scheme:////",
normalized_iri: "scheme:////",
},
{
name: "5 slashes following to the scheme",
composed: "scheme://///",
components: {
scheme: "scheme",
host: "",
path: "///",
},
normalized_uri: "scheme://///",
normalized_iri: "scheme://///",
},
{
name: "1 slash",
composed: "/",
components: {
path: "/",
},
normalized_uri: "/",
normalized_iri: "/",
},
{
name: "2 slash",
composed: "//",
components: {
host: "",
},
normalized_uri: "//",
normalized_iri: "//",
},
{
name: "3 slash",
composed: "///",
components: {
host: "",
path: "/",
},
normalized_uri: "///",
normalized_iri: "///",
},
{
name: "4 slash",
composed: "////",
components: {
host: "",
path: "//",
},
normalized_uri: "////",
normalized_iri: "////",
},
{
name: "5 slash",
composed: "/////",
components: {
host: "",
path: "///",
},
normalized_uri: "/////",
normalized_iri: "/////",
},
{
name: "IPv4 address",
composed: "//192.0.2.0",
components: {
host: "192.0.2.0",
},
normalized_uri: "//192.0.2.0",
normalized_iri: "//192.0.2.0",
},
{
name: "IPv4 address with port",
composed: "//192.0.2.0:80",
components: {
host: "192.0.2.0",
port: "80",
},
normalized_uri: "//192.0.2.0:80",
normalized_iri: "//192.0.2.0:80",
},
{
name: "IPv4 address",
composed: "//255.255.255.255",
components: {
host: "255.255.255.255",
},
normalized_uri: "//255.255.255.255",
normalized_iri: "//255.255.255.255",
},
{
name: "IPv4 address with port",
composed: "//255.255.255.255:65536",
components: {
host: "255.255.255.255",
port: "65536",
},
normalized_uri: "//255.255.255.255:65536",
normalized_iri: "//255.255.255.255:65536",
},
{
name: "IPv4 address",
composed: "//0.0.0.0",
components: {
host: "0.0.0.0",
},
normalized_uri: "//0.0.0.0",
normalized_iri: "//0.0.0.0",
},
{
name: "IPv4 address with port",
composed: "//0.0.0.0:0",
components: {
host: "0.0.0.0",
port: "0",
},
normalized_uri: "//0.0.0.0:0",
normalized_iri: "//0.0.0.0:0",
},
{
name: "IPv6 address",
composed: "//[2001:db8::]",
components: {
host: "[2001:db8::]",
},
normalized_uri: "//[2001:db8::]",
normalized_iri: "//[2001:db8::]",
},
{
name: "IPv6 address with port",
composed: "//[2001:db8::]:80",
components: {
host: "[2001:db8::]",
port: "80",
},
normalized_uri: "//[2001:db8::]:80",
normalized_iri: "//[2001:db8::]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::]",
components: {
host: "[2001:0db8::]",
},
normalized_uri: "//[2001:0db8::]",
normalized_iri: "//[2001:0db8::]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::]:80",
components: {
host: "[2001:0db8::]",
port: "80",
},
normalized_uri: "//[2001:0db8::]:80",
normalized_iri: "//[2001:0db8::]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8:0:0:0:0:0:ffff]",
components: {
host: "[2001:0db8:0:0:0:0:0:ffff]",
},
normalized_uri: "//[2001:0db8:0:0:0:0:0:ffff]",
normalized_iri: "//[2001:0db8:0:0:0:0:0:ffff]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8:0:0:0:0:0:ffff]:80",
components: {
host: "[2001:0db8:0:0:0:0:0:ffff]",
port: "80",
},
normalized_uri: "//[2001:0db8:0:0:0:0:0:ffff]:80",
normalized_iri: "//[2001:0db8:0:0:0:0:0:ffff]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0DB8:0000:0000:0000:000A:BCDE:FFFF]",
components: {
host: "[2001:0DB8:0000:0000:0000:000A:BCDE:FFFF]",
},
normalized_uri: "//[2001:0db8:0000:0000:0000:000a:bcde:ffff]",
normalized_iri: "//[2001:0db8:0000:0000:0000:000a:bcde:ffff]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0DB8:0000:0000:0000:000A:BCDE:FFFF]:80",
components: {
host: "[2001:0DB8:0000:0000:0000:000A:BCDE:FFFF]",
port: "80",
},
normalized_uri: "//[2001:0db8:0000:0000:0000:000a:bcde:ffff]:80",
normalized_iri: "//[2001:0db8:0000:0000:0000:000a:bcde:ffff]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::]",
components: {
host: "[2001:0db8::]",
},
normalized_uri: "//[2001:0db8::]",
normalized_iri: "//[2001:0db8::]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::]:80",
components: {
host: "[2001:0db8::]",
port: "80",
},
normalized_uri: "//[2001:0db8::]:80",
normalized_iri: "//[2001:0db8::]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0DB8:0:0:0:0::1]",
components: {
host: "[2001:0DB8:0:0:0:0::1]",
},
normalized_uri: "//[2001:0db8:0:0:0:0::1]",
normalized_iri: "//[2001:0db8:0:0:0:0::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0DB8:0:0:0:0::1]:80",
components: {
host: "[2001:0DB8:0:0:0:0::1]",
port: "80",
},
normalized_uri: "//[2001:0db8:0:0:0:0::1]:80",
normalized_iri: "//[2001:0db8:0:0:0:0::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::89ab:cdef:89AB:CDEF]",
components: {
host: "[2001:0db8::89ab:cdef:89AB:CDEF]",
},
normalized_uri: "//[2001:0db8::89ab:cdef:89ab:cdef]",
normalized_iri: "//[2001:0db8::89ab:cdef:89ab:cdef]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::89ab:cdef:89AB:CDEF]:80",
components: {
host: "[2001:0db8::89ab:cdef:89AB:CDEF]",
port: "80",
},
normalized_uri: "//[2001:0db8::89ab:cdef:89ab:cdef]:80",
normalized_iri: "//[2001:0db8::89ab:cdef:89ab:cdef]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::1]",
components: {
host: "[2001:0db8::1]",
},
normalized_uri: "//[2001:0db8::1]",
normalized_iri: "//[2001:0db8::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::1]:80",
components: {
host: "[2001:0db8::1]",
port: "80",
},
normalized_uri: "//[2001:0db8::1]:80",
normalized_iri: "//[2001:0db8::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8:0::1]",
components: {
host: "[2001:0db8:0::1]",
},
normalized_uri: "//[2001:0db8:0::1]",
normalized_iri: "//[2001:0db8:0::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8:0::1]:80",
components: {
host: "[2001:0db8:0::1]",
port: "80",
},
normalized_uri: "//[2001:0db8:0::1]:80",
normalized_iri: "//[2001:0db8:0::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8:0:0::1]",
components: {
host: "[2001:0db8:0:0::1]",
},
normalized_uri: "//[2001:0db8:0:0::1]",
normalized_iri: "//[2001:0db8:0:0::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8:0:0::1]:80",
components: {
host: "[2001:0db8:0:0::1]",
port: "80",
},
normalized_uri: "//[2001:0db8:0:0::1]:80",
normalized_iri: "//[2001:0db8:0:0::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8:0:0:0::1]",
components: {
host: "[2001:0db8:0:0:0::1]",
},
normalized_uri: "//[2001:0db8:0:0:0::1]",
normalized_iri: "//[2001:0db8:0:0:0::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8:0:0:0::1]:80",
components: {
host: "[2001:0db8:0:0:0::1]",
port: "80",
},
normalized_uri: "//[2001:0db8:0:0:0::1]:80",
normalized_iri: "//[2001:0db8:0:0:0::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8:0:0:0:0::1]",
components: {
host: "[2001:0db8:0:0:0:0::1]",
},
normalized_uri: "//[2001:0db8:0:0:0:0::1]",
normalized_iri: "//[2001:0db8:0:0:0:0::1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8:0:0:0:0::1]:80",
components: {
host: "[2001:0db8:0:0:0:0::1]",
port: "80",
},
normalized_uri: "//[2001:0db8:0:0:0:0::1]:80",
normalized_iri: "//[2001:0db8:0:0:0:0::1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::0:1]",
components: {
host: "[2001:0db8::0:1]",
},
normalized_uri: "//[2001:0db8::0:1]",
normalized_iri: "//[2001:0db8::0:1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::0:1]:80",
components: {
host: "[2001:0db8::0:1]",
port: "80",
},
normalized_uri: "//[2001:0db8::0:1]:80",
normalized_iri: "//[2001:0db8::0:1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::0:0:1]",
components: {
host: "[2001:0db8::0:0:1]",
},
normalized_uri: "//[2001:0db8::0:0:1]",
normalized_iri: "//[2001:0db8::0:0:1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::0:0:1]:80",
components: {
host: "[2001:0db8::0:0:1]",
port: "80",
},
normalized_uri: "//[2001:0db8::0:0:1]:80",
normalized_iri: "//[2001:0db8::0:0:1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::0:0:0:1]",
components: {
host: "[2001:0db8::0:0:0:1]",
},
normalized_uri: "//[2001:0db8::0:0:0:1]",
normalized_iri: "//[2001:0db8::0:0:0:1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::0:0:0:1]:80",
components: {
host: "[2001:0db8::0:0:0:1]",
port: "80",
},
normalized_uri: "//[2001:0db8::0:0:0:1]:80",
normalized_iri: "//[2001:0db8::0:0:0:1]:80",
},
{
name: "IPv6 address",
composed: "//[2001:0db8::0:0:0:0:1]",
components: {
host: "[2001:0db8::0:0:0:0:1]",
},
normalized_uri: "//[2001:0db8::0:0:0:0:1]",
normalized_iri: "//[2001:0db8::0:0:0:0:1]",
},
{
name: "IPv6 address with port",
composed: "//[2001:0db8::0:0:0:0:1]:80",
components: {
host: "[2001:0db8::0:0:0:0:1]",
port: "80",
},
normalized_uri: "//[2001:0db8::0:0:0:0:1]:80",
normalized_iri: "//[2001:0db8::0:0:0:0:1]:80",
},
{
name: "IPvFuture address",
composed: "//[v9999.this-is-future-version-of-ip-address:::::::::]",
components: {
host: "[v9999.this-is-future-version-of-ip-address:::::::::]",
},
normalized_uri: "//[v9999.this-is-future-version-of-ip-address:::::::::]",
normalized_iri: "//[v9999.this-is-future-version-of-ip-address:::::::::]",
},
{
name: "IPvFuture address with port",
composed: "//[v9999.this-is-future-version-of-ip-address:::::::::]:80",
components: {
host: "[v9999.this-is-future-version-of-ip-address:::::::::]",
port: "80",
},
normalized_uri: "//[v9999.this-is-future-version-of-ip-address:::::::::]:80",
normalized_iri: "//[v9999.this-is-future-version-of-ip-address:::::::::]:80",
},
{
name: "Too large port",
description: "RFC 3986 accepts `*DIGIT` as `port` component",
composed: "//localhost:999999999",
components: {
host: "localhost",
port: "999999999",
},
normalized_uri: "//localhost:999999999",
normalized_iri: "//localhost:999999999",
},
{
name: "Port only",
description: "`host` can be empty",
composed: "//:999999999",
components: {
host: "",
port: "999999999",
},
normalized_uri: "//:999999999",
normalized_iri: "//:999999999",
},
{
name: "Trailing slash should remain after normalization",
description: "Trailing slash should remain after normalization if the path ends with slash",
composed: "https://example.com/../../",
components: {
scheme: "https",
host: "example.com",
path: "/../../",
},
normalized_uri: "https://example.com/",
normalized_iri: "https://example.com/",
},
{
name: "Slash should remain",
description: "Slash should remain after normalization if the IRI ends with a dot segment",
composed: "https://example.com/..",
components: {
scheme: "https",
host: "example.com",
path: "/..",
},
normalized_uri: "https://example.com/",
normalized_iri: "https://example.com/",
},
{
name: "Slash should remain",
description: "Slash should remain after normalization if the IRI ends with a dot segment",
composed: "https://example.com/.",
components: {
scheme: "https",
host: "example.com",
path: "/.",
},
normalized_uri: "https://example.com/",
normalized_iri: "https://example.com/",
},
{
name: "WHATWG URL Standard serialization",
composed: "scheme:/a/b/../..//c",
components: {
scheme: "scheme",
path: "/a/b/../..//c",
},
normalized_uri: "scheme:/.//c",
normalized_iri: "scheme:/.//c",
},
{
name: "WHATWG URL Standard serialization",
composed: "scheme:/a/b/../..//c",
components: {
scheme: "scheme",
path: "/a/b/../..//c",
},
normalized_uri: "scheme:/.//c",
normalized_iri: "scheme:/.//c",
},
];