use winnow::{
Parser,
binary::{u16, u32},
error::EmptyError,
};
use crate::exif::State;
use super::{
NextIfdPointer, Stream,
error::{ExifFatalError, ExifFieldError},
value::parse_value,
};
use raves_metadata_types::exif::{
Field, FieldData, FieldTag,
ifd::IfdGroup,
primitives::Primitive,
tags::{Ifd0Tag, KnownTag, SUB_IFD_POINTER_TAGS},
};
pub const RECURSION_LIMIT: u8 = 32;
#[repr(C)]
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub struct Ifd {
pub fields: Vec<Result<Field, ExifFieldError>>,
pub sub_ifds: Vec<Ifd>,
}
pub fn parse_ifd(input: &mut Stream) -> Result<(Ifd, NextIfdPointer), ExifFatalError> {
let endianness = *input.state.endianness;
{
let ifd_ptr: u32 = (input.state.blob.len() - input.len()) as u32;
for maybe_ptr in &input.state.recursion_stack[..input.state.recursion_ct as usize] {
let Some(ptr) = maybe_ptr else {
unreachable!(
"there should be `RECURSION` elements in the array. this is a bug - please report it!"
);
};
if ifd_ptr == *ptr {
return Err(ExifFatalError::SelfRecursion {
ifd_group: input.state.current_ifd,
call_stack: Box::new(input.state.recursion_stack),
});
}
}
update_recursion_stack_or_error(input, ifd_ptr)?;
}
let entry_count: u16 = u16(endianness).parse_next(input).map_err(|_: EmptyError| {
log::error!("Couldn't find count on IFD - ran out of data!");
ExifFatalError::IfdNoEntryCount
})?;
if entry_count == 0 {
log::error!("IFD reported itself as having zero fields! This is fatal to parsing.");
return Err(ExifFatalError::IfdHadZeroFields);
}
log::trace!("Parsing `{entry_count}` fields...");
let mut ifd = Ifd {
fields: (0..entry_count).map(|_| parse_value(input)).collect(),
sub_ifds: Vec::new(),
};
log::trace!("Completed field parsing!");
log::trace!("Checking for sub-IFDs...");
let sub_ifds: Vec<Ifd> = ifd
.fields
.iter()
.flatten()
.filter(|field| SUB_IFD_POINTER_TAGS.contains(&field.tag))
.flat_map(|sub_ifd_field| {
let ptr: u32 = match sub_ifd_field.data {
FieldData::Primitive(Primitive::Long(long)) => long,
_ => {
log::error!(
"Found a sub-IFD, but its field data wasn't a long! got: {sub_ifd_field:#?}"
);
return None;
}
};
let ifd_group = match sub_ifd_field.tag {
FieldTag::Known(KnownTag::Ifd0Tag(tag)) => match tag {
Ifd0Tag::ExifIfdPointer => IfdGroup::Exif,
Ifd0Tag::GpsInfoIfdPointer => IfdGroup::Gps,
Ifd0Tag::InteroperabilityIfdPointer => IfdGroup::Interop,
_ => todo!(),
},
_ => todo!(),
};
Some((ifd_group, ptr))
})
.flat_map(|(ifd_group, ptr)| {
let new_ifd_input = &input.state.blob[ptr as usize..];
let state = &mut Stream {
input: new_ifd_input,
state: State {
endianness: &endianness,
blob: input.state.blob,
current_ifd: ifd_group,
recursion_ct: input.state.recursion_ct.saturating_add(1_u8),
recursion_stack: input.state.recursion_stack,
},
};
let (sub_ifd, _next_ifd) = parse_ifd.parse_next(state).ok()?;
Some(sub_ifd)
})
.collect();
log::trace!("Found {} sub-IFD(s)! Returning...", sub_ifds.len());
ifd.sub_ifds = sub_ifds;
Ok((ifd, next_ifd_location(input)))
}
fn next_ifd_location(input: &mut Stream) -> Option<u32> {
let endianness = *input.state.endianness;
let raw_location: u32 = u32(endianness)
.parse_next(input)
.inspect_err(|_: &EmptyError| {
log::debug!("IFD didn't contain a pointer to the next IFD!");
})
.ok()?;
if raw_location == 0_u32 {
log::trace!("There won't be a next IFD.");
None
} else {
log::trace!("Another IFD was detected! index: `{raw_location}`");
Some(raw_location)
}
}
fn update_recursion_stack_or_error(input: &mut Stream, ifd_ptr: u32) -> Result<(), ExifFatalError> {
if input.state.recursion_ct >= RECURSION_LIMIT {
log::error!("Hit IFD recursion limit! input: {input:#?}");
return Err(ExifFatalError::HitRecursionLimit {
ifd_group: input.state.current_ifd,
call_stack: Box::new(input.state.recursion_stack.map(|c| {
c.unwrap()
})),
});
}
debug_assert!(
input.state.recursion_stack[input.state.recursion_ct as usize].is_none(),
"array indexing should be correct, but `stack[{}]` is not None!",
input.state.recursion_ct
);
input.state.recursion_stack[input.state.recursion_ct as usize] = Some(ifd_ptr);
Ok(())
}
#[cfg(test)]
mod tests {
use raves_metadata_types::exif::ifd::IfdGroup;
use crate::{
exif::{Stream, error::ExifFatalError, ifd::RECURSION_LIMIT},
util::logger,
};
#[test]
fn hitting_recursion_limit_should_return_err() {
logger();
let bytes = [0_u8; 200];
let mut state = crate::exif::State {
blob: &bytes,
current_ifd: IfdGroup::_0,
endianness: &winnow::binary::Endianness::Big,
recursion_ct: RECURSION_LIMIT,
recursion_stack: (0..RECURSION_LIMIT as u32)
.map(Some)
.collect::<Vec<_>>()
.try_into()
.unwrap(),
};
state.recursion_ct += 1;
let res = super::update_recursion_stack_or_error(
&mut Stream {
input: &bytes,
state,
},
RECURSION_LIMIT as u32,
);
assert!(
matches!(res.unwrap_err(), ExifFatalError::HitRecursionLimit { .. }),
"should hit recursion limit"
);
}
#[test]
fn disallow_self_referential_ifds() {
logger();
let bytes = [100_u8; 23];
let state = crate::exif::State {
blob: &bytes,
current_ifd: IfdGroup::_0,
endianness: &winnow::binary::Endianness::Big,
recursion_ct: 2_u8,
recursion_stack: {
let mut s = [None; RECURSION_LIMIT as usize];
s[0] = Some(1_u32);
s[1] = Some(17_u32);
s
},
};
let res = super::parse_ifd(&mut Stream {
input: &bytes[17..],
state,
});
assert!(
matches!(res, Err(ExifFatalError::SelfRecursion { .. })),
"should detect self-recursion, but res: {res:?}"
);
}
}