#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(unsafe_code)]
use memchr::{memchr, memchr2, memchr_iter, memrchr};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::{Borrow, Cow};
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::error::Error;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::net::{AddrParseError, Ipv6Addr};
use std::ops::Deref;
use std::str::FromStr;
#[derive(Clone, Copy)]
pub struct IriRef<T> {
iri: T,
positions: IriElementsPositions,
}
impl<T: Deref<Target = str>> IriRef<T> {
pub fn parse(iri: T) -> Result<Self, IriParseError> {
let iri = Self::parse_unchecked(iri);
validate_iri_ref(&iri)?;
Ok(iri)
}
pub fn parse_unchecked(iri: T) -> Self {
let positions = find_iri_ref_positions(iri.as_ref());
Self { iri, positions }
}
pub fn resolve<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
) -> Result<IriRef<String>, IriParseError> {
let mut iri = String::new();
let (positions, error) = resolve(self, reference, &mut iri);
if let Some(error) = error {
return Err(error.into());
}
Ok(IriRef { iri, positions })
}
pub fn resolve_unchecked<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
) -> IriRef<String> {
let mut iri = String::new();
let (positions, _) = resolve(self, reference, &mut iri);
IriRef { iri, positions }
}
pub fn resolve_into<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
output_buffer: &mut impl OutputBuffer,
) -> Result<(), IriParseError> {
let (_, error) = resolve(self, reference, output_buffer);
if let Some(error) = error {
return Err(error.into());
}
Ok(())
}
pub fn resolve_into_unchecked<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
output_buffer: &mut impl OutputBuffer,
) {
resolve(self, reference, output_buffer);
}
#[inline]
pub fn as_ref(&self) -> IriRef<&str> {
IriRef {
iri: &self.iri,
positions: self.positions,
}
}
#[inline]
pub fn as_str(&self) -> &str {
&self.iri
}
#[inline]
pub fn into_inner(self) -> T {
self.iri
}
#[inline]
pub fn is_absolute(&self) -> bool {
self.positions.scheme_end != 0
}
#[inline]
pub fn scheme(&self) -> Option<&str> {
if self.positions.scheme_end == 0 {
None
} else {
Some(&self.iri[..self.positions.scheme_end - 1])
}
}
#[inline]
pub fn authority(&self) -> Option<&str> {
if self.positions.scheme_end + 2 > self.positions.authority_end {
None
} else {
Some(&self.iri[self.positions.scheme_end + 2..self.positions.authority_end])
}
}
#[inline]
pub fn path(&self) -> &str {
&self.iri[self.positions.authority_end..self.positions.path_end]
}
#[inline]
pub fn query(&self) -> Option<&str> {
if self.positions.path_end >= self.positions.query_end {
None
} else {
Some(&self.iri[self.positions.path_end + 1..self.positions.query_end])
}
}
#[inline]
pub fn fragment(&self) -> Option<&str> {
if self.positions.query_end >= self.iri.len() {
None
} else {
Some(&self.iri[self.positions.query_end + 1..])
}
}
}
impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<IriRef<Rhs>> for IriRef<Lft> {
#[inline]
fn eq(&self, other: &IriRef<Rhs>) -> bool {
self.iri.eq(&other.iri)
}
}
impl<T: PartialEq<str>> PartialEq<str> for IriRef<T> {
#[inline]
fn eq(&self, other: &str) -> bool {
self.iri.eq(other)
}
}
impl<'a, T: PartialEq<&'a str>> PartialEq<&'a str> for IriRef<T> {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.iri.eq(other)
}
}
impl<T: PartialEq<String>> PartialEq<String> for IriRef<T> {
#[inline]
fn eq(&self, other: &String) -> bool {
self.iri.eq(other)
}
}
impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Cow<'a, str>> for IriRef<T> {
#[inline]
fn eq(&self, other: &Cow<'a, str>) -> bool {
self.iri.eq(other)
}
}
impl<T: PartialEq<str>> PartialEq<IriRef<T>> for str {
#[inline]
fn eq(&self, other: &IriRef<T>) -> bool {
other.iri.eq(self)
}
}
impl<'a, T: PartialEq<&'a str>> PartialEq<IriRef<T>> for &'a str {
#[inline]
fn eq(&self, other: &IriRef<T>) -> bool {
other.iri.eq(self)
}
}
impl<T: PartialEq<String>> PartialEq<IriRef<T>> for String {
#[inline]
fn eq(&self, other: &IriRef<T>) -> bool {
other.iri.eq(self)
}
}
impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<IriRef<T>> for Cow<'a, str> {
#[inline]
fn eq(&self, other: &IriRef<T>) -> bool {
other.iri.eq(self)
}
}
impl<T: Eq> Eq for IriRef<T> {}
impl<T: Hash> Hash for IriRef<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.iri.hash(state)
}
}
impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<IriRef<Rhs>> for IriRef<Lft> {
#[inline]
fn partial_cmp(&self, other: &IriRef<Rhs>) -> Option<Ordering> {
self.iri.partial_cmp(&other.iri)
}
}
impl<T: Ord> Ord for IriRef<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.iri.cmp(&other.iri)
}
}
impl<T: Deref<Target = str>> Deref for IriRef<T> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.iri.deref()
}
}
impl<T: AsRef<str>> AsRef<str> for IriRef<T> {
#[inline]
fn as_ref(&self) -> &str {
self.iri.as_ref()
}
}
impl<T: Borrow<str>> Borrow<str> for IriRef<T> {
#[inline]
fn borrow(&self) -> &str {
self.iri.borrow()
}
}
impl<T: fmt::Debug> fmt::Debug for IriRef<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.iri.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for IriRef<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.iri.fmt(f)
}
}
impl FromStr for IriRef<String> {
type Err = IriParseError;
#[inline]
fn from_str(iri: &str) -> Result<Self, IriParseError> {
Self::parse(iri.to_owned())
}
}
impl<'a> From<IriRef<&'a str>> for IriRef<String> {
#[inline]
fn from(iri: IriRef<&'a str>) -> Self {
Self {
iri: iri.iri.into(),
positions: iri.positions,
}
}
}
impl<'a> From<IriRef<Cow<'a, str>>> for IriRef<String> {
#[inline]
fn from(iri: IriRef<Cow<'a, str>>) -> Self {
Self {
iri: iri.iri.into(),
positions: iri.positions,
}
}
}
impl From<IriRef<Box<str>>> for IriRef<String> {
#[inline]
fn from(iri: IriRef<Box<str>>) -> Self {
Self {
iri: iri.iri.into(),
positions: iri.positions,
}
}
}
impl<'a> From<IriRef<&'a str>> for IriRef<Cow<'a, str>> {
#[inline]
fn from(iri: IriRef<&'a str>) -> Self {
Self {
iri: iri.iri.into(),
positions: iri.positions,
}
}
}
impl From<IriRef<String>> for IriRef<Cow<'_, str>> {
#[inline]
fn from(iri: IriRef<String>) -> Self {
Self {
iri: iri.iri.into(),
positions: iri.positions,
}
}
}
impl<'a> From<&'a IriRef<String>> for IriRef<&'a str> {
#[inline]
fn from(iri: &'a IriRef<String>) -> Self {
Self {
iri: &iri.iri,
positions: iri.positions,
}
}
}
impl<'a> From<&'a IriRef<Cow<'a, str>>> for IriRef<&'a str> {
#[inline]
fn from(iri: &'a IriRef<Cow<'a, str>>) -> Self {
Self {
iri: &iri.iri,
positions: iri.positions,
}
}
}
#[cfg(feature = "serde")]
impl<T: Serialize> Serialize for IriRef<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.iri.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T: Deref<Target = str> + Deserialize<'de>> Deserialize<'de> for IriRef<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
Self::parse(T::deserialize(deserializer)?).map_err(Error::custom)
}
}
#[derive(Clone, Copy)]
pub struct Iri<T>(IriRef<T>);
impl<T: Deref<Target = str>> Iri<T> {
pub fn parse(iri: T) -> Result<Self, IriParseError> {
let iri = Self::parse_unchecked(iri);
validate_iri(&iri)?;
Ok(iri)
}
pub fn parse_unchecked(iri: T) -> Self {
let positions = find_iri_positions(iri.as_ref());
Self(IriRef { iri, positions })
}
pub fn resolve<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
) -> Result<Iri<String>, IriParseError> {
Ok(Iri(self.0.resolve(reference)?))
}
pub fn resolve_unchecked<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
) -> Iri<String> {
Iri(self.0.resolve_unchecked(reference))
}
pub fn resolve_into<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
output_buffer: &mut impl OutputBuffer,
) -> Result<(), IriParseError> {
self.0.resolve_into(reference, output_buffer)
}
pub fn resolve_into_unchecked<T2: Deref<Target = str>>(
&self,
reference: &IriRef<T2>,
output_buffer: &mut impl OutputBuffer,
) {
self.0.resolve_into_unchecked(reference, output_buffer)
}
pub fn relativize<T2: Deref<Target = str>>(
&self,
abs: &Iri<T2>,
) -> Result<IriRef<String>, IriRelativizeError> {
let base = self;
let abs_authority = abs.authority();
let base_authority = base.authority();
let abs_path = abs.path();
let base_path = base.path();
let abs_query = abs.query();
let base_query = base.query();
if has_dot_segment(abs_path) {
return Err(IriRelativizeError {});
}
if abs.scheme() != base.scheme()
|| abs_authority.is_none()
&& (base_authority.is_some()
|| abs_path.is_empty()
&& (!base_path.is_empty() || abs_query.is_none() && base_query.is_some()))
|| abs_path
.split_once(':')
.is_some_and(|(candidate_scheme, _)| !candidate_scheme.contains('/'))
{
return Ok(IriRef {
iri: abs.0.to_string(),
positions: abs.0.positions,
});
}
if abs_authority != base_authority
|| abs_path.is_empty() && (!base_path.is_empty() || base_query.is_some())
|| abs_path.starts_with("//")
{
return Ok(IriRef {
iri: abs.0[abs.0.positions.scheme_end..].to_string(),
positions: IriElementsPositions {
scheme_end: 0,
authority_end: abs.0.positions.authority_end - abs.0.positions.scheme_end,
path_end: abs.0.positions.path_end - abs.0.positions.scheme_end,
query_end: abs.0.positions.query_end - abs.0.positions.scheme_end,
},
});
}
if abs_path != base_path || abs_query.is_none() && base_query.is_some() {
let number_of_shared_characters = abs_path
.bytes()
.zip(base_path.bytes())
.take_while(|(l, r)| l == r)
.count();
let number_of_shared_characters = abs_path.as_bytes()[..number_of_shared_characters]
.iter()
.rposition(|c| *c == b'/')
.map_or(0, |n| n + 1);
return if abs_path[number_of_shared_characters..].starts_with('/')
|| base_path[number_of_shared_characters..].contains('/')
|| abs_path[number_of_shared_characters..].contains(':')
{
if !abs_path.starts_with('/') && !base_path.is_empty() {
Ok(IriRef {
iri: abs.0.to_string(),
positions: abs.0.positions,
})
} else {
Ok(IriRef {
iri: abs.0[abs.0.positions.authority_end..].to_string(),
positions: IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: abs.0.positions.path_end - abs.0.positions.authority_end,
query_end: abs.0.positions.query_end - abs.0.positions.authority_end,
},
})
}
} else if abs_path[number_of_shared_characters..].is_empty() {
let mut iri = String::with_capacity(
abs.0.len() - abs.0.positions.authority_end - number_of_shared_characters + 1,
);
iri.push('.');
iri.push_str(&abs.0[abs.0.positions.authority_end + number_of_shared_characters..]);
Ok(IriRef {
iri,
positions: IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: abs.0.positions.path_end
- abs.0.positions.authority_end
- number_of_shared_characters
+ 1,
query_end: abs.0.positions.query_end
- abs.0.positions.authority_end
- number_of_shared_characters
+ 1,
},
})
} else {
Ok(IriRef {
iri: abs.0[abs.0.positions.authority_end + number_of_shared_characters..]
.to_string(),
positions: IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: abs.0.positions.path_end
- abs.0.positions.authority_end
- number_of_shared_characters,
query_end: abs.0.positions.query_end
- abs.0.positions.authority_end
- number_of_shared_characters,
},
})
};
}
if abs_query != base_query {
return Ok(IriRef {
iri: abs.0[abs.0.positions.path_end..].to_string(),
positions: IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: 0,
query_end: abs.0.positions.query_end - abs.0.positions.path_end,
},
});
}
Ok(IriRef {
iri: abs.0[abs.0.positions.query_end..].to_string(),
positions: IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: 0,
query_end: 0,
},
})
}
#[inline]
pub fn as_ref(&self) -> Iri<&str> {
Iri(self.0.as_ref())
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn into_inner(self) -> T {
self.0.into_inner()
}
#[inline]
pub fn scheme(&self) -> &str {
self.0.scheme().expect("The IRI should be absolute")
}
#[inline]
pub fn authority(&self) -> Option<&str> {
self.0.authority()
}
#[inline]
pub fn path(&self) -> &str {
self.0.path()
}
#[inline]
pub fn query(&self) -> Option<&str> {
self.0.query()
}
#[inline]
pub fn fragment(&self) -> Option<&str> {
self.0.fragment()
}
}
impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<Iri<Rhs>> for Iri<Lft> {
#[inline]
fn eq(&self, other: &Iri<Rhs>) -> bool {
self.0.eq(&other.0)
}
}
impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<IriRef<Rhs>> for Iri<Lft> {
#[inline]
fn eq(&self, other: &IriRef<Rhs>) -> bool {
self.0.eq(other)
}
}
impl<Lft: PartialEq<Rhs>, Rhs> PartialEq<Iri<Rhs>> for IriRef<Lft> {
#[inline]
fn eq(&self, other: &Iri<Rhs>) -> bool {
self.eq(&other.0)
}
}
impl<T: PartialEq<str>> PartialEq<str> for Iri<T> {
#[inline]
fn eq(&self, other: &str) -> bool {
self.0.eq(other)
}
}
impl<'a, T: PartialEq<&'a str>> PartialEq<&'a str> for Iri<T> {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.0.eq(other)
}
}
impl<T: PartialEq<String>> PartialEq<String> for Iri<T> {
#[inline]
fn eq(&self, other: &String) -> bool {
self.0.eq(other)
}
}
impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Cow<'a, str>> for Iri<T> {
#[inline]
fn eq(&self, other: &Cow<'a, str>) -> bool {
self.0.eq(other)
}
}
impl<T: PartialEq<str>> PartialEq<Iri<T>> for str {
#[inline]
fn eq(&self, other: &Iri<T>) -> bool {
self.eq(&other.0)
}
}
impl<'a, T: PartialEq<&'a str>> PartialEq<Iri<T>> for &'a str {
#[inline]
fn eq(&self, other: &Iri<T>) -> bool {
self.eq(&other.0)
}
}
impl<T: PartialEq<String>> PartialEq<Iri<T>> for String {
#[inline]
fn eq(&self, other: &Iri<T>) -> bool {
self.eq(&other.0)
}
}
impl<'a, T: PartialEq<Cow<'a, str>>> PartialEq<Iri<T>> for Cow<'a, str> {
#[inline]
fn eq(&self, other: &Iri<T>) -> bool {
self.eq(&other.0)
}
}
impl<T: Eq> Eq for Iri<T> {}
impl<T: Hash> Hash for Iri<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<Iri<Rhs>> for Iri<Lft> {
#[inline]
fn partial_cmp(&self, other: &Iri<Rhs>) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<IriRef<Rhs>> for Iri<Lft> {
#[inline]
fn partial_cmp(&self, other: &IriRef<Rhs>) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl<Lft: PartialOrd<Rhs>, Rhs> PartialOrd<Iri<Rhs>> for IriRef<Lft> {
#[inline]
fn partial_cmp(&self, other: &Iri<Rhs>) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
impl<T: Ord> Ord for Iri<T> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<T: Deref<Target = str>> Deref for Iri<T> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.0.deref()
}
}
impl<T: AsRef<str>> AsRef<str> for Iri<T> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl<T: Borrow<str>> Borrow<str> for Iri<T> {
#[inline]
fn borrow(&self) -> &str {
self.0.borrow()
}
}
impl<T: fmt::Debug> fmt::Debug for Iri<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Iri<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for Iri<String> {
type Err = IriParseError;
#[inline]
fn from_str(iri: &str) -> Result<Self, IriParseError> {
Self::parse(iri.to_owned())
}
}
impl<'a> From<Iri<&'a str>> for Iri<String> {
#[inline]
fn from(iri: Iri<&'a str>) -> Self {
Self(iri.0.into())
}
}
impl<'a> From<Iri<Cow<'a, str>>> for Iri<String> {
#[inline]
fn from(iri: Iri<Cow<'a, str>>) -> Self {
Self(iri.0.into())
}
}
impl From<Iri<Box<str>>> for Iri<String> {
#[inline]
fn from(iri: Iri<Box<str>>) -> Self {
Self(iri.0.into())
}
}
impl<'a> From<Iri<&'a str>> for Iri<Cow<'a, str>> {
#[inline]
fn from(iri: Iri<&'a str>) -> Self {
Self(iri.0.into())
}
}
impl From<Iri<String>> for Iri<Cow<'_, str>> {
#[inline]
fn from(iri: Iri<String>) -> Self {
Self(iri.0.into())
}
}
impl<'a> From<&'a Iri<String>> for Iri<&'a str> {
#[inline]
fn from(iri: &'a Iri<String>) -> Self {
Self(iri.0.as_ref())
}
}
impl<'a> From<&'a Iri<Cow<'a, str>>> for Iri<&'a str> {
#[inline]
fn from(iri: &'a Iri<Cow<'a, str>>) -> Self {
Self(iri.0.as_ref())
}
}
impl<T: Deref<Target = str>> From<Iri<T>> for IriRef<T> {
fn from(iri: Iri<T>) -> Self {
iri.0
}
}
impl<T: Deref<Target = str>> TryFrom<IriRef<T>> for Iri<T> {
type Error = IriParseError;
fn try_from(iri: IriRef<T>) -> Result<Self, IriParseError> {
if iri.is_absolute() {
Ok(Self(iri))
} else {
Err(IriParseErrorKind::NoScheme.into())
}
}
}
#[cfg(feature = "serde")]
impl<T: Serialize> Serialize for Iri<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, T: Deref<Target = str> + Deserialize<'de>> Deserialize<'de> for Iri<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
IriRef::deserialize(deserializer)?
.try_into()
.map_err(Error::custom)
}
}
#[derive(Debug)]
pub struct IriParseError {
kind: IriParseErrorKind,
}
impl fmt::Display for IriParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
IriParseErrorKind::NoScheme => write!(f, "No scheme found in an absolute IRI"),
IriParseErrorKind::EmptyScheme => write!(f, "Empty schemes are not allowed"),
IriParseErrorKind::InvalidSchemeCharacter(c) => {
write!(f, "Invalid character '{c}' in scheme")
}
IriParseErrorKind::InvalidHostCharacter(c) => {
write!(f, "Invalid character '{c}' in host")
}
IriParseErrorKind::UnmatchedHostBracket => {
write!(f, "'[' bracket must be paired with a closing one in host")
}
IriParseErrorKind::InvalidHostIp(e) => write!(f, "Invalid host IP ({e})"),
IriParseErrorKind::InvalidPortCharacter(c) => write!(f, "Invalid character '{c}'"),
IriParseErrorKind::InvalidIriCodePoint(c) => {
write!(f, "Invalid IRI code point '{c}'")
}
IriParseErrorKind::InvalidPercentEncoding(cs) => write!(
f,
"Invalid IRI percent encoding '{}'",
cs.iter().flatten().cloned().collect::<String>()
),
IriParseErrorKind::PathStartingWithTwoSlashes => {
write!(f, "An IRI path is not allowed to start with //")
}
IriParseErrorKind::ResolvingDotSegmentsNonHierarchical => {
write!(
f,
"Trying to resolve dot segments against a relative non hierarchical IRI path"
)
}
}
}
}
impl Error for IriParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let IriParseErrorKind::InvalidHostIp(e) = &self.kind {
Some(e)
} else {
None
}
}
}
impl From<IriParseErrorKind> for IriParseError {
fn from(kind: IriParseErrorKind) -> Self {
Self { kind }
}
}
#[derive(Debug)]
enum IriParseErrorKind {
NoScheme,
EmptyScheme,
InvalidSchemeCharacter(char),
UnmatchedHostBracket,
InvalidHostCharacter(char),
InvalidHostIp(AddrParseError),
InvalidPortCharacter(char),
InvalidIriCodePoint(char),
InvalidPercentEncoding([Option<char>; 3]),
PathStartingWithTwoSlashes,
ResolvingDotSegmentsNonHierarchical,
}
#[derive(Debug)]
pub struct IriRelativizeError {}
impl fmt::Display for IriRelativizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"It is not possible to make this IRI relative because it contains `/..` or `/.`"
)
}
}
impl Error for IriRelativizeError {}
#[derive(Debug, Clone, Copy)]
struct IriElementsPositions {
scheme_end: usize,
authority_end: usize,
path_end: usize,
query_end: usize,
}
#[inline]
fn find_iri_positions(iri: &str) -> IriElementsPositions {
let iri = iri.as_bytes();
let scheme_end = iri
.iter()
.position(|c| *c == b':')
.map_or(0, |position| position + 1);
find_iri_positions_knowing_scheme_end(iri, scheme_end)
}
#[inline]
fn find_iri_positions_knowing_scheme_end(iri: &[u8], scheme_end: usize) -> IriElementsPositions {
let path_end = memchr2(b'?', b'#', &iri[scheme_end..]).map_or(iri.len(), |l| scheme_end + l);
let query_end = memchr(b'#', &iri[path_end..]).map_or(iri.len(), |l| path_end + l);
let authority_end =
if scheme_end + 2 <= path_end && iri[scheme_end] == b'/' && iri[scheme_end + 1] == b'/' {
memchr(b'/', &iri[scheme_end + 2..path_end]).map_or(path_end, |l| scheme_end + 2 + l)
} else {
scheme_end
};
IriElementsPositions {
scheme_end,
authority_end,
path_end,
query_end,
}
}
#[inline]
fn find_iri_ref_positions(iri: &str) -> IriElementsPositions {
let iri = iri.as_bytes();
match iri.first().copied() {
Some(b'/') => {
find_iri_positions_knowing_scheme_end(iri, 0)
}
Some(b'?') => {
let query_end = memchr(b'#', &iri[1..]).map_or(iri.len(), |p| p + 1);
IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: 0,
query_end,
}
}
Some(b'#') | None => {
IriElementsPositions {
scheme_end: 0,
authority_end: 0,
path_end: 0,
query_end: 0,
}
}
_ => {
let scheme_end = find_scheme_end_for_iri_ref(iri);
find_iri_positions_knowing_scheme_end(iri, scheme_end)
}
}
}
#[inline]
fn find_scheme_end_for_iri_ref(iri: &[u8]) -> usize {
for (index, c) in iri.iter().copied().enumerate() {
match c {
b':' => return index + 1,
b'?' | b'/' | b'#' => return 0,
_ => (),
}
}
0
}
#[inline]
fn has_dot_segment(path: &str) -> bool {
let bytes = path.as_bytes();
for dot_offset in memchr_iter(b'.', bytes) {
if dot_offset == 0
|| bytes[dot_offset - 1] == b'/'
&& bytes.get(dot_offset + 1).is_none_or(|b| {
*b == b'/' || *b == b'.' && bytes.get(dot_offset + 2).is_none_or(|b| *b == b'/')
})
{
return true;
}
}
false
}
#[inline]
fn validate_iri<T: Deref<Target = str>>(iri: &Iri<T>) -> Result<(), IriParseError> {
if !iri.0.is_absolute() {
return Err(IriParseErrorKind::NoScheme.into());
}
validate_iri_ref(&iri.0)?;
Ok(())
}
#[inline]
fn validate_iri_ref<T: Deref<Target = str>>(iri: &IriRef<T>) -> Result<(), IriParseError> {
if iri.positions.scheme_end > 0 {
validate_scheme(&iri.iri[..iri.positions.scheme_end - 1])?;
}
if iri.positions.authority_end > iri.positions.scheme_end {
validate_authority(&iri.iri[iri.positions.scheme_end + 2..iri.positions.authority_end])?;
}
validate_path(&iri.iri[iri.positions.authority_end..iri.positions.path_end])?;
if iri.positions.query_end > iri.positions.path_end {
validate_query(&iri.iri[iri.positions.path_end + 1..iri.positions.query_end])?;
}
if iri.iri.len() > iri.positions.query_end {
validate_fragment(&iri.iri[iri.positions.query_end + 1..])?;
}
Ok(())
}
#[inline]
fn validate_scheme(scheme: &str) -> Result<(), IriParseError> {
const SCHEME_CHARACTER: [bool; 256] = {
let mut allowed = [false; 256];
set_alpha(&mut allowed);
set_digit(&mut allowed);
allowed[b'+' as usize] = true;
allowed[b'-' as usize] = true;
allowed[b'.' as usize] = true;
allowed
};
let mut chars = scheme.bytes();
let first = chars.next().ok_or(IriParseErrorKind::EmptyScheme)?;
if !first.is_ascii_alphabetic() {
return Err(IriParseErrorKind::InvalidSchemeCharacter(
scheme.chars().next().unwrap_or_default(),
)
.into());
}
for c in &mut chars {
if !SCHEME_CHARACTER[usize::from(c)] {
return Err(IriParseErrorKind::InvalidSchemeCharacter(
scheme[scheme.len() - chars.len() - 1..]
.chars()
.next()
.unwrap_or_default(),
)
.into());
}
}
Ok(())
}
#[inline]
fn validate_authority(authority: &str) -> Result<(), IriParseError> {
const UNRESERVED_SUB_DELIMS_TWO_DOTS: [bool; 256] = {
let mut allowed = [false; 256];
set_unreserved(&mut allowed);
set_sub_delims(&mut allowed);
allowed[b':' as usize] = true;
allowed
};
const UNRESERVED_SUB_DELIMS: [bool; 256] = {
let mut allowed = [false; 256];
set_unreserved(&mut allowed);
set_sub_delims(&mut allowed);
allowed
};
let mut remaining_authority = authority;
if remaining_authority
.bytes()
.all(|c| UNRESERVED_SUB_DELIMS[usize::from(c)])
{
return Ok(()); };
if let Some(username_index) = memchr(b'@', remaining_authority.as_bytes()) {
let username = &remaining_authority[..username_index];
remaining_authority = &remaining_authority[username_index + 1..];
validate_code_point_or_echar(username, UNRESERVED_SUB_DELIMS_TWO_DOTS, |c| {
c.is_ascii() && UNRESERVED_SUB_DELIMS_TWO_DOTS[c as usize] || is_ucschar(c)
})?;
}
if let Some(remaining_authority) = remaining_authority.strip_prefix('[') {
let Some(end_of_ip_index) = memchr(b']', remaining_authority.as_bytes()) else {
return Err(IriParseErrorKind::UnmatchedHostBracket.into());
};
validate_ip(&remaining_authority[..end_of_ip_index])?;
let remaining_authority = &remaining_authority[end_of_ip_index + 1..];
if !remaining_authority.is_empty() {
let Some(port) = remaining_authority.strip_prefix(':') else {
return Err(IriParseErrorKind::InvalidHostCharacter(
remaining_authority.chars().next().unwrap(),
)
.into());
};
validate_port(port)?;
}
} else {
if let Some(port_index) = memchr(b':', remaining_authority.as_bytes()) {
validate_port(&remaining_authority[port_index + 1..])?;
remaining_authority = &remaining_authority[..port_index];
}
let host = remaining_authority;
validate_code_point_or_echar(host, UNRESERVED_SUB_DELIMS, |c| {
c.is_ascii() && UNRESERVED_SUB_DELIMS[c as usize] || is_ucschar(c)
})?;
}
Ok(())
}
#[inline]
fn validate_ip(ip: &str) -> Result<(), IriParseError> {
if ip.starts_with('v') || ip.starts_with('V') {
validate_ip_v_future(ip)
} else if let Err(error) = Ipv6Addr::from_str(ip) {
Err(IriParseErrorKind::InvalidHostIp(error).into())
} else {
Ok(())
}
}
#[inline]
fn validate_ip_v_future(ip: &str) -> Result<(), IriParseError> {
const UNRESERVED_SUB_DELIMS_TWO_DOTS: [bool; 256] = {
let mut allowed = [false; 256];
set_unreserved(&mut allowed);
set_sub_delims(&mut allowed);
allowed[b':' as usize] = true;
allowed
};
let Some(mut ip) = ip.strip_prefix(['v', 'V']) else {
return Err(
IriParseErrorKind::InvalidHostCharacter(ip.chars().next().unwrap_or_default()).into(),
);
};
let version_size = ip.bytes().take_while(|c| c.is_ascii_hexdigit()).count();
if version_size == 0 {
return Err(
IriParseErrorKind::InvalidHostCharacter(ip.chars().next().unwrap_or_default()).into(),
);
}
ip = &ip[version_size..];
let Some(ip) = ip.strip_prefix('.') else {
return Err(
IriParseErrorKind::InvalidHostCharacter(ip.chars().next().unwrap_or_default()).into(),
);
};
if ip.is_empty() {
return Err(IriParseErrorKind::InvalidHostCharacter(']').into());
};
let mut chars = ip.bytes();
for c in &mut chars {
if !UNRESERVED_SUB_DELIMS_TWO_DOTS[usize::from(c)] {
return Err(IriParseErrorKind::InvalidHostCharacter(
ip[ip.len() - chars.len() - 1..]
.chars()
.next()
.unwrap_or_default(),
)
.into());
}
}
Ok(())
}
#[inline]
fn validate_port(port: &str) -> Result<(), IriParseError> {
let mut chars = port.bytes();
for c in &mut chars {
if !c.is_ascii_digit() {
return Err(IriParseErrorKind::InvalidPortCharacter(
port[port.len() - chars.len() - 1..]
.chars()
.next()
.unwrap_or_default(),
)
.into());
}
}
Ok(())
}
#[inline]
fn validate_path(path: &str) -> Result<(), IriParseError> {
const PCHAR_OR_SLASH: [bool; 256] = {
let mut allowed = [false; 256];
set_pchar(&mut allowed);
allowed[b'/' as usize] = true;
allowed
};
validate_code_point_or_echar(path, PCHAR_OR_SLASH, |c| {
c.is_ascii() && PCHAR_OR_SLASH[c as usize] || is_ucschar(c)
})
}
#[inline]
fn validate_query(query: &str) -> Result<(), IriParseError> {
const PCHAR_OR_SLASH_OR_QUESTION_MARK: [bool; 256] = {
let mut allowed = [false; 256];
set_pchar(&mut allowed);
allowed[b'/' as usize] = true;
allowed[b'?' as usize] = true;
allowed
};
validate_code_point_or_echar(query, PCHAR_OR_SLASH_OR_QUESTION_MARK, |c| {
c.is_ascii() && PCHAR_OR_SLASH_OR_QUESTION_MARK[c as usize]
|| is_ucschar(c)
|| matches!(c, '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}')
})
}
#[inline]
fn validate_fragment(fragment: &str) -> Result<(), IriParseError> {
const PCHAR_OR_SLASH_OR_QUESTION_MARK: [bool; 256] = {
let mut allowed = [false; 256];
set_pchar(&mut allowed);
allowed[b'/' as usize] = true;
allowed[b'?' as usize] = true;
allowed
};
validate_code_point_or_echar(fragment, PCHAR_OR_SLASH_OR_QUESTION_MARK, |c| {
c.is_ascii() && PCHAR_OR_SLASH_OR_QUESTION_MARK[c as usize] || is_ucschar(c)
})
}
#[inline]
fn validate_code_point_or_echar(
input: &str,
ascii_validator: [bool; 256],
is_valid: impl Fn(char) -> bool,
) -> Result<(), IriParseError> {
let bytes = input.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
c if ascii_validator[usize::from(c)] => i += 1,
b'%' => {
if !(bytes.get(i + 1).is_some_and(|c| c.is_ascii_hexdigit())
&& bytes.get(i + 2).is_some_and(|c| c.is_ascii_hexdigit()))
{
let mut chars = input[i + 1..].chars();
let c1 = chars.next();
let c2 = chars.next();
return Err(
IriParseErrorKind::InvalidPercentEncoding([Some('%'), c1, c2]).into(),
);
}
i += 3;
}
0x80.. => {
let c = input[i..].chars().next().unwrap();
if !is_valid(c) {
return Err(IriParseErrorKind::InvalidIriCodePoint(c).into());
}
i += c.len_utf8();
}
c => {
return Err(IriParseErrorKind::InvalidIriCodePoint(c.into()).into());
}
}
}
Ok(())
}
const fn set_alpha(allowed: &mut [bool; 256]) {
let mut i = b'a';
while i <= b'z' {
allowed[i as usize] = true;
i += 1;
}
let mut i = b'A';
while i <= b'Z' {
allowed[i as usize] = true;
i += 1;
}
}
const fn set_digit(allowed: &mut [bool; 256]) {
let mut i = b'0';
while i <= b'9' {
allowed[i as usize] = true;
i += 1;
}
}
const fn set_unreserved(allowed: &mut [bool; 256]) {
set_alpha(allowed);
set_digit(allowed);
allowed[b'-' as usize] = true;
allowed[b'.' as usize] = true;
allowed[b'_' as usize] = true;
allowed[b'~' as usize] = true;
}
const fn set_sub_delims(allowed: &mut [bool; 256]) {
allowed[b'!' as usize] = true;
allowed[b'$' as usize] = true;
allowed[b'&' as usize] = true;
allowed[b'\'' as usize] = true;
allowed[b'(' as usize] = true;
allowed[b')' as usize] = true;
allowed[b'*' as usize] = true;
allowed[b'+' as usize] = true;
allowed[b',' as usize] = true;
allowed[b';' as usize] = true;
allowed[b'=' as usize] = true;
}
const fn set_pchar(allowed: &mut [bool; 256]) {
set_unreserved(allowed);
set_sub_delims(allowed);
allowed[b':' as usize] = true;
allowed[b'@' as usize] = true;
}
#[inline]
fn is_ucschar(c: char) -> bool {
matches!(
c,
'\u{A0}'..='\u{D7FF}'
| '\u{F900}'..='\u{FDCF}'
| '\u{FDF0}'..='\u{FFEF}'
| '\u{10000}'..='\u{1FFFD}'
| '\u{20000}'..='\u{2FFFD}'
| '\u{30000}'..='\u{3FFFD}'
| '\u{40000}'..='\u{4FFFD}'
| '\u{50000}'..='\u{5FFFD}'
| '\u{60000}'..='\u{6FFFD}'
| '\u{70000}'..='\u{7FFFD}'
| '\u{80000}'..='\u{8FFFD}'
| '\u{90000}'..='\u{9FFFD}'
| '\u{A0000}'..='\u{AFFFD}'
| '\u{B0000}'..='\u{BFFFD}'
| '\u{C0000}'..='\u{CFFFD}'
| '\u{D0000}'..='\u{DFFFD}'
| '\u{E1000}'..='\u{EFFFD}')
}
pub trait OutputBuffer {
fn as_str(&self) -> &str;
fn push_str(&mut self, s: &str);
fn reserve_exact(&mut self, additional: usize);
fn truncate(&mut self, len: usize);
}
impl OutputBuffer for String {
#[inline]
fn as_str(&self) -> &str {
self.as_str()
}
#[inline]
fn push_str(&mut self, s: &str) {
self.push_str(s);
}
#[inline]
fn reserve_exact(&mut self, additional: usize) {
self.reserve_exact(additional);
}
#[inline]
fn truncate(&mut self, new_len: usize) {
self.truncate(new_len);
}
}
#[inline(always)]
fn resolve<T1: Deref<Target = str>, T2: Deref<Target = str>>(
base: &IriRef<T1>,
relative: &IriRef<T2>,
output_buffer: &mut impl OutputBuffer,
) -> (IriElementsPositions, Option<IriParseErrorKind>) {
let mut error = None;
let positions = if relative.is_absolute() {
output_buffer.reserve_exact(relative.iri.len());
output_buffer.push_str(&relative.iri[..relative.positions.authority_end]);
write_path_without_dot_segments_to(
relative.path(),
output_buffer,
relative.positions.authority_end,
false,
);
let path_end = output_buffer.as_str().len();
output_buffer.push_str(&relative.iri[relative.positions.path_end..]);
IriElementsPositions {
scheme_end: relative.positions.scheme_end,
authority_end: relative.positions.authority_end,
path_end,
query_end: path_end + (relative.positions.query_end - relative.positions.path_end),
}
} else if relative.positions.authority_end > 0 {
output_buffer.reserve_exact(base.positions.scheme_end + relative.iri.len());
output_buffer.push_str(&base.iri[..base.positions.scheme_end]);
output_buffer.push_str(&relative.iri[..relative.positions.authority_end]);
write_path_without_dot_segments_to(
relative.path(),
output_buffer,
base.positions.scheme_end + relative.positions.authority_end,
false,
);
let path_end = output_buffer.as_str().len();
output_buffer.push_str(&relative.iri[relative.positions.path_end..]);
IriElementsPositions {
scheme_end: base.positions.scheme_end,
authority_end: base.positions.scheme_end + relative.positions.authority_end,
path_end,
query_end: path_end + (relative.positions.query_end - relative.positions.path_end),
}
} else if relative.positions.path_end > 0 {
if relative.iri.starts_with('/') {
output_buffer.reserve_exact(base.positions.authority_end + relative.iri.len());
output_buffer.push_str(&base.iri[..base.positions.authority_end]);
write_path_without_dot_segments_to(
&relative.iri[..relative.positions.path_end],
output_buffer,
base.positions.authority_end,
false,
);
} else if base.positions.authority_end > base.positions.scheme_end
&& base.positions.authority_end == base.positions.path_end
{
output_buffer.reserve_exact(
base.positions.authority_end
+ 1
+ (relative.iri.len() - relative.positions.authority_end),
);
output_buffer.push_str(&base.iri[..base.positions.authority_end]);
write_path_without_dot_segments_to(
relative.path(),
output_buffer,
base.positions.authority_end,
true,
);
} else if let Some(last_slash_position) = memrchr(b'/', base.path().as_bytes()) {
if base.positions.scheme_end == 0 && &base.path()[last_slash_position + 1..] == ".." {
error = Some(IriParseErrorKind::ResolvingDotSegmentsNonHierarchical);
}
output_buffer.reserve_exact(
base.positions.authority_end
+ last_slash_position
+ (relative.iri.len() - relative.positions.authority_end)
+ 1,
);
output_buffer.push_str(&base.iri[..base.positions.authority_end]);
write_path_without_dot_segments_to(
&base.path()[..last_slash_position + 1],
output_buffer,
base.positions.authority_end,
false,
);
let with_prefix_slash = if output_buffer.as_str().ends_with('/') {
output_buffer.truncate(output_buffer.as_str().len() - 1);
true
} else {
false
};
write_path_without_dot_segments_to(
relative.path(),
output_buffer,
base.positions.authority_end,
with_prefix_slash,
);
} else {
if base.positions.authority_end == 0
&& (relative.path().split('/').any(|c| c == "..") || base.path() == "..")
{
error = Some(IriParseErrorKind::ResolvingDotSegmentsNonHierarchical);
}
output_buffer.reserve_exact(
base.positions.authority_end
+ (relative.iri.len() - relative.positions.authority_end),
);
output_buffer.push_str(&base.iri[..base.positions.authority_end]);
write_path_without_dot_segments_to(
relative.path(),
output_buffer,
base.positions.authority_end,
false,
);
}
let path_end = output_buffer.as_str().len();
output_buffer.push_str(&relative.iri[relative.positions.path_end..]);
IriElementsPositions {
scheme_end: base.positions.scheme_end,
authority_end: base.positions.authority_end,
path_end,
query_end: path_end + (relative.positions.query_end - relative.positions.path_end),
}
} else if relative.positions.query_end > 0 {
output_buffer.reserve_exact(base.positions.path_end + relative.iri.len());
output_buffer.push_str(&base.iri[..base.positions.path_end]);
output_buffer.push_str(&relative.iri);
IriElementsPositions {
scheme_end: base.positions.scheme_end,
authority_end: base.positions.authority_end,
path_end: base.positions.path_end,
query_end: base.positions.path_end + relative.positions.query_end,
}
} else {
output_buffer.reserve_exact(base.positions.query_end + relative.iri.len());
output_buffer.push_str(&base.iri[..base.positions.query_end]);
output_buffer.push_str(&relative.iri);
base.positions
};
if positions.scheme_end == positions.authority_end
&& output_buffer.as_str()[positions.authority_end..].starts_with("//")
{
error = Some(IriParseErrorKind::PathStartingWithTwoSlashes);
}
(positions, error)
}
#[inline]
fn write_path_without_dot_segments_to(
mut input: &str,
output: &mut impl OutputBuffer,
output_path_start: usize,
with_prefix_slash: bool,
) {
if output_path_start == 0
&& !output.as_str().bytes().next().map_or_else(
|| with_prefix_slash || input.starts_with('/'),
|c| c == b'/',
)
|| !has_dot_segment(input)
{
if with_prefix_slash {
output.push_str("/");
}
output.push_str(input);
return;
}
if with_prefix_slash {
if input.starts_with("./") {
input = &input[1..];
} else if input == "." {
input = "/";
} else if input.starts_with("../") {
input = &input[2..];
remove_last_segment(output, output_path_start);
} else if input == ".." {
input = "/";
remove_last_segment(output, output_path_start);
} else {
output.push_str("/");
let slash_index = memchr(b'/', input.as_bytes()).unwrap_or(input.len());
output.push_str(&input[..slash_index]);
input = &input[slash_index..];
}
}
while !input.is_empty() {
if let Some(rest) = input.strip_prefix("../") {
input = rest;
} else if let Some(rest) = input.strip_prefix("./") {
input = rest;
} else if input.starts_with("/./") {
input = &input[2..];
} else if input == "/." {
input = "/";
} else if input.starts_with("/../") {
input = &input[3..];
remove_last_segment(output, output_path_start);
} else if input == "/.." {
input = "/";
remove_last_segment(output, output_path_start);
} else if input == "." || input == ".." {
input = "";
} else {
input = if let Some(rest) = input.strip_prefix('/') {
output.push_str("/");
rest
} else {
input
};
let slash_index = memchr(b'/', input.as_bytes()).unwrap_or(input.len());
output.push_str(&input[..slash_index]);
input = &input[slash_index..];
}
}
}
#[inline]
fn remove_last_segment(output: &mut impl OutputBuffer, output_path_start: usize) {
let last_slash_position =
memrchr(b'/', &output.as_str().as_bytes()[output_path_start..]).unwrap_or(0);
output.truncate(output_path_start + last_slash_position);
}