Parse IFD structures and record the context in Tag.

In the Exif specification, IFDs may be nested and chained.
The interpretation of a tag value depends on the IFD in which it
appears, so the context is added into Tag.
This commit is contained in:
KAMADA Ken'ichi 2016-11-23 19:06:38 +09:00
parent eaaad2418f
commit 6651b7d159
3 changed files with 136 additions and 11 deletions

View File

@ -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;

View File

@ -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.
// <https://github.com/rust-lang/rfcs/pull/1445>
#[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].
);

View File

@ -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<E>(data: &[u8])
return Err(Error::InvalidFormat("Invalid forty two"));
}
let ifd_offset = E::loadu32(data, 4) as usize;
parse_ifd::<E>(data, ifd_offset)
parse_ifd::<E>(data, ifd_offset, Context::Tiff)
}
// Parse IFD [EXIF23 4.6.2].
fn parse_ifd<E>(data: &[u8], offset: usize)
fn parse_ifd<E>(data: &[u8], offset: usize, ctx: Context)
-> Result<Vec<Field>, 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<E>(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::<E>(data, ofs, Context::Exif));
fields.append(&mut v);
} else if tag == tag::GPSInfoIFDPointer {
let mut v = try!(parse_ifd::<E>(data, ofs, Context::Gps));
fields.append(&mut v);
} else if tag == tag::InteropIFDPointer {
let mut v = try!(parse_ifd::<E>(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::<E>(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"));
}
}