use gamut_core::{Error, Result};
use crate::{ByteOrder, FieldType, Ifd, Value, Variant};
#[derive(Debug, Clone, PartialEq)]
pub struct TiffFile {
pub order: ByteOrder,
pub variant: Variant,
pub ifds: Vec<Ifd>,
}
const MAX_IFDS: usize = 1 << 16;
fn u16_at(data: &[u8], pos: usize, order: ByteOrder) -> Result<u16> {
let b = data
.get(pos..pos + 2)
.ok_or(Error::InvalidInput("TIFF: truncated 16-bit field"))?;
Ok(order.u16([b[0], b[1]]))
}
fn u32_at(data: &[u8], pos: usize, order: ByteOrder) -> Result<u32> {
let b = data
.get(pos..pos + 4)
.ok_or(Error::InvalidInput("TIFF: truncated 32-bit field"))?;
Ok(order.u32([b[0], b[1], b[2], b[3]]))
}
#[cfg(feature = "bigtiff")]
fn u64_at(data: &[u8], pos: usize, order: ByteOrder) -> Result<u64> {
let b = data
.get(pos..pos + 8)
.ok_or(Error::InvalidInput("TIFF: truncated 64-bit field"))?;
Ok(order.u64([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]))
}
fn offset_at(data: &[u8], pos: usize, order: ByteOrder, variant: Variant) -> Result<u64> {
match variant {
Variant::Classic => Ok(u64::from(u32_at(data, pos, order)?)),
#[cfg(feature = "bigtiff")]
Variant::Big => u64_at(data, pos, order),
}
}
pub fn read_header(data: &[u8]) -> Result<(ByteOrder, Variant, u64)> {
let head = data
.get(..8)
.ok_or(Error::InvalidInput("TIFF: header too short"))?;
let order = match [head[0], head[1]] {
[0x49, 0x49] => ByteOrder::LittleEndian,
[0x4D, 0x4D] => ByteOrder::BigEndian,
_ => return Err(Error::InvalidInput("TIFF: bad byte-order mark")),
};
match order.u16([head[2], head[3]]) {
42 => Ok((order, Variant::Classic, u64::from(u32_at(data, 4, order)?))),
#[cfg(feature = "bigtiff")]
43 => {
if order.u16([head[4], head[5]]) != 8 {
return Err(Error::InvalidInput("TIFF: BigTIFF offset size must be 8"));
}
if order.u16([head[6], head[7]]) != 0 {
return Err(Error::InvalidInput(
"TIFF: BigTIFF reserved field must be 0",
));
}
Ok((order, Variant::Big, u64_at(data, 8, order)?))
}
_ => Err(Error::InvalidInput("TIFF: bad magic number")),
}
}
fn read_ifd(data: &[u8], offset: usize, order: ByteOrder, variant: Variant) -> Result<(Ifd, u64)> {
let entry_size = variant.entry_size();
let inline = variant.inline_threshold();
let count = match variant {
Variant::Classic => u64::from(u16_at(data, offset, order)?),
#[cfg(feature = "bigtiff")]
Variant::Big => u64_at(data, offset, order)?,
} as usize;
let entries_start = offset + variant.count_size();
let next_pos = entries_start + count * entry_size;
if next_pos + variant.offset_size() > data.len() {
return Err(Error::InvalidInput("TIFF: IFD extends past end of file"));
}
let mut ifd = Ifd::new();
for i in 0..count {
let pos = entries_start + i * entry_size;
let tag = u16_at(data, pos, order)?;
let type_code = u16_at(data, pos + 2, order)?;
let value_count = offset_at(data, pos + 4, order, variant)? as usize;
let value_pos = pos + 4 + variant.offset_size();
let Some(ty) = FieldType::from_code(type_code) else {
continue;
};
let byte_len = value_count
.checked_mul(ty.size())
.ok_or(Error::InvalidInput("TIFF: field length overflow"))?;
let value = if byte_len <= inline {
Value::decode(ty, value_count, &data[value_pos..value_pos + inline], order)?
} else {
let voff = offset_at(data, value_pos, order, variant)? as usize;
let bytes = data
.get(voff..)
.ok_or(Error::InvalidInput("TIFF: value offset out of bounds"))?;
Value::decode(ty, value_count, bytes, order)?
};
ifd.set(tag, value);
}
let next = offset_at(data, next_pos, order, variant)?;
Ok((ifd, next))
}
pub fn read(data: &[u8]) -> Result<TiffFile> {
let (order, variant, first) = read_header(data)?;
let mut ifds = Vec::new();
let mut offset = first as usize;
let mut seen = Vec::new();
while offset != 0 {
if seen.contains(&offset) {
return Err(Error::InvalidInput("TIFF: IFD chain loops"));
}
if ifds.len() >= MAX_IFDS {
return Err(Error::InvalidInput("TIFF: too many IFDs"));
}
seen.push(offset);
let (ifd, next) = read_ifd(data, offset, order, variant)?;
ifds.push(ifd);
offset = next as usize;
}
if ifds.is_empty() {
return Err(Error::InvalidInput("TIFF: file has no IFD"));
}
Ok(TiffFile {
order,
variant,
ifds,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_bad_header() {
assert!(read_header(b"\x49\x49").is_err()); assert!(read_header(b"XX\x2a\x00\x08\x00\x00\x00").is_err()); assert!(read_header(b"II\x00\x00\x08\x00\x00\x00").is_err()); let (order, variant, first) = read_header(b"II\x2a\x00\x08\x00\x00\x00").expect("ok");
assert_eq!(order, ByteOrder::LittleEndian);
assert_eq!(variant, Variant::Classic);
assert_eq!(first, 8);
}
#[cfg(feature = "bigtiff")]
#[test]
fn parses_bigtiff_header() {
let head = b"II\x2b\x00\x08\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00";
let (order, variant, first) = read_header(head).expect("ok");
assert_eq!(order, ByteOrder::LittleEndian);
assert_eq!(variant, Variant::Big);
assert_eq!(first, 16);
assert!(
read_header(b"II\x2b\x00\x04\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00").is_err()
);
assert!(
read_header(b"II\x2b\x00\x08\x00\x01\x00\x10\x00\x00\x00\x00\x00\x00\x00").is_err()
);
assert!(read_header(b"II\x2b\x00\x08\x00\x00\x00").is_err());
}
#[cfg(not(feature = "bigtiff"))]
#[test]
fn rejects_bigtiff_without_feature() {
let head = b"II\x2b\x00\x08\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00";
assert!(read_header(head).is_err());
}
#[test]
fn empty_input_errors() {
assert!(read(&[]).is_err());
}
}