Implement normalize lexically
This commit is contained in:
@@ -2154,6 +2154,13 @@ pub struct Path {
|
||||
#[stable(since = "1.7.0", feature = "strip_prefix")]
|
||||
pub struct StripPrefixError(());
|
||||
|
||||
/// An error returned from [`Path::normalize_lexically`] if a `..` parent reference
|
||||
/// would escape the path.
|
||||
#[unstable(feature = "normalize_lexically", issue = "134694")]
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub struct NormalizeError;
|
||||
|
||||
impl Path {
|
||||
// The following (private!) function allows construction of a path from a u8
|
||||
// slice, which is only safe when it is known to follow the OsStr encoding.
|
||||
@@ -2961,6 +2968,67 @@ impl Path {
|
||||
fs::canonicalize(self)
|
||||
}
|
||||
|
||||
/// Normalize a path, including `..` without traversing the filesystem.
|
||||
///
|
||||
/// Returns an error if normalization would leave leading `..` components.
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// This function always resolves `..` to the "lexical" parent.
|
||||
/// That is "a/b/../c" will always resolve to `a/c` which can change the meaning of the path.
|
||||
/// In particular, `a/c` and `a/b/../c` are distinct on many systems because `b` may be a symbolic link, so its parent isn’t `a`.
|
||||
///
|
||||
/// </div>
|
||||
///
|
||||
/// [`path::absolute`](absolute) is an alternative that preserves `..`.
|
||||
/// Or [`Path::canonicalize`] can be used to resolve any `..` by querying the filesystem.
|
||||
#[unstable(feature = "normalize_lexically", issue = "134694")]
|
||||
pub fn normalize_lexically(&self) -> Result<PathBuf, NormalizeError> {
|
||||
let mut lexical = PathBuf::new();
|
||||
let mut iter = self.components().peekable();
|
||||
|
||||
// Find the root, if any, and add it to the lexical path.
|
||||
// Here we treat the Windows path "C:\" as a single "root" even though
|
||||
// `components` splits it into two: (Prefix, RootDir).
|
||||
let root = match iter.peek() {
|
||||
Some(Component::ParentDir) => return Err(NormalizeError),
|
||||
Some(p @ Component::RootDir) | Some(p @ Component::CurDir) => {
|
||||
lexical.push(p);
|
||||
iter.next();
|
||||
lexical.as_os_str().len()
|
||||
}
|
||||
Some(Component::Prefix(prefix)) => {
|
||||
lexical.push(prefix.as_os_str());
|
||||
iter.next();
|
||||
if let Some(p @ Component::RootDir) = iter.peek() {
|
||||
lexical.push(p);
|
||||
iter.next();
|
||||
}
|
||||
lexical.as_os_str().len()
|
||||
}
|
||||
None => return Ok(PathBuf::new()),
|
||||
Some(Component::Normal(_)) => 0,
|
||||
};
|
||||
|
||||
for component in iter {
|
||||
match component {
|
||||
Component::RootDir => unreachable!(),
|
||||
Component::Prefix(_) => return Err(NormalizeError),
|
||||
Component::CurDir => continue,
|
||||
Component::ParentDir => {
|
||||
// It's an error if ParentDir causes us to go above the "root".
|
||||
if lexical.as_os_str().len() == root {
|
||||
return Err(NormalizeError);
|
||||
} else {
|
||||
lexical.pop();
|
||||
}
|
||||
}
|
||||
Component::Normal(path) => lexical.push(path),
|
||||
}
|
||||
}
|
||||
Ok(lexical)
|
||||
}
|
||||
|
||||
/// Reads a symbolic link, returning the file that the link points to.
|
||||
///
|
||||
/// This is an alias to [`fs::read_link`].
|
||||
@@ -3502,6 +3570,15 @@ impl Error for StripPrefixError {
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "normalize_lexically", issue = "134694")]
|
||||
impl fmt::Display for NormalizeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("parent reference `..` points outside of base directory")
|
||||
}
|
||||
}
|
||||
#[unstable(feature = "normalize_lexically", issue = "134694")]
|
||||
impl Error for NormalizeError {}
|
||||
|
||||
/// Makes the path absolute without accessing the filesystem.
|
||||
///
|
||||
/// If the path is relative, the current directory is used as the base directory.
|
||||
|
||||
Reference in New Issue
Block a user