use std::path::{Component, Path}; use crate::error::BabyError; pub fn validate_local_no_escape(path: &Path) -> Result<(), BabyError> { if path.as_os_str().is_empty() { return Ok(()); } if let Some(s) = path.to_str() { if s.starts_with('-') { return Err(BabyError::PathEscapesRepository); } } else { return Err(BabyError::PathEscapesRepository); } if path.is_absolute() { return Err(BabyError::PathEscapesRepository); } for comp in path.components() { if matches!(comp, Component::ParentDir) { return Err(BabyError::PathEscapesRepository); } } Ok(()) } pub fn validate_revision_non_empty(rev: &str) -> Result<(), BabyError> { if rev.is_empty() { return Err(BabyError::RevisionEmpty); } Ok(()) } pub fn validate_ref_name(name: &str) -> Result<(), BabyError> { if name.is_empty() || name == "@" { return Err(BabyError::InvalidRefName(name.to_string())); } if name.starts_with('-') { return Err(BabyError::InvalidRefName(name.to_string())); } if name.starts_with('/') || name.ends_with('/') { return Err(BabyError::InvalidRefName(name.to_string())); } if name.starts_with('.') || name.ends_with('.') { return Err(BabyError::InvalidRefName(name.to_string())); } if name.contains("..") || name.contains("//") || name.contains("@{") { return Err(BabyError::InvalidRefName(name.to_string())); } if name.contains(".lock/") || name.ends_with(".lock") { return Err(BabyError::InvalidRefName(name.to_string())); } if name.contains("/.") { return Err(BabyError::InvalidRefName(name.to_string())); } for ch in name.chars() { let b = ch as u32; if b < 0o40 || b == 0o177 { return Err(BabyError::InvalidRefName(name.to_string())); } match ch { ' ' | '~' | '^' | ':' | '?' | '*' | '[' | ']' => { return Err(BabyError::InvalidRefName(name.to_string())); } _ => {} } } Ok(()) } pub fn validate_ref_pattern(pattern: &str) -> Result<(), BabyError> { if pattern.is_empty() { return Err(BabyError::InvalidRefName(pattern.to_string())); } if pattern.starts_with('-') { return Err(BabyError::InvalidRefName(pattern.to_string())); } if pattern.contains("..") || pattern.contains("//") || pattern.contains("@{") { return Err(BabyError::InvalidRefName(pattern.to_string())); } for ch in pattern.chars() { let b = ch as u32; if b < 0o40 || b == 0o177 { return Err(BabyError::InvalidRefName(pattern.to_string())); } match ch { ' ' | '~' | '^' | ':' => { return Err(BabyError::InvalidRefName(pattern.to_string())); } _ => {} } } Ok(()) } pub fn to_string_lossy_owned(path: &Path) -> String { path.to_string_lossy().into_owned() } pub fn parse_git_mode(octal: &str) -> Result { u32::from_str_radix(octal, 8) .map_err(|_| BabyError::Custom(format!("invalid git file mode `{}`", octal))) } pub fn parse_object_id(hex: &str) -> Result { gix::ObjectId::from_hex(hex.as_bytes()) .map_err(|source| BabyError::Custom(format!("invalid object id `{}`: {}", hex, source))) } pub fn parse_object_map(content: &str) -> Result, BabyError> { let mut pairs = Vec::new(); for raw_line in content.lines() { let line = raw_line.trim(); if line.is_empty() || line.starts_with('#') || line.starts_with("old new") { continue; } let (old_hex, new_hex) = line .split_once(' ') .ok_or_else(|| BabyError::Custom(format!("object map line missing space: {}", line)))?; let old = parse_object_id(old_hex)?; let new = parse_object_id(new_hex)?; pairs.push((old, new)); } Ok(pairs) }