use core::mem;
use crate::buffer::Buffer;
use crate::normalize::PathSegment;
use crate::parser::str::{find_split, rfind};
#[derive(Clone, Copy)]
pub(crate) struct RemoveDotSegPath<'a>(Option<&'a str>, &'a str);
impl<'a> RemoveDotSegPath<'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(crate) fn merge_and_remove_dot_segments<'b, B: Buffer<'b>>(
&self,
buf: &mut B,
) -> Result<(), B::ExtendError> {
(*self).merge_and_remove_dot_segments_impl(buf)
}
fn merge_and_remove_dot_segments_impl<'b, B: Buffer<'b>>(
mut self,
buf: &mut B,
) -> 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 segname = next_seg.segment();
let segkind = SegmentKind::from(segname);
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!(),
}
} 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(buf)?;
}
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(buf)?;
}
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 SegmentKind::from(seg.segment()) == 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(Debug, Clone, Copy, PartialEq, Eq)]
enum SegmentKind {
Dot,
DotDot,
Normal,
}
impl From<&str> for SegmentKind {
fn from(s: &str) -> Self {
match s {
"." | "%2E" | "%2e" => Self::Dot,
".." | ".%2E" | ".%2e" | "%2E." | "%2E%2E" | "%2E%2e" | "%2e." | "%2e%2E"
| "%2e%2e" => Self::DotDot,
_ => Self::Normal,
}
}
}