use core::mem;
use crate::buffer::Buffer;
use crate::normalize::{normalize_case_and_pct_encodings, NormalizationType};
use crate::parser::str::{find_split, rfind};
use crate::spec::Spec;
#[derive(Debug, Clone, Copy)]
pub(crate) enum Path<'a> {
Done(&'a str),
NeedsProcessing(PathToNormalize<'a>),
}
impl Path<'_> {
#[must_use]
pub(super) fn estimate_max_buf_size_for_resolution(&self) -> usize {
match self {
Self::Done(s) => s.len(),
Self::NeedsProcessing(path) => path.estimate_max_buf_size_for_resolution(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct PathToNormalize<'a>(Option<&'a str>, &'a str);
impl<'a> PathToNormalize<'a> {
#[must_use]
pub(crate) fn from_paths_to_be_resolved(base: &'a str, reference: &'a str) -> Self {
if reference.starts_with('/') {
return Self(None, reference);
}
match rfind(base.as_bytes(), b'/') {
Some(last_slash_pos) => Self(Some(&base[..=last_slash_pos]), reference),
None => Self(None, reference),
}
}
#[inline]
#[must_use]
pub(crate) fn from_single_path(path: &'a str) -> Self {
Self(None, path)
}
#[inline]
#[must_use]
fn is_empty(&self) -> bool {
self.0.is_none() && self.1.is_empty()
}
#[must_use]
fn starts_with_slash(&self) -> bool {
self.0.unwrap_or(self.1).starts_with('/')
}
fn prepend_slash(&mut self) {
assert!(
self.0.is_none(),
"[precondition] `prepend_slash()` must be called only when \
`self.0` is `None`, but it was {:?}",
self.0
);
if self.1.is_empty() {
self.1 = "/";
} else {
self.0 = Some("/");
}
}
fn trim_leading_slash(&mut self) -> bool {
match &mut self.0 {
Some(buf) => match buf.strip_prefix('/') {
Some("") => {
self.0 = None;
true
}
Some(rest) => {
*buf = rest;
true
}
None => false,
},
None => match self.1.strip_prefix('/') {
Some(rest) => {
self.1 = rest;
true
}
None => false,
},
}
}
fn pop_first_segment(&mut self) -> Option<PathSegment<'a>> {
if self.is_empty() {
return None;
}
let leading_slash = self.trim_leading_slash();
let buf: &mut &str = match &mut self.0 {
Some(buf) => buf,
None => &mut self.1,
};
let segment = match find_split(buf, b'/') {
Some((segment, rest)) => {
*buf = rest;
segment
}
None => {
let segment = mem::take(buf);
debug_assert_eq!(
buf as *const &str, &self.1 as *const &str,
"[consistency] the first buffer must not be `Some(\"\")`"
);
segment
}
};
Some(PathSegment {
leading_slash,
segment,
})
}
pub(super) fn normalize<'b, B: Buffer<'b>, S: Spec>(
&self,
buf: &mut B,
op_norm: NormalizationType,
) -> Result<(), B::ExtendError> {
(*self).normalize_impl::<_, S>(buf, op_norm)
}
fn normalize_impl<'b, B: Buffer<'b>, S: Spec>(
mut self,
buf: &mut B,
op_norm: NormalizationType,
) -> Result<(), B::ExtendError> {
let path_start = buf.as_bytes().len();
let mut last_seg_buf: Option<PathSegment<'_>> = None;
while let Some(next_seg) = self.pop_first_segment() {
let segkind = next_seg.kind();
if segkind != SegmentKind::Normal {
match (
next_seg.has_leading_slash(),
segkind,
self.starts_with_slash(),
) {
(false, _, false) => {
assert!(self.is_empty());
}
(false, _, true) => {
let is_next_slash_removed = self.trim_leading_slash();
assert!(is_next_slash_removed);
}
(true, SegmentKind::DotDot, is_not_last_seg) => {
if !is_not_last_seg {
assert!(self.is_empty());
self.prepend_slash();
}
pop_last_seg_and_preceding_slash(buf, path_start, &mut last_seg_buf);
}
(true, SegmentKind::Dot, false) => {
assert!(self.is_empty());
self.prepend_slash();
}
(true, SegmentKind::Dot, true) => {
}
(_, SegmentKind::Normal, _) => unreachable!("[consistency] already checked"),
}
} else {
if let Some(last_seg) = last_seg_buf.take() {
if !last_seg.has_leading_slash() {
assert_eq!(buf.as_bytes().len(), path_start);
}
last_seg.write_to::<_, S>(buf, op_norm)?;
}
last_seg_buf = Some(next_seg);
}
}
if let Some(seg) = last_seg_buf.take() {
if !seg.has_leading_slash() {
assert_eq!(buf.as_bytes().len(), path_start);
}
seg.write_to::<_, S>(buf, op_norm)?;
}
Ok(())
}
#[inline]
#[must_use]
pub(crate) fn estimate_max_buf_size_for_resolution(&self) -> usize {
let mut this = *self;
let mut max = 0;
while let Some(seg) = this.pop_first_segment() {
if seg.has_leading_slash() {
max += 1;
}
if seg.kind() == SegmentKind::Normal {
max += seg.segment().len();
}
}
max
}
}
fn pop_last_seg_and_preceding_slash<'b, B: Buffer<'b>>(
buf: &mut B,
path_start: usize,
last_seg: &mut Option<PathSegment<'_>>,
) {
if let Some(seg) = last_seg.take() {
if !seg.has_leading_slash() {
assert_eq!(buf.as_bytes().len(), path_start);
}
return;
}
match rfind(&buf.as_bytes()[path_start..], b'/') {
Some(slash_pos) => buf.truncate(path_start + slash_pos),
None => buf.truncate(path_start),
}
}
#[derive(Clone, Copy)]
struct PathSegment<'a> {
leading_slash: bool,
segment: &'a str,
}
impl<'a> PathSegment<'a> {
#[inline]
#[must_use]
fn has_leading_slash(&self) -> bool {
self.leading_slash
}
#[inline]
#[must_use]
fn segment(&self) -> &'a str {
self.segment
}
#[must_use]
fn kind(&self) -> SegmentKind {
match self.segment {
"." | "%2E" | "%2e" => SegmentKind::Dot,
".." | ".%2E" | ".%2e" | "%2E." | "%2E%2E" | "%2E%2e" | "%2e." | "%2e%2E"
| "%2e%2e" => SegmentKind::DotDot,
_ => SegmentKind::Normal,
}
}
fn write_to<'b, B: Buffer<'b>, S: Spec>(
&self,
buf: &mut B,
op_norm: NormalizationType,
) -> Result<(), B::ExtendError> {
if self.has_leading_slash() {
buf.push_str("/")?;
}
match op_norm {
NormalizationType::Full => {
buf.extend_chars(normalize_case_and_pct_encodings::<S>(self.segment()))
}
NormalizationType::RemoveDotSegments => buf.push_str(self.segment()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SegmentKind {
Dot,
DotDot,
Normal,
}