@@ -2154,6 +2154,13 @@ pub struct Path {
21542154#[ stable( since = "1.7.0" ,  feature = "strip_prefix" ) ]  
21552155pub  struct  StripPrefixError ( ( ) ) ; 
21562156
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+ 
21572164impl  Path  { 
21582165    // The following (private!) function allows construction of a path from a u8 
21592166    // slice, which is only safe when it is known to follow the OsStr encoding. 
@@ -2961,6 +2968,67 @@ impl Path {
29612968        fs:: canonicalize ( self ) 
29622969    } 
29632970
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, and add it to the lexical path. 
2991+         // Here we treat the Windows path "C:\" as a single "root" even though 
2992+         // `components` splits it into two: (Prefix, RootDir). 
2993+         let  root = match  iter. peek ( )  { 
2994+             Some ( Component :: ParentDir )  => return  Err ( NormalizeError ) , 
2995+             Some ( p @ Component :: RootDir )  | Some ( p @ Component :: CurDir )  => { 
2996+                 lexical. push ( p) ; 
2997+                 iter. next ( ) ; 
2998+                 lexical. as_os_str ( ) . len ( ) 
2999+             } 
3000+             Some ( Component :: Prefix ( prefix) )  => { 
3001+                 lexical. push ( prefix. as_os_str ( ) ) ; 
3002+                 iter. next ( ) ; 
3003+                 if  let  Some ( p @ Component :: RootDir )  = iter. peek ( )  { 
3004+                     lexical. push ( p) ; 
3005+                     iter. next ( ) ; 
3006+                 } 
3007+                 lexical. as_os_str ( ) . len ( ) 
3008+             } 
3009+             None  => return  Ok ( PathBuf :: new ( ) ) , 
3010+             Some ( Component :: Normal ( _) )  => 0 , 
3011+         } ; 
3012+ 
3013+         for  component in  iter { 
3014+             match  component { 
3015+                 Component :: RootDir  => unreachable ! ( ) , 
3016+                 Component :: Prefix ( _)  => return  Err ( NormalizeError ) , 
3017+                 Component :: CurDir  => continue , 
3018+                 Component :: ParentDir  => { 
3019+                     // It's an error if ParentDir causes us to go above the "root". 
3020+                     if  lexical. as_os_str ( ) . len ( )  == root { 
3021+                         return  Err ( NormalizeError ) ; 
3022+                     }  else  { 
3023+                         lexical. pop ( ) ; 
3024+                     } 
3025+                 } 
3026+                 Component :: Normal ( path)  => lexical. push ( path) , 
3027+             } 
3028+         } 
3029+         Ok ( lexical) 
3030+     } 
3031+ 
29643032    /// Reads a symbolic link, returning the file that the link points to. 
29653033/// 
29663034/// This is an alias to [`fs::read_link`]. 
@@ -3502,6 +3570,15 @@ impl Error for StripPrefixError {
35023570    } 
35033571} 
35043572
3573+ #[ unstable( feature = "normalize_lexically" ,  issue = "134694" ) ]  
3574+ impl  fmt:: Display  for  NormalizeError  { 
3575+     fn  fmt ( & self ,  f :  & mut  fmt:: Formatter < ' _ > )  -> fmt:: Result  { 
3576+         f. write_str ( "parent reference `..` points outside of base directory" ) 
3577+     } 
3578+ } 
3579+ #[ unstable( feature = "normalize_lexically" ,  issue = "134694" ) ]  
3580+ impl  Error  for  NormalizeError  { } 
3581+ 
35053582/// Makes the path absolute without accessing the filesystem. 
35063583/// 
35073584/// If the path is relative, the current directory is used as the base directory. 
0 commit comments