Skip to main content

flashraw_core/
tiff.rs

1//! Zero-copy TIFF / TIFF-EP / DNG IFD parsing.
2//!
3//! Most RAW formats are TIFF-based (DNG, CR2, NEF, ARW, PEF, ORF, RW2, SRW, 3FR,
4//! IIQ maker data, ...). [`Tiff::parse`] reads the IFD chain plus SubIFDs and the
5//! EXIF/GPS IFDs into an in-memory tree of [`Ifd`]s whose entries reference the
6//! source bytes. Values are decoded lazily by the typed accessors on [`Entry`].
7//!
8//! Robustness: IFD offsets are tracked in a visited set (cyclic chains are cut),
9//! recursion depth and total IFD count are bounded, and every value access is
10//! bounds checked.
11
12use crate::bytes::{ByteOrder, ReadAt};
13use crate::{Error, Result};
14
15/// TIFF field types.
16pub mod types {
17    pub const BYTE: u16 = 1;
18    pub const ASCII: u16 = 2;
19    pub const SHORT: u16 = 3;
20    pub const LONG: u16 = 4;
21    pub const RATIONAL: u16 = 5;
22    pub const SBYTE: u16 = 6;
23    pub const UNDEFINED: u16 = 7;
24    pub const SSHORT: u16 = 8;
25    pub const SLONG: u16 = 9;
26    pub const SRATIONAL: u16 = 10;
27    pub const FLOAT: u16 = 11;
28    pub const DOUBLE: u16 = 12;
29    pub const IFD: u16 = 13;
30    pub const LONG8: u16 = 16;
31}
32
33/// Byte size of one value of TIFF type `t` (0 for unknown types).
34pub fn type_size(t: u16) -> usize {
35    match t {
36        1 | 2 | 6 | 7 => 1,
37        3 | 8 => 2,
38        4 | 9 | 11 | 13 => 4,
39        5 | 10 | 12 | 16 | 17 | 18 => 8,
40        _ => 0,
41    }
42}
43
44/// Well-known tag numbers.
45#[allow(missing_docs)]
46pub mod tags {
47    pub const NEW_SUBFILE_TYPE: u16 = 0x00FE;
48    pub const IMAGE_WIDTH: u16 = 0x0100;
49    pub const IMAGE_LENGTH: u16 = 0x0101;
50    pub const BITS_PER_SAMPLE: u16 = 0x0102;
51    pub const COMPRESSION: u16 = 0x0103;
52    pub const PHOTOMETRIC: u16 = 0x0106;
53    pub const FILL_ORDER: u16 = 0x010A;
54    pub const IMAGE_DESCRIPTION: u16 = 0x010E;
55    pub const MAKE: u16 = 0x010F;
56    pub const MODEL: u16 = 0x0110;
57    pub const STRIP_OFFSETS: u16 = 0x0111;
58    pub const ORIENTATION: u16 = 0x0112;
59    pub const SAMPLES_PER_PIXEL: u16 = 0x0115;
60    pub const ROWS_PER_STRIP: u16 = 0x0116;
61    pub const STRIP_BYTE_COUNTS: u16 = 0x0117;
62    pub const X_RESOLUTION: u16 = 0x011A;
63    pub const PLANAR_CONFIG: u16 = 0x011C;
64    pub const SOFTWARE: u16 = 0x0131;
65    pub const DATE_TIME: u16 = 0x0132;
66    pub const ARTIST: u16 = 0x013B;
67    pub const PREDICTOR: u16 = 0x013D;
68    pub const TILE_WIDTH: u16 = 0x0142;
69    pub const TILE_LENGTH: u16 = 0x0143;
70    pub const TILE_OFFSETS: u16 = 0x0144;
71    pub const TILE_BYTE_COUNTS: u16 = 0x0145;
72    pub const SUB_IFDS: u16 = 0x014A;
73    pub const SAMPLE_FORMAT: u16 = 0x0153;
74    pub const JPEG_INTERCHANGE_FORMAT: u16 = 0x0201;
75    pub const JPEG_INTERCHANGE_FORMAT_LENGTH: u16 = 0x0202;
76    pub const YCBCR_SUBSAMPLING: u16 = 0x0212;
77    pub const XMP: u16 = 0x02BC;
78    pub const CFA_REPEAT_PATTERN_DIM: u16 = 0x828D;
79    pub const CFA_PATTERN_EP: u16 = 0x828E;
80    pub const COPYRIGHT: u16 = 0x8298;
81    pub const EXPOSURE_TIME: u16 = 0x829A;
82    pub const F_NUMBER: u16 = 0x829D;
83    pub const EXIF_IFD: u16 = 0x8769;
84    pub const EXPOSURE_PROGRAM: u16 = 0x8822;
85    pub const GPS_IFD: u16 = 0x8825;
86    pub const ISO: u16 = 0x8827;
87    pub const DATE_TIME_ORIGINAL: u16 = 0x9003;
88    pub const OFFSET_TIME_ORIGINAL: u16 = 0x9011;
89    pub const EXPOSURE_BIAS: u16 = 0x9204;
90    pub const METERING_MODE: u16 = 0x9207;
91    pub const FLASH: u16 = 0x9209;
92    pub const FOCAL_LENGTH: u16 = 0x920A;
93    pub const MAKER_NOTE: u16 = 0x927C;
94    pub const SUBSEC_TIME_ORIGINAL: u16 = 0x9291;
95    pub const PIXEL_X_DIMENSION: u16 = 0xA002;
96    pub const PIXEL_Y_DIMENSION: u16 = 0xA003;
97    pub const CFA_PATTERN: u16 = 0xA302;
98    pub const FOCAL_LENGTH_35MM: u16 = 0xA405;
99    pub const BODY_SERIAL: u16 = 0xA431;
100    pub const LENS_SPEC: u16 = 0xA432;
101    pub const LENS_MAKE: u16 = 0xA433;
102    pub const LENS_MODEL: u16 = 0xA434;
103    pub const LENS_SERIAL: u16 = 0xA435;
104    // DNG
105    pub const DNG_VERSION: u16 = 0xC612;
106    pub const UNIQUE_CAMERA_MODEL: u16 = 0xC614;
107    pub const CFA_PLANE_COLOR: u16 = 0xC616;
108    pub const CFA_LAYOUT: u16 = 0xC617;
109    pub const LINEARIZATION_TABLE: u16 = 0xC618;
110    pub const BLACK_LEVEL_REPEAT_DIM: u16 = 0xC619;
111    pub const BLACK_LEVEL: u16 = 0xC61A;
112    pub const BLACK_LEVEL_DELTA_H: u16 = 0xC61B;
113    pub const BLACK_LEVEL_DELTA_V: u16 = 0xC61C;
114    pub const WHITE_LEVEL: u16 = 0xC61D;
115    pub const DEFAULT_SCALE: u16 = 0xC61E;
116    pub const DEFAULT_CROP_ORIGIN: u16 = 0xC61F;
117    pub const DEFAULT_CROP_SIZE: u16 = 0xC620;
118    pub const COLOR_MATRIX1: u16 = 0xC621;
119    pub const COLOR_MATRIX2: u16 = 0xC622;
120    pub const CAMERA_CALIBRATION1: u16 = 0xC623;
121    pub const CAMERA_CALIBRATION2: u16 = 0xC624;
122    pub const ANALOG_BALANCE: u16 = 0xC627;
123    pub const AS_SHOT_NEUTRAL: u16 = 0xC628;
124    pub const AS_SHOT_WHITE_XY: u16 = 0xC629;
125    pub const BASELINE_EXPOSURE: u16 = 0xC62A;
126    pub const BASELINE_NOISE: u16 = 0xC62B;
127    pub const BASELINE_SHARPNESS: u16 = 0xC62C;
128    pub const LINEAR_RESPONSE_LIMIT: u16 = 0xC62E;
129    pub const PROFILE_NAME: u16 = 0xC6F8;
130    pub const PROFILE_HUE_SAT_MAP_DIMS: u16 = 0xC6F9;
131    pub const PROFILE_HUE_SAT_MAP_DATA1: u16 = 0xC6FA;
132    pub const PROFILE_HUE_SAT_MAP_DATA2: u16 = 0xC6FB;
133    pub const PROFILE_TONE_CURVE: u16 = 0xC6FC;
134    pub const PROFILE_EMBED_POLICY: u16 = 0xC6FD;
135    pub const PROFILE_LOOK_TABLE_DIMS: u16 = 0xC725;
136    pub const PROFILE_LOOK_TABLE_DATA: u16 = 0xC726;
137    pub const NOISE_PROFILE: u16 = 0xC761;
138    pub const BASELINE_EXPOSURE_OFFSET: u16 = 0xC7A5;
139    pub const DEFAULT_USER_CROP: u16 = 0xC7B5;
140    pub const PROFILE_HUE_SAT_MAP_DATA3: u16 = 0xCD39;
141    pub const DNG_PRIVATE_DATA: u16 = 0xC634;
142    pub const MAKER_NOTE_SAFETY: u16 = 0xC635;
143    pub const CALIBRATION_ILLUMINANT1: u16 = 0xC65A;
144    pub const CALIBRATION_ILLUMINANT2: u16 = 0xC65B;
145    pub const ACTIVE_AREA: u16 = 0xC68D;
146    pub const MASKED_AREAS: u16 = 0xC68E;
147    pub const FORWARD_MATRIX1: u16 = 0xC714;
148    pub const FORWARD_MATRIX2: u16 = 0xC715;
149    pub const OPCODE_LIST1: u16 = 0xC740;
150    pub const OPCODE_LIST2: u16 = 0xC741;
151    pub const OPCODE_LIST3: u16 = 0xC74E;
152    pub const NEW_RAW_IMAGE_DIGEST: u16 = 0xC7A7;
153    pub const CALIBRATION_ILLUMINANT3: u16 = 0xCD31;
154    pub const COLOR_MATRIX3: u16 = 0xCD33;
155    pub const FORWARD_MATRIX3: u16 = 0xCD34;
156    pub const CAMERA_CALIBRATION3: u16 = 0xCD32;
157    pub const ILLUMINANT_DATA1: u16 = 0xCD35;
158    pub const ILLUMINANT_DATA2: u16 = 0xCD36;
159    pub const ILLUMINANT_DATA3: u16 = 0xCD37;
160    pub const ROW_INTERLEAVE_FACTOR: u16 = 0xC71F;
161    pub const COLUMN_INTERLEAVE_FACTOR: u16 = 0xCD43;
162    pub const JXL_DISTANCE: u16 = 0xCD49;
163}
164
165/// Parsing options.
166#[derive(Debug, Clone, Copy)]
167pub struct TiffOptions {
168    /// Follow SubIFDs (0x14A), EXIF (0x8769) and GPS (0x8825) pointers.
169    pub follow_sub_ifds: bool,
170    pub max_depth: u32,
171    pub max_ifds: usize,
172    pub max_entries: usize,
173    /// Accept IFDs whose last entries run past the end of data (keep the parsed prefix).
174    pub lenient: bool,
175}
176
177impl Default for TiffOptions {
178    fn default() -> Self {
179        TiffOptions { follow_sub_ifds: true, max_depth: 6, max_ifds: 256, max_entries: 4096, lenient: true }
180    }
181}
182
183/// A single IFD entry. `data` holds the raw value bytes (inline or referenced).
184#[derive(Clone, Copy)]
185pub struct Entry<'a> {
186    pub tag: u16,
187    pub typ: u16,
188    pub count: u32,
189    pub order: ByteOrder,
190    /// Raw value bytes (`count * type_size` bytes, possibly truncated if lenient).
191    pub data: &'a [u8],
192    /// The raw 4-byte value/offset field (offset relative to the IFD base).
193    pub value_field: u32,
194    /// Absolute position of the value bytes inside the slice the TIFF was parsed from.
195    pub value_pos: usize,
196}
197
198impl std::fmt::Debug for Entry<'_> {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        write!(f, "Entry(tag={:#06x}, type={}, count={})", self.tag, self.typ, self.count)
201    }
202}
203
204impl<'a> Entry<'a> {
205    /// Number of values that are actually available.
206    pub fn len(&self) -> usize {
207        let sz = type_size(self.typ);
208        self.data.len().checked_div(sz).map_or(self.data.len(), |n| n.min(self.count as usize))
209    }
210    pub fn is_empty(&self) -> bool {
211        self.len() == 0
212    }
213
214    /// Value `i` as u32 (integer types; rationals are truncated).
215    pub fn u32(&self, i: usize) -> Option<u32> {
216        let d = self.data;
217        let o = self.order;
218        Some(match self.typ {
219            types::BYTE | types::UNDEFINED | types::ASCII => *d.get(i)? as u32,
220            types::SBYTE => *d.get(i)? as i8 as i32 as u32,
221            types::SHORT => d.u16_at(o, i * 2).ok()? as u32,
222            types::SSHORT => d.u16_at(o, i * 2).ok()? as i16 as i32 as u32,
223            types::LONG | types::SLONG | types::IFD => d.u32_at(o, i * 4).ok()?,
224            types::LONG8 => d.u64_at(o, i * 8).ok()? as u32,
225            types::RATIONAL | types::SRATIONAL | types::FLOAT | types::DOUBLE => {
226                let f = self.f64(i)?;
227                if f.is_finite() && f >= 0.0 { f as u32 } else { return None }
228            }
229            _ => return None,
230        })
231    }
232    pub fn u16(&self, i: usize) -> Option<u16> {
233        self.u32(i).map(|v| v as u16)
234    }
235    /// Value `i` as signed integer.
236    pub fn i32(&self, i: usize) -> Option<i32> {
237        match self.typ {
238            types::SBYTE | types::SSHORT | types::SLONG => self.u32(i).map(|v| v as i32),
239            types::SRATIONAL | types::RATIONAL | types::FLOAT | types::DOUBLE => self.f64(i).map(|v| v as i32),
240            _ => self.u32(i).map(|v| v as i32),
241        }
242    }
243    /// Value `i` as f64 (any numeric type).
244    pub fn f64(&self, i: usize) -> Option<f64> {
245        let d = self.data;
246        let o = self.order;
247        Some(match self.typ {
248            types::RATIONAL => {
249                let n = d.u32_at(o, i * 8).ok()?;
250                let den = d.u32_at(o, i * 8 + 4).ok()?;
251                if den == 0 {
252                    return None;
253                }
254                n as f64 / den as f64
255            }
256            types::SRATIONAL => {
257                let n = d.u32_at(o, i * 8).ok()? as i32;
258                let den = d.u32_at(o, i * 8 + 4).ok()? as i32;
259                if den == 0 {
260                    return None;
261                }
262                n as f64 / den as f64
263            }
264            types::FLOAT => f32::from_bits(d.u32_at(o, i * 4).ok()?) as f64,
265            types::DOUBLE => f64::from_bits(d.u64_at(o, i * 8).ok()?),
266            types::SBYTE | types::SSHORT | types::SLONG => self.i32(i)? as f64,
267            _ => self.u32(i)? as f64,
268        })
269    }
270    pub fn f32(&self, i: usize) -> Option<f32> {
271        self.f64(i).map(|v| v as f32)
272    }
273    /// All values as u32.
274    pub fn u32s(&self) -> Vec<u32> {
275        (0..self.len()).filter_map(|i| self.u32(i)).collect()
276    }
277    /// All values as f64.
278    pub fn f64s(&self) -> Vec<f64> {
279        (0..self.len()).filter_map(|i| self.f64(i)).collect()
280    }
281    pub fn f32s(&self) -> Vec<f32> {
282        (0..self.len()).filter_map(|i| self.f32(i)).collect()
283    }
284    /// Value as an ASCII string (trimmed at the first NUL, whitespace trimmed).
285    pub fn string(&self) -> String {
286        crate::bytes::ascii_field(self.data)
287    }
288    /// Raw value bytes.
289    pub fn bytes(&self) -> &'a [u8] {
290        self.data
291    }
292}
293
294/// An Image File Directory.
295#[derive(Debug, Clone)]
296pub struct Ifd<'a> {
297    /// Offset of this IFD relative to the TIFF base.
298    pub offset: u32,
299    pub order: ByteOrder,
300    pub entries: Vec<Entry<'a>>,
301    /// SubIFDs (tag 0x14A), in order.
302    pub sub_ifds: Vec<Ifd<'a>>,
303    pub exif: Option<Box<Ifd<'a>>>,
304    pub gps: Option<Box<Ifd<'a>>>,
305    /// Offset of the next IFD in the chain (0 if none).
306    pub next: u32,
307}
308
309impl<'a> Ifd<'a> {
310    pub fn get(&self, tag: u16) -> Option<&Entry<'a>> {
311        self.entries.iter().find(|e| e.tag == tag)
312    }
313    pub fn has(&self, tag: u16) -> bool {
314        self.get(tag).is_some()
315    }
316    pub fn u32(&self, tag: u16) -> Option<u32> {
317        self.get(tag)?.u32(0)
318    }
319    pub fn f64(&self, tag: u16) -> Option<f64> {
320        self.get(tag)?.f64(0)
321    }
322    pub fn string(&self, tag: u16) -> Option<String> {
323        let s = self.get(tag)?.string();
324        (!s.is_empty()).then_some(s)
325    }
326    /// Required u32 tag.
327    pub fn req_u32(&self, tag: u16) -> Result<u32> {
328        self.u32(tag).ok_or_else(|| Error::invalid_metadata(format!("missing TIFF tag {tag:#06x}")))
329    }
330    pub fn width(&self) -> u32 {
331        self.u32(tags::IMAGE_WIDTH).unwrap_or(0)
332    }
333    pub fn height(&self) -> u32 {
334        self.u32(tags::IMAGE_LENGTH).unwrap_or(0)
335    }
336    pub fn compression(&self) -> u32 {
337        self.u32(tags::COMPRESSION).unwrap_or(1)
338    }
339    pub fn bps(&self) -> u32 {
340        self.u32(tags::BITS_PER_SAMPLE).unwrap_or(0)
341    }
342    pub fn is_reduced_resolution(&self) -> bool {
343        self.u32(tags::NEW_SUBFILE_TYPE).unwrap_or(0) & 1 != 0
344    }
345
346    /// This IFD and all nested IFDs (sub IFDs, EXIF, GPS), depth first.
347    pub fn walk(&self) -> Vec<&Ifd<'a>> {
348        let mut out = vec![self];
349        for s in &self.sub_ifds {
350            out.extend(s.walk());
351        }
352        if let Some(e) = &self.exif {
353            out.extend(e.walk());
354        }
355        if let Some(g) = &self.gps {
356            out.push(g);
357        }
358        out
359    }
360}
361
362/// A parsed TIFF structure.
363#[derive(Debug, Clone)]
364pub struct Tiff<'a> {
365    /// The bytes offsets are relative to (`file[base..]`).
366    pub data: &'a [u8],
367    pub order: ByteOrder,
368    /// Top-level IFD chain.
369    pub ifds: Vec<Ifd<'a>>,
370}
371
372struct ParseState {
373    visited: Vec<u32>,
374    count: usize,
375}
376
377impl<'a> Tiff<'a> {
378    /// Parse a standard TIFF header (`II*\0` / `MM\0*`, or vendor magics) at the start of `data`.
379    pub fn parse(data: &'a [u8]) -> Result<Self> {
380        Self::parse_with(data, TiffOptions::default())
381    }
382
383    pub fn parse_with(data: &'a [u8], opts: TiffOptions) -> Result<Self> {
384        let order = match data.get(0..2) {
385            Some(b"II") => ByteOrder::Little,
386            Some(b"MM") => ByteOrder::Big,
387            _ => return Err(Error::UnsupportedFormat("not a TIFF header".into())),
388        };
389        // Magic is 42 for TIFF; ORF uses 0x4F52/0x5352, RW2 0x55, others vary — accept any.
390        let first = data.u32_at(order, 4)?;
391        Self::parse_from(data, order, first, opts)
392    }
393
394    /// Parse an IFD chain starting at `first_ifd` with an explicit byte order.
395    pub fn parse_from(data: &'a [u8], order: ByteOrder, first_ifd: u32, opts: TiffOptions) -> Result<Self> {
396        let mut st = ParseState { visited: Vec::new(), count: 0 };
397        let mut ifds = Vec::new();
398        let mut next = first_ifd;
399        while next != 0 && ifds.len() < 64 {
400            match parse_ifd(data, order, next, 0, &opts, &mut st) {
401                Ok(ifd) => {
402                    next = ifd.next;
403                    ifds.push(ifd);
404                }
405                Err(e) => {
406                    if ifds.is_empty() {
407                        return Err(e);
408                    }
409                    break; // keep what we have: trailing garbage chains are common
410                }
411            }
412        }
413        if ifds.is_empty() {
414            return Err(Error::corrupt("TIFF without IFDs"));
415        }
416        Ok(Tiff { data, order, ifds })
417    }
418
419    /// Every IFD in the file (chain, sub IFDs, EXIF, GPS), depth first.
420    pub fn all_ifds(&self) -> Vec<&Ifd<'a>> {
421        self.ifds.iter().flat_map(|i| i.walk()).collect()
422    }
423
424    /// First IFD (anywhere) that contains `tag`.
425    pub fn find_ifd_with(&self, tag: u16) -> Option<&Ifd<'a>> {
426        self.all_ifds().into_iter().find(|i| i.has(tag))
427    }
428
429    /// First entry for `tag` anywhere in the file.
430    pub fn find_entry(&self, tag: u16) -> Option<&Entry<'a>> {
431        self.all_ifds().into_iter().find_map(|i| i.get(tag))
432    }
433
434    /// Parse a nested IFD (e.g. a maker note directory) located at `offset` within `self.data`.
435    pub fn sub_ifd_at(&self, offset: u32) -> Result<Ifd<'a>> {
436        let mut st = ParseState { visited: Vec::new(), count: 0 };
437        parse_ifd(self.data, self.order, offset, 0, &TiffOptions::default(), &mut st)
438    }
439}
440
441/// Parse a single IFD (and its children) at `offset` of `data`.
442pub fn parse_ifd_at<'a>(data: &'a [u8], order: ByteOrder, offset: u32, opts: TiffOptions) -> Result<Ifd<'a>> {
443    let mut st = ParseState { visited: Vec::new(), count: 0 };
444    parse_ifd(data, order, offset, 0, &opts, &mut st)
445}
446
447fn parse_ifd<'a>(
448    data: &'a [u8],
449    order: ByteOrder,
450    offset: u32,
451    depth: u32,
452    opts: &TiffOptions,
453    st: &mut ParseState,
454) -> Result<Ifd<'a>> {
455    if depth > opts.max_depth {
456        return Err(Error::corrupt("IFD nesting too deep"));
457    }
458    if st.count >= opts.max_ifds {
459        return Err(Error::corrupt("too many IFDs"));
460    }
461    if st.visited.contains(&offset) {
462        return Err(Error::corrupt_at("recursive IFD reference", offset as u64));
463    }
464    st.visited.push(offset);
465    st.count += 1;
466    let base = offset as usize;
467    let n = data.u16_at(order, base)? as usize;
468    if n == 0 || n > opts.max_entries {
469        return Err(Error::corrupt_at(format!("invalid IFD with {n} entries"), offset as u64));
470    }
471    let mut entries = Vec::with_capacity(n);
472    for i in 0..n {
473        let p = base + 2 + i * 12;
474        if p + 12 > data.len() {
475            if opts.lenient && !entries.is_empty() {
476                break;
477            }
478            return Err(Error::corrupt_at("IFD entries beyond end of data", p as u64));
479        }
480        let tag = data.u16_at(order, p)?;
481        let typ = data.u16_at(order, p + 2)?;
482        let count = data.u32_at(order, p + 4)?;
483        let value_field = data.u32_at(order, p + 8)?;
484        let sz = type_size(typ);
485        if sz == 0 {
486            continue; // unknown type: skip entry, keep going
487        }
488        let total = (count as u64) * sz as u64;
489        let (vpos, vbytes) = if total <= 4 {
490            (p + 8, &data[p + 8..p + 8 + total as usize])
491        } else {
492            let o = value_field as usize;
493            if o >= data.len() {
494                continue; // dangling pointer: skip entry
495            }
496            let end = (o as u64 + total).min(data.len() as u64) as usize;
497            (o, &data[o..end])
498        };
499        entries.push(Entry { tag, typ, count, order, data: vbytes, value_field, value_pos: vpos });
500    }
501    let next_pos = base + 2 + n * 12;
502    let next = data.u32_at(order, next_pos).unwrap_or(0);
503    let mut ifd = Ifd { offset, order, entries, sub_ifds: Vec::new(), exif: None, gps: None, next };
504    if opts.follow_sub_ifds {
505        if let Some(e) = ifd.get(tags::SUB_IFDS).copied() {
506            for i in 0..e.len().min(32) {
507                if let Some(o) = e.u32(i) {
508                    if let Ok(sub) = parse_ifd(data, order, o, depth + 1, opts, st) {
509                        ifd.sub_ifds.push(sub);
510                    }
511                }
512            }
513        }
514        if let Some(o) = ifd.u32(tags::EXIF_IFD) {
515            if let Ok(sub) = parse_ifd(data, order, o, depth + 1, opts, st) {
516                ifd.exif = Some(Box::new(sub));
517            }
518        }
519        if let Some(o) = ifd.u32(tags::GPS_IFD) {
520            if let Ok(sub) = parse_ifd(data, order, o, depth + 1, opts, st) {
521                ifd.gps = Some(Box::new(sub));
522            }
523        }
524    }
525    Ok(ifd)
526}
527
528#[cfg(test)]
529pub(crate) mod test_util {
530    /// Minimal little-endian TIFF builder for tests.
531    pub struct TiffBuilder {
532        pub entries: Vec<(u16, u16, u32, Vec<u8>)>,
533    }
534    impl TiffBuilder {
535        pub fn new() -> Self {
536            TiffBuilder { entries: vec![] }
537        }
538        pub fn short(mut self, tag: u16, v: &[u16]) -> Self {
539            self.entries.push((tag, 3, v.len() as u32, v.iter().flat_map(|x| x.to_le_bytes()).collect()));
540            self
541        }
542        pub fn long(mut self, tag: u16, v: &[u32]) -> Self {
543            self.entries.push((tag, 4, v.len() as u32, v.iter().flat_map(|x| x.to_le_bytes()).collect()));
544            self
545        }
546        pub fn ascii(mut self, tag: u16, s: &str) -> Self {
547            let mut b = s.as_bytes().to_vec();
548            b.push(0);
549            self.entries.push((tag, 2, b.len() as u32, b));
550            self
551        }
552        pub fn build(mut self, next: u32) -> Vec<u8> {
553            self.entries.sort_by_key(|e| e.0);
554            let mut out = b"II*\0\x08\0\0\0".to_vec();
555            let n = self.entries.len();
556            let mut extra_off = 8 + 2 + n * 12 + 4;
557            let mut extra: Vec<u8> = vec![];
558            out.extend((n as u16).to_le_bytes());
559            for (tag, typ, count, data) in &self.entries {
560                out.extend(tag.to_le_bytes());
561                out.extend(typ.to_le_bytes());
562                out.extend(count.to_le_bytes());
563                if data.len() <= 4 {
564                    let mut d = data.clone();
565                    d.resize(4, 0);
566                    out.extend(d);
567                } else {
568                    out.extend((extra_off as u32).to_le_bytes());
569                    extra.extend(data);
570                    extra_off += data.len();
571                }
572            }
573            out.extend(next.to_le_bytes());
574            out.extend(extra);
575            out
576        }
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use super::test_util::TiffBuilder;
583    use super::*;
584
585    #[test]
586    fn parse_basic() {
587        let data = TiffBuilder::new()
588            .long(tags::IMAGE_WIDTH, &[640])
589            .short(tags::IMAGE_LENGTH, &[480])
590            .ascii(tags::MAKE, "FlashRaw Test Make")
591            .short(tags::BITS_PER_SAMPLE, &[12, 12, 12])
592            .build(0);
593        let t = Tiff::parse(&data).unwrap();
594        let ifd = &t.ifds[0];
595        assert_eq!(ifd.width(), 640);
596        assert_eq!(ifd.height(), 480);
597        assert_eq!(ifd.string(tags::MAKE).unwrap(), "FlashRaw Test Make");
598        assert_eq!(ifd.get(tags::BITS_PER_SAMPLE).unwrap().u32s(), vec![12, 12, 12]);
599    }
600
601    #[test]
602    fn recursive_chain_terminates() {
603        // next pointer points back at the first IFD
604        let data = TiffBuilder::new().long(tags::IMAGE_WIDTH, &[1]).build(8);
605        let t = Tiff::parse(&data).unwrap();
606        assert_eq!(t.ifds.len(), 1);
607    }
608
609    #[test]
610    fn self_referencing_subifd() {
611        let data = TiffBuilder::new().long(tags::SUB_IFDS, &[8]).long(tags::EXIF_IFD, &[8]).build(0);
612        let t = Tiff::parse(&data).unwrap();
613        assert!(t.ifds[0].sub_ifds.is_empty());
614        assert!(t.ifds[0].exif.is_none());
615    }
616
617    #[test]
618    fn garbage_does_not_panic() {
619        for len in 0..64 {
620            let mut d = vec![0u8; len];
621            if len >= 2 {
622                d[0] = b'I';
623                d[1] = b'I';
624            }
625            let _ = Tiff::parse(&d);
626        }
627        let mut d = TiffBuilder::new().long(tags::STRIP_OFFSETS, &[1, 2, 3, 4]).build(0);
628        // Corrupt count to something enormous.
629        d[8 + 2 + 4] = 0xFF;
630        d[8 + 2 + 5] = 0xFF;
631        d[8 + 2 + 6] = 0xFF;
632        let t = Tiff::parse(&d).unwrap();
633        let e = t.ifds[0].get(tags::STRIP_OFFSETS);
634        if let Some(e) = e {
635            let _ = e.u32s();
636        }
637    }
638}