cap_primitives/fs/
read_link.rs1use crate::fs::{errors, read_link_impl};
5#[cfg(racy_asserts)]
6use crate::fs::{map_result, read_link_unchecked, stat, FollowSymlinks};
7use std::path::{Path, PathBuf};
8use std::{fs, io};
9
10#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
13#[inline]
14pub fn read_link_contents(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
15 let result = read_link_impl(start, path);
17
18 #[cfg(racy_asserts)]
19 let unchecked = read_link_unchecked(start, path, PathBuf::new());
20
21 #[cfg(racy_asserts)]
22 check_read_link(start, path, &result, &unchecked);
23
24 result
25}
26
27#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
31#[inline]
32pub fn read_link(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
33 let result = read_link_contents(start, path);
35
36 if let Ok(path) = &result {
42 if path.has_root() {
43 return Err(errors::escape_attempt());
44 }
45 }
46
47 result
48}
49
50#[cfg(racy_asserts)]
51#[allow(clippy::enum_glob_use)]
52fn check_read_link(
53 start: &fs::File,
54 path: &Path,
55 result: &io::Result<PathBuf>,
56 unchecked: &io::Result<PathBuf>,
57) {
58 use io::ErrorKind::*;
59
60 match (map_result(result), map_result(unchecked)) {
61 (Ok(target), Ok(unchecked_target)) => {
62 assert_eq!(target, unchecked_target);
63 }
64
65 (Err((PermissionDenied, message)), _) => {
66 match map_result(&stat(start, path, FollowSymlinks::No)) {
67 Err((PermissionDenied, canon_message)) => {
68 assert_eq!(message, canon_message);
69 }
70 _ => panic!("read_link failed where canonicalize succeeded"),
71 }
72 }
73
74 (Err((_kind, _message)), Err((_unchecked_kind, _unchecked_message))) => {
75 }
80
81 other => panic!(
82 "unexpected result from read_link start='{:?}', path='{}':\n{:#?}",
83 start,
84 path.display(),
85 other,
86 ),
87 }
88}
89
90#[cfg(not(windows))]
91#[test]
92fn test_read_link_contents() {
93 use io_lifetimes::AsFilelike;
94 let td = cap_tempfile::tempdir(cap_tempfile::ambient_authority()).unwrap();
95 let td_view = &td.as_filelike_view::<std::fs::File>();
96 let valid = [
97 "relative/path",
98 "/some/absolute/path",
99 "/",
100 "../",
101 "basepath",
102 ];
103 for case in valid {
104 let linkname = Path::new("linkname");
105 crate::fs::symlink_contents(case, td_view, linkname).unwrap();
106 let contents = crate::fs::read_link_contents(td_view, linkname).unwrap();
107 assert_eq!(contents.to_str().unwrap(), case);
108 crate::fs::remove_file(td_view, linkname).unwrap();
109 }
110}