@@ -55,7 +55,8 @@ pub(crate) fn ensure_path_safe(base_dir: &Path, target_path: &Path) -> Result<()
5555 }
5656 Err ( ref e) if e. kind ( ) == ErrorKind :: NotFound => {
5757 // Target doesn't exist: Check safety based on its intended parent.
58- check_nonexistent_target_safety ( target_path, & canonical_base)
58+ // Pass the original target_path for error reporting context if needed.
59+ check_nonexistent_path_safety ( target_path, & canonical_base)
5960 }
6061 // *** CATCH NotADirectory during metadata check (parent is file) ***
6162 Err ( ref e) if e. kind ( ) == ErrorKind :: NotADirectory => {
@@ -71,68 +72,101 @@ pub(crate) fn ensure_path_safe(base_dir: &Path, target_path: &Path) -> Result<()
7172 }
7273}
7374
74- /// Checks safety for a target path that does not yet exist by examining its parent.
75- fn check_nonexistent_target_safety (
76- target_path : & Path ,
75+ /// Recursively checks safety for a path that does not necessarily exist
76+ /// by examining its ancestors relative to the canonical base.
77+ fn check_nonexistent_path_safety (
78+ path_to_check : & Path ,
7779 canonical_base : & Path ,
7880) -> Result < ( ) , ProcessError > {
79- if let Some ( parent) = target_path. parent ( ) {
80- // Canonicalize the parent directory.
81+ // Base case: If the path_to_check *is* the base directory, it's safe by definition.
82+ // We need to compare canonical paths if possible.
83+ match path_to_check. canonicalize ( ) {
84+ Ok ( canonical_check) if canonical_check == * canonical_base => return Ok ( ( ) ) ,
85+ Ok ( _) => { } // Path exists and is not the base, continue to parent check
86+ Err ( ref e) if e. kind ( ) == ErrorKind :: NotFound => { } // Path doesn't exist, continue
87+ Err ( ref e) if e. kind ( ) == ErrorKind :: NotADirectory => {
88+ // An intermediate component is a file.
89+ return Err ( ProcessError :: ParentIsNotDirectory {
90+ path : path_to_check. to_path_buf ( ) , // Report the path we were checking
91+ parent_path : path_to_check
92+ . parent ( )
93+ . unwrap_or ( path_to_check)
94+ . to_path_buf ( ) ,
95+ } ) ;
96+ }
97+ Err ( e) => {
98+ // Other canonicalization error (permissions?)
99+ return Err ( ProcessError :: PathResolution {
100+ path : path_to_check. to_path_buf ( ) ,
101+ details : format ! ( "Failed to canonicalize path during safety check: {}" , e) ,
102+ } ) ;
103+ }
104+ }
105+
106+ // Get the parent of the path we are currently checking.
107+ if let Some ( parent) = path_to_check. parent ( ) {
108+ // If the parent *is* the base directory, the path is safe (it's directly inside).
109+ // Compare canonical paths if possible for robustness.
81110 match parent. canonicalize ( ) {
82111 Ok ( canonical_parent) => {
83- // Check if the existing parent is within the base directory.
84- if canonical_parent. starts_with ( canonical_base) {
85- // Parent is safe, so creating the target inside it is considered safe.
86- Ok ( ( ) )
87- } else {
88- // Parent exists but is outside the base directory. Unsafe.
89- Err ( ProcessError :: PathNotSafe {
90- resolved_path : canonical_parent, // Report the parent path
112+ if canonical_parent == * canonical_base {
113+ return Ok ( ( ) ) ;
114+ }
115+ // Parent exists and is not the base. Check if it's *within* the base.
116+ if !canonical_parent. starts_with ( canonical_base) {
117+ return Err ( ProcessError :: PathNotSafe {
118+ resolved_path : canonical_parent, // Report the unsafe parent
91119 base_path : canonical_base. to_path_buf ( ) ,
92- } )
120+ } ) ;
93121 }
122+ // Parent exists and is within the base. Now, ensure it's actually a directory.
123+ match fs:: metadata ( & canonical_parent) {
124+ Ok ( meta) if meta. is_dir ( ) => {
125+ // Parent exists, is safe, and is a directory. Path is safe.
126+ Ok ( ( ) )
127+ }
128+ Ok ( _) => {
129+ // Parent exists, is safe, but is NOT a directory.
130+ Err ( ProcessError :: ParentIsNotDirectory {
131+ path : path_to_check. to_path_buf ( ) , // The path we were trying to create/check
132+ parent_path : parent. to_path_buf ( ) , // The parent that is not a directory
133+ } )
134+ }
135+ Err ( e) => {
136+ // Error getting metadata for the existing canonical parent (permissions?)
137+ Err ( ProcessError :: Io { source : e } )
138+ }
139+ }
140+ }
141+ Err ( ref e) if e. kind ( ) == ErrorKind :: NotFound => {
142+ // Parent does not exist. Recursively check the parent's safety.
143+ check_nonexistent_path_safety ( parent, canonical_base)
94144 }
95- // *** CATCH NotADirectory during PARENT canonicalization ***
96- Err ( ref parent_err ) if parent_err . kind ( ) == ErrorKind :: NotADirectory => {
145+ Err ( ref e ) if e . kind ( ) == ErrorKind :: NotADirectory => {
146+ // An intermediate component in the *parent's* path is a file.
97147 Err ( ProcessError :: ParentIsNotDirectory {
98- path : target_path . to_path_buf ( ) , // The target we were trying to create
99- parent_path : parent. to_path_buf ( ) , // The parent that is not a directory
148+ path : path_to_check . to_path_buf ( ) , // The original path being checked
149+ parent_path : parent. to_path_buf ( ) , // The parent path that failed
100150 } )
101151 }
102- Err ( ref parent_err) if parent_err. kind ( ) == ErrorKind :: NotFound => {
103- // Parent directory itself doesn't exist. This implies it needs to be
104- // created. Assume `create_dir_all` will handle safety within the base.
105- // We could recursively check the parent's parent, but relying on
106- // the initial base check and `create_dir_all` is often sufficient.
107- // If the non-existent parent's path *string* looks unsafe (e.g., "../.."),
108- // it might be caught earlier, but canonicalization handles this better.
109- // For simplicity here, assume okay if parent *would* be created inside base.
110- // A stricter check could trace the path components upwards.
111- // Let's check if the parent *path itself* starts with base logically.
112- if parent. starts_with ( canonical_base) {
113- Ok ( ( ) )
114- } else {
115- // If even the logical parent path isn't inside base, it's unsafe.
116- // This check is less robust than canonicalization but handles simple cases.
117- Err ( ProcessError :: PathNotSafe {
118- resolved_path : parent. to_path_buf ( ) , // Report logical parent
119- base_path : canonical_base. to_path_buf ( ) ,
120- } )
121- }
122- }
123- Err ( parent_err) => {
124- // Other error canonicalizing the parent (e.g., permissions).
152+ Err ( e) => {
153+ // Other error canonicalizing the parent (permissions?)
125154 Err ( ProcessError :: PathResolution {
126155 path : parent. to_path_buf ( ) ,
127- details : format ! ( "Failed to canonicalize parent directory: {}" , parent_err ) ,
156+ details : format ! ( "Failed to canonicalize parent directory: {}" , e ) ,
128157 } )
129158 }
130159 }
131160 } else {
132- // Cannot get parent (e.g., root path "/" or similar). Unlikely for relative paths.
161+ // Cannot get parent (e.g., root path "/" or similar).
162+ // If we reached here, it means the path wasn't the base itself.
163+ // This implies an attempt to access something outside the base, potentially the root.
133164 Err ( ProcessError :: PathResolution {
134- path : target_path. to_path_buf ( ) ,
135- details : "Cannot determine parent directory for safety check." . to_string ( ) ,
165+ path : path_to_check. to_path_buf ( ) ,
166+ details : "Cannot determine parent directory for safety check (potentially root path)."
167+ . to_string ( ) ,
136168 } )
137169 }
138170}
171+
172+ // --- REMOVED old check_nonexistent_target_safety function ---
0 commit comments