use core::{
cmp::Ordering,
hash::{self, Hash},
ops::Deref,
};
use static_automata::grammar;
pub use crate::{InvalidScheme, Scheme};
#[cfg(feature = "std")]
pub use crate::SchemeBuf;
mod authority;
mod error;
mod fragment;
mod path;
mod query;
mod reference;
pub use authority::*;
pub use error::*;
pub use fragment::*;
pub use path::*;
pub use query::*;
pub use reference::*;
#[grammar(
file = "grammar.abnf",
export("URI", "URI-reference" as UriRef, "authority", "host", "userinfo" as UserInfo, "path", "segment", "query", "fragment")
)]
pub(crate) mod grammar {}
#[derive(static_automata::Validate, str_newtype::StrNewType)]
#[automaton(grammar::Uri)]
#[newtype(name = "URI", no_deref, ord([u8], &[u8], str, &str))]
#[cfg_attr(
feature = "std",
newtype(ord(Vec<u8>, String), owned(UriBuf, derive(PartialEq, Eq, PartialOrd, Ord, Hash)))
)]
#[cfg_attr(feature = "serde", newtype(serde))]
pub struct Uri(str);
impl Uri {
pub fn parts(&self) -> UriParts<'_> {
let bytes = self.as_bytes();
let ranges = crate::common::parse::parts(bytes, 0);
UriParts {
scheme: unsafe { Scheme::new_unchecked_from_bytes(&bytes[ranges.scheme]) },
authority: ranges
.authority
.map(|r| unsafe { Authority::new_unchecked(&self.0[r]) }),
path: unsafe { Path::new_unchecked(&self.0[ranges.path]) },
query: ranges
.query
.map(|r| unsafe { Query::new_unchecked(&self.0[r]) }),
fragment: ranges
.fragment
.map(|r| unsafe { Fragment::new_unchecked(&self.0[r]) }),
}
}
#[inline]
pub fn scheme(&self) -> &Scheme {
let bytes = self.as_bytes();
let range = crate::common::parse::scheme(bytes, 0);
unsafe { Scheme::new_unchecked_from_bytes(&bytes[range]) }
}
#[inline]
pub fn base(&self) -> &Self {
let bytes = self.as_bytes();
let path_range = crate::common::parse::find_path(bytes, 0);
let path_start = path_range.start;
let path = unsafe { Path::new_unchecked_from_bytes(&bytes[path_range]) };
let directory_path = path.directory();
let end = path_start + directory_path.len();
unsafe { Self::new_unchecked_from_bytes(&bytes[..end]) }
}
#[cfg(feature = "std")]
pub fn joined(&self, input: impl AsRef<UriRef>) -> UriBuf {
let mut result = self.to_owned();
result.join(input);
result
}
#[cfg(feature = "std")]
pub fn try_joined<'r>(&self, input: &'r str) -> Result<UriBuf, InvalidUriRef<&'r str>> {
UriRef::new(input).map(|r| self.joined(r))
}
pub fn as_uri_ref(&self) -> &UriRef {
unsafe { UriRef::new_unchecked(&self.0) }
}
#[cfg(feature = "std")]
pub fn with_scheme(&self, scheme: &Scheme) -> UriBuf {
let mut buf = self.to_owned();
buf.set_scheme(scheme);
buf
}
#[cfg(feature = "std")]
pub fn with_authority(&self, authority: Option<&Authority>) -> UriBuf {
let mut buf = self.to_owned();
buf.set_authority(authority);
buf
}
#[cfg(feature = "std")]
pub fn with_path(&self, path: &Path) -> UriBuf {
let mut buf = self.to_owned();
buf.set_path(path);
buf
}
#[cfg(feature = "std")]
pub fn with_query(&self, query: Option<&Query>) -> UriBuf {
let mut buf = self.to_owned();
buf.set_query(query);
buf
}
#[cfg(feature = "std")]
pub fn with_fragment(&self, fragment: Option<&Fragment>) -> UriBuf {
let mut buf = self.to_owned();
buf.set_fragment(fragment);
buf
}
}
impl Deref for Uri {
type Target = UriRef;
fn deref(&self) -> &Self::Target {
self.as_uri_ref()
}
}
impl PartialEq for Uri {
fn eq(&self, other: &Self) -> bool {
self.parts() == other.parts()
}
}
impl<'a> PartialEq<&'a Uri> for Uri {
fn eq(&self, other: &&'a Self) -> bool {
*self == **other
}
}
impl Eq for Uri {}
impl PartialOrd for Uri {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> PartialOrd<&'a Uri> for Uri {
fn partial_cmp(&self, other: &&'a Self) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl Ord for Uri {
fn cmp(&self, other: &Self) -> Ordering {
self.parts().cmp(&other.parts())
}
}
impl Hash for Uri {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.parts().hash(state)
}
}
impl PartialEq<UriRef> for Uri {
fn eq(&self, other: &UriRef) -> bool {
*self.as_uri_ref() == *other
}
}
impl PartialOrd<UriRef> for Uri {
fn partial_cmp(&self, other: &UriRef) -> Option<Ordering> {
self.as_uri_ref().partial_cmp(other)
}
}
#[cfg(feature = "std")]
impl PartialEq<UriRefBuf> for Uri {
fn eq(&self, other: &UriRefBuf) -> bool {
*self.as_uri_ref() == *other.as_uri_ref()
}
}
#[cfg(feature = "std")]
impl PartialOrd<UriRefBuf> for Uri {
fn partial_cmp(&self, other: &UriRefBuf) -> Option<Ordering> {
self.as_uri_ref().partial_cmp(other.as_uri_ref())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UriParts<'a> {
pub scheme: &'a Scheme,
pub authority: Option<&'a Authority>,
pub path: &'a Path,
pub query: Option<&'a Query>,
pub fragment: Option<&'a Fragment>,
}
#[cfg(feature = "std")]
impl UriBuf {
#[inline]
unsafe fn replace(&mut self, range: core::ops::Range<usize>, content: &[u8]) {
crate::utils::replace(unsafe { self.as_mut_vec() }, range, content)
}
#[inline]
unsafe fn allocate(&mut self, range: core::ops::Range<usize>, len: usize) {
crate::utils::allocate_range(unsafe { self.as_mut_vec() }, range, len)
}
#[inline]
pub fn authority_mut(&mut self) -> Option<AuthorityMut<'_>> {
crate::common::parse::find_authority(self.as_bytes(), 0)
.ok()
.map(|range| unsafe { AuthorityMut::new_unchecked(self.as_mut_vec(), range) })
}
#[inline]
pub fn set_authority(&mut self, authority: Option<&Authority>) {
let bytes = self.as_bytes();
match authority {
Some(new_authority) => match crate::common::parse::find_authority(bytes, 0) {
Ok(range) => unsafe { self.replace(range, new_authority.as_bytes()) },
Err(start) => {
if !bytes[start..].starts_with(b"/") {
unsafe {
self.allocate(start..start, new_authority.len() + 3);
let bytes = self.as_mut_vec();
let delim_end = start + 2;
bytes[start..delim_end].copy_from_slice(b"//");
bytes[delim_end..(delim_end + new_authority.len())]
.copy_from_slice(new_authority.as_bytes());
bytes[delim_end + new_authority.len()] = b'/';
}
} else {
unsafe {
self.allocate(start..start, new_authority.len() + 2);
let bytes = self.as_mut_vec();
let delim_end = start + 2;
bytes[start..delim_end].copy_from_slice(b"//");
bytes[delim_end..(delim_end + new_authority.len())]
.copy_from_slice(new_authority.as_bytes())
}
}
}
},
None => {
if let Ok(range) = crate::common::parse::find_authority(bytes, 0) {
let value: &[u8] = if bytes[range.end..].starts_with(b"//") {
b"/."
} else {
b""
};
unsafe {
self.replace((range.start - 2)..range.end, value);
}
}
}
}
}
pub fn try_set_authority<'s>(
&mut self,
authority: Option<&'s str>,
) -> Result<(), InvalidAuthority<&'s str>> {
self.set_authority(authority.map(TryInto::try_into).transpose()?);
Ok(())
}
#[inline]
pub fn path_mut(&mut self) -> PathMut<'_> {
let range = crate::common::parse::find_path(self.as_bytes(), 0);
unsafe { PathMut::new_unchecked(self.as_mut_vec(), range) }
}
#[inline]
pub fn set_path(&mut self, path: &Path) {
self.path_mut().replace(path);
}
pub fn try_set_path<'s>(&mut self, path: &'s str) -> Result<(), InvalidPath<&'s str>> {
self.set_path(path.try_into()?);
Ok(())
}
pub fn set_and_normalize_path(&mut self, path: &Path) {
self.set_path(path);
self.path_mut().normalize();
}
#[inline]
pub fn set_query(&mut self, query: Option<&Query>) {
match query {
Some(new_query) => match crate::common::parse::find_query(self.as_bytes(), 0) {
Ok(range) => unsafe { self.replace(range, new_query.as_bytes()) },
Err(start) => unsafe {
self.allocate(start..start, new_query.len() + 1);
let bytes = self.as_mut_vec();
let delim_end = start + 1;
bytes[start] = b'?';
bytes[delim_end..(delim_end + new_query.len())]
.copy_from_slice(new_query.as_bytes())
},
},
None => {
if let Ok(range) = crate::common::parse::find_query(self.as_bytes(), 0) {
unsafe {
self.replace((range.start - 1)..range.end, b"");
}
}
}
}
}
pub fn try_set_query<'s>(
&mut self,
query: Option<&'s str>,
) -> Result<(), InvalidQuery<&'s str>> {
self.set_query(query.map(TryInto::try_into).transpose()?);
Ok(())
}
#[inline]
pub fn set_fragment(&mut self, fragment: Option<&Fragment>) {
match fragment {
Some(new_fragment) => match crate::common::parse::find_fragment(self.as_bytes(), 0) {
Ok(range) => unsafe { self.replace(range, new_fragment.as_bytes()) },
Err(start) => unsafe {
self.allocate(start..start, new_fragment.len() + 1);
let bytes = self.as_mut_vec();
let delim_end = start + 1;
bytes[start] = b'#';
bytes[delim_end..(delim_end + new_fragment.len())]
.copy_from_slice(new_fragment.as_bytes())
},
},
None => {
if let Ok(range) = crate::common::parse::find_fragment(self.as_bytes(), 0) {
unsafe {
self.replace((range.start - 1)..range.end, b"");
}
}
}
}
}
pub fn try_set_fragment<'s>(
&mut self,
fragment: Option<&'s str>,
) -> Result<(), InvalidFragment<&'s str>> {
self.set_fragment(fragment.map(TryInto::try_into).transpose()?);
Ok(())
}
#[inline]
pub fn from_scheme(scheme: SchemeBuf) -> Self {
let mut bytes = scheme.into_bytes();
bytes.push(b':');
unsafe { Self::new_unchecked(bytes) }
}
#[inline]
pub fn set_scheme(&mut self, new_scheme: &Scheme) {
let range = crate::common::parse::scheme(self.as_bytes(), 0);
unsafe { self.replace(range, new_scheme.as_bytes()) }
}
pub fn join(&mut self, input: impl AsRef<UriRef>) {
let input = input.as_ref();
let parts = input.parts();
match parts.scheme {
Some(scheme) => {
self.set_scheme(scheme);
self.set_authority(parts.authority);
self.set_and_normalize_path(parts.path);
self.set_query(parts.query);
self.set_fragment(parts.fragment);
}
None => match parts.authority {
Some(authority) => {
self.set_authority(Some(authority));
self.set_and_normalize_path(parts.path);
self.set_query(parts.query);
self.set_fragment(parts.fragment);
}
None => {
if parts.path.is_relative() && parts.path.is_empty() {
if let Some(query) = parts.query {
self.set_query(Some(query))
}
} else if parts.path.is_absolute() {
self.set_query(parts.query);
self.set_and_normalize_path(parts.path);
} else {
self.set_query(parts.query);
let has_authority = self.authority().is_some();
let mut path = self.path_mut();
if path.is_empty() && has_authority {
path.replace(Path::EMPTY_ABSOLUTE);
} else {
path.pop();
}
path.lazy_append(parts.path);
path.normalize();
}
self.set_fragment(parts.fragment);
}
},
}
}
pub fn try_join<'r>(
&mut self,
input: &'r str,
) -> Result<(), <&'r UriRef as TryFrom<&'r str>>::Error> {
self.join(UriRef::new(input)?);
Ok(())
}
}
#[macro_export]
macro_rules! uri {
($value:literal) => {
match $crate::uri::Uri::from_str($value) {
Ok(value) => value,
Err(_) => panic!("invalid URI"),
}
};
}
#[cfg(feature = "std")]
impl UriBuf {
pub fn into_uri_ref(self) -> UriRefBuf {
unsafe { UriRefBuf::new_unchecked(self.0) }
}
pub unsafe fn as_mut_vec(&mut self) -> &mut Vec<u8> {
unsafe { self.0.as_mut_vec() }
}
}
#[cfg(feature = "std")]
impl AsRef<UriRef> for UriBuf {
fn as_ref(&self) -> &UriRef {
self.as_uri_ref()
}
}
#[cfg(feature = "std")]
impl std::borrow::Borrow<UriRef> for UriBuf {
fn borrow(&self) -> &UriRef {
self.as_uri_ref()
}
}
#[cfg(feature = "std")]
impl From<UriBuf> for UriRefBuf {
fn from(value: UriBuf) -> Self {
value.into_uri_ref()
}
}
#[cfg(feature = "std")]
impl PartialEq<UriRef> for UriBuf {
fn eq(&self, other: &UriRef) -> bool {
*self.as_uri_ref() == *other
}
}
#[cfg(feature = "std")]
impl<'a> PartialEq<&'a UriRef> for UriBuf {
fn eq(&self, other: &&'a UriRef) -> bool {
*self.as_uri_ref() == **other
}
}
#[cfg(feature = "std")]
impl PartialEq<UriRefBuf> for UriBuf {
fn eq(&self, other: &UriRefBuf) -> bool {
*self.as_uri_ref() == *other.as_uri_ref()
}
}
#[cfg(feature = "std")]
impl PartialOrd<UriRef> for UriBuf {
fn partial_cmp(&self, other: &UriRef) -> Option<Ordering> {
self.as_uri_ref().partial_cmp(other)
}
}
#[cfg(feature = "std")]
impl<'a> PartialOrd<&'a UriRef> for UriBuf {
fn partial_cmp(&self, other: &&'a UriRef) -> Option<Ordering> {
self.as_uri_ref().partial_cmp(*other)
}
}
#[cfg(feature = "std")]
impl PartialOrd<UriRefBuf> for UriBuf {
fn partial_cmp(&self, other: &UriRefBuf) -> Option<Ordering> {
self.as_uri_ref().partial_cmp(other.as_uri_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scheme() {
let vectors: &[(&str, &str)] = &[
("http:", "http"),
("https://example.org", "https"),
("ftp://ftp.example.org/file", "ftp"),
("mailto:user@example.org", "mailto"),
("urn:isbn:0451450523", "urn"),
("file:///path/to/file", "file"),
("custom-scheme://host", "custom-scheme"),
("a://b", "a"),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(uri.scheme().as_str(), *expected, "input: {input}");
}
}
#[test]
fn authority() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", Some("example.org")),
("http://example.org:8080", Some("example.org:8080")),
("http://user@example.org", Some("user@example.org")),
(
"http://user:pass@example.org:8080",
Some("user:pass@example.org:8080"),
),
("http://[::1]", Some("[::1]")),
("http://[::1]:8080", Some("[::1]:8080")),
("mailto:user@example.org", None),
("urn:isbn:0451450523", None),
("file:///path/to/file", Some("")),
("http://", Some("")),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(
uri.authority().map(|a| a.as_str()),
*expected,
"input: {input}"
);
}
}
#[test]
fn authority_host() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", Some("example.org")),
("http://example.org:8080", Some("example.org")),
("http://user@example.org", Some("example.org")),
("http://user:pass@example.org:8080", Some("example.org")),
("http://[::1]", Some("[::1]")),
("http://[::1]:8080", Some("[::1]")),
("http://192.168.1.1", Some("192.168.1.1")),
("mailto:user@example.org", None),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(
uri.authority().map(|a| a.host().as_str()),
*expected,
"input: {input}"
);
}
}
#[test]
fn authority_port() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", None),
("http://example.org:8080", Some("8080")),
("http://example.org:", Some("")),
("http://user@example.org:443", Some("443")),
("http://[::1]:8080", Some("8080")),
("mailto:user@example.org", None),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(
uri.authority().and_then(|a| a.port()).map(|p| p.as_str()),
*expected,
"input: {input}"
);
}
}
#[test]
fn authority_user_info() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", None),
("http://user@example.org", Some("user")),
("http://user:pass@example.org", Some("user:pass")),
("http://user:@example.org", Some("user:")),
("http://:pass@example.org", Some(":pass")),
("http://@example.org", Some("")),
("mailto:user@example.org", None),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(
uri.authority()
.and_then(|a| a.user_info())
.map(|u| u.as_str()),
*expected,
"input: {input}"
);
}
}
#[test]
fn path() {
let vectors: &[(&str, &str)] = &[
("http://example.org", ""),
("http://example.org/", "/"),
("http://example.org/path", "/path"),
("http://example.org/path/to/resource", "/path/to/resource"),
("http://example.org/path?query", "/path"),
("http://example.org/path#fragment", "/path"),
("mailto:user@example.org", "user@example.org"),
("urn:isbn:0451450523", "isbn:0451450523"),
("file:///path/to/file", "/path/to/file"),
("http://example.org/a/b/../c", "/a/b/../c"),
("http://example.org/path/", "/path/"),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(uri.path().as_str(), *expected, "input: {input}");
}
}
#[test]
fn query() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", None),
("http://example.org?", Some("")),
("http://example.org?query", Some("query")),
("http://example.org?key=value", Some("key=value")),
(
"http://example.org?key=value&other=123",
Some("key=value&other=123"),
),
("http://example.org/path?query", Some("query")),
("http://example.org?query#fragment", Some("query")),
("http://example.org#fragment", None),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(uri.query().map(|q| q.as_str()), *expected, "input: {input}");
}
}
#[test]
fn fragment() {
let vectors: &[(&str, Option<&str>)] = &[
("http://example.org", None),
("http://example.org#", Some("")),
("http://example.org#fragment", Some("fragment")),
("http://example.org#section-1", Some("section-1")),
("http://example.org/path#fragment", Some("fragment")),
("http://example.org?query#fragment", Some("fragment")),
("http://example.org?query", None),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(
uri.fragment().map(|f| f.as_str()),
*expected,
"input: {input}"
);
}
}
#[test]
fn parts() {
let vectors: &[(&str, &str, Option<&str>, &str, Option<&str>, Option<&str>)] = &[
(
"http://example.org",
"http",
Some("example.org"),
"",
None,
None,
),
(
"https://user:pass@example.org:8080/path?query#fragment",
"https",
Some("user:pass@example.org:8080"),
"/path",
Some("query"),
Some("fragment"),
),
(
"mailto:user@example.org",
"mailto",
None,
"user@example.org",
None,
None,
),
(
"urn:isbn:0451450523",
"urn",
None,
"isbn:0451450523",
None,
None,
),
(
"file:///etc/passwd",
"file",
Some(""),
"/etc/passwd",
None,
None,
),
(
"http://[::1]:8080/",
"http",
Some("[::1]:8080"),
"/",
None,
None,
),
(
"http://example.org?",
"http",
Some("example.org"),
"",
Some(""),
None,
),
(
"http://example.org#",
"http",
Some("example.org"),
"",
None,
Some(""),
),
(
"http://example.org/?#",
"http",
Some("example.org"),
"/",
Some(""),
Some(""),
),
];
for (input, scheme, authority, path, query, fragment) in vectors {
let uri = Uri::new(input).unwrap();
let parts = uri.parts();
assert_eq!(
parts.scheme.as_str(),
*scheme,
"scheme mismatch for {input}"
);
assert_eq!(
parts.authority.map(|a| a.as_str()),
*authority,
"authority mismatch for {input}"
);
assert_eq!(parts.path.as_str(), *path, "path mismatch for {input}");
assert_eq!(
parts.query.map(|q| q.as_str()),
*query,
"query mismatch for {input}"
);
assert_eq!(
parts.fragment.map(|f| f.as_str()),
*fragment,
"fragment mismatch for {input}"
);
}
}
#[test]
fn base() {
let vectors: &[(&str, &str)] = &[
("http://example.org/a/b/c", "http://example.org/a/b/"),
("http://example.org/a/b/c/", "http://example.org/a/b/c/"),
("http://example.org/a", "http://example.org/"),
("http://example.org/", "http://example.org/"),
("http://example.org", "http://example.org"),
];
for (input, expected) in vectors {
let uri = Uri::new(input).unwrap();
assert_eq!(uri.base().as_str(), *expected, "input: {input}");
}
}
#[test]
fn invalid() {
let vectors: [&[u8]; _] = [
b"", b"://host", b"/path", b"../..", b"http://host name", b"http://host\0name", b"http://[::1", b"http://ho st/path", b"htt p://host", b"http://host/pa th", b"http://host?qu ery", b"http://host#fra gment", b"\xff://host", ];
for input in vectors {
assert!(Uri::new(input).is_err(), "should reject: {input:?}");
}
}
}