use core::fmt;
use std::ops::{Deref, Range};
use crate::PathContext;
use super::{InvalidPath, InvalidSegment, Path, PathBuf, Segment};
const CURRENT_SEGMENT: &[u8] = b".";
const PARENT_SEGMENT: &[u8] = b"..";
const NORMALIZE_IN_PLACE_BUFFER_LEN: usize = 512;
pub struct PathMut<'a> {
buffer: &'a mut Vec<u8>,
range: Range<usize>,
context: PathContext,
}
impl<'a> Deref for PathMut<'a> {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.as_path()
}
}
impl<'a> PathMut<'a> {
pub fn new(
buffer: &'a mut Vec<u8>,
range: Range<usize>,
) -> Result<Self, InvalidPath<&'a [u8]>> {
if Path::validate_bytes(&buffer[range.clone()]) {
Ok(unsafe { Self::new_unchecked(buffer, range) })
} else {
Err(InvalidPath(buffer))
}
}
pub unsafe fn new_unchecked(buffer: &'a mut Vec<u8>, range: Range<usize>) -> Self {
let context = PathContext::from_bytes(&buffer[..range.start]);
Self {
buffer,
range,
context,
}
}
pub fn from_path(path: &'a mut PathBuf) -> Self {
let buffer = unsafe {
path.as_mut_vec()
};
let end = buffer.len();
Self {
buffer,
range: 0..end,
context: PathContext::default(),
}
}
pub fn from_path_with_context(path: &'a mut PathBuf, context: PathContext) -> Self {
assert!(!context.has_authority || path.is_absolute() || path.is_empty());
let buffer = unsafe {
path.as_mut_vec()
};
let end = buffer.len();
Self {
buffer,
range: 0..end,
context,
}
}
pub fn as_path(&self) -> &Path {
unsafe { Path::new_unchecked_from_bytes(&self.buffer[self.range.clone()]) }
}
fn first_segment_offset(&self) -> usize {
if self.is_absolute() {
self.range.start + 1
} else {
self.range.start
}
}
pub fn lazy_push(&mut self, segment: &super::Segment) -> &mut Self {
let prepend_slash = !self.is_empty() || (self.context.has_authority && self.is_relative());
let prepend_dot = self.is_empty()
&& ((!self.context.has_scheme
&& !self.context.has_authority
&& segment.looks_like_scheme())
|| segment.is_empty());
let prefix: &[u8] = match (prepend_slash, prepend_dot) {
(true, true) => b"/./",
(true, false) => b"/",
(false, true) => b"./",
(false, false) => b"",
};
let i = self.range.end;
let len = prefix.len() + segment.len();
self.range.end += len;
crate::utils::allocate_range(self.buffer, i..i, len);
self.buffer[i..(i + prefix.len())].copy_from_slice(prefix);
self.buffer[(i + prefix.len())..self.range.end].copy_from_slice(segment.as_bytes());
self
}
pub fn try_lazy_push<'s>(
&mut self,
segment: &'s str,
) -> Result<&mut Self, super::InvalidSegment<&'s str>> {
self.lazy_push(segment.try_into()?);
Ok(self)
}
#[inline]
pub(crate) fn push_inner(&mut self, segment: &super::Segment) -> bool {
match segment.as_bytes() {
CURRENT_SEGMENT => true,
PARENT_SEGMENT => {
self.pop();
true
}
_ => {
if !segment.is_empty() || !self.is_empty() {
self.lazy_push(segment);
}
false
}
}
}
#[inline]
pub fn push(&mut self, segment: &super::Segment) -> &mut Self {
if self.push_inner(segment) && !self.is_empty() {
self.lazy_push(super::Segment::EMPTY)
} else {
self
}
}
#[inline]
pub fn try_push<'s>(
&mut self,
segment: &'s str,
) -> Result<&mut Self, super::InvalidSegment<&'s str>> {
Ok(self.push(segment.try_into()?))
}
#[inline]
pub fn lazy_append<'s, S: IntoIterator<Item = &'s Segment>>(&mut self, path: S) -> &mut Self {
for s in path {
self.lazy_push(s);
}
self
}
#[inline]
pub fn try_lazy_append<'s, S: IntoIterator<Item = &'s str>>(
&mut self,
path: S,
) -> Result<&mut Self, InvalidSegment<&'s str>> {
for segment in path {
self.try_lazy_push(segment)?;
}
Ok(self)
}
#[inline]
pub fn append<'s, S: IntoIterator<Item = &'s Segment>>(&mut self, path: S) -> &mut Self {
let mut open = false;
for segment in path {
open = self.push_inner(segment);
}
if open && !self.is_empty() {
self.lazy_push(super::Segment::EMPTY)
} else {
self
}
}
#[inline]
pub fn try_append<'s, S: IntoIterator<Item = &'s str>>(
&mut self,
path: S,
) -> Result<&mut Self, InvalidSegment<&'s str>> {
let mut open = false;
for segment in path {
open = self.push_inner(segment.try_into()?);
}
if open && !self.is_empty() {
Ok(self.lazy_push(Segment::EMPTY))
} else {
Ok(self)
}
}
pub fn join(&mut self, path: &Path) -> &mut Self {
if path.is_absolute() {
self.replace(path)
} else {
self.append(path)
}
}
pub fn try_join<'s>(&mut self, path: &'s str) -> Result<&mut Self, InvalidPath<&'s str>> {
self.join(Path::new(path)?);
Ok(self)
}
pub fn pop(&mut self) -> &mut Self {
self.try_pop();
self
}
pub fn try_pop(&mut self) -> bool {
let is_empty = self.is_empty();
if (is_empty && self.is_relative()) || self.last() == Some(super::Segment::PARENT) {
self.lazy_push(super::Segment::PARENT);
true
} else if !is_empty {
let start = self.first_segment_offset();
let mut i = self.range.end - 1;
while i > start && self.buffer[i] != b'/' {
i -= 1
}
crate::utils::replace(self.buffer, i..self.range.end, &[]);
self.range.end = i;
true
} else {
false
}
}
pub fn clear(&mut self) -> &mut Self {
let start = self.first_segment_offset();
crate::utils::replace(self.buffer, start..self.range.end, b"");
self.range.end = start;
self
}
pub fn replace(&mut self, path: &Path) -> &mut Self {
let prefix: &[u8] = if !self.context.has_authority && path.as_bytes().starts_with(b"//") {
b"/."
} else if self.context.has_authority && !path.is_empty() && path.is_relative() {
b"/"
} else if !self.context.has_scheme
&& !self.context.has_authority
&& path.looks_like_scheme()
{
b"./"
} else {
b""
};
let i = self.range.start;
let len = prefix.len() + path.len();
crate::utils::allocate_range(self.buffer, self.range.start..self.range.end, len);
self.buffer[i..(i + prefix.len())].copy_from_slice(prefix);
self.buffer[(i + prefix.len())..(i + len)].copy_from_slice(path.as_bytes());
self.range.end = self.range.start + len;
self
}
pub fn try_replace<'p>(&mut self, path: &'p str) -> Result<&mut Self, InvalidPath<&'p str>> {
self.replace(Path::new(path)?);
Ok(self)
}
#[inline]
pub fn normalize(&mut self) -> &mut Self {
let mut buffer: smallvec::SmallVec<[u8; NORMALIZE_IN_PLACE_BUFFER_LEN]> =
smallvec::SmallVec::new();
if self.is_absolute() {
buffer.push(b'/')
}
for (i, segment) in self.normalized_segments().enumerate() {
if i > 0 {
buffer.push(b'/')
}
buffer.extend_from_slice(segment.as_bytes())
}
self.replace(unsafe { Path::new_unchecked_from_bytes(buffer.as_slice()) })
}
}
impl<'a> fmt::Display for PathMut<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_path().fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uri::{PathBuf, Segment};
#[test]
fn lazy_push() {
let vectors: [(&str, &str, &str); _] = [
("", "foo", "foo"),
("/", "foo", "/foo"),
("", "", "./"),
("/", "", "/./"),
("foo", "bar", "foo/bar"),
("/foo", "bar", "/foo/bar"),
("foo", "", "foo/"),
("foo/bar", "", "foo/bar/"),
("foo/", "", "foo//"),
("a/b/c", "d", "a/b/c/d"),
("/a/b/c", "d", "/a/b/c/d"),
("a/b/c/", "d", "a/b/c//d"),
];
for (path, segment, expected) in vectors {
let mut path = PathBuf::new(path.to_owned()).unwrap();
let mut path_mut = PathMut::from_path(&mut path);
let segment = Segment::new(&segment).unwrap();
path_mut.lazy_push(segment);
assert_eq!(path_mut.as_str(), expected)
}
}
#[test]
fn lazy_push_following_authority() {
let vectors: [(&[u8], &[u8], &[u8]); _] = [
(b"", b"foo", b"/foo"),
(b"/", b"foo", b"/foo"),
(b"", b"", b"/./"),
(b"/", b"", b"/./"),
(b"/foo", b"bar", b"/foo/bar"),
(b"/a/b/c", b"d", b"/a/b/c/d"),
];
for (path, segment, expected) in vectors {
let mut path = PathBuf::new(path.to_vec()).unwrap();
let mut path_mut = PathMut::from_path_with_context(
&mut path,
PathContext {
has_scheme: false,
has_authority: true,
},
);
let segment = Segment::new(&segment).unwrap();
path_mut.lazy_push(segment);
assert_eq!(path_mut.as_bytes(), expected)
}
}
#[test]
fn push() {
let vectors: [(&str, &str, &str); _] =
[("foo/bar", "..", "foo/"), ("foo/bar", ".", "foo/bar/")];
for (path, segment, expected) in vectors {
let mut path = PathBuf::new(path.to_owned()).unwrap();
let mut path_mut = PathMut::from_path(&mut path);
let segment = Segment::new(&segment).unwrap();
path_mut.push(segment);
assert_eq!(path_mut.as_str(), expected)
}
}
#[test]
fn append() {
let vectors: [(&str, &str, &str); _] = [("foo/bar", "..", "foo/")];
for (a, b, expected) in vectors {
let mut a = PathBuf::new(a.to_owned()).unwrap();
let mut a_mut = PathMut::from_path(&mut a);
let b = Path::new(b).unwrap();
a_mut.append(b.segments());
assert_eq!(a_mut.as_str(), expected)
}
}
#[test]
fn replace() {
let vectors = [
("a", "foo", "foo"),
("a/b", "//foo", "/.//foo"), ("a/b/c", "foo:bar", "./foo:bar"), ("../", "/foo:bar", "/foo:bar"),
];
for (a, b, expected) in vectors {
let mut path = PathBuf::new(a.to_owned()).unwrap();
PathMut::from_path(&mut path).try_replace(b).unwrap();
assert_eq!(path.as_str(), expected)
}
}
#[test]
fn replace_following_authority() {
let vectors = [
("", "foo", "/foo"), ("/", "//foo", "//foo"),
];
for (a, b, expected) in vectors {
let mut path = PathBuf::new(a.to_owned()).unwrap();
PathMut::from_path_with_context(
&mut path,
PathContext {
has_scheme: false,
has_authority: true,
},
)
.try_replace(b)
.unwrap();
assert_eq!(path.as_str(), expected)
}
}
#[test]
fn pop() {
let vectors: [(&[u8], &[u8]); 6] = [
(b"", b".."),
(b"/", b"/"),
(b"/..", b"/../.."),
(b"foo", b""),
(b"foo/bar", b"foo"),
(b"foo/bar/", b"foo/bar"),
];
for (path, expected) in vectors {
let mut path = PathBuf::new(path.to_vec()).unwrap();
let mut path_mut = PathMut::from_path(&mut path);
path_mut.pop();
assert_eq!(path_mut.as_bytes(), expected)
}
}
#[test]
fn pop_following_authority() {
let vectors: [(&[u8], &[u8]); _] = [(b"", b"/.."), (b"/", b"/"), (b"/..", b"/../..")];
for (path, expected) in vectors {
let mut path = PathBuf::new(path.to_vec()).unwrap();
let mut path_mut = PathMut::from_path_with_context(
&mut path,
PathContext {
has_scheme: false,
has_authority: true,
},
);
path_mut.pop();
assert_eq!(path_mut.as_bytes(), expected)
}
}
#[test]
fn normalized() {
let vectors: [(&str, &str); _] = [
("", ""),
("a/b/c", "a/b/c"),
("a/..", ""),
("a/b/..", "a/"),
("a/b/../", "a/"),
("a/b/c/..", "a/b/"),
("a/b/c/.", "a/b/c/"),
("a/../..", "../"),
("/a/../..", "/"),
];
for (input, expected) in vectors {
let mut path = PathBuf::new(input.to_owned()).unwrap();
let mut path_mut = PathMut::from_path(&mut path);
path_mut.normalize();
assert_eq!(path_mut.as_str(), expected);
}
}
}