use super::*;
use core::ops::{Deref, Range};
use core::str::FromStr;
#[cfg(feature = "alloc")]
use alloc::borrow::Cow;
#[derive(Eq, Hash)]
pub struct UriRef(pub(super) str);
_impl_uri_traits_base!(UriRef);
impl Deref for UriRef {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Default for &UriRef {
fn default() -> Self {
iuri_ref!("")
}
}
impl Default for &mut UriRef {
fn default() -> Self {
use core::slice::from_raw_parts_mut;
use core::str::from_utf8_unchecked_mut;
unsafe {
let empty_slice = from_raw_parts_mut(core::ptr::NonNull::<u8>::dangling().as_ptr(), 0);
let empty_string = from_utf8_unchecked_mut(empty_slice);
UriRef::from_str_unchecked_mut(empty_string)
}
}
}
impl AnyUriRef for UriRef {
unsafe fn write_to_unsafe<T: core::fmt::Write + ?Sized>(
&self,
write: &mut T,
) -> Result<(), core::fmt::Error> {
write.write_str(self.as_str())
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn uri_type(&self) -> UriType {
if self.starts_with('#') {
return UriType::Fragment;
}
if self.starts_with('?') {
return UriType::Query;
}
if self.starts_with("//") {
return UriType::NetworkPath;
}
if self.starts_with('/') {
return UriType::AbsolutePath;
}
let pat = |c| c == ':' || c == '/' || c == '?' || c == '#';
if let Some(i) = self.find(pat) {
if self[i..].starts_with("://") {
return UriType::Uri;
} else if self[i..].starts_with(":/") {
return UriType::UriNoAuthority;
} else if self[i..].starts_with(':') {
return UriType::UriCannotBeABase;
}
}
UriType::RelativePath
}
fn components(&self) -> UriRawComponents<'_> {
UriRawComponents::from_str(self.as_str()).unwrap()
}
}
impl core::fmt::Display for UriRef {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.write_to(f)
}
}
impl UriRef {
pub fn from_str(s: &str) -> Result<&UriRef, ParseError> {
UriRawComponents::from_str(s)?;
Ok(unsafe { Self::from_str_unchecked(s) })
}
pub fn is_str_valid<S: AsRef<str>>(s: S) -> bool {
let str_ref = s.as_ref();
UriRawComponents::from_str(str_ref).is_ok()
}
#[inline(always)]
pub const fn as_str(&self) -> &str {
&self.0
}
pub fn as_uri(&self) -> Option<&Uri> {
if self.uri_type().can_borrow_as_uri() {
Some(unsafe { Uri::from_str_unchecked(self.as_str()) })
} else {
None
}
}
pub fn as_rel_ref(&self) -> Option<&RelRef> {
if self.uri_type().can_borrow_as_rel_ref() {
Some(unsafe { RelRef::from_str_unchecked(self.as_str()) })
} else {
None
}
}
}
impl UriRef {
pub fn heir_part_start(&self) -> usize {
let pat = |c| c == ':' || c == '/' || c == '?' || c == '#';
if let Some(i) = self.find(pat) {
if self[i..].starts_with(':') {
return i + 1;
}
}
0
}
pub fn path_start(&self) -> usize {
let heir_part_start = self.heir_part_start();
let heir_part = &self[heir_part_start..];
if heir_part.starts_with("//") {
let authority = &heir_part[2..];
let pat = |c| c == '/' || c == '?' || c == '#';
if let Some(j) = authority.find(pat) {
heir_part_start + 2 + j
} else {
self.len()
}
} else {
heir_part_start
}
}
pub fn path_end(&self) -> usize {
let pat = |c| c == '?' || c == '#';
if let Some(i) = self.find(pat) {
i
} else {
self.len()
}
}
pub fn query_start(&self) -> Option<usize> {
let pat = |c| c == '?' || c == '#';
if let Some(i) = self.find(pat) {
if self[i..].starts_with('?') {
return Some(i);
}
}
None
}
pub fn fragment_start(&self) -> Option<usize> {
self.find('#')
}
pub fn authority_range(&self) -> Option<Range<usize>> {
let pat = |c| c == '/' || c == '?' || c == '#';
if let Some(i) = self.find(pat) {
let step1 = &self[i..];
if !step1.starts_with("//") {
return None;
}
let step2 = &step1[2..];
if let Some(j) = step2.find(pat) {
return Some(i + 2..i + 2 + j);
} else {
return Some(i + 2..self.len());
}
}
None
}
}
impl UriRef {
pub fn split(&self) -> (Option<&Uri>, &RelRef) {
let path_start = self.path_start();
if path_start == 0 {
(None, unsafe { RelRef::from_str_unchecked(self.as_str()) })
} else {
let (base, rel) = self.split_at(path_start);
let base = unsafe { Uri::from_str_unchecked(base) };
let rel = unsafe { RelRef::from_str_unchecked(rel) };
(Some(base), rel)
}
}
pub fn split_mut(&mut self) -> (Option<&mut Uri>, &mut RelRef) {
let path_start = self.path_start();
if path_start == 0 {
(None, unsafe {
RelRef::from_str_unchecked_mut(self.as_mut_str())
})
} else {
unsafe {
let (base, rel) = self.as_mut_str().split_at_mut(path_start);
(
Some(Uri::from_str_unchecked_mut(base)),
RelRef::from_str_unchecked_mut(rel),
)
}
}
}
pub fn base(&self) -> Option<&Uri> {
self.split().0
}
pub fn rel(&self) -> &RelRef {
self.split().1
}
pub fn rel_mut(&mut self) -> &mut RelRef {
self.split_mut().1
}
}
impl UriRef {
pub fn scheme(&self) -> Option<&str> {
let pat = |c| c == ':' || c == '/' || c == '?' || c == '#';
if let Some(i) = self.find(pat) {
if self[i..].starts_with(':') {
return Some(&self[..i]);
}
}
None
}
pub fn raw_authority(&self) -> Option<&str> {
Some(&self[self.authority_range()?])
}
#[cfg(feature = "alloc")]
pub fn authority(&self) -> Option<Cow<'_, str>> {
self.raw_authority().map(|f| f.unescape_uri().to_cow())
}
pub fn raw_userinfo_host_port(&self) -> Option<(Option<&str>, &str, Option<u16>)> {
let authority = self.raw_authority()?;
let userinfo;
let host_and_port;
if let Some(i) = authority.find('@') {
userinfo = Some(&authority[..i]);
host_and_port = &authority[i + 1..];
} else {
userinfo = None;
host_and_port = authority;
}
let host;
let port;
if host_and_port.starts_with('[') {
if let Some(i) = host_and_port.rfind("]:") {
host = &host_and_port[1..i];
port = u16::from_str(&host_and_port[i + 2..]).ok();
} else {
host = &host_and_port[1..host_and_port.len() - 1];
port = None;
}
} else {
if let Some(i) = host_and_port.rfind(':') {
host = &host_and_port[..i];
port = u16::from_str(&host_and_port[i + 1..]).ok();
} else {
host = host_and_port;
port = None;
}
}
Some((userinfo, host, port))
}
#[cfg(feature = "alloc")]
pub fn userinfo_host_port(&self) -> Option<(Option<Cow<'_, str>>, Cow<'_, str>, Option<u16>)> {
self.raw_userinfo_host_port().map(|item| {
(
item.0.map(|s| s.unescape_uri().to_cow()),
item.1.unescape_uri().to_cow(),
item.2,
)
})
}
#[cfg(feature = "alloc")]
pub fn host(&self) -> Option<Cow<'_, str>> {
self.raw_userinfo_host_port()
.map(|item| item.1.unescape_uri().to_cow())
}
#[must_use]
#[inline(always)]
pub fn raw_path(&self) -> &str {
self.path_as_rel_ref().as_str()
}
pub fn path_as_rel_ref(&self) -> &RelRef {
self.rel().path_as_rel_ref()
}
pub fn path_query_as_rel_ref(&self) -> &RelRef {
self.trim_fragment().rel()
}
pub fn raw_path_segments(&self) -> impl Iterator<Item = &str> {
self.rel().raw_path_segments()
}
#[cfg(feature = "alloc")]
pub fn path_segments(&self) -> impl Iterator<Item = Cow<'_, str>> {
self.raw_path_segments()
.map(|item| item.unescape_uri().to_cow())
}
pub fn query_fragment_as_rel_ref(&self) -> Option<&RelRef> {
if let Some(i) = self.query_start() {
Some(unsafe { RelRef::from_str_unchecked(&self[i..]) })
} else {
None
}
}
pub fn query_as_rel_ref(&self) -> Option<&RelRef> {
self.trim_fragment().query_fragment_as_rel_ref()
}
pub fn raw_query(&self) -> Option<&str> {
self.query_as_rel_ref().map(|s| &s[1..])
}
pub fn raw_query_items(&self) -> impl Iterator<Item = &str> {
let pattern = |c| c == '&' || c == ';';
match self.raw_query() {
Some(query) => query.split(pattern),
None => {
let mut ret = "".split(pattern);
let _ = ret.next();
ret
}
}
}
pub fn raw_query_key_values(&self) -> impl Iterator<Item = (&str, &str)> {
self.raw_query_items().map(|comp| match comp.find('=') {
Some(x) => {
let split = comp.split_at(x);
(split.0, &split.1[1..])
}
None => (comp, ""),
})
}
#[cfg(feature = "alloc")]
pub fn query_items(&self) -> impl Iterator<Item = Cow<'_, str>> {
self.raw_query_items()
.map(|item| item.unescape_uri().to_cow())
}
#[cfg(feature = "alloc")]
pub fn query_key_values(&self) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)> {
self.raw_query_key_values().map(|item| {
(
item.0.unescape_uri().to_cow(),
item.1.unescape_uri().to_cow(),
)
})
}
pub fn fragment_as_rel_ref(&self) -> Option<&RelRef> {
if let Some(i) = self.fragment_start() {
Some(unsafe { RelRef::from_str_unchecked(&self[i..]) })
} else {
None
}
}
pub fn raw_fragment(&self) -> Option<&str> {
self.fragment_as_rel_ref().map(|s| &s[1..])
}
#[cfg(feature = "alloc")]
pub fn fragment(&self) -> Option<Cow<'_, str>> {
self.raw_fragment().map(|f| f.unescape_uri().to_cow())
}
}
impl UriRef {
pub fn has_trailing_slash(&self) -> bool {
let path_end = self.path_end();
path_end > 0 && &self[path_end - 1..path_end] == "/"
}
}
impl UriRef {
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_fragment(&self) -> &UriRef {
if let Some(i) = self.fragment_start() {
unsafe { Self::from_str_unchecked(&self[..i]) }
} else {
self
}
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_query(&self) -> &UriRef {
if let Some(i) = self.find(|c| c == '?' || c == '#') {
unsafe { Self::from_str_unchecked(&self[..i]) }
} else {
self
}
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_path(&self) -> &UriRef {
let i = self.path_start();
unsafe { Self::from_str_unchecked(&self[..i]) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_heir_part(&self) -> &UriRef {
let i = self.heir_part_start();
unsafe { Self::from_str_unchecked(&self[..i]) }
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_resource(&self) -> &UriRef {
let mut ret = self.trim_query();
let path_start = self.path_start();
if let Some(i) = ret.rfind('/') {
if i + 1 > path_start {
ret = unsafe { Self::from_str_unchecked(&self[..i + 1]) };
}
} else if path_start == 0 {
ret = iuri_ref!("");
}
ret
}
#[must_use = "this returns the trimmed uri as a new slice, \
without modifying the original"]
pub fn trim_trailing_slash(&self) -> &UriRef {
let path_end = self.path_end();
if path_end > self.path_start() + 1 && &self[path_end - 1..path_end] == "/" {
unsafe { Self::from_str_unchecked(&self[..path_end - 1]) }
} else {
self.trim_query()
}
}
#[must_use]
pub fn trim_to_shorten(&self, base: &UriRef) -> Option<&RelRef> {
let (base_abs_part, base_rel_part) = base.trim_resource().split();
let (self_abs_part, self_rel_part) = self.split();
if self_abs_part.is_some() && (base_abs_part.is_none() || base_abs_part != self_abs_part) {
return None;
}
if self_rel_part.starts_with(base_rel_part.as_str()) {
Some(unsafe { RelRef::from_str_unchecked(&self_rel_part[base_rel_part.len()..]) })
} else {
None
}
}
}
impl UriRef {
#[inline(always)]
pub unsafe fn from_str_unchecked(s: &str) -> &UriRef {
&*(s as *const str as *const UriRef)
}
#[inline(always)]
pub unsafe fn from_str_unchecked_mut(s: &mut str) -> &mut UriRef {
&mut *(s as *mut str as *mut UriRef)
}
#[inline(always)]
pub unsafe fn as_mut_str(&mut self) -> &mut str {
&mut self.0
}
#[doc(hidden)]
#[must_use = "this returns a new slice, without modifying the original"]
pub unsafe fn query_as_rel_ref_mut(&mut self) -> Option<&mut RelRef> {
let self_ptr = self.as_ptr();
let no_mut = self.query_as_rel_ref()?;
let begin = no_mut.as_ptr() as usize - self_ptr as usize;
let end = begin + no_mut.len();
Some(RelRef::from_str_unchecked_mut(
&mut self.as_mut_str()[begin..end],
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::{vec, vec::Vec, string::ToString};
#[test]
fn test_from_str() {
assert!(UriRef::from_str("http://example.com/").is_ok());
assert!(UriRef::from_str("//example.com/").is_ok());
assert!(UriRef::from_str("/a/b/c").is_ok());
assert!(UriRef::from_str("a/b/c").is_ok());
assert!(UriRef::from_str("?q=123").is_ok());
assert!(UriRef::from_str("#frag").is_ok());
assert!(UriRef::from_str("not%auri://a/b/c").is_err());
assert!(UriRef::from_str("coap+sms://+1-234-567-8901/1/s/levl/v?inc").is_ok());
assert!(UriRef::from_str("not a uri://a/b/c").is_err());
}
#[test]
fn path_as_rel_ref() {
assert_eq!(
irel_ref!("example/"),
iuri_ref!("example/").path_as_rel_ref()
);
assert_eq!(
irel_ref!("/blah/"),
iuri_ref!("http://example.com/blah/").path_as_rel_ref()
);
assert_eq!(
irel_ref!("example.com/blah/"),
iuri_ref!("http:example.com/blah/?q").path_as_rel_ref()
);
}
#[test]
fn has_trailing_slash() {
assert_eq!(true, iuri_ref!("example/").has_trailing_slash());
assert_eq!(true, iuri_ref!("/example/").has_trailing_slash());
assert_eq!(true, iuri_ref!("/example/#frag").has_trailing_slash());
assert_eq!(true, iuri_ref!("example/?query#frag").has_trailing_slash());
assert_eq!(true, iuri_ref!("coap://example//").has_trailing_slash());
assert_eq!(false, iuri_ref!("example").has_trailing_slash());
assert_eq!(false, iuri_ref!("example?/").has_trailing_slash());
assert_eq!(false, iuri_ref!("example#/").has_trailing_slash());
assert_eq!(false, iuri_ref!("example/x").has_trailing_slash());
assert_eq!(false, iuri_ref!("e/x/a/m/p/l/e?/#/").has_trailing_slash());
}
#[test]
fn try_trim_resource() {
assert_eq!(iuri_ref!("example/"), iuri_ref!("example/").trim_resource());
assert_eq!(
iuri_ref!("/example/"),
iuri_ref!("/example/").trim_resource()
);
assert_eq!(
iuri_ref!("/example/"),
iuri_ref!("/example/#frag").trim_resource()
);
assert_eq!(
iuri_ref!("example/"),
iuri_ref!("example/?query#frag").trim_resource()
);
assert_eq!(
iuri_ref!("coap://example//"),
iuri_ref!("coap://example//").trim_resource()
);
assert_eq!(iuri_ref!(""), iuri_ref!("example").trim_resource());
assert_eq!(iuri_ref!(""), iuri_ref!("example?/").trim_resource());
assert_eq!(iuri_ref!(""), iuri_ref!("example#/").trim_resource());
assert_eq!(
iuri_ref!("example/"),
iuri_ref!("example/x").trim_resource()
);
assert_eq!(
iuri_ref!("e/x/a/m/p/l/"),
iuri_ref!("e/x/a/m/p/l/e?/#/").trim_resource()
);
}
#[test]
fn trim_to_shorten() {
assert_eq!(
Some(irel_ref!("c")),
iuri_ref!("/a/b/c").trim_to_shorten(iuri_ref!("/a/b/"))
);
assert_eq!(
Some(irel_ref!("c/d/e")),
iuri_ref!("/a/b/c/d/e").trim_to_shorten(iuri_ref!("/a/b/"))
);
assert_eq!(
None,
iuri_ref!("/a/b/c/d/e").trim_to_shorten(iuri_ref!("/a/c/"))
);
assert_eq!(
Some(irel_ref!("c/d/e")),
iuri_ref!("/a/b/c/d/e").trim_to_shorten(iuri_ref!("coap://blah/a/b/"))
);
assert_eq!(
Some(irel_ref!("c/d/e")),
iuri_ref!("coap://blah/a/b/c/d/e").trim_to_shorten(iuri_ref!("coap://blah/a/b/"))
);
assert_eq!(
None,
iuri_ref!("coap://blah/a/b/c/d/e").trim_to_shorten(iuri_ref!("/a/b/"))
);
assert_eq!(
Some(irel_ref!("c")),
iuri_ref!("/a/b/c").trim_to_shorten(iuri_ref!("/a/b/d"))
);
}
#[test]
fn userinfo_host_port() {
let uri_test_table = vec![
(
iuri_ref!("http://example.com/a/b/c"),
Some((None, "example.com", None)),
),
(
iuri_ref!("http://example.com:1234/a/b/c"),
Some((None, "example.com", Some(1234u16))),
),
(
iuri_ref!("http://example.com:/a/b/c"),
Some((None, "example.com", None)),
),
(
iuri_ref!("http://username@example.com/a/b/c"),
Some((Some("username"), "example.com", None)),
),
(
iuri_ref!("http://username:password@example.com/a/b/c"),
Some((Some("username:password"), "example.com", None)),
),
(
iuri_ref!("http://username:password@example.com:1234/a/b/c"),
Some((Some("username:password"), "example.com", Some(1234))),
),
(
iuri_ref!("http://username:password@example.com:1234567/a/b/c"),
Some((Some("username:password"), "example.com", None)),
),
(iuri_ref!("http://[::1]/a/b/c"), Some((None, "::1", None))),
(
iuri_ref!("http://[::1]:1234/a/b/c"),
Some((None, "::1", Some(1234))),
),
(
iuri_ref!("http://username:password@[::1]:1234/a/b/c"),
Some((Some("username:password"), "::1", Some(1234))),
),
];
for (a, b) in uri_test_table.iter() {
assert_eq!(*b, a.raw_userinfo_host_port(), "Failed for: <{}>", a);
}
}
}