From 7365bc564f26a4fd6a7657a500a825cc81e4ae3f Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Sat, 5 Jun 2021 19:32:24 +0900 Subject: [PATCH 1/6] Wrap TIFF parser functions into a struct. --- src/lib.rs | 2 +- src/reader.rs | 13 ++- src/tiff.rs | 216 +++++++++++++++++++++++++++----------------------- 3 files changed, 123 insertions(+), 108 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bc3df6c..2ef4d05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; pub use reader::{Exif, Reader}; pub use tag::{Context, Tag}; pub use tiff::{DateTime, Field, In}; -pub use tiff::parse_exif_compat03 as parse_exif; +pub use tiff::parse_exif; pub use value::Value; pub use value::{Rational, SRational}; diff --git a/src/reader.rs b/src/reader.rs index dd76bf1..003c51b 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -71,16 +71,15 @@ impl Reader { /// Parses the Exif attributes from raw Exif data. /// If an error occurred, `exif::Error` is returned. pub fn read_raw(&self, data: Vec) -> Result { - let buf = data; - let (entries, le) = tiff::parse_exif(&buf)?; - let entry_map = entries.iter().enumerate() + let mut parser = tiff::Parser::new(); + parser.parse(&data)?; + let entry_map = parser.entries.iter().enumerate() .map(|(i, e)| (e.ifd_num_tag(), i)).collect(); - Ok(Exif { - buf: buf, - entries: entries, + buf: data, + entries: parser.entries, entry_map: entry_map, - little_endian: le, + little_endian: parser.little_endian, }) } diff --git a/src/tiff.rs b/src/tiff.rs index 9b15bc6..b93a89c 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -150,118 +150,135 @@ impl fmt::Display for In { /// Returns a Vec of Exif fields and a bool. /// The boolean value is true if the data is little endian. /// If an error occurred, `exif::Error` is returned. -pub fn parse_exif_compat03(data: &[u8]) -> Result<(Vec, bool), Error> { - parse_exif(data).map(|(entries, le)| { - let fields = entries.into_iter() - .map(|e| e.into_field(data, le)).collect(); - (fields, le) - }) +pub fn parse_exif(data: &[u8]) -> Result<(Vec, bool), Error> { + let mut parser = Parser::new(); + parser.parse(data)?; + let (entries, le) = (parser.entries, parser.little_endian); + Ok((entries.into_iter().map(|e| e.into_field(data, le)).collect(), le)) } -pub fn parse_exif(data: &[u8]) -> Result<(Vec, bool), Error> { - // Check the byte order and call the real parser. - if data.len() < 8 { - return Err(Error::InvalidFormat("Truncated TIFF header")); - } - match BigEndian::loadu16(data, 0) { - TIFF_BE => parse_exif_sub::(data).map(|v| (v, false)), - TIFF_LE => parse_exif_sub::(data).map(|v| (v, true)), - _ => Err(Error::InvalidFormat("Invalid TIFF byte order")), - } +#[derive(Debug)] +pub struct Parser { + pub entries: Vec, + pub little_endian: bool, } -fn parse_exif_sub(data: &[u8]) - -> Result, Error> where E: Endian { - // Parse the rest of the header (42 and the IFD offset). - if E::loadu16(data, 2) != TIFF_FORTY_TWO { - return Err(Error::InvalidFormat("Invalid forty two")); +impl Parser { + pub fn new() -> Self { + Self { entries: Vec::new(), little_endian: false } } - let mut ifd_offset = E::loadu32(data, 4) as usize; - let mut ifd_num_ck = Some(0); - let mut entries = Vec::new(); - while ifd_offset != 0 { - let ifd_num = ifd_num_ck.ok_or(Error::InvalidFormat("Too many IFDs"))?; - // Limit the number of IFDs to defend against resource exhaustion - // attacks. - if ifd_num >= 8 { - return Err(Error::InvalidFormat("Limit the IFD count to 8")); + + pub fn parse(&mut self, data: &[u8]) -> Result<(), Error> { + // Check the byte order and call the real parser. + if data.len() < 8 { + return Err(Error::InvalidFormat("Truncated TIFF header")); + } + match BigEndian::loadu16(data, 0) { + TIFF_BE => { + self.little_endian = false; + self.parse_sub::(data) + }, + TIFF_LE => { + self.little_endian = true; + self.parse_sub::(data) + }, + _ => Err(Error::InvalidFormat("Invalid TIFF byte order")), } - ifd_offset = parse_ifd::( - &mut entries, data, ifd_offset, Context::Tiff, ifd_num)?; - ifd_num_ck = ifd_num.checked_add(1); } - Ok(entries) -} -// Parse IFD [EXIF23 4.6.2]. -fn parse_ifd(entries: &mut Vec, data: &[u8], - offset: usize, ctx: Context, ifd_num: u16) - -> Result where E: Endian { - // Count (the number of the entries). - if data.len() < offset || data.len() - offset < 2 { - return Err(Error::InvalidFormat("Truncated IFD count")); - } - let count = E::loadu16(data, offset) as usize; - - // Array of entries. (count * 12) never overflows. - if data.len() - offset - 2 < count * 12 { - return Err(Error::InvalidFormat("Truncated IFD")); - } - for i in 0..count as usize { - let tag = E::loadu16(data, offset + 2 + i * 12); - let typ = E::loadu16(data, offset + 2 + i * 12 + 2); - let cnt = E::loadu32(data, offset + 2 + i * 12 + 4); - let valofs_at = offset + 2 + i * 12 + 8; - let (unitlen, _parser) = get_type_info::(typ); - let vallen = unitlen.checked_mul(cnt as usize).ok_or( - Error::InvalidFormat("Invalid entry count"))?; - let mut val = if vallen <= 4 { - Value::Unknown(typ, cnt, valofs_at as u32) - } else { - let ofs = E::loadu32(data, valofs_at) as usize; - if data.len() < ofs || data.len() - ofs < vallen { - return Err(Error::InvalidFormat("Truncated field value")); + fn parse_sub(&mut self, data: &[u8]) + -> Result<(), Error> where E: Endian { + // Parse the rest of the header (42 and the IFD offset). + if E::loadu16(data, 2) != TIFF_FORTY_TWO { + return Err(Error::InvalidFormat("Invalid forty two")); + } + let mut ifd_offset = E::loadu32(data, 4) as usize; + let mut ifd_num_ck = Some(0); + while ifd_offset != 0 { + let ifd_num = ifd_num_ck + .ok_or(Error::InvalidFormat("Too many IFDs"))?; + // Limit the number of IFDs to defend against resource exhaustion + // attacks. + if ifd_num >= 8 { + return Err(Error::InvalidFormat("Limit the IFD count to 8")); } - Value::Unknown(typ, cnt, ofs as u32) - }; - - // No infinite recursion will occur because the context is not - // recursively defined. - let tag = Tag(ctx, tag); - match tag { - Tag::ExifIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Exif, ifd_num)?, - Tag::GPSInfoIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Gps, ifd_num)?, - Tag::InteropIFDPointer => parse_child_ifd::( - entries, data, &mut val, Context::Interop, ifd_num)?, - _ => entries.push(IfdEntry { field: Field { - tag: tag, ifd_num: In(ifd_num), value: val }.into()}), + ifd_offset = self.parse_ifd::( + data, ifd_offset, Context::Tiff, ifd_num)?; + ifd_num_ck = ifd_num.checked_add(1); } + Ok(()) } - // Offset to the next IFD. - if data.len() - offset - 2 - count * 12 < 4 { - return Err(Error::InvalidFormat("Truncated next IFD offset")); + // Parse IFD [EXIF23 4.6.2]. + fn parse_ifd(&mut self, data: &[u8], + offset: usize, ctx: Context, ifd_num: u16) + -> Result where E: Endian { + // Count (the number of the entries). + if data.len() < offset || data.len() - offset < 2 { + return Err(Error::InvalidFormat("Truncated IFD count")); + } + let count = E::loadu16(data, offset) as usize; + + // Array of entries. (count * 12) never overflows. + if data.len() - offset - 2 < count * 12 { + return Err(Error::InvalidFormat("Truncated IFD")); + } + for i in 0..count as usize { + let tag = E::loadu16(data, offset + 2 + i * 12); + let typ = E::loadu16(data, offset + 2 + i * 12 + 2); + let cnt = E::loadu32(data, offset + 2 + i * 12 + 4); + let valofs_at = offset + 2 + i * 12 + 8; + let (unitlen, _parser) = get_type_info::(typ); + let vallen = unitlen.checked_mul(cnt as usize).ok_or( + Error::InvalidFormat("Invalid entry count"))?; + let mut val = if vallen <= 4 { + Value::Unknown(typ, cnt, valofs_at as u32) + } else { + let ofs = E::loadu32(data, valofs_at) as usize; + if data.len() < ofs || data.len() - ofs < vallen { + return Err(Error::InvalidFormat("Truncated field value")); + } + Value::Unknown(typ, cnt, ofs as u32) + }; + + // No infinite recursion will occur because the context is not + // recursively defined. + let tag = Tag(ctx, tag); + match tag { + Tag::ExifIFDPointer => self.parse_child_ifd::( + data, &mut val, Context::Exif, ifd_num)?, + Tag::GPSInfoIFDPointer => self.parse_child_ifd::( + data, &mut val, Context::Gps, ifd_num)?, + Tag::InteropIFDPointer => self.parse_child_ifd::( + data, &mut val, Context::Interop, ifd_num)?, + _ => self.entries.push(IfdEntry { field: Field { + tag: tag, ifd_num: In(ifd_num), value: val }.into()}), + } + } + + // Offset to the next IFD. + if data.len() - offset - 2 - count * 12 < 4 { + return Err(Error::InvalidFormat("Truncated next IFD offset")); + } + let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12); + Ok(next_ifd_offset as usize) } - let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12) as usize; - Ok(next_ifd_offset) -} -fn parse_child_ifd(entries: &mut Vec, data: &[u8], - pointer: &mut Value, ctx: Context, ifd_num: u16) - -> Result<(), Error> where E: Endian { - // The pointer is not yet parsed, so do it here. - IfdEntry::parse_value::(pointer, data); + fn parse_child_ifd(&mut self, data: &[u8], + pointer: &mut Value, ctx: Context, ifd_num: u16) + -> Result<(), Error> where E: Endian { + // The pointer is not yet parsed, so do it here. + IfdEntry::parse_value::(pointer, data); - // A pointer field has type == LONG and count == 1, so the - // value (IFD offset) must be embedded in the "value offset" - // element of the field. - let ofs = pointer.get_uint(0).ok_or( - Error::InvalidFormat("Invalid pointer"))? as usize; - match parse_ifd::(entries, data, ofs, ctx, ifd_num)? { - 0 => Ok(()), - _ => Err(Error::InvalidFormat("Unexpected next IFD")), + // A pointer field has type == LONG and count == 1, so the + // value (IFD offset) must be embedded in the "value offset" + // element of the field. + let ofs = pointer.get_uint(0).ok_or( + Error::InvalidFormat("Invalid pointer"))? as usize; + match self.parse_ifd::(data, ofs, ctx, ifd_num)? { + 0 => Ok(()), + _ => Err(Error::InvalidFormat("Unexpected next IFD")), + } } } @@ -567,10 +584,9 @@ mod tests { fn unknown_field() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\xff\xff\0\0\0\x01\0\x14\0\0\0\0\0\0"; - let (v, le) = parse_exif(data).unwrap(); + let (v, _le) = parse_exif(data).unwrap(); assert_eq!(v.len(), 1); - assert_pat!(v[0].ref_field(data, le).value, - Value::Unknown(0xffff, 1, 0x12)); + assert_pat!(v[0].value, Value::Unknown(0xffff, 1, 0x12)); } #[test] From 5b9bc48ba65970cff2d0ea35c0c10f46cb22b727 Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Sun, 1 Aug 2021 23:39:45 +0900 Subject: [PATCH 2/6] Add NEWS file. --- NEWS | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 NEWS diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..6072363 --- /dev/null +++ b/NEWS @@ -0,0 +1,106 @@ +0.5.4 (2021-03-26) + + - WebP format support has been added. + +0.5.3 (2021-01-05) + + - An infinite loop in reading PNG files has been fixed. + - PNG reader now handles `std::io::ErrorKind::Interrupted` correctly. + +0.5.2 (2020-08-07) + + - PNG format support has been added. + - DCF (Design rule for Camera File system) 2.0 tags have been added. + +0.5.1 (2020-02-15) + + - Exif 2.32 tags have been added. + +0.5 (2020-01-26) + + - Support for HEIF has been added. + - `Exif` has been separated from `Reader`. + - `Error::description` has been removed because it has been + soft-deprecated. + +0.4 (2019-12-22) + + - Support for displaying values with units has been added. + - Rust 1.40 or later is required. + - The deprecated `tag` module has been removed. + - Support for reading up to 8 IFDs has been added. + - Enums `Context` and `Error` are now non_exhaustive. + - `Value` and `Field` no longer borrows the raw buffer. + - Struct `In` has been added to indicate primary/thumbnail images. + - `Reader::fields` now returns an iterator. + - The associated value of `Value::Undefined` and `Value::Ascii` has been + changed from a slice to a `Vec`. + +0.3.1 (2018-06-17) + + - IFDs other than 0th and 1st are ignored for now. + +0.3 (2017-10-22) + + - Enum `Error` now has two new variants: `TooBig` and `NotSupported`. + - `Value::Undefined` now has the 2nd member to keep the offset of the + value. + - Struct `DateTime` now has two new fields: `nanosecond` and + `time_offset`. + - The tag constants have been changed to associated constants of + struct `Tag`. Use `Tag::TagName` instead of `tag::TagName`. + +0.2.3 (2017-07-16) + + - Experimental support for writing Exif data has been added. + - The `Hash` trait has been derived for `Tag` and `Context`. + - `Reader::get_value` and `Value::iter_uint` have been added. + +0.2.2 (2017-06-17) + + - The `std::fmt::Display` trait has been implemented for `Rational`, + `SRational`, and `DateTime`. + - The `Copy` and `Clone` traits have been derived for `Rational` + and `SRational`. + - Converters from `Rational`/`SRational` to `f64`/`f32` have been added. + - `Rational::to_f64` and `SRational::to_f64`. + - `From` and `From` traits for `f64` and `f32`. + - Human readable printing of a `Value` has been supported. + The `Value::display_as` method returns an object that implements + the `Display` trait. + - The `Value::get_uint` method has been added. + +0.2.1 (2017-03-27) + + - A typo in the documentation has been fixed. + +0.2 (2017-03-26) + + - The `Copy` and `Clone` traits have been derived for `Tag`. + - The `Tag::default_value` function has been added. + - DateTime parser has been added. + - A new variant `Error::BlankValue` has been added. + - Rust 1.15 is now required to compile. + - The `Reader::fields` method now returns a slice instead of a reference + to a `Vec`. + - The `parse_image` function has been removed. + - The `Tag::value` method was renamed to `Tag::number`. + +0.1.3 (2017-03-12) + + - Constants for the new tags in Exif 2.31 have been added. + - An ASCII field with zero count 0 is parsed to an empty Vec. + - `Tag` and `Context` are no longer re-exported. + +0.1.2 (2017-02-25) + + - Struct `Reader` has been added. + - The `parse_image` function has been deperecated. + +0.1.1 (2017-01-12) + + - The `parse_image` function has been added. + +0.1 (2016-12-30) + + - The first public version. From d65ccedc22749be022f8febb30483c7270bc50fe Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Sun, 1 Aug 2021 23:40:11 +0900 Subject: [PATCH 3/6] Link to the NEWS file from rustdoc. --- src/doc.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/doc.rs b/src/doc.rs index fca304a..6b1e7d5 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -26,6 +26,24 @@ //! Documentation +// Rust 1.54 stabilized invoking function-like macros in attributes. +// We will use it after bumping MSRV. +// #![feature(extended_key_value_attributes)], +// #[doc = include_str!("../NEWS")] +// pub mod news {} +macro_rules! doc_module_with_external_source { + ($( #[$attr:meta] )* + $name: ident, $doc: expr) => { + $( #[$attr] )* + #[doc = ""] + #[doc = $doc] + pub mod $name {} + } +} +doc_module_with_external_source!( + /// # News + news, include_str!("../NEWS")); + /// # Upgrade Guide /// /// ## Upgrade from 0.4.x to 0.5.x From 86eaa73be07a716b26e6f9c345466f75772059b3 Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Mon, 18 Oct 2021 23:48:40 +0900 Subject: [PATCH 4/6] Simply use debug_assert!() instead of cfg!(debug_assertions). --- src/util.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/util.rs b/src/util.rs index 9c834a8..2da8ba0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -105,9 +105,7 @@ impl ReadExt for T where T: io::Read { // This function must not be called with more than 4 bytes. pub fn atou16(bytes: &[u8]) -> Result { - if cfg!(debug_assertions) && bytes.len() >= 5 { - panic!("atou16 accepts up to 4 bytes"); - } + debug_assert!(bytes.len() <= 4); if bytes.len() == 0 { return Err(Error::InvalidFormat("Not a number")); } From 6db71de2fca2a71a95de15dc76310db01ee42212 Mon Sep 17 00:00:00 2001 From: sharnoff Date: Sat, 11 Dec 2021 09:45:49 -0800 Subject: [PATCH 5/6] Generate documentation for Tag consts --- src/tag.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tag.rs b/src/tag.rs index 68f43f9..4dc6382 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -175,6 +175,7 @@ macro_rules! generate_well_known_tag_constants { impl Tag { $($( $( #[$attr] )* + #[doc = $desc] #[allow(non_upper_case_globals)] pub const $name: Tag = Tag($ctx, $num); )+)+ From ab77d185919e256e87fd271dd881ef84f10dff8d Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Wed, 15 Dec 2021 23:21:50 +0900 Subject: [PATCH 6/6] Use std::{f32,f64}::MIN to fix regression before Rust 1.43. --- src/reader.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reader.rs b/src/reader.rs index 003c51b..8bcdba9 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -258,12 +258,12 @@ mod tests { assert_eq!(f.display_value().to_string(), "65"); let f = exif.get_field(Tag(Context::Tiff, 65000), In(0)).unwrap(); match f.value { - Value::Float(ref v) => assert_eq!(v[0], f32::MIN), + Value::Float(ref v) => assert_eq!(v[0], std::f32::MIN), _ => panic!(), } let f = exif.get_field(Tag(Context::Tiff, 65001), In(0)).unwrap(); match f.value { - Value::Double(ref v) => assert_eq!(v[0], f64::MIN), + Value::Double(ref v) => assert_eq!(v[0], std::f64::MIN), _ => panic!(), } }