use crate::error::{Result, TransportError};
use std::path::{Component, Path, PathBuf};
pub fn sanitize_remote_path(raw: &str) -> Result<PathBuf> {
if raw.contains('\0') {
return Err(crate::error::IroshError::Transport(
TransportError::Transfer(crate::transport::transfer::TransferError::InvalidPath(
"path contains null byte".to_string(),
)),
));
}
let raw_path = Path::new(raw);
if raw_path.is_absolute() {
return Err(crate::error::IroshError::Transport(
TransportError::Transfer(crate::transport::transfer::TransferError::InvalidPath(
format!("absolute path not allowed: {}", raw),
)),
));
}
let mut sanitized = PathBuf::new();
for component in raw_path.components() {
match component {
Component::Normal(c) => sanitized.push(c),
Component::CurDir => {}
Component::ParentDir => {
if !sanitized.pop() {
return Err(crate::error::IroshError::Transport(
TransportError::Transfer(
crate::transport::transfer::TransferError::InvalidPath(format!(
"path traversal attempt detected: {}",
raw
)),
),
));
}
}
Component::RootDir | Component::Prefix(_) => {
return Err(crate::error::IroshError::Transport(
TransportError::Transfer(
crate::transport::transfer::TransferError::InvalidPath(format!(
"root or prefix components not allowed: {}",
raw
)),
),
));
}
}
}
if sanitized.as_os_str().is_empty() {
return Err(crate::error::IroshError::Transport(
TransportError::Transfer(crate::transport::transfer::TransferError::InvalidPath(
"sanitized path is empty".to_string(),
)),
));
}
Ok(sanitized)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sanitize_remote_path() {
assert_eq!(
sanitize_remote_path("file.txt").unwrap(),
PathBuf::from("file.txt")
);
assert_eq!(
sanitize_remote_path("dir/file.txt").unwrap(),
PathBuf::from("dir/file.txt")
);
assert_eq!(
sanitize_remote_path("./file.txt").unwrap(),
PathBuf::from("file.txt")
);
assert!(sanitize_remote_path("/etc/passwd").is_err());
assert!(sanitize_remote_path("../file.txt").is_err());
assert!(sanitize_remote_path("dir/../../file.txt").is_err());
assert_eq!(
sanitize_remote_path("dir/subdir/../file.txt").unwrap(),
PathBuf::from("dir/file.txt")
);
assert!(sanitize_remote_path("file\0.txt").is_err());
}
}