Repository: sxyazi/yazi
File: yazi-fs/src/provider/local/casefold.rs (Rust)

Code before:
}

#[cfg(target_os = "windows")]
fn valid_name_case_impl(path: PathBuf) -> io::Result<bool> {
	use std::{ffi::OsString, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}};

	use windows_sys::Win32::{Foundation::{HANDLE, MAX_PATH}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}};

	let Some(name) = path.file_name() else { return Ok(true) };

	let file = std::fs::OpenOptions::new()
		.access_mode(0)
		.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
		.open(&path)?;

	let mut buf = [0u16; MAX_PATH as usize];
	let len = unsafe {
		GetFinalPathNameByHandleW(
			file.as_raw_handle() as HANDLE,
			buf.as_mut_ptr(),
			buf.len() as u32,
			VOLUME_NAME_DOS,
		)
	};

	if len == 0 {
		Err(io::Error::last_os_error())
	} else {
		Ok(PathBuf::from(OsString::from_wide(&buf[0..len as usize])).file_name() == Some(name))
	}
}


Review comment (security):
[nitpick] The logic for handling different numbers of entries (1, exact match, case-insensitive match) could be simplified and made more readable by restructuring the conditional flow.
```suggestion
	// Prefer exact match, then case-insensitive match, then unique inode match
	if let Some(i) = entries.iter().position(|p| p == path) {
		// Exact match
		Ok(entries[i].clone())
	} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) {
		// Case-insensitive match
		Ok(entries[i].clone())
	} else if entries.len() == 1 {
		// Only one entry with matching inode
		Ok(entries[0].clone())
```

Code after:
		&& let Some(b) = p.as_os_str().as_bytes().get(..oss.len())
		&& b.eq_ignore_ascii_case(oss.as_bytes())
	{
		let mut b = p.into_os_string().into_vec();
		b.truncate(oss.len());
		return Ok(PathBuf::from(OsString::from_vec(b)));
	}

	// Fallback: scan the directory for matching inodes
	let meta = file.metadata()?;
	let mut entries: Vec<_> = std::fs::read_dir(parent)?
		.filter_map(Result::ok)
		.filter_map(|e| e.metadata().ok().map(|m| (e, m)))
		.filter(|(_, m)| m.dev() == meta.dev() && m.ino() == meta.ino())
		.map(|(e, _)| e.path())
		.collect();

	if entries.len() == 1 {
		// No hardlink that shares the same inode
		Ok(entries.remove(0))
	} else if let Some(i) = entries.iter().position(|p| p == path) {
		// Exact match
		Ok(entries.swap_remove(i))
	} else if let Some(i) = entries.iter().position(|p| p.as_os_str().eq_ignore_ascii_case(oss)) {
		// Case-insensitive match
		Ok(entries.swap_remove(i))
	} else {
		Err(io::Error::new(io::ErrorKind::NotFound, "file not found"))
	}
}

#[cfg(target_os = "windows")]
fn casefold_impl(path: PathBuf) -> io::Result<PathBuf> {
	use std::{ffi::OsString, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}};

	use windows_sys::Win32::{Foundation::{HANDLE, MAX_PATH}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}};

	let file = std::fs::OpenOptions::new()
		.access_mode(0)
		.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
		.open(&path)?;

	let mut buf = [0u16; MAX_PATH as usize];
	let len = unsafe {
		GetFinalPathNameByHandleW(
			file.as_raw_handle() as HANDLE,
			buf.as_mut_ptr(),
			buf.len() as u32,
			VOLUME_NAME_DOS,
		)
	};