use super::*;
use core::fmt::Display;
pub trait AnyUriRef {
#[must_use]
fn components(&self) -> UriRawComponents<'_>;
fn write_to<T: core::fmt::Write + ?Sized>(
&self,
write: &mut T,
) -> Result<(), core::fmt::Error> {
self.components().write_to(write)
}
#[must_use]
fn display(&self) -> UriDisplay<'_, Self> {
UriDisplay(self)
}
#[must_use]
fn is_empty(&self) -> bool {
self.components().is_empty()
}
#[must_use]
fn uri_type(&self) -> UriType {
self.components().uri_type()
}
#[cfg(feature = "std")]
#[must_use]
fn to_uri_ref_buf(&self) -> UriRefBuf {
unsafe { UriRefBuf::from_string_unchecked(self.display().to_string()) }
}
fn write_resolved<T: core::fmt::Write + ?Sized, D: AnyUriRef + ?Sized>(
&self,
target: &D,
f: &mut T,
) -> Result<(), ResolveError> {
if target.is_empty() {
self.write_to(f)?;
return Ok(());
}
let target_type = target.uri_type();
let target_components = target.components();
let base_type = self.uri_type();
if base_type.cannot_be_a_base() {
match target_type {
UriType::Fragment => {
self.components().trim_fragment().write_to(f)?;
target.write_to(f)?;
return Ok(());
}
UriType::Query => {
self.components().trim_query().write_to(f)?;
target.write_to(f)?;
return Ok(());
}
x if x.is_ietf_rfc3986_relative_reference() => {
return Err(ResolveError::CannotBeABase);
}
_ => (),
}
}
if target_components.scheme.is_some() {
target.write_to(f)?;
return Ok(());
}
let mut components = self.components();
if target_components.authority.is_some() {
components.authority = target_components.authority;
}
components.fragment = target_components.fragment;
if target_components.query.is_some() {
components.query = target_components.query;
} else if !target_components.path.is_empty() || target_components.authority.is_some() {
components.query = None;
}
if let Some(scheme) = components.scheme {
f.write_str(scheme)?;
f.write_char(':')?;
}
if let Some(authority) = components.authority {
f.write_str("//")?;
f.write_str(authority)?;
}
let mut base_path = components.path_as_rel_ref();
let target_path = target_components.path_as_rel_ref();
if !target_path.is_empty() || !target_type.has_absolute_path() {
let target_starts_with_slash = target_path.starts_with("/");
let base_starts_with_slash = base_path.starts_with("/");
if target_type.has_absolute_path() {
if base_starts_with_slash {
base_path = rel_ref!("");
} else {
base_path = rel_ref!("/");
}
} else if !target_path.is_empty() {
base_path = base_path.trim_resource();
}
let mut out_path_vec = Vec::new();
let seg_iter = base_path
.raw_path_segments()
.chain(target_path.raw_path_segments());
let path_will_be_absolute = target_starts_with_slash
|| base_starts_with_slash
|| (base_type.has_absolute_path() && !target_path.is_empty());
for seg in seg_iter {
match seg {
"." => {
let last = out_path_vec.last().map(|x| *x);
if last.map(str::is_empty) == Some(false) {
out_path_vec.push("");
}
continue;
}
".." => {
let mut last = out_path_vec.pop();
if last == Some("") {
last = out_path_vec.pop();
}
match (last, path_will_be_absolute, out_path_vec.is_empty()) {
(Some("."), false, _) => out_path_vec.push(".."),
(Some(".."), false, _) => {
out_path_vec.push("..");
out_path_vec.push("..");
}
(Some(_), true, _) => out_path_vec.push(""),
(Some(_), false, false) => out_path_vec.push(""),
(Some(_), false, true) => out_path_vec.push("."),
(None, _, _) => (),
};
}
seg => {
match out_path_vec.last().map(|x| *x) {
Some(".") if seg.is_empty() => continue,
Some(".") | Some("") => {
out_path_vec.pop();
}
_ => (),
};
out_path_vec.push(seg)
}
}
}
if path_will_be_absolute {
f.write_char('/')?;
}
for (n, seg) in out_path_vec.into_iter().enumerate() {
if n != 0 {
f.write_char('/')?;
}
f.write_str(seg)?;
}
}
if let Some(query) = components.query {
f.write_char('?')?;
f.write_str(query)?;
}
if let Some(fragment) = components.fragment {
f.write_char('#')?;
f.write_str(fragment)?;
}
Ok(())
}
#[cfg(feature = "std")]
#[must_use]
fn resolved<T: AnyUriRef + ?Sized>(&self, dest: &T) -> Result<UriRefBuf, ResolveError> {
if dest.is_empty() {
return Ok(self.to_uri_ref_buf());
}
let mut ret = String::new();
self.write_resolved(dest, &mut ret)?;
Ok(unsafe { UriRefBuf::from_string_unchecked(ret) })
}
}
use core::ops::Deref;
impl<'a, T: AnyUriRef + Clone + ?Sized> AnyUriRef for Cow<'a, T> {
fn components(&self) -> UriRawComponents<'_> {
self.deref().components()
}
fn write_to<W: core::fmt::Write + ?Sized>(
&self,
write: &mut W,
) -> Result<(), core::fmt::Error> {
self.deref().write_to(write)
}
fn is_empty(&self) -> bool {
self.deref().is_empty()
}
fn uri_type(&self) -> UriType {
self.deref().uri_type()
}
#[cfg(feature = "std")]
fn to_uri_ref_buf(&self) -> UriRefBuf {
self.deref().to_uri_ref_buf()
}
fn write_resolved<W: core::fmt::Write + ?Sized, D: AnyUriRef + ?Sized>(
&self,
dest: &D,
output: &mut W,
) -> Result<(), ResolveError> {
self.deref().write_resolved(dest, output)
}
fn resolved<W: AnyUriRef + ?Sized>(&self, dest: &W) -> Result<UriRefBuf, ResolveError> {
self.deref().resolved(dest)
}
}
#[derive(Debug, Copy, Clone)]
pub struct UriDisplay<'a, T: AnyUriRef + ?Sized>(&'a T);
impl<'a, T: AnyUriRef + ?Sized> Display for UriDisplay<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
self.0.write_to(f)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn resolve_simple() {
let uri_test_table = vec![
(
"http://x/a/b/c",
"/abs-path",
Some(uri_ref!("http://x/abs-path")),
),
(
"http://x/a/b/c",
"f/s?c",
Some(uri_ref!("http://x/a/b/f/s?c")),
),
(
"http://x/a/b/c",
"/abs-path",
Some(uri_ref!("http://x/abs-path")),
),
(
"http://x/a/b/c",
"path",
Some(uri_ref!("http://x/a/b/path")),
),
(
"http://x/a/b/c/",
"path",
Some(uri_ref!("http://x/a/b/c/path")),
),
(
"http://x/a/b/c/",
"//y/d/e/f/",
Some(uri_ref!("http://y/d/e/f/")),
),
("http://x/a/b/c", "?", Some(uri_ref!("http://x/a/b/c?"))),
("http://x", "a/b/c", Some(uri_ref!("http://x/a/b/c"))),
("http://x", "/a/b/c", Some(uri_ref!("http://x/a/b/c"))),
("http://x/", "a/b/c", Some(uri_ref!("http://x/a/b/c"))),
("http://x/a/b/c", "coap://x", Some(uri_ref!("coap://x"))),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
#[test]
fn resolve_relative_base() {
let uri_test_table = vec![
("b/c/d;p?q", "g:h", Some(uri_ref!("g:h"))),
("b/c/d;p?q", "g", Some(uri_ref!("b/c/g"))),
("b/c/d;p?q", "./g", Some(uri_ref!("b/c/g"))),
("b/c/d;p?q", "g/", Some(uri_ref!("b/c/g/"))),
("b/c/d;p?q", "/g", Some(uri_ref!("/g"))),
("b/c/d;p?q", "//g", Some(uri_ref!("//g"))),
("b/c/d;p?q", "?y", Some(uri_ref!("b/c/d;p?y"))),
("b/c/d;p?q", "g?y", Some(uri_ref!("b/c/g?y"))),
("b/c/d;p?q", "#s", Some(uri_ref!("b/c/d;p?q#s"))),
("b/c/d;p?q", "g#s", Some(uri_ref!("b/c/g#s"))),
("b/c/d;p?q", "g?y#s", Some(uri_ref!("b/c/g?y#s"))),
("b/c/d;p?q", ";x", Some(uri_ref!("b/c/;x"))),
("b/c/d;p?q", "g;x", Some(uri_ref!("b/c/g;x"))),
("b/c/d;p?q", "g;x?y#s", Some(uri_ref!("b/c/g;x?y#s"))),
("b/c/d;p?q", "", Some(uri_ref!("b/c/d;p?q"))),
("b/c/d;p?q", ".", Some(uri_ref!("b/c/"))),
("b/c/d;p?q", "./", Some(uri_ref!("b/c/"))),
("b/c/d;p?q", "/./g", Some(uri_ref!("/g"))),
("b/c/d;p?q", "g.", Some(uri_ref!("b/c/g."))),
("b/c/d;p?q", ".g", Some(uri_ref!("b/c/.g"))),
("b/c/d;p?q", "g..", Some(uri_ref!("b/c/g.."))),
("b/c/d;p?q", "..g", Some(uri_ref!("b/c/..g"))),
("b/c/d;p?q", "g?y/./x", Some(uri_ref!("b/c/g?y/./x"))),
("b/c/d;p?q", "g?y/../x", Some(uri_ref!("b/c/g?y/../x"))),
("b/c/d;p?q", "g#s/./x", Some(uri_ref!("b/c/g#s/./x"))),
("b/c/d;p?q", "g#s/../x", Some(uri_ref!("b/c/g#s/../x"))),
("b/c/d;p?q", "..", Some(uri_ref!("b/"))),
("b/c/d;p?q", "../", Some(uri_ref!("b/"))),
("b/c/d;p?q", "../g", Some(uri_ref!("b/g"))),
("b/c/d;p?q", "../..", Some(uri_ref!("."))),
("b/c/d;p?q", "../../", Some(uri_ref!("."))),
("b/c/d;p?q", "../../g", Some(uri_ref!("g"))),
("b/c/d;p?q", "../../../g", Some(uri_ref!("../g"))),
("b/c/d;p?q", "../../../../g", Some(uri_ref!("../../g"))),
("b/c/d;p?q", "/../g", Some(uri_ref!("/g"))),
("b/c/d;p?q", "./../g", Some(uri_ref!("b/g"))),
("b/c/d;p?q", "./g/.", Some(uri_ref!("b/c/g/"))),
("b/c/d;p?q", "g/./h", Some(uri_ref!("b/c/g/h"))),
("b/c/d;p?q", "g/../h", Some(uri_ref!("b/c/h"))),
("b/c/d;p?q", "g;x=1/./y", Some(uri_ref!("b/c/g;x=1/y"))),
("b/c/d;p?q", "g;x=1/../y", Some(uri_ref!("b/c/y"))),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
#[test]
fn resolve_cannot_be_a_base() {
let uri_test_table = vec![
("s:123", "/a/b/c", None),
("s:123", "//a/b/c", None),
("s:123", ".", None),
("s:123", "", Some(uri_ref!("s:123"))),
("s:123", "?q=123", Some(uri_ref!("s:123?q=123"))),
("s:123", "#frag", Some(uri_ref!("s:123#frag"))),
("s:123", "#frag", Some(uri_ref!("s:123#frag"))),
("s:123", "file:/d/e/f", Some(uri_ref!("file:/d/e/f"))),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
#[test]
fn resolve_no_authority() {
let uri_test_table = vec![
("file:/d/e/f", "//a/b/c", Some(uri_ref!("file://a/b/c"))),
("file:/d/e/f", "g", Some(uri_ref!("file:/d/e/g"))),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
#[test]
fn resolve_rfc3986_simple() {
let uri_test_table = vec![
("http://a/b/c/d;p?q", "g:h", Some(uri_ref!("g:h"))),
("http://a/b/c/d;p?q", "g", Some(uri_ref!("http://a/b/c/g"))),
(
"http://a/b/c/d;p?q",
"./g",
Some(uri_ref!("http://a/b/c/g")),
),
(
"http://a/b/c/d;p?q",
"g/",
Some(uri_ref!("http://a/b/c/g/")),
),
("http://a/b/c/d;p?q", "/g", Some(uri_ref!("http://a/g"))),
("http://a/b/c/d;p?q", "//g", Some(uri_ref!("http://g"))),
(
"http://a/b/c/d;p?q",
"?y",
Some(uri_ref!("http://a/b/c/d;p?y")),
),
(
"http://a/b/c/d;p?q",
"g?y",
Some(uri_ref!("http://a/b/c/g?y")),
),
(
"http://a/b/c/d;p?q",
"#s",
Some(uri_ref!("http://a/b/c/d;p?q#s")),
),
(
"http://a/b/c/d;p?q",
"g#s",
Some(uri_ref!("http://a/b/c/g#s")),
),
(
"http://a/b/c/d;p?q",
"g?y#s",
Some(uri_ref!("http://a/b/c/g?y#s")),
),
(
"http://a/b/c/d;p?q",
";x",
Some(uri_ref!("http://a/b/c/;x")),
),
(
"http://a/b/c/d;p?q",
"g;x",
Some(uri_ref!("http://a/b/c/g;x")),
),
(
"http://a/b/c/d;p?q",
"g;x?y#s",
Some(uri_ref!("http://a/b/c/g;x?y#s")),
),
(
"http://a/b/c/d;p?q",
"",
Some(uri_ref!("http://a/b/c/d;p?q")),
),
("http://a/b/c/d;p?q", ".", Some(uri_ref!("http://a/b/c/"))),
("http://a/b/c/d;p?q", "./", Some(uri_ref!("http://a/b/c/"))),
("http://a/b/c/d;p?q", "/./g", Some(uri_ref!("http://a/g"))),
(
"http://a/b/c/d;p?q",
"g.",
Some(uri_ref!("http://a/b/c/g.")),
),
(
"http://a/b/c/d;p?q",
".g",
Some(uri_ref!("http://a/b/c/.g")),
),
(
"http://a/b/c/d;p?q",
"g..",
Some(uri_ref!("http://a/b/c/g..")),
),
(
"http://a/b/c/d;p?q",
"..g",
Some(uri_ref!("http://a/b/c/..g")),
),
(
"http://a/b/c/d;p?q",
"g?y/./x",
Some(uri_ref!("http://a/b/c/g?y/./x")),
),
(
"http://a/b/c/d;p?q",
"g?y/../x",
Some(uri_ref!("http://a/b/c/g?y/../x")),
),
(
"http://a/b/c/d;p?q",
"g#s/./x",
Some(uri_ref!("http://a/b/c/g#s/./x")),
),
(
"http://a/b/c/d;p?q",
"g#s/../x",
Some(uri_ref!("http://a/b/c/g#s/../x")),
),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
#[test]
fn resolve_rfc3986_dot_dot() {
let uri_test_table = vec![
("http://a/b/c/d;p?q", "..", Some(uri_ref!("http://a/b/"))),
("http://a/b/c/d;p?q", "../", Some(uri_ref!("http://a/b/"))),
("http://a/b/c/d;p?q", "../g", Some(uri_ref!("http://a/b/g"))),
("http://a/b/c/d;p?q", "../..", Some(uri_ref!("http://a/"))),
("http://a/b/c/d;p?q", "../../", Some(uri_ref!("http://a/"))),
(
"http://a/b/c/d;p?q",
"../../g",
Some(uri_ref!("http://a/g")),
),
(
"http://a/b/c/d;p?q",
"../../../g",
Some(uri_ref!("http://a/g")),
),
(
"http://a/b/c/d;p?q",
"../../../../g",
Some(uri_ref!("http://a/g")),
),
("http://a/b/c/d;p?q", "/../g", Some(uri_ref!("http://a/g"))),
(
"http://a/b/c/d;p?q",
"./../g",
Some(uri_ref!("http://a/b/g")),
),
(
"http://a/b/c/d;p?q",
"./g/.",
Some(uri_ref!("http://a/b/c/g/")),
),
(
"http://a/b/c/d;p?q",
"g/./h",
Some(uri_ref!("http://a/b/c/g/h")),
),
(
"http://a/b/c/d;p?q",
"g/../h",
Some(uri_ref!("http://a/b/c/h")),
),
(
"http://a/b/c/d;p?q",
"g;x=1/./y",
Some(uri_ref!("http://a/b/c/g;x=1/y")),
),
(
"http://a/b/c/d;p?q",
"g;x=1/../y",
Some(uri_ref!("http://a/b/c/y")),
),
];
for (a, b, c) in uri_test_table {
let uri_a = UriRef::from_str(a).expect(a);
let uri_b = UriRef::from_str(b).expect(b);
assert_eq!(
uri_a.resolved(uri_b).ok(),
c.map(|x| x.to_owned()),
"uri_a.resolved(): a:{} b:{} c:{:?}",
a,
b,
c
);
}
}
}