@@ -1031,6 +1031,9 @@ impl FileSystem for InMemoryFs {
10311031 }
10321032
10331033 async fn remove ( & self , path : & Path , recursive : bool ) -> Result < ( ) > {
1034+ self . limits
1035+ . validate_path ( path)
1036+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
10341037 let path = Self :: normalize_path ( path) ;
10351038 let mut entries = self . entries . write ( ) . unwrap ( ) ;
10361039
@@ -1072,6 +1075,9 @@ impl FileSystem for InMemoryFs {
10721075 }
10731076
10741077 async fn stat ( & self , path : & Path ) -> Result < Metadata > {
1078+ self . limits
1079+ . validate_path ( path)
1080+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
10751081 let path = Self :: normalize_path ( path) ;
10761082 let entries = self . entries . read ( ) . unwrap ( ) ;
10771083
@@ -1084,6 +1090,9 @@ impl FileSystem for InMemoryFs {
10841090 }
10851091
10861092 async fn read_dir ( & self , path : & Path ) -> Result < Vec < DirEntry > > {
1093+ self . limits
1094+ . validate_path ( path)
1095+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
10871096 let path = Self :: normalize_path ( path) ;
10881097 let entries = self . entries . read ( ) . unwrap ( ) ;
10891098
@@ -1116,12 +1125,21 @@ impl FileSystem for InMemoryFs {
11161125 }
11171126
11181127 async fn exists ( & self , path : & Path ) -> Result < bool > {
1128+ self . limits
1129+ . validate_path ( path)
1130+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11191131 let path = Self :: normalize_path ( path) ;
11201132 let entries = self . entries . read ( ) . unwrap ( ) ;
11211133 Ok ( entries. contains_key ( & path) )
11221134 }
11231135
11241136 async fn rename ( & self , from : & Path , to : & Path ) -> Result < ( ) > {
1137+ self . limits
1138+ . validate_path ( from)
1139+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
1140+ self . limits
1141+ . validate_path ( to)
1142+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11251143 let from = Self :: normalize_path ( from) ;
11261144 let to = Self :: normalize_path ( to) ;
11271145 let mut entries = self . entries . write ( ) . unwrap ( ) ;
@@ -1135,6 +1153,12 @@ impl FileSystem for InMemoryFs {
11351153 }
11361154
11371155 async fn copy ( & self , from : & Path , to : & Path ) -> Result < ( ) > {
1156+ self . limits
1157+ . validate_path ( from)
1158+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
1159+ self . limits
1160+ . validate_path ( to)
1161+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11381162 let from = Self :: normalize_path ( from) ;
11391163 let to = Self :: normalize_path ( to) ;
11401164 let mut entries = self . entries . write ( ) . unwrap ( ) ;
@@ -1144,11 +1168,24 @@ impl FileSystem for InMemoryFs {
11441168 . cloned ( )
11451169 . ok_or_else ( || IoError :: new ( ErrorKind :: NotFound , "not found" ) ) ?;
11461170
1171+ // Check write limits before creating the copy
1172+ let entry_size = match & entry {
1173+ FsEntry :: File { content, .. } => content. len ( ) as u64 ,
1174+ _ => 0 ,
1175+ } ;
1176+ let is_new = !entries. contains_key ( & to) ;
1177+ if is_new {
1178+ self . check_write_limits ( & entries, & to, entry_size as usize ) ?;
1179+ }
1180+
11471181 entries. insert ( to, entry) ;
11481182 Ok ( ( ) )
11491183 }
11501184
11511185 async fn symlink ( & self , target : & Path , link : & Path ) -> Result < ( ) > {
1186+ self . limits
1187+ . validate_path ( link)
1188+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11521189 let link = Self :: normalize_path ( link) ;
11531190 let mut entries = self . entries . write ( ) . unwrap ( ) ;
11541191
@@ -1170,6 +1207,9 @@ impl FileSystem for InMemoryFs {
11701207 }
11711208
11721209 async fn read_link ( & self , path : & Path ) -> Result < PathBuf > {
1210+ self . limits
1211+ . validate_path ( path)
1212+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11731213 let path = Self :: normalize_path ( path) ;
11741214 let entries = self . entries . read ( ) . unwrap ( ) ;
11751215
@@ -1181,6 +1221,9 @@ impl FileSystem for InMemoryFs {
11811221 }
11821222
11831223 async fn chmod ( & self , path : & Path , mode : u32 ) -> Result < ( ) > {
1224+ self . limits
1225+ . validate_path ( path)
1226+ . map_err ( |e| IoError :: other ( e. to_string ( ) ) ) ?;
11841227 let path = Self :: normalize_path ( path) ;
11851228 let mut entries = self . entries . write ( ) . unwrap ( ) ;
11861229
@@ -1686,4 +1729,63 @@ mod tests {
16861729 let result = fs. append_file ( Path :: new ( "/tmp/dir" ) , b"data" ) . await ;
16871730 assert ! ( result. is_err( ) ) ;
16881731 }
1732+
1733+ // Issue #421: validate_path should be called on all methods
1734+ #[ tokio:: test]
1735+ async fn test_validate_path_on_copy ( ) {
1736+ let limits = FsLimits :: new ( ) . max_path_depth ( 3 ) ;
1737+ let fs = InMemoryFs :: with_limits ( limits) ;
1738+ fs. write_file ( Path :: new ( "/tmp/src.txt" ) , b"data" )
1739+ . await
1740+ . unwrap ( ) ;
1741+
1742+ let deep = Path :: new ( "/a/b/c/d/e/f.txt" ) ;
1743+ let result = fs. copy ( Path :: new ( "/tmp/src.txt" ) , deep) . await ;
1744+ assert ! ( result. is_err( ) , "copy to deep path should be rejected" ) ;
1745+ }
1746+
1747+ #[ tokio:: test]
1748+ async fn test_validate_path_on_rename ( ) {
1749+ let limits = FsLimits :: new ( ) . max_path_depth ( 3 ) ;
1750+ let fs = InMemoryFs :: with_limits ( limits) ;
1751+ fs. write_file ( Path :: new ( "/tmp/src.txt" ) , b"data" )
1752+ . await
1753+ . unwrap ( ) ;
1754+
1755+ let deep = Path :: new ( "/a/b/c/d/e/f.txt" ) ;
1756+ let result = fs. rename ( Path :: new ( "/tmp/src.txt" ) , deep) . await ;
1757+ assert ! ( result. is_err( ) , "rename to deep path should be rejected" ) ;
1758+ }
1759+
1760+ #[ tokio:: test]
1761+ async fn test_copy_respects_write_limits ( ) {
1762+ let limits = FsLimits :: new ( ) . max_file_count ( 10 ) ;
1763+ let fs = InMemoryFs :: with_limits ( limits) ;
1764+
1765+ // Fill up to limit
1766+ for i in 0 ..10 {
1767+ let _ = fs
1768+ . write_file ( Path :: new ( & format ! ( "/tmp/f{i}.txt" ) ) , b"x" )
1769+ . await ;
1770+ }
1771+
1772+ // Copy should fail - at file count limit
1773+ let result = fs
1774+ . copy ( Path :: new ( "/tmp/f0.txt" ) , Path :: new ( "/tmp/copy.txt" ) )
1775+ . await ;
1776+ assert ! (
1777+ result. is_err( ) ,
1778+ "copy should respect file count write limits"
1779+ ) ;
1780+ }
1781+
1782+ #[ tokio:: test]
1783+ async fn test_validate_path_on_chmod ( ) {
1784+ let limits = FsLimits :: new ( ) . max_path_depth ( 3 ) ;
1785+ let fs = InMemoryFs :: with_limits ( limits) ;
1786+
1787+ let deep = Path :: new ( "/a/b/c/d/e/f.txt" ) ;
1788+ let result = fs. chmod ( deep, 0o755 ) . await ;
1789+ assert ! ( result. is_err( ) , "chmod on deep path should be rejected" ) ;
1790+ }
16891791}
0 commit comments