diff --git a/src/lib.rs b/src/lib.rs index 98384ab..135cfa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub use error::Error; pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; -pub use tag::Tag; +pub use tag::{Context, Tag}; pub use tiff::Field; pub use tiff::parse_exif; pub use value::Value; @@ -41,7 +41,7 @@ mod tmacro; mod endian; mod error; mod jpeg; -mod tag; +pub mod tag; mod tiff; mod util; mod value; diff --git a/src/tag.rs b/src/tag.rs index 432d5d2..26e59f2 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -24,6 +24,15 @@ // SUCH DAMAGE. // +//! Compatibility warning: Exif tag constants in this module will be +//! converted to associated constants of Tag when the feature is +//! stabilized. + +/// A tag of a TIFF field. +/// +/// Use `exif::Tag` instead of `exif::tag::Tag`. They are the same, +/// but `exif::tag` will become private in the future versions. +// // This is not an enum to keep safety and API stability, while // supporting unknown tag values. This comment is based on the // behavior of Rust 1.12. @@ -34,7 +43,82 @@ // tends to break backward compatibility. When Tag::VariantFoo is // defined in a new version of the library, the old codes using // Tag::Unknown(Foo's value) will break. +// +// Use of constants is restricted in patterns. As of Rust 1.12, +// PartialEq and Eq need to be _automatically derived_ for Tag to +// emulate structural equivalency. +// +#[derive(Debug, PartialEq, Eq)] +pub struct Tag(pub Context, pub u16); -/// A tag of a TIFF field. -#[derive(Debug)] -pub struct Tag(pub u16); +impl Tag { + /// Returns the context of the tag. + #[inline] + pub fn context(&self) -> Context { + self.0 + } + + /// Returns the value of the tag. + #[inline] + pub fn value(&self) -> u16 { + self.1 + } +} + +/// An enum that indicates how a tag value is interpreted. +/// +/// Use `exif::Context` instead of `exif::tag::Context`. They are the +/// same, but `exif::tag` will become private in the future versions. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Context { + /// TIFF attributes defined in the TIFF Rev. 6.0 specification. + Tiff, // 0th IFD + /// Exif attributes. + Exif, // 0th IFD -- Exif IFD + /// GPS attributes. + Gps, // 0th IFD -- GPS IFD + /// Interoperability attributes. + Interop, // 0th IFD -- Exif IFD -- Interoperability IFD + /// TIFF fields in the 1st IFD, which represents the thumbnail image. + Thumb, // 1st IFD +} + +macro_rules! generate_well_known_tag_constants { + ( + $( + // Copy the doc attribute to the actual definition. + $( #[$attr:meta] )* + ($name:ident, $ctx:ident, $num:expr, $desc:expr) + ),+, + ) => ( + $( + $( #[$attr] )* + #[allow(non_upper_case_globals)] + pub const $name: Tag = Tag(Context::$ctx, $num); + )+ + ) +} + +// Tag constant names do not follow the Rust naming conventions but +// the Exif field names: camel cases and all-capital acronyms. +generate_well_known_tag_constants!( + // Exif-specific IFDs [EXIF23 4.6.3]. + + /// A pointer to the Exif IFD. This is used for the internal structure + /// of Exif data and will not be returned to the user. + (ExifIFDPointer, Tiff, 0x8769, "Exif IFD pointer"), + /// A pointer to the GPS IFD. This is used for the internal structure + /// of Exif data and will not be returned to the user. + (GPSInfoIFDPointer, Tiff, 0x8825, "GPS Info IFD pointer"), + /// A pointer to the interoperability IFD. This is used for the internal + /// structure of Exif data and will not be returned to the user. + (InteropIFDPointer, Exif, 0xa005, "Interoperability IFD pointer"), + + // TIFF attributes [EXIF23 4.6.4]. + + // Exif IFD attributes [EXIF23 4.6.5]. + + // GPS attributes [EXIF23 4.6.6]. + + // Interoperability attributes [EXIF23 4.6.7]. +); diff --git a/src/tiff.rs b/src/tiff.rs index 23f0b98..eb027f6 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -26,7 +26,8 @@ use endian::{Endian, BigEndian, LittleEndian}; use error::Error; -use tag::Tag; +use tag; +use tag::{Context, Tag}; use value::Value; use value::get_type_info; @@ -62,11 +63,11 @@ fn parse_exif_sub(data: &[u8]) return Err(Error::InvalidFormat("Invalid forty two")); } let ifd_offset = E::loadu32(data, 4) as usize; - parse_ifd::(data, ifd_offset) + parse_ifd::(data, ifd_offset, Context::Tiff) } // Parse IFD [EXIF23 4.6.2]. -fn parse_ifd(data: &[u8], offset: usize) +fn parse_ifd(data: &[u8], offset: usize, ctx: Context) -> Result, Error> where E: Endian { // Count (the number of the entries). if data.len() < offset || data.len() - offset < 2 { @@ -98,17 +99,57 @@ fn parse_ifd(data: &[u8], offset: usize) } val = parser(data, ofs, cnt); } - fields.push(Field { tag: Tag(tag), value: val }); + + // No infinite recursion will occur because the context is not + // recursively defined. + // XXX Should we check the type and count of a pointer? + // 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 tag = Tag(ctx, tag); + if tag == tag::ExifIFDPointer { + let mut v = try!(parse_ifd::(data, ofs, Context::Exif)); + fields.append(&mut v); + } else if tag == tag::GPSInfoIFDPointer { + let mut v = try!(parse_ifd::(data, ofs, Context::Gps)); + fields.append(&mut v); + } else if tag == tag::InteropIFDPointer { + let mut v = try!(parse_ifd::(data, ofs, Context::Interop)); + fields.append(&mut v); + } else { + fields.push(Field { tag: tag, value: val }); + } } // Offset to the next IFD. if data.len() - offset - 2 - count * 12 < 4 { return Err(Error::InvalidFormat("Truncated IFD")); } - let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12); + let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12) as usize; if next_ifd_offset != 0 { - unimplemented!(); + if ctx != Context::Tiff { + return Err(Error::InvalidFormat("Unexpected next IFD")); + } + let mut v = try!( + parse_ifd::(data, next_ifd_offset, Context::Thumb)); + fields.append(&mut v); } Ok(fields) } + +#[cfg(test)] +mod tests { + use error::Error; + use super::*; + + // Before the error is returned, the IFD is parsed twice as the + // 0th and 1st IFDs. + #[test] + fn inf_loop_by_next() { + let data = b"MM\0\x2a\0\0\0\x08\ + \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08"; + assert_err_pat!(parse_exif(data), + Error::InvalidFormat("Unexpected next IFD")); + } +}