use crate::errors::FileParseError;
use crate::field::Field;
const GLOBAL_MAGIC: &[u8; 8] = b"!<arch>\n";
pub struct ArchiveMember {
pub header_offset: usize,
pub name: Field<String>,
pub timestamp: Field<String>,
pub owner_id: Field<String>,
pub group_id: Field<String>,
pub mode: Field<String>,
pub size: Field<u64>,
pub data_offset: usize,
}
impl ArchiveMember {
pub fn data<'a>(&self, buffer: &'a [u8]) -> Result<&'a [u8], FileParseError> {
let end = self
.data_offset
.checked_add(self.size.value as usize)
.ok_or(FileParseError::BufferOverflow)?;
buffer
.get(self.data_offset..end)
.ok_or(FileParseError::BufferOverflow)
}
}
pub struct Archive {
pub members: Vec<ArchiveMember>,
}
impl Archive {
pub fn is_archive(buffer: &[u8]) -> bool {
buffer.starts_with(GLOBAL_MAGIC)
}
pub fn parse(buffer: &[u8]) -> Result<Self, FileParseError> {
if !Self::is_archive(buffer) {
return Err(FileParseError::InvalidFileFormat);
}
let mut cursor = GLOBAL_MAGIC.len();
let mut members = Vec::new();
while cursor + 60 <= buffer.len() {
let header_offset = cursor;
let name = field_string(buffer, cursor, 16)?;
let timestamp = field_string(buffer, cursor + 16, 12)?;
let owner_id = field_string(buffer, cursor + 28, 6)?;
let group_id = field_string(buffer, cursor + 34, 6)?;
let mode = field_string(buffer, cursor + 40, 8)?;
let size_text = field_string(buffer, cursor + 48, 10)?;
if buffer.get(cursor + 58..cursor + 60) != Some(&b"`\n"[..]) {
return Err(FileParseError::InvalidFileFormat);
}
let size = size_text
.value
.trim()
.parse::<u64>()
.map_err(|_| FileParseError::InvalidFileFormat)?;
let data_offset = cursor + 60;
members.push(ArchiveMember {
header_offset,
name,
timestamp,
owner_id,
group_id,
mode,
size: Field::new(size, cursor + 48, 10),
data_offset,
});
cursor = data_offset
.checked_add(size as usize)
.ok_or(FileParseError::BufferOverflow)?;
if !cursor.is_multiple_of(2) {
cursor += 1;
}
}
Ok(Self { members })
}
}
fn field_string(
buffer: &[u8],
offset: usize,
size: usize,
) -> Result<Field<String>, FileParseError> {
let bytes = buffer
.get(offset..offset + size)
.ok_or(FileParseError::BufferOverflow)?;
Ok(Field::new(
String::from_utf8_lossy(bytes).trim_end().to_string(),
offset,
size,
))
}