the obvious answer would be something like this:
(ignore the atrocious error handling, it makes the code shorter)
...
let path = ...;
let canonical = path.canonicalize();
if path != canonical {
panic!();
}
// the path can't contain symlinks as that would make it differ from canonical.
let file = File::open(path).unwrap();
let mut buffer = String::new();
file.read_to_string(&mut buffer).unwrap();
...
but that is fundementally flawed, some entry in path could be replaced by a symlink after the check, but before the file is opened and read.
The thing with paths is: This can always happen, even if it is not a symlink. There could always be a rename changing out the path underneath you. If you really want to be sure you are in the same directory you just checked, you need to use a dirfd.
You can use attributes(0x00200000) from std::os::windows::fs::OpenOptionsExt to open the file or symlink with FILE_FLAG_OPEN_REPARSE_POINT, but unlike O_NOFOLLOW on Linux, this will succeed. You’ll probably just want to check f.metadata()?.is_symlink() after opening it in this case.
does FILE_FLAG_OPEN_REPARSE_POINT follow directory symlinks halfway through the path or does it stop at the first symlink? (e.g. if i open foo/<symlink to elswhere>/bar will it open bar as a normal file or stop at the symlink and open that without following it?)
while looking up the maze that is win32 documentation to look up your attribute i also found FILE_FLAG_DISALLOW_PATH_REDIRECTS0x00010000, which sounds like a exact match for the linux flag,
but its only available in CreateFile3 and std uses CreateFile2 (why is win32 like this?)
I think FILE_FLAG_OPEN_REPARSE_POINT still follows directory symlinks, junction points and mount points, the same as O_NOFOLLOW. It looks like FILE_FLAG_DISALLOW_PATH_REDIRECTS does what you want, though it will allow mount points. I didn’t know about it because it’s only available from Windows 11 24H2, and I haven’t been keeping up to date with Win32.
If your program will only ever run on the latest versions of Windows you could call CreateFile3 yourself and convert the resulting handle into a File, but it wouldn’t work on Windows 10 LTSC or Windows IoT.
oh, that's why std doesn't use CreateFile3, so i'll just have to accept my library being being less secure on windows or find some workaround.
there are functions like GetFinalPathNameByHandleW (which has 4+ versions, and i'd assume only some work on old windows), but unless there's a std-compatible wrapper on crates.io they look like too much of a pain to use.
GetFinalPathNameByHandleW is used by std::fs::canonicalize in the standard library, so it should be available everywhere. Using it via canonicalize might not be right for you because of the time-of-check to time-of-use problem.
I’m not sure how I’d use it anyway, since the path that comes out will almost always be different from what went in, even without symlinks, like c:/WINDOWS/System32/notepad32.exe’s final path being something like \\?\C:\windows\system32\notepad.exe.