use std::borrow::Cow;
use std::convert::{Infallible, TryFrom};
use std::error::Error;
use std::fmt::{self, Display, Formatter, Write};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::str;
use crate::utility::{
get_percent_encoded_value, normalize_string, percent_encoded_equality, percent_encoded_hash,
UNRESERVED_CHAR_MAP,
};
#[rustfmt::skip]
const PATH_CHAR_MAP: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, b'!', 0, 0, b'$', b'%', b'&',b'\'', b'(', b')', b'*', b'+', b',', b'-', b'.', 0,
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', 0, b'=', 0, 0,
b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O',
b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', 0, 0, 0, 0, b'_',
0, b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', 0, 0, 0, b'~', 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Path<'path> {
absolute: bool,
double_dot_segment_count: u16,
leading_double_dot_segment_count: u16,
segments: Vec<Segment<'path>>,
single_dot_segment_count: u16,
unnormalized_count: u16,
}
impl<'path> Path<'path> {
pub fn clear(&mut self) {
self.segments.clear();
self.segments.push(Segment::empty());
}
pub fn into_owned(self) -> Path<'static> {
let segments = self
.segments
.into_iter()
.map(Segment::into_owned)
.collect::<Vec<Segment<'static>>>();
Path {
absolute: self.absolute,
double_dot_segment_count: self.double_dot_segment_count,
leading_double_dot_segment_count: self.leading_double_dot_segment_count,
segments,
single_dot_segment_count: self.single_dot_segment_count,
unnormalized_count: self.unnormalized_count,
}
}
pub fn is_absolute(&self) -> bool {
self.absolute
}
pub fn is_normalized(&self, as_reference: bool) -> bool {
if self.unnormalized_count != 0 {
return false;
}
if self.absolute || !as_reference {
self.single_dot_segment_count == 0 && self.double_dot_segment_count == 0
} else {
(self.single_dot_segment_count == 0
|| (self.single_dot_segment_count == 1
&& self.segments[0].is_single_dot_segment()
&& self.segments.len() > 1
&& self.segments[1].contains(':')))
&& self.double_dot_segment_count == self.leading_double_dot_segment_count
}
}
pub fn is_relative(&self) -> bool {
!self.absolute
}
pub(crate) unsafe fn new_with_no_segments(absolute: bool) -> Path<'static> {
Path {
absolute,
double_dot_segment_count: 0,
leading_double_dot_segment_count: 0,
segments: Vec::new(),
single_dot_segment_count: 0,
unnormalized_count: 0,
}
}
pub fn normalize(&mut self, as_reference: bool) {
if self.is_normalized(as_reference) {
return;
}
self.unnormalized_count = 0;
if self.absolute || !as_reference {
self.remove_dot_segments_helper(true);
return;
}
let mut double_dot_segment_count = 0;
let mut last_dot_segment = None;
let mut new_length = 0;
for i in 0..self.segments.len() {
let segment = &self.segments[i];
if segment.is_single_dot_segment()
&& (new_length > 0
|| i == self.segments.len() - 1
|| !self.segments[i + 1].as_str().contains(':'))
{
continue;
}
if segment.is_double_dot_segment() {
match last_dot_segment {
None if new_length == 0 => (),
Some(index) if index == new_length - 1 => (),
_ => {
if new_length == 2
&& self.segments[0].is_single_dot_segment()
&& (i == self.segments.len() - 1
|| !self.segments[i + 1].as_str().contains(':'))
{
new_length -= 1
}
new_length -= 1;
continue;
}
}
double_dot_segment_count += 1;
last_dot_segment = Some(new_length);
}
self.segments.swap(i, new_length);
self.segments[new_length].normalize();
new_length += 1;
}
if new_length == 0 {
self.segments[0] = Segment::empty();
new_length = 1;
}
self.double_dot_segment_count = double_dot_segment_count;
self.leading_double_dot_segment_count = double_dot_segment_count;
self.single_dot_segment_count = if self.segments[0].is_single_dot_segment() {
1
} else {
0
};
self.segments.truncate(new_length);
}
pub fn pop(&mut self) {
let segment = self.segments.pop().unwrap();
if segment.is_single_dot_segment() {
self.single_dot_segment_count =
self.single_dot_segment_count.checked_sub(1).unwrap_or(0);
}
if segment.is_double_dot_segment() {
self.double_dot_segment_count =
self.double_dot_segment_count.checked_sub(1).unwrap_or(0);
if self.double_dot_segment_count < self.leading_double_dot_segment_count {
self.leading_double_dot_segment_count -= 1;
}
}
if !segment.is_normalized() {
self.unnormalized_count = self.unnormalized_count.checked_sub(1).unwrap_or(0);
}
if self.segments.is_empty() {
self.segments.push(Segment::empty());
}
}
pub fn push<TSegment, TSegmentError>(&mut self, segment: TSegment) -> Result<(), PathError>
where
Segment<'path>: TryFrom<TSegment, Error = TSegmentError>,
PathError: From<TSegmentError>,
{
if self.segments.len() as u16 == u16::max_value() {
return Err(PathError::ExceededMaximumLength);
}
let segment = Segment::try_from(segment)?;
if segment.is_single_dot_segment() {
self.single_dot_segment_count += 1;
}
if segment.is_double_dot_segment() {
if self.segments.len() as u16 == self.double_dot_segment_count {
self.leading_double_dot_segment_count += 1;
}
self.double_dot_segment_count += 1;
}
if !segment.is_normalized() {
self.unnormalized_count += 1;
}
if segment != "" && self.segments.len() == 1 && self.segments[0].as_str().is_empty() {
self.segments[0] = segment;
} else {
self.segments.push(segment);
}
Ok(())
}
pub fn remove_dot_segments(&mut self) {
if self.single_dot_segment_count == 0 && self.double_dot_segment_count == 0 {
return;
}
self.remove_dot_segments_helper(false);
}
fn remove_dot_segments_helper(&mut self, normalize_segments: bool) {
let mut input_absolute = self.absolute;
let mut new_length = 0;
for i in 0..self.segments.len() {
let segment = &self.segments[i];
if input_absolute {
if segment.is_single_dot_segment() {
continue;
} else if segment.is_double_dot_segment() {
if new_length > 0 {
new_length -= 1;
} else {
self.absolute = false;
}
continue;
}
if new_length == 0 {
self.absolute = true;
}
} else if segment.is_single_dot_segment() || segment.is_double_dot_segment() {
continue;
}
self.segments.swap(i, new_length);
if normalize_segments {
self.segments[new_length].normalize();
}
new_length += 1;
if i < self.segments.len() - 1 {
input_absolute = true;
} else {
input_absolute = false;
}
}
if input_absolute {
if new_length == 0 {
self.absolute = true;
} else {
self.segments[new_length] = Segment::empty();
new_length += 1;
}
}
if new_length == 0 {
self.segments[0] = Segment::empty();
new_length = 1;
}
self.double_dot_segment_count = 0;
self.leading_double_dot_segment_count = 0;
self.single_dot_segment_count = 0;
self.segments.truncate(new_length);
}
pub fn segments(&self) -> &[Segment<'path>] {
&self.segments
}
pub fn segments_mut(&mut self) -> &mut [Segment<'path>] {
&mut self.segments
}
pub fn set_absolute(&mut self, absolute: bool) {
self.absolute = absolute;
}
pub fn to_borrowed(&self) -> Path {
let segments = self.segments.iter().map(Segment::as_borrowed).collect();
Path {
absolute: self.absolute,
double_dot_segment_count: self.double_dot_segment_count,
leading_double_dot_segment_count: self.leading_double_dot_segment_count,
segments,
single_dot_segment_count: self.single_dot_segment_count,
unnormalized_count: self.unnormalized_count,
}
}
}
impl Display for Path<'_> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
if self.absolute {
formatter.write_char('/')?;
}
for (index, segment) in self.segments.iter().enumerate() {
formatter.write_str(segment.as_str())?;
if index < self.segments.len() - 1 {
formatter.write_char('/')?;
}
}
Ok(())
}
}
impl<'path> From<Path<'path>> for String {
fn from(value: Path<'path>) -> Self {
value.to_string()
}
}
impl PartialEq<[u8]> for Path<'_> {
fn eq(&self, mut other: &[u8]) -> bool {
if self.absolute {
match other.get(0) {
Some(&byte) => {
if byte != b'/' {
return false;
}
}
None => return false,
}
other = &other[1..];
}
for (index, segment) in self.segments.iter().enumerate() {
let len = segment.as_str().len();
if other.len() < len || &other[..len] != segment {
return false;
}
other = &other[len..];
if index < self.segments.len() - 1 {
match other.get(0) {
Some(&byte) => {
if byte != b'/' {
return false;
}
}
None => return false,
}
other = &other[1..];
}
}
true
}
}
impl<'path> PartialEq<Path<'path>> for [u8] {
fn eq(&self, other: &Path<'path>) -> bool {
self == other
}
}
impl<'a> PartialEq<&'a [u8]> for Path<'_> {
fn eq(&self, other: &&'a [u8]) -> bool {
self == *other
}
}
impl<'a, 'path> PartialEq<Path<'path>> for &'a [u8] {
fn eq(&self, other: &Path<'path>) -> bool {
self == other
}
}
impl PartialEq<str> for Path<'_> {
fn eq(&self, other: &str) -> bool {
self == other.as_bytes()
}
}
impl<'path> PartialEq<Path<'path>> for str {
fn eq(&self, other: &Path<'path>) -> bool {
self.as_bytes() == other
}
}
impl<'a> PartialEq<&'a str> for Path<'_> {
fn eq(&self, other: &&'a str) -> bool {
self == other.as_bytes()
}
}
impl<'a, 'path> PartialEq<Path<'path>> for &'a str {
fn eq(&self, other: &Path<'path>) -> bool {
self.as_bytes() == other
}
}
impl<'path> TryFrom<&'path [u8]> for Path<'path> {
type Error = PathError;
fn try_from(value: &'path [u8]) -> Result<Self, Self::Error> {
let (path, rest) = parse_path(value)?;
if rest.is_empty() {
Ok(path)
} else {
Err(PathError::InvalidCharacter)
}
}
}
impl<'path> TryFrom<&'path str> for Path<'path> {
type Error = PathError;
fn try_from(value: &'path str) -> Result<Self, Self::Error> {
Path::try_from(value.as_bytes())
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Segment<'segment> {
normalized: bool,
segment: Cow<'segment, str>,
}
impl Segment<'_> {
pub fn as_borrowed(&self) -> Segment {
use self::Cow::*;
let segment = match &self.segment {
Borrowed(borrowed) => *borrowed,
Owned(owned) => owned.as_str(),
};
Segment {
normalized: self.normalized,
segment: Cow::Borrowed(segment),
}
}
pub fn as_str(&self) -> &str {
&self.segment
}
pub fn empty() -> Segment<'static> {
Segment {
normalized: true,
segment: Cow::from(""),
}
}
pub fn into_owned(self) -> Segment<'static> {
Segment {
normalized: self.normalized,
segment: Cow::from(self.segment.into_owned()),
}
}
pub fn is_dot_segment(&self) -> bool {
self == "." || self == ".."
}
pub fn is_double_dot_segment(&self) -> bool {
self == ".."
}
pub fn is_normalized(&self) -> bool {
self.normalized
}
pub fn is_single_dot_segment(&self) -> bool {
self == "."
}
pub fn normalize(&mut self) {
if !self.normalized {
unsafe { normalize_string(&mut self.segment.to_mut(), true) };
self.normalized = true;
}
}
}
impl AsRef<[u8]> for Segment<'_> {
fn as_ref(&self) -> &[u8] {
self.segment.as_bytes()
}
}
impl AsRef<str> for Segment<'_> {
fn as_ref(&self) -> &str {
&self.segment
}
}
impl Deref for Segment<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.segment
}
}
impl Display for Segment<'_> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str(&self.segment)
}
}
impl Eq for Segment<'_> {}
impl<'segment> From<Segment<'segment>> for String {
fn from(value: Segment<'segment>) -> Self {
value.to_string()
}
}
impl Hash for Segment<'_> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
percent_encoded_hash(self.segment.as_bytes(), state, true);
}
}
impl PartialEq for Segment<'_> {
fn eq(&self, other: &Segment) -> bool {
percent_encoded_equality(self.segment.as_bytes(), other.segment.as_bytes(), true)
}
}
impl PartialEq<[u8]> for Segment<'_> {
fn eq(&self, other: &[u8]) -> bool {
percent_encoded_equality(self.segment.as_bytes(), other, true)
}
}
impl<'segment> PartialEq<Segment<'segment>> for [u8] {
fn eq(&self, other: &Segment<'segment>) -> bool {
percent_encoded_equality(self, other.segment.as_bytes(), true)
}
}
impl<'a> PartialEq<&'a [u8]> for Segment<'_> {
fn eq(&self, other: &&'a [u8]) -> bool {
percent_encoded_equality(self.segment.as_bytes(), other, true)
}
}
impl<'a, 'segment> PartialEq<Segment<'segment>> for &'a [u8] {
fn eq(&self, other: &Segment<'segment>) -> bool {
percent_encoded_equality(self, other.segment.as_bytes(), true)
}
}
impl PartialEq<str> for Segment<'_> {
fn eq(&self, other: &str) -> bool {
percent_encoded_equality(self.segment.as_bytes(), other.as_bytes(), true)
}
}
impl<'segment> PartialEq<Segment<'segment>> for str {
fn eq(&self, other: &Segment<'segment>) -> bool {
percent_encoded_equality(self.as_bytes(), other.segment.as_bytes(), true)
}
}
impl<'a> PartialEq<&'a str> for Segment<'_> {
fn eq(&self, other: &&'a str) -> bool {
percent_encoded_equality(self.segment.as_bytes(), other.as_bytes(), true)
}
}
impl<'a, 'segment> PartialEq<Segment<'segment>> for &'a str {
fn eq(&self, other: &Segment<'segment>) -> bool {
percent_encoded_equality(self.as_bytes(), other.segment.as_bytes(), true)
}
}
impl<'segment> TryFrom<&'segment [u8]> for Segment<'segment> {
type Error = PathError;
fn try_from(value: &'segment [u8]) -> Result<Self, Self::Error> {
let mut bytes = value.iter();
let mut normalized = true;
while let Some(&byte) = bytes.next() {
match PATH_CHAR_MAP[byte as usize] {
0 => return Err(PathError::InvalidCharacter),
b'%' => {
match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) {
Ok((hex_value, uppercase)) => {
if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 {
normalized = false;
}
}
Err(_) => return Err(PathError::InvalidPercentEncoding),
}
}
_ => (),
}
}
let segment = Segment {
normalized,
segment: Cow::Borrowed(unsafe { str::from_utf8_unchecked(value) }),
};
Ok(segment)
}
}
impl<'segment> TryFrom<&'segment str> for Segment<'segment> {
type Error = PathError;
fn try_from(value: &'segment str) -> Result<Self, Self::Error> {
Segment::try_from(value.as_bytes())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum PathError {
ExceededMaximumLength,
InvalidCharacter,
InvalidPercentEncoding,
}
impl Display for PathError {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
use self::PathError::*;
match self {
ExceededMaximumLength => write!(formatter, "exceeded maximum path length"),
InvalidCharacter => write!(formatter, "invalid path character"),
InvalidPercentEncoding => write!(formatter, "invalid path percent encoding"),
}
}
}
impl Error for PathError {}
impl From<Infallible> for PathError {
fn from(_: Infallible) -> Self {
PathError::InvalidCharacter
}
}
pub(crate) fn parse_path(value: &[u8]) -> Result<(Path, &[u8]), PathError> {
struct SegmentInfo {
absolute: bool,
double_dot_segment_count: u16,
index: u16,
last_double_dot_segment: Option<u16>,
leading_double_dot_segment_count: u16,
normalized: bool,
single_dot_segment_count: u16,
unnormalized_count: u16,
}
impl SegmentInfo {
fn into_path<'path>(self, segments: Vec<Segment<'path>>) -> Path<'path> {
Path {
absolute: self.absolute,
double_dot_segment_count: self.double_dot_segment_count,
leading_double_dot_segment_count: self.leading_double_dot_segment_count,
segments,
single_dot_segment_count: self.single_dot_segment_count,
unnormalized_count: self.unnormalized_count,
}
}
}
#[allow(clippy::too_many_arguments)]
fn new_segment<'segment>(
segment: &'segment [u8],
segment_info: &mut SegmentInfo,
) -> Segment<'segment> {
if !segment_info.normalized {
segment_info.unnormalized_count += 1;
}
if segment == b"." {
segment_info.single_dot_segment_count += 1;
}
if segment == b".." {
let index = segment_info.index - 1;
segment_info.double_dot_segment_count += 1;
if index == 0 || segment_info.last_double_dot_segment == Some(index - 1) {
segment_info.leading_double_dot_segment_count += 1;
segment_info.last_double_dot_segment = Some(index);
}
}
Segment {
normalized: segment_info.normalized,
segment: Cow::from(unsafe { str::from_utf8_unchecked(segment) }),
}
}
let (value, absolute) = if value.starts_with(b"/") {
(&value[1..], true)
} else {
(value, false)
};
let mut bytes = value.iter();
let mut segment_info = SegmentInfo {
absolute,
double_dot_segment_count: 0,
index: 1,
last_double_dot_segment: None,
leading_double_dot_segment_count: 0,
normalized: true,
single_dot_segment_count: 0,
unnormalized_count: 0,
};
let mut segment_end_index = 0;
let mut segment_start_index = 0;
let mut segments = Vec::with_capacity(10);
while let Some(&byte) = bytes.next() {
match PATH_CHAR_MAP[byte as usize] {
0 if byte == b'?' || byte == b'#' => {
let segment = new_segment(
&value[segment_start_index..segment_end_index],
&mut segment_info,
);
segments.push(segment);
let path = segment_info.into_path(segments);
return Ok((path, &value[segment_end_index..]));
}
0 if byte == b'/' => {
let segment = new_segment(
&value[segment_start_index..segment_end_index],
&mut segment_info,
);
segments.push(segment);
segment_end_index += 1;
segment_start_index = segment_end_index;
segment_info.index = segment_info
.index
.checked_add(1)
.ok_or(PathError::ExceededMaximumLength)?;
segment_info.normalized = true;
}
0 => return Err(PathError::InvalidCharacter),
b'%' => match get_percent_encoded_value(bytes.next().cloned(), bytes.next().cloned()) {
Ok((hex_value, uppercase)) => {
if !uppercase || UNRESERVED_CHAR_MAP[hex_value as usize] != 0 {
segment_info.normalized = false;
}
segment_end_index += 3;
}
Err(_) => return Err(PathError::InvalidPercentEncoding),
},
_ => segment_end_index += 1,
}
}
let segment = new_segment(&value[segment_start_index..], &mut segment_info);
segments.push(segment);
let path = segment_info.into_path(segments);
Ok((path, b""))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_path_normalize() {
fn test_case(value: &str, expected: &str, as_reference: bool) {
let mut path = Path::try_from(value).unwrap();
path.normalize(as_reference);
let expected_single_dot_segment_count = if expected.starts_with("./") { 1 } else { 0 };
let expected_double_dot_segment_count = expected
.split('/')
.filter(|&segment| segment == "..")
.count() as u16;
assert!(!path.segments().is_empty());
assert!(path.is_normalized(as_reference));
assert_eq!(
path.single_dot_segment_count,
expected_single_dot_segment_count
);
assert_eq!(
path.double_dot_segment_count,
expected_double_dot_segment_count
);
assert_eq!(
path.leading_double_dot_segment_count,
expected_double_dot_segment_count
);
assert_eq!(path.to_string(), expected);
}
test_case("", "", true);
test_case(".", "", true);
test_case("..", "..", true);
test_case("../", "../", true);
test_case("/.", "/", true);
test_case("./././././././.", "", true);
test_case("././././././././", "", true);
test_case("/..", "/", true);
test_case("../..", "../..", true);
test_case("../a/../..", "../..", true);
test_case("a", "a", true);
test_case("a/..", "", true);
test_case("a/../", "", true);
test_case("a/../..", "..", true);
test_case("./a:b", "./a:b", true);
test_case("./a:b/..", "", true);
test_case("./a:b/../c:d", "./c:d", true);
test_case("./../a:b", "../a:b", true);
test_case("../a/../", "../", true);
test_case("../../.././.././../../../.", "../../../../../../..", true);
test_case("a/.././a:b", "./a:b", true);
test_case("", "", false);
test_case(".", "", false);
test_case("..", "", false);
test_case("../", "", false);
test_case("/.", "/", false);
test_case("/..", "/", false);
test_case("../../.././.././../../../.", "", false);
test_case("a/../..", "/", false);
test_case("a/../../", "/", false);
test_case("/a/../../../../", "/", false);
test_case("/a/./././././././c", "/a/c", false);
test_case("/a/.", "/a/", false);
test_case("/a/./", "/a/", false);
test_case("/a/..", "/", false);
test_case("/a/b/./..", "/a/", false);
test_case("/a/b/./../", "/a/", false);
test_case("/a/b/c/./../../g", "/a/g", false);
test_case("mid/content=5/../6", "mid/6", false);
test_case("this/is/a/t%65st/path/%ff", "this/is/a/test/path/%FF", true);
test_case(
"this/is/a/t%65st/path/%ff",
"this/is/a/test/path/%FF",
false,
);
}
#[test]
fn test_path_parse() {
use self::PathError::*;
let slash = "/".to_string();
assert_eq!(Path::try_from("").unwrap(), "");
assert_eq!(Path::try_from("/").unwrap(), "/");
assert_eq!(
Path::try_from("/tHiS/iS/a/PaTh").unwrap(),
"/tHiS/iS/a/PaTh"
);
assert_eq!(Path::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41");
assert!(Path::try_from(&*slash.repeat(65535)).is_ok());
assert_eq!(
Path::try_from(&*slash.repeat(65536)),
Err(ExceededMaximumLength)
);
assert_eq!(Path::try_from(" "), Err(InvalidCharacter));
assert_eq!(Path::try_from("#"), Err(InvalidCharacter));
assert_eq!(Path::try_from("%"), Err(InvalidPercentEncoding));
assert_eq!(Path::try_from("%f"), Err(InvalidPercentEncoding));
assert_eq!(Path::try_from("%zz"), Err(InvalidPercentEncoding));
}
#[test]
fn test_path_remove_dot_segments() {
fn test_case(value: &str, expected: &str) {
let mut path = Path::try_from(value).unwrap();
path.remove_dot_segments();
assert!(!path.segments().is_empty());
assert_eq!(path.single_dot_segment_count, 0);
assert_eq!(path.double_dot_segment_count, 0);
assert_eq!(path.leading_double_dot_segment_count, 0);
assert_eq!(path.to_string(), expected);
}
test_case("", "");
test_case(".", "");
test_case("..", "");
test_case("../", "");
test_case("/.", "/");
test_case("/..", "/");
test_case("../../.././.././../../../.", "");
test_case("a/../..", "/");
test_case("a/../../", "/");
test_case("/a/../../../..", "/");
test_case("/a/../../../../", "/");
test_case("/a/./././././././c", "/a/c");
test_case("/a/.", "/a/");
test_case("/a/./", "/a/");
test_case("/a/..", "/");
test_case("/a/b/./..", "/a/");
test_case("/a/b/./../", "/a/");
test_case("/a/b/c/./../../g", "/a/g");
test_case("mid/content=5/../6", "mid/6");
}
#[test]
fn test_segment_normalize() {
fn test_case(value: &str, expected: &str) {
let mut segment = Segment::try_from(value).unwrap();
segment.normalize();
assert_eq!(segment, expected);
}
test_case("", "");
test_case("%ff", "%FF");
test_case("%41", "A");
}
#[test]
fn test_segment_parse() {
use self::PathError::*;
assert_eq!(Segment::try_from("").unwrap(), "");
assert_eq!(Segment::try_from("segment").unwrap(), "segment");
assert_eq!(Segment::try_from("sEgMeNt").unwrap(), "sEgMeNt");
assert_eq!(Segment::try_from("%ff%ff%ff%41").unwrap(), "%ff%ff%ff%41");
assert_eq!(Segment::try_from(" "), Err(InvalidCharacter));
assert_eq!(Segment::try_from("/"), Err(InvalidCharacter));
assert_eq!(Segment::try_from("%"), Err(InvalidPercentEncoding));
assert_eq!(Segment::try_from("%f"), Err(InvalidPercentEncoding));
assert_eq!(Segment::try_from("%zz"), Err(InvalidPercentEncoding));
}
}