//! Shared username sanitizer for path-segment use across pending/recovery stores. use std::path::{Path, PathBuf}; use thiserror::Error; #[derive(Debug, Error)] pub(crate) enum SegmentError { #[error("invalid username segment: {0:?}")] Invalid(String), } /// Reject any username that could escape a single path segment. Centralized /// so pending / recovery / future stores don't drift on what's "safe." pub(crate) fn join_user_segment(dir: &Path, user: &str) -> Result { if user.is_empty() || user == "." || user == ".." || user.contains('/') || user.contains('\0') { return Err(SegmentError::Invalid(user.to_string())); } Ok(dir.join(user)) } #[cfg(test)] mod tests { use super::*; #[test] fn accepts_normal_username() { let p = join_user_segment(Path::new("/tmp/x"), "alice").unwrap(); assert_eq!(p, PathBuf::from("/tmp/x/alice")); } #[test] fn rejects_traversal_and_separators() { for evil in ["", ".", "..", "a/b", "../etc", "x\0y"] { assert!( join_user_segment(Path::new("/tmp/x"), evil).is_err(), "{evil:?} should be rejected" ); } } }