fn is_hex(b: u8) -> bool { b.is_ascii_hexdigit() } pub fn validate_oid(s: &str, len: u64) -> Result<(), crate::Error> { if s.len() as u64 != len || !s.bytes().all(is_hex) { return Err(crate::Error::InvalidRevSpec(format!( "invalid object id: expected {len} hex chars" ))); } Ok(()) } pub fn validate_short_name(kind: &str, name: &str) -> Result<(), crate::Error> { if name.is_empty() { return Err(crate::Error::InvalidRefName(format!("empty {kind} name"))); } if name.contains('/') || name.contains("..") || name.starts_with('/') { return Err(crate::Error::InvalidRefName(format!( "invalid {kind} name: {name}" ))); } if name.starts_with("refs/") { return Err(crate::Error::InvalidRefName(format!( "expected short {kind} name, got qualified: {name}" ))); } if name .bytes() .any(|b| b <= 0x20 || b == 0x7f || b == b'~' || b == b'^' || b == b':') { return Err(crate::Error::InvalidRefName(format!( "invalid {kind} name: {name}" ))); } Ok(()) } pub fn validate_qualified_ref(name: &str) -> Result<(), crate::Error> { if !name.starts_with("refs/") { return Err(crate::Error::InvalidRefName(format!( "expected qualified ref, got: {name}" ))); } if name.contains("..") || name.contains("@{") || name.contains("//") { return Err(crate::Error::InvalidRefName(format!( "invalid ref name: {name}" ))); } Ok(()) } /// Validate a remote URL scheme; returns the scheme if allowed. pub fn allowed_remote_scheme(url: &str) -> Result<(), crate::Error> { // are allowed for remote operations. let scheme = url .split_once("://") .map(|(s, _)| s.to_ascii_lowercase()) .unwrap_or_default(); match scheme.as_str() { "http" | "https" | "ssh" => Ok(()), _ => Err(crate::Error::InvalidRevSpec(format!( "remote url scheme not allowed: {url}" ))), } } pub const MAX_OBJECT_SIZE: u64 = 100 * 1024 * 1024; pub const MAX_BLOB_SIZE: u64 = 100 * 1024 * 1024; pub fn enforce_max_size(data: &[u8], max: u64, what: &str) -> Result<(), crate::Error> { if data.len() as u64 > max { return Err(crate::Error::Other(format!( "{what} exceeds maximum size of {max} bytes" ))); } Ok(()) }