Skip to main content

otf_pixels_codec_tiff/
ifd.rs

1//! TIFF's image file directory: tags, types, and the endianness they arrive in.
2//!
3//! A TIFF is a header naming a byte order and pointing at an IFD; an IFD is a
4//! count followed by twelve-byte entries, each a tag, a type, a count and
5//! either a value or an offset to one. Everything about the image — its size,
6//! its layout, where its pixels live — is a tag.
7//!
8//! # Both endiannesses are real
9//!
10//! `II` (Intel, little) and `MM` (Motorola, big) are both common in the wild;
11//! scanners and Adobe tools disagree. A decoder that assumed one would fail
12//! half the files it met, so byte order is a value threaded through every
13//! read rather than a compile-time choice.
14//!
15//! # Unknown tags are skipped
16//!
17//! SPEC §Formats: "exotic tags are skipped, not errors". TIFF's extensibility
18//! is the whole point of the format, and every real file carries tags a given
19//! reader does not know. Only tags that change how pixels are *laid out* can
20//! be fatal when unsupported.
21
22use otf_pixels_core::{PixelsError, Result};
23
24/// Byte order, from the two-character header.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum ByteOrder {
27    /// `II` — least significant byte first.
28    Little,
29    /// `MM` — most significant byte first.
30    Big,
31}
32
33impl ByteOrder {
34    /// Read a 16-bit value at `at`.
35    #[must_use]
36    pub fn u16(self, data: &[u8], at: usize) -> u16 {
37        let a = data.get(at).copied().unwrap_or(0);
38        let b = data.get(at + 1).copied().unwrap_or(0);
39        match self {
40            Self::Little => u16::from_le_bytes([a, b]),
41            Self::Big => u16::from_be_bytes([a, b]),
42        }
43    }
44
45    /// Read a 32-bit value at `at`.
46    #[must_use]
47    pub fn u32(self, data: &[u8], at: usize) -> u32 {
48        let mut bytes = [0_u8; 4];
49        for (slot, offset) in bytes.iter_mut().zip(0..4) {
50            *slot = data.get(at + offset).copied().unwrap_or(0);
51        }
52        match self {
53            Self::Little => u32::from_le_bytes(bytes),
54            Self::Big => u32::from_be_bytes(bytes),
55        }
56    }
57
58    /// Write a 16-bit value.
59    #[must_use]
60    pub const fn write_u16(self, value: u16) -> [u8; 2] {
61        match self {
62            Self::Little => value.to_le_bytes(),
63            Self::Big => value.to_be_bytes(),
64        }
65    }
66
67    /// Write a 32-bit value.
68    #[must_use]
69    pub const fn write_u32(self, value: u32) -> [u8; 4] {
70        match self {
71            Self::Little => value.to_le_bytes(),
72            Self::Big => value.to_be_bytes(),
73        }
74    }
75}
76
77/// The baseline tags this codec understands (TIFF 6.0 §Section 8).
78pub mod tag {
79    /// Image width in pixels.
80    pub const IMAGE_WIDTH: u16 = 256;
81    /// Image height in pixels.
82    pub const IMAGE_LENGTH: u16 = 257;
83    /// Bits per sample, one entry per channel.
84    pub const BITS_PER_SAMPLE: u16 = 258;
85    /// Compression scheme.
86    pub const COMPRESSION: u16 = 259;
87    /// How samples are interpreted: greyscale, RGB, palette.
88    pub const PHOTOMETRIC: u16 = 262;
89    /// Byte offset of each strip.
90    pub const STRIP_OFFSETS: u16 = 273;
91    /// How the stored image must be turned to display upright.
92    pub const ORIENTATION: u16 = 274;
93    /// The embedded ICC profile (`InterColorProfile`, TIFF/EP), UNDEFINED.
94    pub const ICC_PROFILE: u16 = 34_675;
95    /// Channels per pixel.
96    pub const SAMPLES_PER_PIXEL: u16 = 277;
97    /// Rows per strip.
98    pub const ROWS_PER_STRIP: u16 = 278;
99    /// Compressed byte count of each strip.
100    pub const STRIP_BYTE_COUNTS: u16 = 279;
101    /// The colour map, for palette images.
102    pub const COLOR_MAP: u16 = 320;
103    /// How channels are arranged: interleaved or planar.
104    pub const PLANAR_CONFIG: u16 = 284;
105    /// Predictor applied before compression.
106    pub const PREDICTOR: u16 = 317;
107    /// Tile width in pixels.
108    pub const TILE_WIDTH: u16 = 322;
109    /// Tile height in pixels.
110    pub const TILE_LENGTH: u16 = 323;
111    /// Byte offset of each tile.
112    pub const TILE_OFFSETS: u16 = 324;
113    /// Compressed byte count of each tile.
114    pub const TILE_BYTE_COUNTS: u16 = 325;
115    /// Extra channel semantics, which is where alpha is declared.
116    pub const EXTRA_SAMPLES: u16 = 338;
117    /// Sample format: unsigned, signed or float.
118    pub const SAMPLE_FORMAT: u16 = 339;
119}
120
121/// A TIFF field type, and how many bytes one value of it occupies.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum FieldType {
124    /// 8-bit unsigned.
125    Byte,
126    /// NUL-terminated string.
127    Ascii,
128    /// 16-bit unsigned.
129    Short,
130    /// 32-bit unsigned.
131    Long,
132    /// Two 32-bit values, numerator and denominator.
133    Rational,
134    /// A type this codec does not interpret, with its declared size.
135    Other(u16, usize),
136}
137
138impl FieldType {
139    /// The type for a field's type code.
140    #[must_use]
141    pub const fn from_code(code: u16) -> Self {
142        match code {
143            1 => Self::Byte,
144            2 => Self::Ascii,
145            3 => Self::Short,
146            4 => Self::Long,
147            5 => Self::Rational,
148            // Signed variants and floats occupy known sizes even though we do
149            // not interpret them; knowing the size is what lets an unknown tag
150            // be skipped rather than desynchronise the directory.
151            6 => Self::Other(code, 1),
152            7 => Self::Other(code, 1),
153            8 => Self::Other(code, 2),
154            9 => Self::Other(code, 4),
155            10 => Self::Other(code, 8),
156            11 => Self::Other(code, 4),
157            12 => Self::Other(code, 8),
158            other => Self::Other(other, 0),
159        }
160    }
161
162    /// Bytes per value of this type.
163    #[must_use]
164    pub const fn size(self) -> usize {
165        match self {
166            Self::Byte | Self::Ascii => 1,
167            Self::Short => 2,
168            Self::Long => 4,
169            Self::Rational => 8,
170            Self::Other(_, size) => size,
171        }
172    }
173}
174
175/// One directory entry, with its values already resolved.
176#[derive(Debug, Clone)]
177pub struct Entry {
178    /// The tag this entry carries.
179    pub tag: u16,
180    /// The field type.
181    pub field_type: FieldType,
182    /// The values, widened to `u32`. Types wider than 32 bits are not
183    /// interpreted, so their entries carry no values.
184    pub values: Vec<u32>,
185}
186
187impl Entry {
188    /// The first value, if any.
189    #[must_use]
190    pub fn first(&self) -> Option<u32> {
191        self.values.first().copied()
192    }
193}
194
195/// A parsed image file directory.
196#[derive(Debug, Clone)]
197pub struct Directory {
198    entries: Vec<Entry>,
199    /// Offset of the next IFD, or zero if this is the last.
200    next: u32,
201}
202
203/// The largest value array read from one tag.
204///
205/// A tiled 2 GB TIFF legitimately has hundreds of thousands of tile offsets,
206/// so this cannot be small — but it must exist, because the count is a 32-bit
207/// field an attacker controls.
208const MAX_VALUES: usize = 16 * 1024 * 1024;
209
210impl Directory {
211    /// Parse the IFD at `offset` within `data`.
212    ///
213    /// # Errors
214    ///
215    /// Returns [`PixelsError::Malformed`] for a directory that runs past the
216    /// end of the data or declares an implausible value count.
217    pub fn parse(data: &[u8], order: ByteOrder, offset: usize) -> Result<Self> {
218        let count = order.u16(data, offset) as usize;
219        if data.len() < offset + 2 + count * 12 + 4 {
220            return Err(PixelsError::malformed(
221                "tiff",
222                format!("directory of {count} entries runs past the end of the file"),
223            ));
224        }
225
226        let mut entries = Vec::with_capacity(count);
227        for index in 0..count {
228            let at = offset + 2 + index * 12;
229            let tag = order.u16(data, at);
230            let field_type = FieldType::from_code(order.u16(data, at + 2));
231            let value_count = order.u32(data, at + 4) as usize;
232            let size = field_type.size();
233
234            if size == 0 || value_count > MAX_VALUES {
235                // An unknown type or an implausible count: keep the tag so a
236                // caller can see it was present, but read no values. Skipping
237                // rather than failing is what SPEC §Formats requires.
238                entries.push(Entry {
239                    tag,
240                    field_type,
241                    values: Vec::new(),
242                });
243                continue;
244            }
245
246            let total = value_count.saturating_mul(size);
247            // Values of four bytes or fewer live in the entry itself; anything
248            // larger is an offset. Getting this backwards is the classic TIFF
249            // parsing bug, and it silently reads the offset as data.
250            let values_at = if total <= 4 {
251                at + 8
252            } else {
253                order.u32(data, at + 8) as usize
254            };
255
256            let mut values = Vec::with_capacity(value_count.min(4096));
257            for value_index in 0..value_count {
258                let value_at = values_at + value_index * size;
259                if value_at + size > data.len() {
260                    break;
261                }
262                let value = match field_type {
263                    // UNDEFINED (7) is opaque bytes, which is how an ICC
264                    // profile is stored.
265                    FieldType::Byte | FieldType::Ascii | FieldType::Other(7, _) => {
266                        u32::from(data.get(value_at).copied().unwrap_or(0))
267                    }
268                    FieldType::Short => u32::from(order.u16(data, value_at)),
269                    FieldType::Long => order.u32(data, value_at),
270                    // A rational's numerator is the useful half for the tags
271                    // we read (resolution), and we read none of them for
272                    // pixels, so the denominator is dropped rather than lost.
273                    FieldType::Rational => order.u32(data, value_at),
274                    FieldType::Other(..) => break,
275                };
276                values.push(value);
277            }
278
279            entries.push(Entry {
280                tag,
281                field_type,
282                values,
283            });
284        }
285
286        let next = order.u32(data, offset + 2 + count * 12);
287        Ok(Self { entries, next })
288    }
289
290    /// The entry for `tag`, if present.
291    #[must_use]
292    pub fn get(&self, tag: u16) -> Option<&Entry> {
293        self.entries.iter().find(|entry| entry.tag == tag)
294    }
295
296    /// The first value of `tag`, if present.
297    #[must_use]
298    pub fn value(&self, tag: u16) -> Option<u32> {
299        self.get(tag).and_then(Entry::first)
300    }
301
302    /// The first value of `tag`, or `default` if absent.
303    #[must_use]
304    pub fn value_or(&self, tag: u16, default: u32) -> u32 {
305        self.value(tag).unwrap_or(default)
306    }
307
308    /// Every value of `tag`, or an empty slice if absent.
309    #[must_use]
310    pub fn values(&self, tag: u16) -> &[u32] {
311        self.get(tag).map_or(&[], |entry| &entry.values)
312    }
313
314    /// The first value of `tag`, or a malformed-input error naming it.
315    ///
316    /// # Errors
317    ///
318    /// Returns [`PixelsError::Malformed`] if the tag is absent or empty.
319    pub fn require(&self, tag: u16, name: &str) -> Result<u32> {
320        self.value(tag)
321            .ok_or_else(|| PixelsError::malformed("tiff", format!("missing required tag {name}")))
322    }
323
324    /// The offset of the next directory, or `None` if this is the last.
325    #[must_use]
326    pub const fn next_offset(&self) -> Option<u32> {
327        if self.next == 0 {
328            None
329        } else {
330            Some(self.next)
331        }
332    }
333
334    /// How many entries the directory holds.
335    #[must_use]
336    pub fn len(&self) -> usize {
337        self.entries.len()
338    }
339
340    /// Whether the directory is empty.
341    #[must_use]
342    pub fn is_empty(&self) -> bool {
343        self.entries.is_empty()
344    }
345}
346
347/// Parse the eight-byte TIFF header, returning the byte order and first IFD.
348///
349/// # Errors
350///
351/// Returns [`PixelsError::Malformed`] for a bad byte-order mark or magic
352/// number.
353pub fn parse_header(data: &[u8]) -> Result<(ByteOrder, usize)> {
354    let order = match data.get(..2) {
355        Some(b"II") => ByteOrder::Little,
356        Some(b"MM") => ByteOrder::Big,
357        _ => {
358            return Err(PixelsError::malformed(
359                "tiff",
360                "byte-order mark is neither II nor MM",
361            ));
362        }
363    };
364    // 42 is the magic, and reading it *in the declared order* is what proves
365    // the byte-order mark was honest.
366    let magic = order.u16(data, 2);
367    if magic != 42 {
368        return Err(PixelsError::malformed(
369            "tiff",
370            format!("magic number is {magic}, not 42"),
371        ));
372    }
373    Ok((order, order.u32(data, 4) as usize))
374}
375
376/// Whether `prefix` starts with a TIFF header.
377#[must_use]
378pub fn probe(prefix: &[u8]) -> bool {
379    parse_header(prefix).is_ok()
380}
381
382#[cfg(test)]
383#[allow(
384    clippy::unwrap_used,
385    clippy::expect_used,
386    clippy::indexing_slicing,
387    clippy::panic,
388    reason = "tests operate on known-good values and assert shapes directly"
389)]
390mod tests {
391    use super::*;
392
393    /// Build a minimal TIFF with the given entries, in the given byte order.
394    fn build(order: ByteOrder, entries: &[(u16, FieldType, Vec<u32>)]) -> Vec<u8> {
395        let mut out = Vec::new();
396        out.extend_from_slice(if order == ByteOrder::Little {
397            b"II"
398        } else {
399            b"MM"
400        });
401        out.extend_from_slice(&order.write_u16(42));
402        out.extend_from_slice(&order.write_u32(8));
403
404        // Directory, then any values that did not fit inline.
405        let mut directory = Vec::new();
406        directory.extend_from_slice(&order.write_u16(entries.len() as u16));
407        let heap_start = 8 + 2 + entries.len() * 12 + 4;
408        let mut heap = Vec::new();
409
410        for (tag, field_type, values) in entries {
411            directory.extend_from_slice(&order.write_u16(*tag));
412            let code = match field_type {
413                FieldType::Byte => 1,
414                FieldType::Ascii => 2,
415                FieldType::Short => 3,
416                FieldType::Long => 4,
417                FieldType::Rational => 5,
418                FieldType::Other(code, _) => *code,
419            };
420            directory.extend_from_slice(&order.write_u16(code));
421            directory.extend_from_slice(&order.write_u32(values.len() as u32));
422
423            let size = field_type.size();
424            let total = values.len() * size;
425            let mut encoded = Vec::new();
426            for &value in values {
427                match size {
428                    1 => encoded.push(value as u8),
429                    2 => encoded.extend_from_slice(&order.write_u16(value as u16)),
430                    _ => encoded.extend_from_slice(&order.write_u32(value)),
431                }
432            }
433            if total <= 4 {
434                encoded.resize(4, 0);
435                directory.extend_from_slice(&encoded);
436            } else {
437                directory.extend_from_slice(&order.write_u32((heap_start + heap.len()) as u32));
438                heap.extend_from_slice(&encoded);
439            }
440        }
441        directory.extend_from_slice(&order.write_u32(0));
442
443        out.extend_from_slice(&directory);
444        out.extend_from_slice(&heap);
445        out
446    }
447
448    #[test]
449    fn both_byte_orders_parse() {
450        // Half the TIFFs in the world are big-endian; assuming one would fail
451        // them all.
452        for order in [ByteOrder::Little, ByteOrder::Big] {
453            let bytes = build(order, &[(tag::IMAGE_WIDTH, FieldType::Long, vec![640])]);
454            let (parsed_order, offset) = parse_header(&bytes).unwrap();
455            assert_eq!(parsed_order, order);
456            let directory = Directory::parse(&bytes, order, offset).unwrap();
457            assert_eq!(directory.value(tag::IMAGE_WIDTH), Some(640), "{order:?}");
458        }
459    }
460
461    #[test]
462    fn a_wrong_magic_number_is_rejected() {
463        let mut bytes = build(ByteOrder::Little, &[]);
464        bytes[2] = 43;
465        assert!(parse_header(&bytes).is_err());
466    }
467
468    #[test]
469    fn a_bad_byte_order_mark_is_rejected() {
470        let mut bytes = build(ByteOrder::Little, &[]);
471        bytes[0] = b'X';
472        assert!(parse_header(&bytes).is_err());
473    }
474
475    #[test]
476    fn small_values_live_inline_and_large_ones_are_offsets() {
477        // The classic TIFF parsing bug is getting this backwards, which reads
478        // the offset itself as data and produces plausible nonsense.
479        for order in [ByteOrder::Little, ByteOrder::Big] {
480            // Two shorts fit in four bytes; three do not.
481            let inline = build(
482                order,
483                &[(tag::BITS_PER_SAMPLE, FieldType::Short, vec![8, 8])],
484            );
485            let (o, at) = parse_header(&inline).unwrap();
486            let directory = Directory::parse(&inline, o, at).unwrap();
487            assert_eq!(directory.values(tag::BITS_PER_SAMPLE), &[8, 8], "{order:?}");
488
489            let offset = build(
490                order,
491                &[(tag::BITS_PER_SAMPLE, FieldType::Short, vec![8, 8, 8])],
492            );
493            let (o, at) = parse_header(&offset).unwrap();
494            let directory = Directory::parse(&offset, o, at).unwrap();
495            assert_eq!(
496                directory.values(tag::BITS_PER_SAMPLE),
497                &[8, 8, 8],
498                "{order:?}"
499            );
500        }
501    }
502
503    #[test]
504    fn every_field_type_width_reads_correctly() {
505        let bytes = build(
506            ByteOrder::Little,
507            &[
508                (100, FieldType::Byte, vec![1, 2, 3]),
509                (101, FieldType::Short, vec![1000, 2000]),
510                (102, FieldType::Long, vec![100_000]),
511            ],
512        );
513        let (order, at) = parse_header(&bytes).unwrap();
514        let directory = Directory::parse(&bytes, order, at).unwrap();
515        assert_eq!(directory.values(100), &[1, 2, 3]);
516        assert_eq!(directory.values(101), &[1000, 2000]);
517        assert_eq!(directory.values(102), &[100_000]);
518    }
519
520    #[test]
521    fn an_unknown_tag_is_kept_but_not_fatal() {
522        // SPEC §Formats: exotic tags are skipped, not errors. TIFF's
523        // extensibility is the point of the format.
524        let bytes = build(
525            ByteOrder::Little,
526            &[
527                (60_000, FieldType::Long, vec![7]),
528                (tag::IMAGE_WIDTH, FieldType::Long, vec![32]),
529            ],
530        );
531        let (order, at) = parse_header(&bytes).unwrap();
532        let directory = Directory::parse(&bytes, order, at).unwrap();
533        assert_eq!(directory.value(60_000), Some(7));
534        assert_eq!(
535            directory.value(tag::IMAGE_WIDTH),
536            Some(32),
537            "an unknown tag desynchronised the directory"
538        );
539    }
540
541    #[test]
542    fn an_unknown_field_type_does_not_desynchronise_the_directory() {
543        // Entries are a fixed twelve bytes whatever their type, so a type we
544        // cannot read must not stop us reading the tags after it.
545        let bytes = build(
546            ByteOrder::Little,
547            &[
548                (60_001, FieldType::Other(31_000, 0), vec![]),
549                (tag::IMAGE_LENGTH, FieldType::Long, vec![48]),
550            ],
551        );
552        let (order, at) = parse_header(&bytes).unwrap();
553        let directory = Directory::parse(&bytes, order, at).unwrap();
554        assert_eq!(directory.value(tag::IMAGE_LENGTH), Some(48));
555    }
556
557    #[test]
558    fn a_missing_required_tag_names_itself() {
559        let bytes = build(ByteOrder::Little, &[]);
560        let (order, at) = parse_header(&bytes).unwrap();
561        let directory = Directory::parse(&bytes, order, at).unwrap();
562        let error = directory
563            .require(tag::IMAGE_WIDTH, "ImageWidth")
564            .unwrap_err();
565        assert!(error.to_string().contains("ImageWidth"), "{error}");
566    }
567
568    #[test]
569    fn a_directory_running_past_the_file_is_an_error() {
570        let mut bytes = build(
571            ByteOrder::Little,
572            &[(tag::IMAGE_WIDTH, FieldType::Long, vec![1])],
573        );
574        // Claim a hundred entries in a file that holds one.
575        bytes[8] = 100;
576        let (order, at) = parse_header(&bytes).unwrap();
577        assert!(Directory::parse(&bytes, order, at).is_err());
578    }
579
580    #[test]
581    fn a_value_offset_past_the_file_truncates_rather_than_panicking() {
582        let mut bytes = build(
583            ByteOrder::Little,
584            &[(tag::STRIP_OFFSETS, FieldType::Long, vec![1, 2, 3])],
585        );
586        // Point the value array at the far end of the address space.
587        let at = 8 + 2 + 8;
588        bytes[at..at + 4].copy_from_slice(&0xFFFF_FF00_u32.to_le_bytes());
589        let (order, start) = parse_header(&bytes).unwrap();
590        let directory = Directory::parse(&bytes, order, start).unwrap();
591        assert!(
592            directory.values(tag::STRIP_OFFSETS).is_empty(),
593            "an out-of-range offset should yield no values"
594        );
595    }
596
597    #[test]
598    fn arbitrary_bytes_never_panic() {
599        let mut seed = 0x2468_ACE0_u32;
600        for _ in 0..2000 {
601            let len = (seed % 300) as usize + 8;
602            let data: Vec<u8> = (0..len)
603                .map(|_| {
604                    seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
605                    (seed >> 16) as u8
606                })
607                .collect();
608            if let Ok((order, offset)) = parse_header(&data) {
609                let _ = Directory::parse(&data, order, offset);
610            }
611        }
612    }
613
614    #[test]
615    fn probe_accepts_only_a_real_header() {
616        assert!(probe(b"II\x2a\x00\x08\x00\x00\x00"));
617        assert!(probe(b"MM\x00\x2a\x00\x00\x00\x08"));
618        assert!(!probe(b"II\x2b\x00\x08\x00\x00\x00"), "BigTIFF is not v1");
619        assert!(!probe(b"GIF89a"));
620        assert!(!probe(b""));
621    }
622}