use std::{fs, path::Path};
use crate::{Object, ObjectId, Result, error::invalid, inflate};
pub(crate) fn read(
objects: &Path,
id: ObjectId,
max_object_bytes: usize,
) -> Result<Option<Object>> {
let hex = id.to_hex();
let path = objects.join(&hex[..2]).join(&hex[2..]);
let compressed = match fs::read(path) {
Ok(value) => value,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(error.into()),
};
let header_budget = 64;
let inflate_limit = max_object_bytes
.checked_add(header_budget)
.ok_or_else(|| invalid("object size limit overflow"))?;
let (raw, used) = inflate::zlib(&compressed, inflate_limit)?;
if used != compressed.len() {
return Err(invalid("loose object has trailing compressed data"));
}
let nul = raw
.iter()
.position(|byte| *byte == 0)
.ok_or_else(|| invalid("loose object has no header terminator"))?;
let space = raw[..nul]
.iter()
.position(|byte| *byte == b' ')
.ok_or_else(|| invalid("loose object header has no size"))?;
let kind = &raw[..space];
let size = &raw[space + 1..nul];
let size = std::str::from_utf8(size)
.map_err(|_| invalid("loose object size is not ASCII"))?
.parse::<usize>()
.map_err(|_| invalid("invalid loose object size"))?;
let data = raw[nul + 1..].to_vec();
if data.len() != size {
return Err(invalid("loose object size mismatch"));
}
if size > max_object_bytes {
return Err(crate::GitError::LimitExceeded {
resource: "object bytes",
limit: max_object_bytes,
});
}
Ok(Some(Object {
id,
kind: crate::ObjectKind::parse(kind)?,
data,
}))
}