use core::marker::PhantomData;
use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1, take_while_m_n},
character::complete::{char as char_, one_of},
combinator::{cut, map, not, opt, recognize},
error::ParseError,
multi::{fold_many_m_n, many0_count, many1_count},
sequence::{delimited, pair, preceded, terminated, tuple},
IResult,
};
use crate::{
parser::{char::is_sub_delim, RiReferenceComponents},
spec::{Spec, SpecInternal, UriSpec},
};
#[inline(always)]
fn context<I: Clone, E: ParseError<I>, F, O>(
_context: &'static str,
f: F,
) -> impl Fn(I) -> IResult<I, O, E>
where
F: Fn(I) -> IResult<I, O, E>,
{
f
}
fn one_is<I, F, E: ParseError<I>>(pred: F) -> impl Fn(I) -> IResult<I, char, E>
where
I: nom::Slice<core::ops::RangeFrom<usize>> + nom::InputIter,
<I as nom::InputIter>::Item: nom::AsChar + Copy,
F: Fn(<I as nom::InputIter>::Item) -> bool,
{
use nom::AsChar;
move |i: I| match i.iter_elements().next().map(|c| (c, pred(c))) {
Some((c, true)) => Ok((i.slice(c.len()..), c.as_char())),
_ => Err(nom::Err::Error(E::from_error_kind(
i,
nom::error::ErrorKind::OneOf,
))),
}
}
fn many_m_n_count<I, O, E, F>(m: usize, n: usize, f: F) -> impl Fn(I) -> IResult<I, usize, E>
where
F: Fn(I) -> IResult<I, O, E>,
I: Clone + PartialEq,
E: ParseError<I>,
{
fold_many_m_n(m, n, f, 0, |count, _| count + 1)
}
pub(crate) fn uri<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"uri",
tuple((
scheme,
char_(':'),
hier_part::<E, S>,
opt(preceded(char_('?'), query::<E, S>)),
opt(preceded(char_('#'), fragment::<E, S>)),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn decompose_uri<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
context(
"uri",
map(
tuple((
scheme,
char_(':'),
decompose_hier_part::<E, S>,
opt(preceded(char_('?'), query::<E, S>)),
opt(preceded(char_('#'), fragment::<E, S>)),
)),
|(scheme, _colon, (authority, path), query, fragment)| RiReferenceComponents {
scheme: Some(scheme),
authority,
path,
query,
fragment,
_spec: PhantomData,
},
),
)(i)
}
fn hier_part<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"hier-part",
alt((
recognize(preceded(
tag("//"),
pair(authority::<E, S>, path_abempty::<E, S>),
)),
path_absolute::<E, S>,
path_rootless::<E, S>,
path_empty::<E>,
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn decompose_hier_part<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, (Option<&'a str>, &'a str), E> {
context(
"hier-part",
alt((
preceded(
tag("//"),
pair(map(authority::<E, S>, Some), path_abempty::<E, S>),
),
map(path_absolute::<E, S>, |path| (None, path)),
map(path_rootless::<E, S>, |path| (None, path)),
map(path_empty::<E>, |path| (None, path)),
)),
)(i)
}
pub(crate) fn uri_reference<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, &'a str, E> {
context("uri_reference", alt((uri::<E, S>, relative_ref::<E, S>)))(i)
}
pub(crate) fn decompose_uri_reference<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
context(
"uri_reference",
alt((decompose_uri::<E, S>, decompose_relative_ref::<E, S>)),
)(i)
}
pub(crate) fn absolute_uri<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, &'a str, E> {
context(
"absolute_uri",
tuple((
scheme,
char_(':'),
hier_part::<E, S>,
opt(preceded(char_('?'), query::<E, S>)),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
pub(crate) fn relative_ref<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, &'a str, E> {
context(
"relative_ref",
tuple((
relative_part::<E, S>,
opt(preceded(char_('?'), query::<E, S>)),
opt(preceded(char_('#'), fragment::<E, S>)),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn decompose_relative_ref<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
context(
"relative_ref",
map(
tuple((
decompose_relative_part::<E, S>,
opt(preceded(char_('?'), query::<E, S>)),
opt(preceded(char_('#'), fragment::<E, S>)),
)),
|((authority, path), query, fragment)| RiReferenceComponents {
scheme: None,
authority,
path,
query,
fragment,
_spec: PhantomData,
},
),
)(i)
}
fn relative_part<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"relative-part",
alt((
recognize(tuple((tag("//"), authority::<E, S>, path_abempty::<E, S>))),
path_absolute::<E, S>,
path_noscheme::<E, S>,
path_empty,
)),
)(i)
}
fn decompose_relative_part<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, (Option<&'a str>, &'a str), E> {
context(
"relative-part",
alt((
preceded(
tag("//"),
pair(map(authority::<E, S>, Some), path_abempty::<E, S>),
),
map(path_absolute::<E, S>, |path| (None, path)),
map(path_noscheme::<E, S>, |path| (None, path)),
map(path_empty, |path| (None, path)),
)),
)(i)
}
fn scheme<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"scheme",
pair(
one_is(|c: char| c.is_ascii_alphabetic()),
take_while(|c: char| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.'),
),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn authority<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"authority",
tuple((
opt(terminated(userinfo::<E, S>, char_('@'))),
host::<E, S>,
opt(preceded(char_(':'), port)),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn userinfo<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"userinfo",
many0_count(alt((
map(
take_while1(|c: char| S::is_char_unreserved(c) || is_sub_delim(c) || c == ':'),
|_| (),
),
map(pct_encoded, |_| ()),
))),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn host<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context("host", alt((ip_literal, ipv4address, reg_name::<E, S>)))(i)
}
fn port<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context("port", take_while(|c: char| c.is_ascii_digit()))(i)
}
fn ip_literal<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"IP-literal",
delimited(char_('['), alt((ipv6address, ipvfuture)), char_(']')),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn ipvfuture<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"IPvFuture",
tuple((
alt((char_('v'), char_('V'))),
take_while1(|c: char| c.is_ascii_hexdigit()),
char_('.'),
take_while1(|c: char| UriSpec::is_char_unreserved(c) || is_sub_delim(c) || c == ':'),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn ipv6address<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
fn before_and_double_colon<'b, E: ParseError<&'b str>>(
num_h16: usize,
) -> impl Fn(&'b str) -> IResult<&'b str, &'b str, E> {
assert!(num_h16 >= 1);
recognize(terminated(
pair(
h16,
many_m_n_count(num_h16 - 1, num_h16 - 1, preceded(char_(':'), h16)),
),
tag("::"),
))
}
fn after_double_colon<'b, E: ParseError<&'b str>>(
num_max_h16: usize,
) -> impl Fn(&'b str) -> IResult<&'b str, &'b str, E> {
assert!(num_max_h16 >= 2);
recognize(alt((
pair(
many_m_n_count(0, num_max_h16 - 1, terminated(h16, char_(':'))),
terminated(h16, not(char_('.'))),
),
pair(
many_m_n_count(0, num_max_h16 - 2, terminated(h16, char_(':'))),
ipv4address,
),
)))
}
context(
"IPv6Address",
alt((
recognize(tuple((tag("::"), after_double_colon(7)))),
recognize(pair(before_and_double_colon(1), after_double_colon(6))),
recognize(pair(before_and_double_colon(2), after_double_colon(5))),
recognize(pair(before_and_double_colon(3), after_double_colon(4))),
recognize(pair(before_and_double_colon(4), after_double_colon(3))),
recognize(pair(before_and_double_colon(5), after_double_colon(2))),
recognize(pair(before_and_double_colon(6), h16)),
before_and_double_colon(7),
recognize(pair(
many_m_n_count(0, 7, terminated(h16, char_(':'))),
terminated(h16, not(char_('.'))),
)),
recognize(pair(
many_m_n_count(0, 6, terminated(h16, char_(':'))),
ipv4address,
)),
)),
)(i)
}
fn h16<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
take_while_m_n(1, 4, |c: char| c.is_ascii_hexdigit())(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn ipv4address<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"IPv4Address",
tuple((
terminated(dec_octet, char_('.')),
terminated(dec_octet, char_('.')),
terminated(dec_octet, char_('.')),
dec_octet,
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn dec_octet<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"dec-octet",
alt((
recognize(pair(tag("25"), one_of("012345"))),
recognize(tuple((
char_('2'),
one_of("01234"),
one_is(|c: char| c.is_ascii_digit()),
))),
recognize(pair(
char_('1'),
take_while_m_n(2, 2, |c: char| c.is_ascii_digit()),
)),
recognize(pair(
one_is(|c: char| c.is_ascii_digit() || c != '0'),
one_is(|c: char| c.is_ascii_digit()),
)),
recognize(one_is(|c: char| c.is_ascii_digit())),
)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn reg_name<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"reg-name",
many0_count(alt((
map(
take_while1(|c: char| S::is_char_unreserved(c) || is_sub_delim(c)),
|_| (),
),
map(pct_encoded, |_| ()),
))),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
pub(crate) fn path<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, &'a str, E> {
context(
"path",
alt((
path_absolute::<E, S>,
path_noscheme::<E, S>,
path_rootless::<E, S>,
path_empty,
)),
)(i)
}
fn path_abempty<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"path-abempty",
many0_count(preceded(char_('/'), segment::<E, S>)),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn path_absolute<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"path-absolute",
preceded(
char_('/'),
opt(pair(
segment_nz::<E, S>,
many0_count(preceded(char_('/'), segment::<E, S>)),
)),
),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn path_noscheme<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"path-noscheme",
pair(
segment_nz_nc::<E, S>,
many0_count(preceded(char_('/'), segment::<E, S>)),
),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn path_rootless<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"path-rootless",
pair(
segment_nz::<E, S>,
many0_count(preceded(char_('/'), segment::<E, S>)),
),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn path_empty<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, &'a str, E> {
Ok((i, &i[0..0]))
}
fn segment<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context("segment", many0_count(pchar::<E, S>))(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn segment_nz<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context("segment-nz", many1_count(pchar::<E, S>))(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn segment_nz_nc<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"segment-nz-nc",
many1_count(alt((
map(
one_is(|c: char| S::is_char_unreserved(c) || is_sub_delim(c) || c == '@'),
|_| (),
),
map(pct_encoded, |_| ()),
))),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn pchar<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
alt((
map(
one_is(|c: char| S::is_char_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'),
|_| (),
),
map(pct_encoded, |_| ()),
))(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn query<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
context(
"query",
cut(many0_count(alt((
pchar::<E, S>,
private::<E, S>,
tag("/"),
tag("?"),
)))),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
pub(crate) fn fragment<'a, E: ParseError<&'a str>, S: Spec>(
i: &'a str,
) -> IResult<&'a str, &'a str, E> {
context(
"fragment",
cut(many0_count(alt((pchar::<E, S>, tag("/"), tag("?"))))),
)(i)
.map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn pct_encoded<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, (char, char), E> {
context(
"pct-encoded",
preceded(
char_('%'),
context("pct-encoded/hexdigits", cut(pair(hexdig, hexdig))),
),
)(i)
}
fn private<'a, E: ParseError<&'a str>, S: Spec>(i: &'a str) -> IResult<&'a str, &'a str, E> {
one_is(S::is_char_private)(i).map(|(rest, _)| (rest, &i[..(i.len() - rest.len())]))
}
fn hexdig<'a, E: ParseError<&'a str>>(i: &'a str) -> IResult<&'a str, char, E> {
one_is(|c: char| c.is_ascii_hexdigit())(i)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::format;
use crate::spec::IriSpec;
type Error<'a> = ();
macro_rules! assert_invalid {
($parser:expr, $input:expr $(,)?) => {{
let _ = nom::combinator::all_consuming($parser)($input).unwrap_err();
}};
}
macro_rules! assert_complete {
($parser:expr, $input:expr, $expected:expr $(,)?) => {{
assert_eq!(
nom::combinator::all_consuming($parser)($input),
Ok(("", $expected))
);
}};
}
macro_rules! assert_validate {
($parser:expr, $($input:expr),* $(,)?) => {{
$({
let input = $input;
let input: &str = input.as_ref();
assert_complete!($parser, input, input);
})*
}};
}
macro_rules! assert_validate_list {
($parser:expr, $($list:expr),* $(,)?) => {{
$({
for input in $list {
assert_validate!($parser, input);
}
})*
}};
}
const OK_URI_LIST: &[&str] = &[
"https://tools.ietf.org/html/rfc3986",
"ftp://ftp.is.co.za/rfc/rfc1808.txt",
"http://www.ietf.org/rfc/rfc2396.txt",
"ldap://[2001:db8::7]/c=GB?objectClass?one",
"mailto:John.Doe@example.com",
"news:comp.infosystems.www.servers.unix",
"tel:+1-816-555-1212",
"telnet://192.0.2.16:80/",
"urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
"foo://example.com:8042/over/there?name=ferret#nose",
"urn:example:animal:ferret:nose",
"mailto:fred@example.com",
"foo://info.example.com?fred",
"http://a/b/c/d;p?q",
"g:h",
"http://a/b/c/g",
"http://a/b/c/g/",
"http://a/g",
"http://g",
"http://a/b/c/d;p?y",
"http://a/b/c/g?y",
"http://a/b/c/d;p?q#s",
"http://a/b/c/g#s",
"http://a/b/c/g?y#s",
"http://a/b/c/;x",
"http://a/b/c/g;x",
"http://a/b/c/g;x?y#s",
"http://a/b/c/d;p?q",
"http://a/b/c/",
"http://a/b/",
"http://a/b/g",
"http://a/",
"http://a/b/c/g.",
"http://a/b/c/.g",
"http://a/b/c/g..",
"http://a/b/c/..g",
"http://a/b/c/g/h",
"http://a/b/c/h",
"http://a/b/c/g;x=1/y",
"http://a/b/c/y",
"http://a/b/c/g?y/./x",
"http://a/b/c/g?y/../x",
"http://a/b/c/g#s/./x",
"http://a/b/c/g#s/../x",
"example://a/b/c/%7Bfoo%7D",
"eXAMPLE://a/./b/../b/%63/%7bfoo%7d",
"HTTP://www.EXAMPLE.com/",
"http://www.example.com/",
"http://example.com",
"http://example.com/",
"http://example.com:/",
"http://example.com:80/",
"http://example.com/?",
"mailto:Joe@Example.COM",
"mailto:Joe@example.com",
"http://example.com/data",
"http://example.com/data/",
"ftp://cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm",
"http://www.ics.uci.edu/pub/ietf/uri/#Related",
"http://www.w3.org/Addressing/",
"ftp://foo.example.com/rfc/",
"http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING",
];
const OK_IRI_LIST: &[&str] = &[
"https://tools.ietf.org/html/rfc3987",
"http://r\u{E9}sum\u{E9}.example.org",
"http://xn--rsum-bpad.example.org",
"http://r%C3%A9sum%C3%A9.example.org",
"http://www.example.org/red%09ros\u{E9}#red",
"http://www.example.org/red%09ros%C3%A9#red",
"http://example.com/\u{10300}\u{10301}\u{10302}",
"http://example.com/%F0%90%8C%80%F0%90%8C%81%F0%90%8C%82",
"http://www.example.org/r%C3%A9sum%C3%A9.html",
"http://www.example.org/r%E9sum%E9.html",
"http://www.example.org/D%C3%BCrst",
"http://www.example.org/D%FCrst",
"http://www.example.org/D\u{FC}rst",
"http://xn--99zt52a.example.org/%e2%80%ae",
"http://xn--99zt52a.example.org/%E2%80%AE",
"http://\u{7D0D}\u{8C46}.example.org/%E2%80%AE",
"http://ab.CDEFGH.ij/kl/mn/op.html",
"http://ab.CDE.FGH/ij/kl/mn/op.html",
"http://AB.CD.EF/GH/IJ/KL?MN=OP;QR=ST#UV",
"http://AB.CD.ef/gh/IJ/KL.html",
"http://ab.cd.EF/GH/ij/kl.html",
"http://ab.CD.EF/GH/IJ/kl.html",
"http://ab.CDE123FGH.ij/kl/mn/op.html",
"http://ab.cd.ef/GH1/2IJ/KL.html",
"http://ab.cd.ef/GH%31/%32IJ/KL.html",
"http://ab.CDEFGH.123/kl/mn/op.html",
"http://example.org/ros\u{E9}",
"example://a/b/c/%7Bfoo%7D/ros\u{E9}",
"eXAMPLE://a/./b/../b/%63/%7bfoo%7d/ros%C3%A9",
"HTTP://www.EXAMPLE.com/",
"http://www.example.com/",
"http://www.example.org/r\u{E9}sum\u{E9}.html",
"http://www.example.org/re\u{301}sume\u{301}.html",
"http://example.org/~user",
"http://example.org/%7euser",
"http://example.org/%7Euser",
"http://example.com",
"http://example.com/",
"http://example.com:/",
"http://example.com:80/",
"http://example.com/data",
"http://example.com/data/",
"http://www.example.org/r%E9sum%E9.xml#r\u{E9}sum\u{E9}",
];
#[test]
fn test_uri() {
assert_validate_list!(uri::<Error<'_>, UriSpec>, OK_URI_LIST);
}
#[test]
fn test_iri() {
assert_validate_list!(uri::<Error<'_>, IriSpec>, OK_URI_LIST, OK_IRI_LIST);
}
#[test]
fn test_invalid_chars() {
assert_invalid!(uri::<Error<'_>, UriSpec>, "foo://bar/<foo>");
assert_invalid!(uri::<Error<'_>, IriSpec>, "foo://bar/<foo>");
assert_invalid!(uri::<Error<'_>, UriSpec>, "foo://bar/\u{FFFD}");
assert_invalid!(uri::<Error<'_>, IriSpec>, "foo://bar/\u{FFFD}");
assert_invalid!(uri::<Error<'_>, UriSpec>, "foo://bar/\u{3044}");
assert_validate!(uri::<Error<'_>, IriSpec>, "foo://bar/\u{3044}");
}
#[test]
fn test_invalid_pct_encoded() {
assert_invalid!(uri::<Error<'_>, UriSpec>, "%zz");
assert_invalid!(uri::<Error<'_>, UriSpec>, "%0");
assert_invalid!(uri::<Error<'_>, UriSpec>, "foo://bar/%0");
assert_invalid!(uri::<Error<'_>, UriSpec>, "foo://bar/%0/");
}
#[test]
#[cfg(feature = "alloc")]
fn test_ipv6address() {
assert_validate!(ipv6address::<Error<'_>>, "a:bB:cCc:dDdD:e:F:a:B");
assert_validate!(ipv6address::<Error<'_>>, "1:1:1:1:1:1:1:1");
assert_validate!(ipv6address::<Error<'_>>, "1:1:1:1:1:1:1.1.1.1");
let make_sub = |n: usize| {
let mut s = "1:".repeat(n);
s.pop();
s
};
for len_pref in 0..=7 {
let prefix = make_sub(len_pref);
for len_suf in 1..=(7 - len_pref) {
assert_validate!(
ipv6address::<Error<'_>>,
&format!("{}::{}", prefix, make_sub(len_suf))
);
if len_suf > 2 {
assert_validate!(
ipv6address::<Error<'_>>,
&format!("{}::{}:1.1.1.1", prefix, make_sub(len_suf - 2))
);
} else if len_suf == 2 {
assert_validate!(ipv6address::<Error<'_>>, &format!("{}::1.1.1.1", prefix));
}
}
}
}
#[test]
fn test_hier_part_only_slashes() {
assert_validate_list!(
hier_part::<Error<'_>, IriSpec>,
&["", "/", "//", "///", "////", "/////"]
);
}
#[test]
fn test_decompose_hier_part_only_slashes() {
assert_complete!(decompose_hier_part::<Error<'_>, IriSpec>, "", (None, ""));
assert_complete!(decompose_hier_part::<Error<'_>, IriSpec>, "/", (None, "/"));
assert_complete!(
decompose_hier_part::<Error<'_>, IriSpec>,
"//",
(Some(""), "")
);
assert_complete!(
decompose_hier_part::<Error<'_>, IriSpec>,
"///",
(Some(""), "/")
);
assert_complete!(
decompose_hier_part::<Error<'_>, IriSpec>,
"////",
(Some(""), "//")
);
assert_complete!(
decompose_hier_part::<Error<'_>, IriSpec>,
"/////",
(Some(""), "///")
);
}
#[test]
fn test_decompose_relative_part_only_slashes() {
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"",
(None, "")
);
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"/",
(None, "/")
);
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"//",
(Some(""), "")
);
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"///",
(Some(""), "/")
);
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"////",
(Some(""), "//")
);
assert_complete!(
decompose_relative_part::<Error<'_>, IriSpec>,
"/////",
(Some(""), "///")
);
}
}