exif/
tiff.rs

1//
2// Copyright (c) 2016 KAMADA Ken'ichi.
3// All rights reserved.
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions
7// are met:
8// 1. Redistributions of source code must retain the above copyright
9//    notice, this list of conditions and the following disclaimer.
10// 2. Redistributions in binary form must reproduce the above copyright
11//    notice, this list of conditions and the following disclaimer in the
12//    documentation and/or other materials provided with the distribution.
13//
14// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24// SUCH DAMAGE.
25//
26
27use std::fmt;
28use mutate_once::MutOnce;
29
30use crate::endian::{Endian, BigEndian, LittleEndian};
31use crate::error::Error;
32use crate::tag::{Context, Tag, UnitPiece};
33use crate::value;
34use crate::value::Value;
35use crate::value::get_type_info;
36use crate::util::{atou16, ctou32};
37
38// TIFF header magic numbers [EXIF23 4.5.2].
39const TIFF_BE: u16 = 0x4d4d;
40const TIFF_LE: u16 = 0x4949;
41const TIFF_FORTY_TWO: u16 = 0x002a;
42pub const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a];
43pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00];
44
45// Partially parsed TIFF field (IFD entry).
46// Value::Unknown is abused to represent a partially parsed value.
47// Such a value must never be exposed to the users of this library.
48#[derive(Debug)]
49pub struct IfdEntry {
50    // When partially parsed, the value is stored as Value::Unknown.
51    // Do not leak this field to the outside.
52    field: MutOnce<Field>,
53}
54
55impl IfdEntry {
56    pub fn ifd_num_tag(&self) -> (In, Tag) {
57        if self.field.is_fixed() {
58            let field = self.field.get_ref();
59            (field.ifd_num, field.tag)
60        } else {
61            let field = self.field.get_mut();
62            (field.ifd_num, field.tag)
63        }
64    }
65
66    pub fn ref_field<'a>(&'a self, data: &[u8], le: bool) -> &'a Field {
67        self.parse(data, le);
68        self.field.get_ref()
69    }
70
71    fn into_field(self, data: &[u8], le: bool) -> Field {
72        self.parse(data, le);
73        self.field.into_inner()
74    }
75
76    fn parse(&self, data: &[u8], le: bool) {
77        if !self.field.is_fixed() {
78            let mut field = self.field.get_mut();
79            if le {
80                Self::parse_value::<LittleEndian>(&mut field.value, data);
81            } else {
82                Self::parse_value::<BigEndian>(&mut field.value, data);
83            }
84        }
85    }
86
87    // Converts a partially parsed value into a real one.
88    fn parse_value<E>(value: &mut Value, data: &[u8]) where E: Endian {
89        match *value {
90            Value::Unknown(typ, cnt, ofs) => {
91                let (unitlen, parser) = get_type_info::<E>(typ);
92                if unitlen != 0 {
93                    *value = parser(data, ofs as usize, cnt as usize);
94                }
95            },
96            _ => panic!("value is already parsed"),
97        }
98    }
99}
100
101/// A TIFF/Exif field.
102#[derive(Debug, Clone)]
103pub struct Field {
104    /// The tag of this field.
105    pub tag: Tag,
106    /// The index of the IFD to which this field belongs.
107    pub ifd_num: In,
108    /// The value of this field.
109    pub value: Value,
110}
111
112/// An IFD number.
113///
114/// The IFDs are indexed from 0.  The 0th IFD is for the primary image
115/// and the 1st one is for the thumbnail.  Two associated constants,
116/// `In::PRIMARY` and `In::THUMBNAIL`, are defined for them respectively.
117///
118/// # Examples
119/// ```
120/// use exif::In;
121/// assert_eq!(In::PRIMARY.index(), 0);
122/// assert_eq!(In::THUMBNAIL.index(), 1);
123/// ```
124#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125pub struct In(pub u16);
126
127impl In {
128    pub const PRIMARY: In = In(0);
129    pub const THUMBNAIL: In = In(1);
130
131    /// Returns the IFD number.
132    #[inline]
133    pub fn index(self) -> u16 {
134        self.0
135    }
136}
137
138impl fmt::Display for In {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        match self.0 {
141            0 => f.pad("primary"),
142            1 => f.pad("thumbnail"),
143            n => f.pad(&format!("IFD{}", n)),
144        }
145    }
146}
147
148/// Parse the Exif attributes in the TIFF format.
149///
150/// Returns a Vec of Exif fields and a bool.
151/// The boolean value is true if the data is little endian.
152/// If an error occurred, `exif::Error` is returned.
153pub fn parse_exif(data: &[u8]) -> Result<(Vec<Field>, bool), Error> {
154    let mut parser = Parser::new();
155    parser.parse(data)?;
156    let (entries, le) = (parser.entries, parser.little_endian);
157    Ok((entries.into_iter().map(|e| e.into_field(data, le)).collect(), le))
158}
159
160#[derive(Debug)]
161pub struct Parser {
162    pub entries: Vec<IfdEntry>,
163    pub little_endian: bool,
164}
165
166impl Parser {
167    pub fn new() -> Self {
168        Self { entries: Vec::new(), little_endian: false }
169    }
170
171    pub fn parse(&mut self, data: &[u8]) -> Result<(), Error> {
172        // Check the byte order and call the real parser.
173        if data.len() < 8 {
174            return Err(Error::InvalidFormat("Truncated TIFF header"));
175        }
176        match BigEndian::loadu16(data, 0) {
177            TIFF_BE => {
178                self.little_endian = false;
179                self.parse_sub::<BigEndian>(data)
180            },
181            TIFF_LE => {
182                self.little_endian = true;
183                self.parse_sub::<LittleEndian>(data)
184            },
185            _ => Err(Error::InvalidFormat("Invalid TIFF byte order")),
186        }
187    }
188
189    fn parse_sub<E>(&mut self, data: &[u8])
190                    -> Result<(), Error> where E: Endian {
191        // Parse the rest of the header (42 and the IFD offset).
192        if E::loadu16(data, 2) != TIFF_FORTY_TWO {
193            return Err(Error::InvalidFormat("Invalid forty two"));
194        }
195        let mut ifd_offset = E::loadu32(data, 4) as usize;
196        let mut ifd_num_ck = Some(0);
197        while ifd_offset != 0 {
198            let ifd_num = ifd_num_ck
199                .ok_or(Error::InvalidFormat("Too many IFDs"))?;
200            // Limit the number of IFDs to defend against resource exhaustion
201            // attacks.
202            if ifd_num >= 8 {
203                return Err(Error::InvalidFormat("Limit the IFD count to 8"));
204            }
205            ifd_offset = self.parse_ifd::<E>(
206                data, ifd_offset, Context::Tiff, ifd_num)?;
207            ifd_num_ck = ifd_num.checked_add(1);
208        }
209        Ok(())
210    }
211
212    // Parse IFD [EXIF23 4.6.2].
213    fn parse_ifd<E>(&mut self, data: &[u8],
214                    offset: usize, ctx: Context, ifd_num: u16)
215                    -> Result<usize, Error> where E: Endian {
216        // Count (the number of the entries).
217        if data.len() < offset || data.len() - offset < 2 {
218            return Err(Error::InvalidFormat("Truncated IFD count"));
219        }
220        let count = E::loadu16(data, offset) as usize;
221
222        // Array of entries.  (count * 12) never overflows.
223        if data.len() - offset - 2 < count * 12 {
224            return Err(Error::InvalidFormat("Truncated IFD"));
225        }
226        for i in 0..count as usize {
227            let tag = E::loadu16(data, offset + 2 + i * 12);
228            let typ = E::loadu16(data, offset + 2 + i * 12 + 2);
229            let cnt = E::loadu32(data, offset + 2 + i * 12 + 4);
230            let valofs_at = offset + 2 + i * 12 + 8;
231            let (unitlen, _parser) = get_type_info::<E>(typ);
232            let vallen = unitlen.checked_mul(cnt as usize).ok_or(
233                Error::InvalidFormat("Invalid entry count"))?;
234            let mut val = if vallen <= 4 {
235                Value::Unknown(typ, cnt, valofs_at as u32)
236            } else {
237                let ofs = E::loadu32(data, valofs_at) as usize;
238                if data.len() < ofs || data.len() - ofs < vallen {
239                    return Err(Error::InvalidFormat("Truncated field value"));
240                }
241                Value::Unknown(typ, cnt, ofs as u32)
242            };
243
244            // No infinite recursion will occur because the context is not
245            // recursively defined.
246            let tag = Tag(ctx, tag);
247            match tag {
248                Tag::ExifIFDPointer => self.parse_child_ifd::<E>(
249                    data, &mut val, Context::Exif, ifd_num)?,
250                Tag::GPSInfoIFDPointer => self.parse_child_ifd::<E>(
251                    data, &mut val, Context::Gps, ifd_num)?,
252                Tag::InteropIFDPointer => self.parse_child_ifd::<E>(
253                    data, &mut val, Context::Interop, ifd_num)?,
254                _ => self.entries.push(IfdEntry { field: Field {
255                    tag: tag, ifd_num: In(ifd_num), value: val }.into()}),
256            }
257        }
258
259        // Offset to the next IFD.
260        if data.len() - offset - 2 - count * 12 < 4 {
261            return Err(Error::InvalidFormat("Truncated next IFD offset"));
262        }
263        let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12);
264        Ok(next_ifd_offset as usize)
265    }
266
267    fn parse_child_ifd<E>(&mut self, data: &[u8],
268                          pointer: &mut Value, ctx: Context, ifd_num: u16)
269                          -> Result<(), Error> where E: Endian {
270        // The pointer is not yet parsed, so do it here.
271        IfdEntry::parse_value::<E>(pointer, data);
272
273        // A pointer field has type == LONG and count == 1, so the
274        // value (IFD offset) must be embedded in the "value offset"
275        // element of the field.
276        let ofs = pointer.get_uint(0).ok_or(
277            Error::InvalidFormat("Invalid pointer"))? as usize;
278        match self.parse_ifd::<E>(data, ofs, ctx, ifd_num)? {
279            0 => Ok(()),
280            _ => Err(Error::InvalidFormat("Unexpected next IFD")),
281        }
282    }
283}
284
285pub fn is_tiff(buf: &[u8]) -> bool {
286    buf.starts_with(&TIFF_BE_SIG) || buf.starts_with(&TIFF_LE_SIG)
287}
288
289/// A struct used to parse a DateTime field.
290///
291/// # Examples
292/// ```
293/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
294/// use exif::DateTime;
295/// let dt = DateTime::from_ascii(b"2016:05:04 03:02:01")?;
296/// assert_eq!(dt.year, 2016);
297/// assert_eq!(dt.to_string(), "2016-05-04 03:02:01");
298/// # Ok(()) }
299/// ```
300#[derive(Debug)]
301pub struct DateTime {
302    pub year: u16,
303    pub month: u8,
304    pub day: u8,
305    pub hour: u8,
306    pub minute: u8,
307    pub second: u8,
308    /// The subsecond data in nanoseconds.  If the Exif attribute has
309    /// more sigfinicant digits, they are rounded down.
310    pub nanosecond: Option<u32>,
311    /// The offset of the time zone in minutes.
312    pub offset: Option<i16>,
313}
314
315impl DateTime {
316    /// Parse an ASCII data of a DateTime field.  The range of a number
317    /// is not validated, so, for example, 13 may be returned as the month.
318    ///
319    /// If the value is blank, `Error::BlankValue` is returned.
320    pub fn from_ascii(data: &[u8]) -> Result<DateTime, Error> {
321        if data == b"    :  :     :  :  " || data == b"                   " {
322            return Err(Error::BlankValue("DateTime is blank"));
323        } else if data.len() < 19 {
324            return Err(Error::InvalidFormat("DateTime too short"));
325        } else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' &&
326                    data[13] == b':' && data[16] == b':') {
327            return Err(Error::InvalidFormat("Invalid DateTime delimiter"));
328        }
329        Ok(DateTime {
330            year: atou16(&data[0..4])?,
331            month: atou16(&data[5..7])? as u8,
332            day: atou16(&data[8..10])? as u8,
333            hour: atou16(&data[11..13])? as u8,
334            minute: atou16(&data[14..16])? as u8,
335            second: atou16(&data[17..19])? as u8,
336            nanosecond: None,
337            offset: None,
338        })
339    }
340
341    /// Parses an SubsecTime-like field.
342    pub fn parse_subsec(&mut self, data: &[u8]) -> Result<(), Error> {
343        let mut subsec = 0;
344        let mut ndigits = 0;
345        for &c in data {
346            if c == b' ' {
347                break;
348            }
349            subsec = subsec * 10 + ctou32(c)?;
350            ndigits += 1;
351            if ndigits >= 9 {
352                break;
353            }
354        }
355        if ndigits == 0 {
356            self.nanosecond = None;
357        } else {
358            for _ in ndigits..9 {
359                subsec *= 10;
360            }
361            self.nanosecond = Some(subsec);
362        }
363        Ok(())
364    }
365
366    /// Parses an OffsetTime-like field.
367    pub fn parse_offset(&mut self, data: &[u8]) -> Result<(), Error> {
368        if data == b"   :  " || data == b"      " {
369            return Err(Error::BlankValue("OffsetTime is blank"));
370        } else if data.len() < 6 {
371            return Err(Error::InvalidFormat("OffsetTime too short"));
372        } else if data[3] != b':' {
373            return Err(Error::InvalidFormat("Invalid OffsetTime delimiter"));
374        }
375        let hour = atou16(&data[1..3])?;
376        let min = atou16(&data[4..6])?;
377        let offset = (hour * 60 + min) as i16;
378        self.offset = Some(match data[0] {
379            b'+' => offset,
380            b'-' => -offset,
381            _ => return Err(Error::InvalidFormat("Invalid OffsetTime sign")),
382        });
383        Ok(())
384    }
385}
386
387impl fmt::Display for DateTime {
388    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389        write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
390               self.year, self.month, self.day,
391               self.hour, self.minute, self.second)
392    }
393}
394
395impl Field {
396    /// Returns an object that implements `std::fmt::Display` for
397    /// printing the value of this field in a tag-specific format.
398    ///
399    /// To print the value with the unit, call `with_unit` method on the
400    /// returned object.  It takes a parameter, which is either `()`,
401    /// `&Field`, or `&Exif`, that provides the unit information.
402    /// If the unit does not depend on another field, `()` can be used.
403    /// Otherwise, `&Field` or `&Exif` should be used.
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// use exif::{Field, In, Tag, Value};
409    ///
410    /// let xres = Field {
411    ///     tag: Tag::XResolution,
412    ///     ifd_num: In::PRIMARY,
413    ///     value: Value::Rational(vec![(72, 1).into()]),
414    /// };
415    /// let resunit = Field {
416    ///     tag: Tag::ResolutionUnit,
417    ///     ifd_num: In::PRIMARY,
418    ///     value: Value::Short(vec![3]),
419    /// };
420    /// assert_eq!(xres.display_value().to_string(), "72");
421    /// assert_eq!(resunit.display_value().to_string(), "cm");
422    /// // The unit of XResolution is indicated by ResolutionUnit.
423    /// assert_eq!(xres.display_value().with_unit(&resunit).to_string(),
424    ///            "72 pixels per cm");
425    /// // If ResolutionUnit is not given, the default value is used.
426    /// assert_eq!(xres.display_value().with_unit(()).to_string(),
427    ///            "72 pixels per inch");
428    /// assert_eq!(xres.display_value().with_unit(&xres).to_string(),
429    ///            "72 pixels per inch");
430    ///
431    /// let flen = Field {
432    ///     tag: Tag::FocalLengthIn35mmFilm,
433    ///     ifd_num: In::PRIMARY,
434    ///     value: Value::Short(vec![24]),
435    /// };
436    /// // The unit of the focal length is always mm, so the argument
437    /// // has nothing to do with the result.
438    /// assert_eq!(flen.display_value().with_unit(()).to_string(),
439    ///            "24 mm");
440    /// assert_eq!(flen.display_value().with_unit(&resunit).to_string(),
441    ///            "24 mm");
442    /// ```
443    #[inline]
444    pub fn display_value(&self) -> DisplayValue {
445        DisplayValue {
446            tag: self.tag,
447            ifd_num: self.ifd_num,
448            value_display: self.value.display_as(self.tag),
449        }
450    }
451}
452
453/// Helper struct for printing a value in a tag-specific format.
454pub struct DisplayValue<'a> {
455    tag: Tag,
456    ifd_num: In,
457    value_display: value::Display<'a>,
458}
459
460impl<'a> DisplayValue<'a> {
461    #[inline]
462    pub fn with_unit<T>(&self, unit_provider: T)
463                        -> DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
464        DisplayValueUnit {
465            ifd_num: self.ifd_num,
466            value_display: self.value_display,
467            unit: self.tag.unit(),
468            unit_provider: unit_provider,
469        }
470    }
471}
472
473impl<'a> fmt::Display for DisplayValue<'a> {
474    #[inline]
475    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
476        self.value_display.fmt(f)
477    }
478}
479
480/// Helper struct for printing a value with its unit.
481pub struct DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
482    ifd_num: In,
483    value_display: value::Display<'a>,
484    unit: Option<&'static [UnitPiece]>,
485    unit_provider: T,
486}
487
488impl<'a, T> fmt::Display for DisplayValueUnit<'a, T> where T: ProvideUnit<'a> {
489    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
490        if let Some(unit) = self.unit {
491            assert!(!unit.is_empty());
492            for piece in unit {
493                match *piece {
494                    UnitPiece::Value => self.value_display.fmt(f),
495                    UnitPiece::Str(s) => f.write_str(s),
496                    UnitPiece::Tag(tag) =>
497                        if let Some(x) = self.unit_provider.get_field(
498                                tag, self.ifd_num) {
499                            x.value.display_as(tag).fmt(f)
500                        } else if let Some(x) = tag.default_value() {
501                            x.display_as(tag).fmt(f)
502                        } else {
503                            write!(f, "[{} missing]", tag)
504                        },
505                }?
506            }
507            Ok(())
508        } else {
509            self.value_display.fmt(f)
510        }
511    }
512}
513
514pub trait ProvideUnit<'a>: Copy {
515    fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field>;
516}
517
518impl<'a> ProvideUnit<'a> for () {
519    fn get_field(self, _tag: Tag, _ifd_num: In) -> Option<&'a Field> {
520        None
521    }
522}
523
524impl<'a> ProvideUnit<'a> for &'a Field {
525    fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
526        Some(self).filter(|x| x.tag == tag && x.ifd_num == ifd_num)
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533
534    #[test]
535    fn in_convert() {
536        assert_eq!(In::PRIMARY.index(), 0);
537        assert_eq!(In::THUMBNAIL.index(), 1);
538        assert_eq!(In(2).index(), 2);
539        assert_eq!(In(65535).index(), 65535);
540        assert_eq!(In::PRIMARY, In(0));
541    }
542
543    #[test]
544    fn in_display() {
545        assert_eq!(format!("{:10}", In::PRIMARY), "primary   ");
546        assert_eq!(format!("{:>10}", In::THUMBNAIL), " thumbnail");
547        assert_eq!(format!("{:10}", In(2)), "IFD2      ");
548        assert_eq!(format!("{:^10}", In(65535)), " IFD65535 ");
549    }
550
551    #[test]
552    fn truncated() {
553        let mut data =
554            b"MM\0\x2a\0\0\0\x08\
555              \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\0".to_vec();
556        parse_exif(&data).unwrap();
557        while let Some(_) = data.pop() {
558            parse_exif(&data).unwrap_err();
559        }
560    }
561
562    // Before the error is returned, the IFD is parsed multiple times
563    // as the 0th, 1st, ..., and n-th IFDs.
564    #[test]
565    fn inf_loop_by_next() {
566        let data = b"MM\0\x2a\0\0\0\x08\
567                     \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08";
568        assert_err_pat!(parse_exif(data),
569                        Error::InvalidFormat("Limit the IFD count to 8"));
570    }
571
572    #[test]
573    fn inf_loop_by_exif_next() {
574        let data = b"MM\x00\x2a\x00\x00\x00\x08\
575                     \x00\x01\x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\
576                     \x00\x00\x00\x00\
577                     \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\
578                     \x00\x00\x00\x08";
579        assert_err_pat!(parse_exif(data),
580                        Error::InvalidFormat("Unexpected next IFD"));
581    }
582
583    #[test]
584    fn unknown_field() {
585        let data = b"MM\0\x2a\0\0\0\x08\
586                     \0\x01\x01\0\xff\xff\0\0\0\x01\0\x14\0\0\0\0\0\0";
587        let (v, _le) = parse_exif(data).unwrap();
588        assert_eq!(v.len(), 1);
589        assert_pat!(v[0].value, Value::Unknown(0xffff, 1, 0x12));
590    }
591
592    #[test]
593    fn date_time() {
594        let mut dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap();
595        assert_eq!(dt.year, 2016);
596        assert_eq!(dt.to_string(), "2016-05-04 03:02:01");
597
598        dt.parse_subsec(b"987").unwrap();
599        assert_eq!(dt.nanosecond.unwrap(), 987000000);
600        dt.parse_subsec(b"000987").unwrap();
601        assert_eq!(dt.nanosecond.unwrap(), 987000);
602        dt.parse_subsec(b"987654321").unwrap();
603        assert_eq!(dt.nanosecond.unwrap(), 987654321);
604        dt.parse_subsec(b"9876543219").unwrap();
605        assert_eq!(dt.nanosecond.unwrap(), 987654321);
606        dt.parse_subsec(b"130   ").unwrap();
607        assert_eq!(dt.nanosecond.unwrap(), 130000000);
608        dt.parse_subsec(b"0").unwrap();
609        assert_eq!(dt.nanosecond.unwrap(), 0);
610        dt.parse_subsec(b"").unwrap();
611        assert!(dt.nanosecond.is_none());
612        dt.parse_subsec(b" ").unwrap();
613        assert!(dt.nanosecond.is_none());
614
615        dt.parse_offset(b"+00:00").unwrap();
616        assert_eq!(dt.offset.unwrap(), 0);
617        dt.parse_offset(b"+01:23").unwrap();
618        assert_eq!(dt.offset.unwrap(), 83);
619        dt.parse_offset(b"+99:99").unwrap();
620        assert_eq!(dt.offset.unwrap(), 6039);
621        dt.parse_offset(b"-01:23").unwrap();
622        assert_eq!(dt.offset.unwrap(), -83);
623        dt.parse_offset(b"-99:99").unwrap();
624        assert_eq!(dt.offset.unwrap(), -6039);
625        assert_err_pat!(dt.parse_offset(b"   :  "), Error::BlankValue(_));
626        assert_err_pat!(dt.parse_offset(b"      "), Error::BlankValue(_));
627    }
628
629    #[test]
630    fn display_value_with_unit() {
631        let cm = Field {
632            tag: Tag::ResolutionUnit,
633            ifd_num: In::PRIMARY,
634            value: Value::Short(vec![3]),
635        };
636        let cm_tn = Field {
637            tag: Tag::ResolutionUnit,
638            ifd_num: In::THUMBNAIL,
639            value: Value::Short(vec![3]),
640        };
641        // No unit.
642        let exifver = Field {
643            tag: Tag::ExifVersion,
644            ifd_num: In::PRIMARY,
645            value: Value::Undefined(b"0231".to_vec(), 0),
646        };
647        assert_eq!(exifver.display_value().to_string(),
648                   "2.31");
649        assert_eq!(exifver.display_value().with_unit(()).to_string(),
650                   "2.31");
651        assert_eq!(exifver.display_value().with_unit(&cm).to_string(),
652                   "2.31");
653        // Fixed string.
654        let width = Field {
655            tag: Tag::ImageWidth,
656            ifd_num: In::PRIMARY,
657            value: Value::Short(vec![257]),
658        };
659        assert_eq!(width.display_value().to_string(),
660                   "257");
661        assert_eq!(width.display_value().with_unit(()).to_string(),
662                   "257 pixels");
663        assert_eq!(width.display_value().with_unit(&cm).to_string(),
664                   "257 pixels");
665        // Unit tag (with a non-default value).
666        // Unit tag is missing but the default is specified.
667        let xres = Field {
668            tag: Tag::XResolution,
669            ifd_num: In::PRIMARY,
670            value: Value::Rational(vec![(300, 1).into()]),
671        };
672        assert_eq!(xres.display_value().to_string(),
673                   "300");
674        assert_eq!(xres.display_value().with_unit(()).to_string(),
675                   "300 pixels per inch");
676        assert_eq!(xres.display_value().with_unit(&cm).to_string(),
677                   "300 pixels per cm");
678        assert_eq!(xres.display_value().with_unit(&cm_tn).to_string(),
679                   "300 pixels per inch");
680        // Unit tag is missing and the default is not specified.
681        let gpslat = Field {
682            tag: Tag::GPSLatitude,
683            ifd_num: In::PRIMARY,
684            value: Value::Rational(vec![
685                (10, 1).into(), (0, 1).into(), (1, 10).into()]),
686        };
687        assert_eq!(gpslat.display_value().to_string(),
688                   "10 deg 0 min 0.1 sec");
689        assert_eq!(gpslat.display_value().with_unit(()).to_string(),
690                   "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]");
691        assert_eq!(gpslat.display_value().with_unit(&cm).to_string(),
692                   "10 deg 0 min 0.1 sec [GPSLatitudeRef missing]");
693    }
694
695    #[test]
696    fn no_borrow_no_move() {
697        let resunit = Field {
698            tag: Tag::ResolutionUnit,
699            ifd_num: In::PRIMARY,
700            value: Value::Short(vec![3]),
701        };
702        // This fails to compile with "temporary value dropped while
703        // borrowed" error if with_unit() borrows self.
704        let d = resunit.display_value().with_unit(());
705        assert_eq!(d.to_string(), "cm");
706        // This fails to compile if with_unit() moves self.
707        let d1 = resunit.display_value();
708        let d2 = d1.with_unit(());
709        assert_eq!(d1.to_string(), "cm");
710        assert_eq!(d2.to_string(), "cm");
711    }
712}