diff --git a/src/error.rs b/src/error.rs index 9fd5f77..76fe977 100644 --- a/src/error.rs +++ b/src/error.rs @@ -38,6 +38,10 @@ pub enum Error { Io(io::Error), /// Exif attribute information was not found. NotFound(&'static str), + /// The value of the field is blank. Some fields have blank values + /// whose meanings are defined as "unknown". Such a blank value + /// should be treated the same as the absence of the field. + BlankValue(&'static str), } impl From for Error { @@ -52,6 +56,7 @@ impl fmt::Display for Error { Error::InvalidFormat(msg) => f.write_str(msg), Error::Io(ref err) => err.fmt(f), Error::NotFound(msg) => f.write_str(msg), + Error::BlankValue(msg) => f.write_str(msg), } } } @@ -62,6 +67,7 @@ impl error::Error for Error { Error::InvalidFormat(msg) => msg, Error::Io(ref err) => err.description(), Error::NotFound(msg) => msg, + Error::BlankValue(msg) => msg, } } @@ -70,6 +76,7 @@ impl error::Error for Error { Error::InvalidFormat(_) => None, Error::Io(ref err) => Some(err), Error::NotFound(_) => None, + Error::BlankValue(_) => None, } } } diff --git a/src/lib.rs b/src/lib.rs index 6e542e4..16526f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; pub use reader::Reader; pub use tag_priv::{Context, Tag}; pub use tag_priv::constants as tag; -pub use tiff::Field; +pub use tiff::{DateTime, Field}; pub use tiff::parse_exif; pub use value::Value; pub use value::{Rational, SRational}; diff --git a/src/tiff.rs b/src/tiff.rs index f7cd11e..32c7292 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -30,6 +30,7 @@ use tag; use tag_priv::{Context, Tag}; use value::Value; use value::get_type_info; +use util::atou16; // TIFF header magic numbers [EXIF23 4.5.2]. const TIFF_BE: u16 = 0x4d4d; @@ -155,6 +156,40 @@ pub fn is_tiff(buf: &[u8]) -> bool { buf.starts_with(&TIFF_BE_SIG) || buf.starts_with(&TIFF_LE_SIG) } +/// A struct used to parse a DateTime field. +#[derive(Debug)] +pub struct DateTime { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, +} + +impl DateTime { + /// Parse an ASCII data of a DateTime field. The range of a number + /// is not validated, so, for example, 13 may be returned as the month. + pub fn from_ascii(data: &[u8]) -> Result { + if data == b" : : : : " || data == b" " { + return Err(Error::BlankValue("DateTime is blank")); + } else if data.len() < 19 { + return Err(Error::InvalidFormat("DateTime too short")); + } else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' && + data[13] == b':' && data[16] == b':') { + return Err(Error::InvalidFormat("Invalid DateTime delimiter")); + } + Ok(DateTime { + year: try!(atou16(&data[0..4])), + month: try!(atou16(&data[5..7])) as u8, + day: try!(atou16(&data[8..10])) as u8, + hour: try!(atou16(&data[11..13])) as u8, + minute: try!(atou16(&data[14..16])) as u8, + second: try!(atou16(&data[17..19])) as u8, + }) + } +} + #[cfg(test)] mod tests { use error::Error; diff --git a/src/util.rs b/src/util.rs index a73b67e..f549c8a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -26,6 +26,8 @@ use std::io; +use error::Error; + pub fn read8(reader: &mut R) -> Result where R: io::Read { let mut buf: [u8; 1] = unsafe { ::std::mem::uninitialized() }; reader.read_exact(&mut buf).and(Ok(buf[0])) @@ -37,6 +39,27 @@ pub fn read16(reader: &mut R) -> Result where R: io::Read { Ok(((buf[0] as u16) << 8) + buf[1] as u16) } +// This function must not be called with more than 4 bytes. +pub fn atou16(bytes: &[u8]) -> Result { + const ASCII_0: u8 = 0x30; + const ASCII_9: u8 = 0x39; + + if cfg!(debug_assertions) && bytes.len() >= 5 { + panic!("atou16 accepts up to 4 bytes"); + } + if bytes.len() == 0 { + return Err(Error::InvalidFormat("Not a number")); + } + let mut n = 0; + for &c in bytes { + if c < ASCII_0 || ASCII_9 < c { + return Err(Error::InvalidFormat("Not a number")); + } + n = n * 10 + (c - ASCII_0) as u16; + } + Ok(n) +} + #[cfg(test)] mod tests { use std::io::Cursor; @@ -71,4 +94,15 @@ mod tests { assert_ok!(reader.read_to_end(&mut buf), 1); assert_eq!(buf, [0x03]); } + + #[test] + fn atou16_misc() { + assert_ok!(atou16(b"0"), 0); + assert_ok!(atou16(b"0010"), 10); + assert_ok!(atou16(b"9999"), 9999); + assert_err_pat!(atou16(b""), Error::InvalidFormat(_)); + assert_err_pat!(atou16(b"/"), Error::InvalidFormat(_)); + assert_err_pat!(atou16(b":"), Error::InvalidFormat(_)); + assert_err_pat!(atou16(b"-1"), Error::InvalidFormat(_)); + } }