#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod enc;
mod fmt;
mod view;
pub use view::*;
mod parser;
use crate::enc::{EStr, Split};
use std::{
marker::PhantomData,
mem::ManuallyDrop,
net::{Ipv4Addr, Ipv6Addr},
ptr::NonNull,
slice, str,
};
mod internal;
use internal::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParseErrorKind {
InvalidOctet,
UnexpectedChar,
InvalidIpLiteral,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ParseError {
index: u32,
kind: ParseErrorKind,
}
impl ParseError {
#[inline]
pub fn index(&self) -> usize {
self.index as usize
}
#[inline]
pub fn kind(&self) -> ParseErrorKind {
self.kind
}
}
impl std::error::Error for ParseError {}
type Result<T, E = ParseError> = std::result::Result<T, E>;
#[cold]
fn len_overflow() -> ! {
panic!("input length exceeds i32::MAX");
}
#[repr(C)]
pub struct Uri<T: Storage> {
ptr: T::Ptr,
data: Data,
_marker: PhantomData<T>,
}
impl<'a> Uri<&'a str> {
pub fn parse<S: AsRef<[u8]> + ?Sized>(s: &S) -> Result<Uri<&str>> {
let bytes = s.as_ref();
if bytes.len() > i32::MAX as usize {
len_overflow();
}
unsafe { parser::parse(bytes.as_ptr() as *mut _, bytes.len() as u32, 0) }
}
#[inline]
pub fn dup(&self) -> Uri<&'a str> {
Uri {
data: self.data.clone(),
..*self
}
}
#[inline]
pub fn to_owned(&self) -> Uri<String> {
let len = self.len();
let mut vec = ManuallyDrop::new(Vec::with_capacity(len as usize));
let ptr = vec.as_mut_ptr();
unsafe {
self.ptr.get().copy_to_nonoverlapping(ptr, len as usize);
}
Uri {
ptr: unsafe { Capped::new(ptr, len, len) },
data: self.data.clone(),
_marker: PhantomData,
}
}
}
impl<'i, 'o, T: Io<'i, 'o> + AsRef<str>> Uri<T> {
#[inline]
pub fn as_str(&'i self) -> &'o str {
unsafe { self.slice(0, self.len()) }
}
#[cfg(feature = "unstable")]
#[inline]
pub fn to_mut_in<'b, B: crate::enc::internal::Buf + ?Sized>(
&self,
buf: &'b mut B,
) -> Result<Uri<&'b mut [u8]>, B::PrepareError> {
let len = self.len();
let ptr = buf.prepare(len as usize)?;
unsafe {
self.ptr.get().copy_to_nonoverlapping(ptr, len as usize);
buf.finish(len as usize);
}
Ok(Uri {
ptr: unsafe { Uncapped::new(ptr, len, 0) },
data: self.data.clone(),
_marker: PhantomData,
})
}
}
#[cold]
fn component_taken() -> ! {
panic!("component already taken");
}
impl<'i, 'o, T: Io<'i, 'o>> Uri<T> {
#[inline]
fn len(&self) -> u32 {
self.ptr.len()
}
#[inline]
unsafe fn slice(&'i self, start: u32, end: u32) -> &'o str {
debug_assert!(start <= end && end <= self.len());
let bytes = unsafe {
slice::from_raw_parts(self.ptr.get().add(start as usize), (end - start) as usize)
};
unsafe { str::from_utf8_unchecked(bytes) }
}
#[inline]
unsafe fn eslice(&'i self, start: u32, end: u32) -> &'o EStr {
let s = unsafe { self.slice(start, end) };
unsafe { EStr::new_unchecked(s.as_bytes()) }
}
#[inline]
pub fn scheme(&'i self) -> Option<&'o Scheme> {
self.scheme_end
.map(|i| Scheme::new(unsafe { self.slice(0, i.get()) }))
}
#[inline]
pub fn authority(&self) -> Option<&Authority<T>> {
if T::is_mut() && self.tag.contains(Tag::AUTH_TAKEN) {
return None;
}
if self.auth.is_some() {
Some(unsafe { Authority::new(self) })
} else {
None
}
}
#[inline]
pub fn path(&'i self) -> &'o Path {
if T::is_mut() && self.tag.contains(Tag::PATH_TAKEN) {
component_taken();
}
Path::new(unsafe { self.eslice(self.path_bounds.0, self.path_bounds.1) })
}
#[inline]
pub fn query(&'i self) -> Option<&'o EStr> {
self.query_end
.map(|i| unsafe { self.eslice(self.path_bounds.1 + 1, i.get()) })
}
#[inline]
pub fn fragment(&'i self) -> Option<&'o EStr> {
self.fragment_start
.map(|i| unsafe { self.eslice(i.get(), self.len()) })
}
#[inline]
pub fn is_relative(&self) -> bool {
self.scheme_end.is_none()
}
#[inline]
pub fn is_absolute(&self) -> bool {
self.scheme_end.is_some() && self.fragment_start.is_none()
}
}
impl<'a> Uri<&'a mut [u8]> {
#[inline]
pub fn parse_mut<S: AsMut<[u8]> + ?Sized>(s: &mut S) -> Result<Uri<&mut [u8]>> {
let bytes = s.as_mut();
if bytes.len() > i32::MAX as usize {
len_overflow();
}
unsafe { parser::parse(bytes.as_mut_ptr(), bytes.len() as u32, 0) }
}
#[inline]
unsafe fn view<T>(&mut self, start: u32, end: u32) -> View<'a, T>
where
T: ?Sized + Lens<Target = [u8]>,
{
debug_assert!(start <= end && end <= self.len());
let bytes = unsafe {
slice::from_raw_parts_mut(self.ptr.get().add(start as usize), (end - start) as usize)
};
unsafe { View::new(bytes) }
}
#[inline]
pub fn take_scheme(&mut self) -> Option<View<'a, Scheme>> {
self.scheme_end
.take()
.map(|i| unsafe { self.view(0, i.get()) })
}
#[inline]
pub fn take_authority(&mut self) -> Option<View<'_, Authority<&'a mut [u8]>>> {
if self.tag.contains(Tag::AUTH_TAKEN) {
return None;
}
self.tag |= Tag::AUTH_TAKEN;
if self.auth.is_some() {
Some(unsafe { View::new(self) })
} else {
None
}
}
#[inline]
pub fn take_path(&mut self) -> View<'a, Path> {
if self.tag.contains(Tag::PATH_TAKEN) {
component_taken();
}
self.tag |= Tag::PATH_TAKEN;
unsafe { self.view(self.path_bounds.0, self.path_bounds.1) }
}
#[inline]
pub fn take_query(&mut self) -> Option<View<'a, EStr>> {
self.query_end
.take()
.map(|i| unsafe { self.view(self.path_bounds.1 + 1, i.get()) })
}
#[inline]
pub fn take_fragment(&mut self) -> Option<View<'a, EStr>> {
self.fragment_start
.take()
.map(|i| unsafe { self.view(i.get(), self.len()) })
}
}
impl Uri<String> {
#[inline]
pub fn parse_from<T: IntoOwnedUri>(t: T) -> Result<Uri<String>, (T, ParseError)> {
#[cold]
fn cap_overflow() -> ! {
panic!("input capacity exceeds i32::MAX");
}
let buf = ManuallyDrop::new(t);
let (ptr, len, cap) = buf.as_raw_parts();
if cap > i32::MAX as usize {
cap_overflow();
}
match unsafe { parser::parse(ptr, len as u32, cap as u32) } {
Ok(out) => Ok(out),
Err(e) => Err((ManuallyDrop::into_inner(buf), e)),
}
}
#[inline]
pub fn into_string(self) -> String {
self.ptr.into_string()
}
#[inline]
#[allow(clippy::should_implement_trait)]
pub fn borrow(&self) -> &Uri<&str> {
unsafe { &*(self as *const Uri<String> as *const Uri<&str>) }
}
}
impl Clone for Uri<String> {
#[inline]
fn clone(&self) -> Self {
self.borrow().to_owned()
}
}
unsafe impl<T: Storage> Send for Uri<T> {}
unsafe impl<T: Storage> Sync for Uri<T> {}
#[repr(transparent)]
pub struct Scheme(str);
const ASCII_CASE_MASK: u8 = 0b010_0000;
impl Scheme {
#[inline]
fn new(scheme: &str) -> &Scheme {
unsafe { &*(scheme as *const str as *const Scheme) }
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn to_lowercase(&self) -> String {
let bytes = self.0.bytes().map(|x| x | ASCII_CASE_MASK).collect();
unsafe { String::from_utf8_unchecked(bytes) }
}
#[inline]
pub fn eq_lowercase(&self, other: &str) -> bool {
let (a, b) = (self.0.as_bytes(), other.as_bytes());
if a.len() != b.len() {
false
} else {
for i in 0..a.len() {
if a[i] | ASCII_CASE_MASK != b[i] {
return false;
}
}
true
}
}
}
#[repr(transparent)]
pub struct Authority<T: Storage> {
uri: Uri<T>,
}
impl<'i, 'o, T: Io<'i, 'o>> Authority<T> {
#[inline]
unsafe fn new(uri: &Uri<T>) -> &Authority<T> {
unsafe { &*(uri as *const Uri<T> as *const Authority<T>) }
}
#[inline]
fn data(&self) -> &AuthData {
unsafe { self.uri.auth.as_ref().unwrap_unchecked() }
}
#[inline]
fn start(&self) -> u32 {
self.data().start.get().get()
}
#[inline]
fn end(&self) -> u32 {
if T::is_mut() && self.uri.tag.contains(Tag::PORT_TAKEN) {
self.host_bounds().1
} else {
self.uri.path_bounds.0
}
}
#[inline]
fn host_bounds(&self) -> (u32, u32) {
self.data().host_bounds
}
#[inline]
pub fn as_str(&'i self) -> &'o str {
if T::is_mut() && self.uri.tag.contains(Tag::HOST_TAKEN) {
component_taken();
}
unsafe { self.uri.slice(self.start(), self.end()) }
}
#[inline]
pub fn userinfo(&'i self) -> Option<&'o EStr> {
let (start, host_start) = (self.start(), self.host_bounds().0);
(start != host_start).then(|| unsafe { self.uri.eslice(start, host_start - 1) })
}
pub fn host(&self) -> &Host<T> {
if T::is_mut() && self.uri.tag.contains(Tag::HOST_TAKEN) {
component_taken();
}
unsafe { Host::new(self) }
}
#[inline]
pub fn port(&'i self) -> Option<&'o str> {
if T::is_mut() && self.uri.tag.contains(Tag::PORT_TAKEN) {
return None;
}
let (host_end, end) = (self.host_bounds().1, self.uri.path_bounds.0);
(host_end != end).then(|| unsafe { self.uri.slice(host_end + 1, end) })
}
}
#[repr(transparent)]
pub struct Host<T: Storage> {
auth: Authority<T>,
}
impl<'i, 'o, T: Io<'i, 'o>> Host<T> {
#[inline]
unsafe fn new(auth: &Authority<T>) -> &Host<T> {
unsafe { &*(auth as *const Authority<T> as *const Host<T>) }
}
#[inline]
fn bounds(&self) -> (u32, u32) {
self.auth.host_bounds()
}
#[inline]
fn raw_data(&self) -> &RawHostData {
&self.auth.data().host_data
}
#[inline]
pub fn as_str(&'i self) -> &'o str {
unsafe { self.auth.uri.slice(self.bounds().0, self.bounds().1) }
}
#[inline]
pub fn data(&'i self) -> HostData<'o> {
let data = self.raw_data();
let tag = self.auth.uri.tag;
unsafe {
if tag.contains(Tag::HOST_REG_NAME) {
return HostData::RegName(EStr::new_unchecked(self.as_str().as_bytes()));
} else if tag.contains(Tag::HOST_IPV4) {
return HostData::Ipv4(data.ipv4_addr);
}
#[cfg(feature = "ipv_future")]
if !tag.contains(Tag::HOST_IPV6) {
let dot_i = data.ipv_future_dot_i;
let bounds = self.bounds();
return HostData::IpvFuture {
ver: self.auth.uri.slice(bounds.0 + 2, dot_i),
addr: self.auth.uri.slice(dot_i + 1, bounds.1 - 1),
};
}
HostData::Ipv6 {
addr: data.ipv6.addr,
#[cfg(feature = "rfc6874bis")]
zone_id: data
.ipv6
.zone_id_start
.map(|start| self.auth.uri.slice(start.get(), self.bounds().1 - 1)),
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HostData<'a> {
Ipv4(Ipv4Addr),
Ipv6 {
addr: Ipv6Addr,
#[cfg(feature = "rfc6874bis")]
zone_id: Option<&'a str>,
},
#[cfg(feature = "ipv_future")]
IpvFuture {
ver: &'a str,
addr: &'a str,
},
RegName(&'a EStr),
}
#[repr(transparent)]
pub struct Path {
inner: EStr,
}
impl Path {
#[inline]
fn new(path: &EStr) -> &Path {
unsafe { &*(path as *const EStr as *const Path) }
}
#[inline]
pub fn as_estr(&self) -> &EStr {
&self.inner
}
#[inline]
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
#[inline]
pub fn is_absolute(&self) -> bool {
self.as_str().starts_with('/')
}
#[inline]
pub fn is_rootless(&self) -> bool {
!self.is_absolute()
}
#[inline]
pub fn segments(&self) -> Split<'_> {
let mut path = self.inner.as_str();
if self.is_absolute() {
path = unsafe { path.get_unchecked(1..) };
}
let path = unsafe { EStr::new_unchecked(path.as_bytes()) };
let mut split = path.split('/');
split.finished = self.as_str().is_empty();
split
}
}