Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support BigTiff #32

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bbox/idat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct IdatBox<'a> {

#[allow(unused)]
impl<'a> IdatBox<'a> {
pub fn parse(input: &'a [u8]) -> IResult<&'a [u8], IdatBox> {
pub fn parse(input: &'a [u8]) -> IResult<&'a [u8], IdatBox<'a>> {
let (remain, header) = BoxHeader::parse(input)?;

let box_size = usize::try_from(header.box_size).expect("box size must fit into a `usize`.");
Expand Down
9 changes: 6 additions & 3 deletions src/exif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ pub(crate) fn extract_exif_with_mime(
}
_ => unreachable!(),
};

// full fill TIFF data
let mut iter =
IfdHeaderTravel::new(&buf[data_start..], header.ifd0_offset, header.endian);
let mut iter = IfdHeaderTravel::new(
&buf[data_start..],
header.ifd0_offset,
header.endian,
header.bigtiff,
);
iter.travel_ifd(0)
.map_err(|e| ParsingErrorState::new(e, state.clone()))?;

Expand Down
70 changes: 58 additions & 12 deletions src/exif/exif_exif.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use nom::{
branch::alt, bytes::streaming::tag, combinator, number::Endianness, sequence, IResult, Needed,
};
use nom::{branch::alt, bytes::streaming::tag, combinator, number::Endianness, IResult, Needed};

use crate::{EntryValue, ExifIter, ExifTag, GPSInfo, ParsedExifEntry};

Expand Down Expand Up @@ -158,43 +156,75 @@ impl From<ExifIter> for Exif {
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct TiffHeader {
pub endian: Endianness,
pub ifd0_offset: u32,
pub bigtiff: bool,
pub ifd0_offset: u64,
}

impl Default for TiffHeader {
fn default() -> Self {
Self {
endian: Endianness::Big,
bigtiff: false,
ifd0_offset: 0,
}
}
}

pub(crate) const IFD_ENTRY_SIZE: usize = 12;
pub(crate) const IFD_REGULAR_TIFF_ENTRY_SIZE: usize = 12;
pub(crate) const IFD_BIG_TIFF_ENTRY_SIZE: usize = 20;

impl TiffHeader {
pub fn parse(input: &[u8]) -> IResult<&[u8], TiffHeader> {
use nom::number::streaming::{u16, u32};
let (remain, endian) = TiffHeader::parse_endian(input)?;
let (_, (_, offset)) = sequence::tuple((
combinator::verify(u16(endian), |magic| *magic == 0x2a),
u32(endian),
))(remain)?;
let (remain, bigtiff) = TiffHeader::parse_bigtiff(remain, endian)?;
let (remain, offset) = if bigtiff {
// http://bigtiff.org/ describes the BigTIFF header additions as constants 0x8 and 0x0.
let (remain, first_word) = nom::number::streaming::u16(endian)(remain)?;
if first_word != 0x8 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::Fail,
)));
}
let (remain, second_word) = nom::number::streaming::u16(endian)(remain)?;
if second_word != 0x0 {
return Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::Fail,
)));
}
// offset
nom::number::streaming::u64(endian)(remain)?
} else {
let (remain, offset) = nom::number::streaming::u32(endian)(remain)?;
(remain, offset as u64)
};

let header = Self {
endian,
bigtiff,
ifd0_offset: offset,
};

Ok((remain, header))
}

pub fn parse_ifd_entry_num(input: &[u8], endian: Endianness) -> IResult<&[u8], u16> {
let (remain, num) = nom::number::streaming::u16(endian)(input)?; // Safe-slice
pub fn parse_ifd_entry_num(
input: &[u8],
endian: Endianness,
bigtiff: bool,
) -> IResult<&[u8], u64> {
let (remain, num) = if bigtiff {
nom::number::streaming::u64(endian)(input)?
} else {
let (remain, num) = nom::number::streaming::u16(endian)(input)?;
(remain, num as u64)
};
if num == 0 {
return Ok((remain, 0));
}

/* TODO
// 12 bytes per entry
let size = (num as usize)
.checked_mul(IFD_ENTRY_SIZE)
Expand All @@ -203,6 +233,7 @@ impl TiffHeader {
if size > remain.len() {
return Err(nom::Err::Incomplete(Needed::new(size - remain.len())));
}
*/

Ok((remain, num))
}
Expand All @@ -227,6 +258,20 @@ impl TiffHeader {
}
})(input)
}

fn parse_bigtiff(input: &[u8], endian: Endianness) -> IResult<&[u8], bool> {
let (remain, magic_marker) = nom::number::streaming::u16(endian)(input)?;
if magic_marker == 43 {
Ok((remain, true))
} else if magic_marker == 42 {
Ok((remain, false))
} else {
Err(nom::Err::Failure(nom::error::make_error(
input,
nom::error::ErrorKind::Fail,
)))
}
}
}

pub(crate) fn check_exif_header(data: &[u8]) -> Result<bool, nom::Err<nom::error::Error<&[u8]>>> {
Expand Down Expand Up @@ -270,6 +315,7 @@ mod tests {
header,
TiffHeader {
endian: Endianness::Big,
bigtiff: false,
ifd0_offset: 8,
}
);
Expand Down
56 changes: 37 additions & 19 deletions src/exif/exif_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::{
EntryValue, ExifTag,
};

use super::{exif_exif::IFD_ENTRY_SIZE, tags::ExifTagCode, GPSInfo, TiffHeader};
// exif_exif::IFD_ENTRY_SIZE,
use super::{tags::ExifTagCode, GPSInfo, TiffHeader};

/// Parses header from input data, and returns an [`ExifIter`].
///
Expand Down Expand Up @@ -54,6 +55,7 @@ pub(crate) fn input_into_iter(
input.partial(&data[start..]),
header.ifd0_offset,
header.endian,
header.bigtiff,
None,
)?;

Expand Down Expand Up @@ -182,8 +184,9 @@ impl ExifIter {
let mut gps_subifd = match IfdIter::try_new(
gps.ifd,
iter.input.partial(&iter.input[offset as usize..]), // Safe-slice
offset,
offset as u64,
iter.tiff_header.endian,
iter.tiff_header.bigtiff,
iter.tz.clone(),
) {
Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
Expand Down Expand Up @@ -413,7 +416,7 @@ impl Iterator for ExifIter {
return Some(ParsedExifEntry::make_ok(
ifd_idx,
tag_code.unwrap(),
EntryValue::U32(offset),
EntryValue::U32(offset as u32),
));
}
}
Expand Down Expand Up @@ -446,11 +449,12 @@ pub(crate) struct IfdIter {
input: AssociatedInput,

// IFD data offset relative to the TIFF header.
offset: u32,
offset: u64,

pub tz: Option<String>,
endian: Endianness,
entry_num: u16,
bigtiff: bool,
entry_num: u64,

// Iterating status
index: u16,
Expand Down Expand Up @@ -506,8 +510,9 @@ impl IfdIter {
pub fn try_new(
ifd_idx: usize,
input: AssociatedInput,
offset: u32,
offset: u64,
endian: Endianness,
bigtiff: bool,
tz: Option<String>,
) -> crate::Result<Self> {
if input.len() < 2 {
Expand All @@ -516,7 +521,7 @@ impl IfdIter {
));
}
// should use the complete header data to parse ifd entry num
let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&input[..], endian)?;
let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&input[..], endian, bigtiff)?;

Ok(Self {
ifd_idx,
Expand All @@ -526,6 +531,7 @@ impl IfdIter {
entry_num,
tz,
endian,
bigtiff,
// Skip the first two bytes, which is the entry num
pos: 2,
index: 0,
Expand All @@ -541,6 +547,7 @@ impl IfdIter {
complete::u32(endian),
))(entry_data)
.ok()?;
let value_or_offset = value_or_offset as u64;

if tag == 0 {
return None;
Expand All @@ -558,7 +565,7 @@ impl IfdIter {
Some((tag, res))
}

fn get_data_pos(&self, value_or_offset: u32) -> u32 {
fn get_data_pos(&self, value_or_offset: u64) -> u64 {
value_or_offset.saturating_sub(self.offset)
}

Expand All @@ -568,7 +575,7 @@ impl IfdIter {
data_format: DataFormat,
components_num: u32,
entry_data: &[u8],
value_or_offset: u32,
value_or_offset: u64,
) -> (u16, IfdEntry) {
// get component_size according to data format
let component_size = data_format.component_size();
Expand Down Expand Up @@ -617,7 +624,7 @@ impl IfdIter {
fn new_ifd_iter(
&self,
ifd_idx: usize,
value_or_offset: u32,
value_or_offset: u64,
tag: Option<u16>,
) -> Option<IfdEntry> {
let pos = self.get_data_pos(value_or_offset) as usize;
Expand All @@ -627,6 +634,7 @@ impl IfdIter {
self.input.partial(&self.input[pos..]),
value_or_offset,
self.endian,
self.bigtiff,
self.tz.clone(),
) {
Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
Expand All @@ -650,11 +658,16 @@ impl IfdIter {
let endian = self.endian;
// find ExifOffset
for i in 0..self.entry_num {
let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
let entry_size = if self.bigtiff {
20
} else {
12 // IFD_ENTRY_SIZE
};
let pos = self.pos + i as usize * entry_size;
let (_, tag) =
complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
if tag == ExifTag::ExifOffset.code() {
let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
let entry_data = self.input.slice_checked(pos..pos + entry_size)?;
let (_, entry) = self.parse_tag_entry(entry_data)?;
match entry {
IfdEntry::IfdNew(iter) => return Some(iter),
Expand Down Expand Up @@ -841,15 +854,20 @@ impl Iterator for IfdIter {
// pos = format!("{:08x}", self.pos),
// "next IFD entry"
// );
if self.input.len() < self.pos + IFD_ENTRY_SIZE {
let entry_size: usize = if self.bigtiff {
20
} else {
12 // IFD_ENTRY_SIZE
};
if self.input.len() < (self.pos + entry_size) as usize {
return None;
}

let endian = self.endian;
if self.index > self.entry_num {
if (self.index as u64) > self.entry_num {
return None;
}
if self.index == self.entry_num {
if (self.index as u64) == self.entry_num {
tracing::debug!(
self.ifd_idx,
self.index,
Expand All @@ -862,6 +880,7 @@ impl Iterator for IfdIter {
let (_, offset) =
complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;

let offset = offset as u64;
if offset == 0 {
// IFD parsing completed
tracing::debug!(?self, "IFD parsing completed");
Expand All @@ -873,11 +892,9 @@ impl Iterator for IfdIter {
.map(|x| (None, x));
}

let entry_data = self
.input
.slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
let entry_data = self.input.slice_checked(self.pos..self.pos + entry_size)?;
self.index += 1;
self.pos += IFD_ENTRY_SIZE;
self.pos += entry_size;

let (tag, res) = self.parse_tag_entry(entry_data)?;

Expand All @@ -899,6 +916,7 @@ mod tests {
#[test_case("broken.jpg", "", MimeImage::Jpeg)]
#[test_case("exif.heic", "+08:00", MimeImage::Heic)]
#[test_case("tif.tif", "", MimeImage::Tiff)]
//#[test_case("bif.tif", "", MimeImage::Tiff)]
#[test_case("fujifilm_x_t1_01.raf.meta", "", MimeImage::Raf)]
fn exif_iter_tz(path: &str, tz: &str, img_type: MimeImage) {
let buf = read_sample(path).unwrap();
Expand Down
Loading