@@ -2154,6 +2154,13 @@ pub struct Path {
2154
2154
#[ stable( since = "1.7.0" , feature = "strip_prefix" ) ]
2155
2155
pub struct StripPrefixError ( ( ) ) ;
2156
2156
2157
+ /// An error returned from [`Path::normalize_lexically`] if a `..` parent reference
2158
+ /// would escape the path.
2159
+ #[ unstable( feature = "normalize_lexically" , issue = "134694" ) ]
2160
+ #[ derive( Debug , PartialEq ) ]
2161
+ #[ non_exhaustive]
2162
+ pub struct NormalizeError ;
2163
+
2157
2164
impl Path {
2158
2165
// The following (private!) function allows construction of a path from a u8
2159
2166
// slice, which is only safe when it is known to follow the OsStr encoding.
@@ -2961,6 +2968,64 @@ impl Path {
2961
2968
fs:: canonicalize ( self )
2962
2969
}
2963
2970
2971
+ /// Normalize a path, including `..` without traversing the filesystem.
2972
+ ///
2973
+ /// Returns an error if normalization would leave leading `..` components.
2974
+ ///
2975
+ /// <div class="warning">
2976
+ ///
2977
+ /// This function always resolves `..` to the "lexical" parent.
2978
+ /// That is "a/b/../c" will always resolve to `a/c` which can change the meaning of the path.
2979
+ /// 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`.
2980
+ ///
2981
+ /// </div>
2982
+ ///
2983
+ /// [`path::absolute`](absolute) is an alternative that preserves `..`.
2984
+ /// Or [`Path::canonicalize`] can be used to resolve any `..` by querying the filesystem.
2985
+ #[ unstable( feature = "normalize_lexically" , issue = "134694" ) ]
2986
+ pub fn normalize_lexically ( & self ) -> Result < PathBuf , NormalizeError > {
2987
+ let mut lexical = PathBuf :: new ( ) ;
2988
+ let mut iter = self . components ( ) . peekable ( ) ;
2989
+
2990
+ // Find the root, if any.
2991
+ let root = match iter. peek ( ) {
2992
+ Some ( Component :: ParentDir ) => return Err ( NormalizeError ) ,
2993
+ Some ( p @ Component :: RootDir ) | Some ( p @ Component :: CurDir ) => {
2994
+ lexical. push ( p) ;
2995
+ iter. next ( ) ;
2996
+ lexical. as_os_str ( ) . len ( )
2997
+ }
2998
+ Some ( Component :: Prefix ( prefix) ) => {
2999
+ lexical. push ( prefix. as_os_str ( ) ) ;
3000
+ iter. next ( ) ;
3001
+ if let Some ( p @ Component :: RootDir ) = iter. peek ( ) {
3002
+ lexical. push ( p) ;
3003
+ iter. next ( ) ;
3004
+ }
3005
+ lexical. as_os_str ( ) . len ( )
3006
+ }
3007
+ None => return Ok ( PathBuf :: new ( ) ) ,
3008
+ Some ( Component :: Normal ( _) ) => 0 ,
3009
+ } ;
3010
+
3011
+ for component in iter {
3012
+ match component {
3013
+ Component :: RootDir => unreachable ! ( ) ,
3014
+ Component :: Prefix ( _) => return Err ( NormalizeError ) ,
3015
+ Component :: CurDir => continue ,
3016
+ Component :: ParentDir => {
3017
+ if lexical. as_os_str ( ) . len ( ) == root {
3018
+ return Err ( NormalizeError ) ;
3019
+ } else {
3020
+ lexical. pop ( ) ;
3021
+ }
3022
+ }
3023
+ Component :: Normal ( path) => lexical. push ( path) ,
3024
+ }
3025
+ }
3026
+ Ok ( lexical)
3027
+ }
3028
+
2964
3029
/// Reads a symbolic link, returning the file that the link points to.
2965
3030
///
2966
3031
/// This is an alias to [`fs::read_link`].
@@ -3502,6 +3567,15 @@ impl Error for StripPrefixError {
3502
3567
}
3503
3568
}
3504
3569
3570
+ #[ unstable( feature = "normalize_lexically" , issue = "134694" ) ]
3571
+ impl fmt:: Display for NormalizeError {
3572
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
3573
+ f. write_str ( "parent reference `..` points outside of base directory" )
3574
+ }
3575
+ }
3576
+ #[ unstable( feature = "normalize_lexically" , issue = "134694" ) ]
3577
+ impl Error for NormalizeError { }
3578
+
3505
3579
/// Makes the path absolute without accessing the filesystem.
3506
3580
///
3507
3581
/// If the path is relative, the current directory is used as the base directory.
0 commit comments