Skip to content
Clone
7b02b53
123 lines 4.0 KB Rust
use crate::share::errors::GitError;
/// Maximum length of a lock path (git-lfs enforces a similar cap client-side).
pub const MAX_LOCK_PATH_LEN: usize = 1024;
/// Normalize a client-supplied oid into the canonical form used as the
/// storage key / DB value: lowercase `sha256:<64 hex>`.
///
/// Both the spec form (`sha256:<hex>`) and the bare 64-hex form accepted by
/// some servers/clients are accepted.
pub fn canonical_oid(raw: &str) -> Result<String, GitError> {
let raw = raw.trim();
let hex = match raw.strip_prefix("sha256:") {
Some(hex) => hex,
None if raw.len() == 64 => raw,
None => return Err(oid_error(raw)),
};
if hex.len() != 64 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(oid_error(raw));
}
let mut out = String::with_capacity(70);
out.push_str("sha256:");
for c in hex.bytes() {
out.push(c.to_ascii_lowercase() as char);
}
Ok(out)
}
pub fn is_canonical_oid(oid: &str) -> bool {
oid.len() == 71 && oid.starts_with("sha256:") && oid[7..].bytes().all(|b| b.is_ascii_hexdigit())
}
/// Reject zero/negative sizes and anything above the configured cap.
pub fn validate_size(size: i64, cap_bytes: u64) -> Result<(), GitError> {
if size < 0 {
return Err(GitError::InvalidArgument(format!("invalid size {size}")));
}
let bytes = size as u64;
if bytes > cap_bytes {
return Err(GitError::InvalidArgument(format!(
"object size {bytes} exceeds limit {cap_bytes}"
)));
}
Ok(())
}
/// Locks paths must be non-empty, NUL-free and within a sane length.
pub fn validate_lock_path(path: &str) -> Result<&str, GitError> {
let path = path.trim();
if path.is_empty() {
return Err(GitError::InvalidArgument(
"lock path must not be empty".to_string(),
));
}
if path.len() > MAX_LOCK_PATH_LEN {
return Err(GitError::InvalidArgument(format!(
"lock path exceeds {MAX_LOCK_PATH_LEN} chars"
)));
}
if path.contains('\0') {
return Err(GitError::InvalidArgument(
"lock path contains NUL".to_string(),
));
}
Ok(path)
}
fn oid_error(raw: &str) -> GitError {
GitError::InvalidArgument(format!("invalid oid `{raw}` (expected sha256:<64 hex>)"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonicalizes_spec_and_bare_oids() {
let hex = "a".repeat(64);
assert_eq!(
canonical_oid(&format!("sha256:{hex}")).unwrap(),
format!("sha256:{hex}")
);
assert_eq!(canonical_oid(&hex).unwrap(), format!("sha256:{hex}"));
let upper = "A".repeat(64);
assert_eq!(
canonical_oid(&format!("sha256:{upper}")).unwrap(),
format!("sha256:{}", "a".repeat(64))
);
}
#[test]
fn rejects_bad_oids() {
assert!(canonical_oid("").is_err());
assert!(canonical_oid("sha1:abcd").is_err());
assert!(canonical_oid("sha256:xyz").is_err());
assert!(canonical_oid(&"a".repeat(63)).is_err());
assert!(canonical_oid(&format!("sha256:{}g", "a".repeat(63))).is_err());
assert!(canonical_oid(&format!("sha256:{}", "a".repeat(65))).is_err());
}
#[test]
fn is_canonical_checks_exact_shape() {
assert!(is_canonical_oid(&format!("sha256:{}", "a".repeat(64))));
assert!(!is_canonical_oid(&"a".repeat(64)));
assert!(!is_canonical_oid(&format!("SHA256:{}", "a".repeat(64))));
}
#[test]
fn size_validation_respects_cap() {
assert!(validate_size(0, 100).is_ok());
assert!(validate_size(100, 100).is_ok());
assert!(validate_size(101, 100).is_err());
assert!(validate_size(-1, 100).is_err());
}
#[test]
fn lock_path_validation() {
assert_eq!(validate_lock_path(" a/b.png ").unwrap(), "a/b.png");
assert!(validate_lock_path(" ").is_err());
assert!(validate_lock_path(&"a".repeat(1025)).is_err());
assert!(validate_lock_path("a\0b").is_err());
}
}