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

Code before:
	async fn casefold<P>(&self, path: P) -> io::Result<PathBuf>
	where
		P: AsRef<Path>,
	{
		let path = path.as_ref();
		let Some((parent, name)) = path.parent().zip(path.file_name()) else {
			return Ok(path.to_owned());
		};

		if !self.symlink_metadata(path).await?.is_link() {
			return match self.canonicalize(path).await?.file_name() {
				Some(name) => Ok(parent.join(name)),
				None => Err(io::Error::other("Cannot get filename")),
			};
		}

		let mut it = self.read_dir(parent).await?;
		let mut similar = None;
		while let Some(entry) = it.next().await? {
			let s = entry.name();
			if !s.eq_ignore_ascii_case(name) {
				continue;
			} else if s == name {
				return Ok(entry.path());
			} else {
				similar = Some(name);
			}
		}

		similar.map(|n| parent.join(n)).ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))
	}

	async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
	where
		P: AsRef<Path>,
		Q: AsRef<Path>,
	{
		let attrs = Attrs::from(cha);

		let op = self.op().await?;
		let from = op.open(&from, Flags::READ, &Attrs::default()).await?;
		let to = op.open(&to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, &attrs).await?;

		let mut reader = BufReader::with_capacity(524288, from);
		let mut writer = BufWriter::with_capacity(524288, to);

		let written = tokio::io::copy(&mut reader, &mut writer).await?;
		writer.into_inner().fsetstat(&attrs).await.ok();

		Ok(written)
	}

Review comment (security):
The variable assignment should be `Some(s)` instead of `Some(name)`. The logic should store the actual case-sensitive filename found in the directory (`s`), not the original search name (`name`).
```suggestion
				similar = Some(s);
```

Code after:
	async fn casefold<P>(&self, path: P) -> io::Result<PathBuf>
	where
		P: AsRef<Path>,
	{
		let path = path.as_ref();
		let Some((parent, name)) = path.parent().zip(path.file_name()) else {
			return Ok(path.to_owned());
		};

		if !self.symlink_metadata(path).await?.is_link() {
			return match self.canonicalize(path).await?.file_name() {
				Some(name) => Ok(parent.join(name)),
				None => Err(io::Error::other("Cannot get filename")),
			};
		}

		let mut it = self.read_dir(parent).await?;
		let mut similar = None;
		while let Some(entry) = it.next().await? {
			let s = entry.name();
			if !s.eq_ignore_ascii_case(name) {
				continue;
			} else if s == name {
				return Ok(entry.path());
			} else if similar.is_none() {
				similar = Some(s.into_owned());
			}
		}

		similar.map(|n| parent.join(n)).ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))
	}

	async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
	where
		P: AsRef<Path>,
		Q: AsRef<Path>,
	{
		let attrs = Attrs::from(cha);

		let op = self.op().await?;
		let from = op.open(&from, Flags::READ, &Attrs::default()).await?;
		let to = op.open(&to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, &attrs).await?;

		let mut reader = BufReader::with_capacity(524288, from);
		let mut writer = BufWriter::with_capacity(524288, to);

		let written = tokio::io::copy(&mut reader, &mut writer).await?;
		writer.into_inner().fsetstat(&attrs).await.ok();

		Ok(written)
	}