1use crate::tiff::{
13 malformed, read_ifd, type_size, Reader, MAX_ENTRIES_PER_IFD, MAX_IFDS, MAX_VALUES,
14};
15use crate::{Error, Result};
16use std::fs::File;
17use std::io::{Read, Seek};
18use std::path::Path;
19
20const TAG_SUB_IFDS: u16 = 330;
22const TAG_EXIF_IFD: u16 = 34665;
23const TAG_GPS_IFD: u16 = 34853;
24const TAG_INTEROP_IFD: u16 = 40965;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
32pub enum IfdKind {
33 Main(u16),
35 Sub(u16),
37 Exif,
38 Gps,
39 Interop,
40}
41
42impl IfdKind {
43 pub fn label(&self) -> String {
44 match self {
45 IfdKind::Main(0) => "IFD0".into(),
46 IfdKind::Main(n) => format!("IFD{n}"),
47 IfdKind::Sub(n) => format!("SubIFD{n}"),
48 IfdKind::Exif => "Exif".into(),
49 IfdKind::Gps => "GPS".into(),
50 IfdKind::Interop => "Interop".into(),
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct RawEntry {
58 pub tag: u16,
59 pub dtype: u16,
62 pub count: u32,
63 pub value_offset: u64,
66 pub inline: bool,
68 pub bytes: Vec<u8>,
72 pub unreadable: Option<String>,
74}
75
76impl RawEntry {
77 pub fn declared_len(&self) -> u64 {
79 type_size(self.dtype).unwrap_or(0) * u64::from(self.count)
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct RawIfd {
85 pub kind: IfdKind,
86 pub offset: u64,
87 pub entries: Vec<RawEntry>,
88}
89
90#[derive(Debug, Clone)]
91pub struct Directories {
92 pub little_endian: bool,
93 pub file_len: u64,
94 pub tiff_base: u64,
101 pub ifds: Vec<RawIfd>,
102}
103
104impl Directories {
105 pub fn entry(&self, kind: IfdKind, tag: u16) -> Option<&RawEntry> {
106 self.ifds
107 .iter()
108 .find(|i| i.kind == kind)?
109 .entries
110 .iter()
111 .find(|e| e.tag == tag)
112 }
113}
114
115pub struct Window<R> {
121 inner: R,
122 base: u64,
123 len: u64,
124 pos: u64,
125}
126
127impl<R: Read + Seek> Window<R> {
128 pub fn new(inner: R, base: u64, len: u64) -> Self {
129 Self {
130 inner,
131 base,
132 len,
133 pos: 0,
134 }
135 }
136}
137
138impl<R: Read + Seek> Read for Window<R> {
139 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
140 let left = self.len.saturating_sub(self.pos);
141 if left == 0 {
142 return Ok(0);
143 }
144 let want = buf.len().min(left as usize);
145 self.inner
146 .seek(std::io::SeekFrom::Start(self.base + self.pos))?;
147 let n = self.inner.read(&mut buf[..want])?;
148 self.pos += n as u64;
149 Ok(n)
150 }
151}
152
153impl<R: Read + Seek> Seek for Window<R> {
154 fn seek(&mut self, from: std::io::SeekFrom) -> std::io::Result<u64> {
155 use std::io::SeekFrom::*;
156 let p = match from {
157 Start(n) => n as i64,
158 End(n) => self.len as i64 + n,
159 Current(n) => self.pos as i64 + n,
160 };
161 if p < 0 {
162 return Err(std::io::Error::new(
163 std::io::ErrorKind::InvalidInput,
164 "seek before start of window",
165 ));
166 }
167 self.pos = p as u64;
168 Ok(self.pos)
169 }
170}
171
172fn jpeg_exif_window<R: Read + Seek>(src: &mut R, file_len: u64) -> Result<(u64, u64)> {
176 let mut pos = 2u64; loop {
178 if pos + 4 > file_len {
179 return Err(malformed("no APP1 Exif segment".into()));
180 }
181 src.seek(std::io::SeekFrom::Start(pos))?;
182 let mut hdr = [0u8; 4];
183 src.read_exact(&mut hdr)
184 .map_err(|_| malformed("truncated JPEG segment header".into()))?;
185 if hdr[0] != 0xFF {
186 return Err(malformed(format!("bad JPEG marker at {pos}")));
187 }
188 let marker = hdr[1];
189 if marker == 0xDA || marker == 0xD9 {
191 return Err(malformed("no APP1 Exif segment before image data".into()));
192 }
193 if marker == 0x01 || (0xD0..=0xD8).contains(&marker) {
195 pos += 2;
196 continue;
197 }
198 let seg_len = u64::from(u16::from_be_bytes([hdr[2], hdr[3]]));
199 if seg_len < 2 {
200 return Err(malformed(format!(
201 "JPEG segment at {pos} declares {seg_len}"
202 )));
203 }
204 let data = pos + 4;
205 let data_len = seg_len - 2;
206
207 if marker == 0xE1 && data_len > 6 {
208 let mut tag = [0u8; 6];
209 src.seek(std::io::SeekFrom::Start(data))?;
210 if src.read_exact(&mut tag).is_ok() && &tag == b"Exif\0\0" {
211 return Ok((data + 6, data_len - 6));
212 }
213 }
214 pos = data + data_len;
215 }
216}
217
218pub fn read(path: &Path) -> Result<Directories> {
220 let mut f = File::open(path)?;
221 let file_len = f.metadata()?.len();
222 read_from(&mut f, file_len)
223}
224
225pub fn read_from<R: Read + Seek>(src: &mut R, file_len: u64) -> Result<Directories> {
226 let mut magic = [0u8; 4];
227 src.seek(std::io::SeekFrom::Start(0))?;
228 src.read_exact(&mut magic)
229 .map_err(|_| Error::UnknownFormat)?;
230
231 if magic[0] == 0xFF && magic[1] == 0xD8 {
234 let (base, len) = jpeg_exif_window(src, file_len)?;
235 let mut w = Window::new(src, base, len);
236 let mut d = read_inner(&mut w, len)?;
237 d.tiff_base = base;
238 d.file_len = file_len;
239 for i in &mut d.ifds {
240 i.offset += base;
241 for e in &mut i.entries {
242 e.value_offset += base;
243 }
244 }
245 return Ok(d);
246 }
247
248 read_inner(src, file_len)
249}
250
251fn read_inner<R: Read + Seek>(src: &mut R, file_len: u64) -> Result<Directories> {
252 let mut magic = [0u8; 4];
253 src.seek(std::io::SeekFrom::Start(0))?;
254 src.read_exact(&mut magic)
255 .map_err(|_| Error::UnknownFormat)?;
256 let little_endian = match &magic {
257 [b'I', b'I', 42, 0] => true,
258 [b'M', b'M', 0, 42] => false,
259 _ => return Err(Error::UnknownFormat),
260 };
261
262 let mut r = Reader {
263 src,
264 little_endian,
265 file_len,
266 };
267 let first = u64::from(r.u32_at(4)?);
268
269 let mut ifds: Vec<RawIfd> = Vec::new();
272 let mut seen: Vec<u64> = Vec::new();
273 let mut queue: std::collections::VecDeque<(u64, IfdKind)> = std::collections::VecDeque::new();
274 queue.push_back((first, IfdKind::Main(0)));
275
276 let mut sub_n = 0u16;
277 let mut main_n = 0u16;
278
279 while let Some((off, kind)) = queue.pop_front() {
280 if off == 0 || off >= file_len || seen.contains(&off) || ifds.len() >= MAX_IFDS {
281 continue;
282 }
283 seen.push(off);
284
285 let ifd = match read_ifd(&mut r, off) {
286 Ok(i) => i,
287 Err(_) => continue,
289 };
290 if ifd.entries.len() > usize::from(MAX_ENTRIES_PER_IFD) {
291 continue;
292 }
293
294 let mut entries = Vec::with_capacity(ifd.entries.len());
295 for (i, e) in ifd.entries.iter().enumerate() {
296 let entry_off = off + 2 + (i as u64) * 12;
297 let esize = type_size(e.dtype).unwrap_or(0);
298 let total = esize.saturating_mul(u64::from(e.count));
299 let inline = total <= 4;
300 let value_offset = if inline {
301 entry_off + 8
302 } else {
303 u64::from(if little_endian {
304 u32::from_le_bytes(e.value_field)
305 } else {
306 u32::from_be_bytes(e.value_field)
307 })
308 };
309
310 let (bytes, unreadable) = if esize == 0 {
311 (Vec::new(), Some(format!("unknown TIFF type {}", e.dtype)))
312 } else if u64::from(e.count) > MAX_VALUES {
313 (
314 Vec::new(),
315 Some(format!("{} values exceeds the {MAX_VALUES} cap", e.count)),
316 )
317 } else if inline {
318 (e.value_field[..total as usize].to_vec(), None)
319 } else {
320 match r.bytes_at(value_offset, total as usize) {
321 Ok(b) => (b, None),
322 Err(err) => (Vec::new(), Some(err.to_string())),
323 }
324 };
325
326 if unreadable.is_none() {
328 let ptrs: Vec<u64> = match e.tag {
329 TAG_SUB_IFDS => decode_offsets(&bytes, esize, little_endian),
330 TAG_EXIF_IFD | TAG_GPS_IFD | TAG_INTEROP_IFD => {
331 decode_offsets(&bytes, esize, little_endian)
332 }
333 _ => Vec::new(),
334 };
335 for p in ptrs {
336 let k = match e.tag {
337 TAG_SUB_IFDS => {
338 sub_n += 1;
339 IfdKind::Sub(sub_n - 1)
340 }
341 TAG_EXIF_IFD => IfdKind::Exif,
342 TAG_GPS_IFD => IfdKind::Gps,
343 _ => IfdKind::Interop,
344 };
345 queue.push_back((p, k));
346 }
347 }
348
349 entries.push(RawEntry {
350 tag: e.tag,
351 dtype: e.dtype,
352 count: e.count,
353 value_offset,
354 inline,
355 bytes,
356 unreadable,
357 });
358 }
359
360 if matches!(kind, IfdKind::Main(_)) {
363 let next_off = off + 2 + (ifd.entries.len() as u64) * 12;
364 if next_off + 4 <= file_len {
365 if let Ok(next) = r.u32_at(next_off) {
366 main_n += 1;
367 queue.push_back((u64::from(next), IfdKind::Main(main_n)));
368 }
369 }
370 }
371
372 ifds.push(RawIfd {
373 kind,
374 offset: off,
375 entries,
376 });
377 }
378
379 if ifds.is_empty() {
380 return Err(malformed("no readable directories".into()));
381 }
382
383 Ok(Directories {
384 little_endian,
385 file_len,
386 tiff_base: 0,
387 ifds,
388 })
389}
390
391fn decode_offsets(bytes: &[u8], esize: u64, le: bool) -> Vec<u64> {
392 if esize != 4 {
393 return Vec::new();
394 }
395 bytes
396 .chunks_exact(4)
397 .map(|c| {
398 let b = [c[0], c[1], c[2], c[3]];
399 u64::from(if le {
400 u32::from_le_bytes(b)
401 } else {
402 u32::from_be_bytes(b)
403 })
404 })
405 .collect()
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use std::io::Cursor;
412
413 fn tiff(entries: &[(u16, u16, u32, [u8; 4])]) -> Vec<u8> {
415 let mut b = vec![b'I', b'I', 42, 0, 8, 0, 0, 0];
416 b.extend((entries.len() as u16).to_le_bytes());
417 for (tag, dtype, count, val) in entries {
418 b.extend(tag.to_le_bytes());
419 b.extend(dtype.to_le_bytes());
420 b.extend(count.to_le_bytes());
421 b.extend(val);
422 }
423 b.extend(0u32.to_le_bytes());
424 b
425 }
426
427 #[test]
428 fn reads_entries_with_offsets_and_inline_flag() {
429 let buf = tiff(&[(271, 2, 3, *b"HB\0\0")]);
430 let len = buf.len() as u64;
431 let d = read_from(&mut Cursor::new(&buf), len).unwrap();
432 assert_eq!(d.ifds.len(), 1);
433 let e = &d.ifds[0].entries[0];
434 assert_eq!(e.tag, 271);
435 assert!(e.inline);
436 assert_eq!(e.bytes, b"HB\0");
437 assert_eq!(e.declared_len(), 3);
438 }
439
440 #[test]
443 fn unknown_type_is_reported_not_dropped() {
444 let buf = tiff(&[(999, 77, 1, [0; 4])]);
445 let len = buf.len() as u64;
446 let d = read_from(&mut Cursor::new(&buf), len).unwrap();
447 let e = &d.ifds[0].entries[0];
448 assert_eq!(e.tag, 999);
449 assert!(e.unreadable.is_some());
450 assert!(e.bytes.is_empty());
451 }
452
453 #[test]
455 fn value_pointer_past_eof_is_survivable() {
456 let buf = tiff(&[(700, 1, 64, 0xFFFF_0000u32.to_le_bytes())]);
457 let len = buf.len() as u64;
458 let d = read_from(&mut Cursor::new(&buf), len).unwrap();
459 assert!(d.ifds[0].entries[0].unreadable.is_some());
460 }
461
462 #[test]
465 fn jpeg_app1_exif_is_found_and_offsets_are_absolute() {
466 let inner = tiff(&[(271, 2, 3, *b"HB\0\0")]);
467 let mut j = vec![0xFF, 0xD8]; j.extend([0xFF, 0xE0, 0x00, 0x04, 0x00, 0x00]); let payload_len = (inner.len() + 6 + 2) as u16;
470 j.extend([0xFF, 0xE1]);
471 j.extend(payload_len.to_be_bytes());
472 let tiff_base = j.len() as u64 + 6;
473 j.extend(b"Exif\0\0");
474 j.extend(&inner);
475 j.extend([0xFF, 0xD9]);
476
477 let len = j.len() as u64;
478 let d = read_from(&mut Cursor::new(&j), len).unwrap();
479 assert_eq!(d.tiff_base, tiff_base);
480 assert_eq!(d.file_len, len);
481 assert_eq!(d.ifds[0].entries[0].tag, 271);
482 assert_eq!(d.ifds[0].offset, tiff_base + 8);
484 }
485
486 #[test]
488 fn jpeg_without_exif_is_rejected_at_the_scan_marker() {
489 let j = vec![0xFF, 0xD8, 0xFF, 0xDA, 0x00, 0x02, 0xFF, 0xD9];
490 assert!(read_from(&mut Cursor::new(&j), j.len() as u64).is_err());
491 }
492
493 #[test]
494 fn non_tiff_is_rejected() {
495 let buf = vec![0u8; 32];
496 assert!(read_from(&mut Cursor::new(&buf), 32).is_err());
497 }
498
499 #[test]
501 fn self_referential_subifd_terminates() {
502 let buf = tiff(&[(330, 4, 1, 8u32.to_le_bytes())]);
503 let len = buf.len() as u64;
504 let d = read_from(&mut Cursor::new(&buf), len).unwrap();
505 assert_eq!(d.ifds.len(), 1);
506 }
507}