@@ -42,25 +42,16 @@ pub fn head_file_contents(repo: &Repository, path: &Path) -> String {
4242
4343/// Reads the contents of a file at the given path from the working directory of the repository.
4444/// Returns an empty string if the file does not exist on disk.
45+ /// Symlinks are followed, but only if the resolved target remains inside the repository workdir.
4546pub fn workdir_file_contents ( repo : & Repository , path : & Path ) -> String {
4647 let Some ( workdir) = repo. workdir ( ) else {
4748 return String :: new ( ) ;
4849 } ;
4950
50- let full_path = workdir. join ( path) ;
51- let Ok ( metadata) = std:: fs:: symlink_metadata ( & full_path) else {
52- return String :: new ( ) ;
53- } ;
54-
55- if metadata. file_type ( ) . is_symlink ( ) {
56- return std:: fs:: read_link ( & full_path)
57- . map ( |target| target. to_string_lossy ( ) . into_owned ( ) )
58- . unwrap_or_default ( ) ;
59- }
60-
6151 let Ok ( workdir_canonical) = workdir. canonicalize ( ) else {
6252 return String :: new ( ) ;
6353 } ;
54+ let full_path = workdir. join ( path) ;
6455 let Ok ( full_path_canonical) = full_path. canonicalize ( ) else {
6556 return String :: new ( ) ;
6657 } ;
@@ -189,4 +180,34 @@ mod tests {
189180 ""
190181 ) ;
191182 }
183+
184+ #[ cfg( unix) ]
185+ #[ test]
186+ fn workdir_file_contents_follows_symlink_inside_workdir ( ) {
187+ let temp = TempDir :: new ( ) . expect ( "create temp dir" ) ;
188+ let repo = Repository :: init ( temp. path ( ) ) . expect ( "init repo" ) ;
189+
190+ fs:: write ( temp. path ( ) . join ( "target.txt" ) , "inside\n " ) . expect ( "write target file" ) ;
191+ std:: os:: unix:: fs:: symlink ( "target.txt" , temp. path ( ) . join ( "link.txt" ) )
192+ . expect ( "create symlink" ) ;
193+
194+ assert_eq ! (
195+ workdir_file_contents( & repo, Path :: new( "link.txt" ) ) ,
196+ "inside\n "
197+ ) ;
198+ }
199+
200+ #[ cfg( unix) ]
201+ #[ test]
202+ fn workdir_file_contents_rejects_symlink_outside_workdir ( ) {
203+ let temp = TempDir :: new ( ) . expect ( "create temp dir" ) ;
204+ let repo = Repository :: init ( temp. path ( ) ) . expect ( "init repo" ) ;
205+ let outside = temp. path ( ) . join ( "../outside.txt" ) ;
206+ fs:: write ( & outside, "outside" ) . expect ( "write outside file" ) ;
207+
208+ std:: os:: unix:: fs:: symlink ( & outside, temp. path ( ) . join ( "link.txt" ) )
209+ . expect ( "create symlink" ) ;
210+
211+ assert_eq ! ( workdir_file_contents( & repo, Path :: new( "link.txt" ) ) , "" ) ;
212+ }
192213}
0 commit comments