Skip to main content

nom_exif/exif/
exif_iter.rs

1use std::{collections::HashSet, fmt::Debug};
2
3use bytes::Bytes;
4use nom::{number::complete, Parser};
5
6use crate::{
7    error::EntryError,
8    slice::SliceChecked,
9    values::{DataFormat, EntryData, IRational, URational},
10    EntryValue, ExifTag,
11};
12
13use super::{exif_exif::IFD_ENTRY_SIZE, GPSInfo, LatLng, TiffHeader};
14use crate::TagOrCode;
15
16/// Index of an IFD (Image File Directory) within an EXIF blob.
17///
18/// `0` = main image (`IfdIndex::MAIN`), `1` = thumbnail (`IfdIndex::THUMBNAIL`),
19/// `>=2` = sub-IFDs in the order encountered. Use the constants for the common
20/// cases and [`IfdIndex::new`] for raw indexing.
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
23pub struct IfdIndex(usize);
24
25impl IfdIndex {
26    /// Index of the main image IFD (always `0`).
27    pub const MAIN: Self = IfdIndex(0);
28
29    /// Index of the thumbnail IFD (`1` when present).
30    pub const THUMBNAIL: Self = IfdIndex(1);
31
32    /// Construct from a raw index. `0`/`1` correspond to [`Self::MAIN`] /
33    /// [`Self::THUMBNAIL`]; values `>= 2` are sub-IFDs.
34    pub const fn new(index: usize) -> Self {
35        IfdIndex(index)
36    }
37
38    /// Underlying raw index as a `usize`.
39    pub const fn as_usize(self) -> usize {
40        self.0
41    }
42}
43
44impl std::fmt::Display for IfdIndex {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(f, "ifd{}", self.0)
47    }
48}
49
50/// Eager view into a single Exif entry. Yielded by [`crate::Exif::iter`] and
51/// designed to be cheap to copy: the `value` is a borrow into the parent
52/// [`crate::Exif`].
53///
54/// # Why pub fields instead of getters?
55///
56/// `ifd`, `tag`, and `value` are independent — there is no cross-field
57/// invariant to enforce. The Rust idiom for plain data carriers (cf.
58/// [`std::ops::Range`]) is `pub` fields. The lazy yield type
59/// [`crate::ExifIterEntry`] uses *private* fields because it carries a
60/// `value xor error` invariant.
61#[derive(Clone, Copy, Debug)]
62pub struct ExifEntry<'a> {
63    pub ifd: IfdIndex,
64    pub tag: TagOrCode,
65    pub value: &'a crate::EntryValue,
66}
67
68/// Represents an additional TIFF data block to be processed after the primary block.
69/// Used for CR3 files with multiple CMT boxes (CMT1, CMT2, CMT3).
70#[derive(Clone)]
71pub(crate) struct TiffDataBlock {
72    /// Block identifier (e.g., "CMT1", "CMT2", "CMT3")
73    #[allow(dead_code)]
74    pub block_id: String,
75    /// Pre-sliced bytes view for this block's data
76    pub data: Bytes,
77    /// TIFF header information (optional, if known)
78    pub header: Option<TiffHeader>,
79}
80
81/// Parses header from input data, and returns an [`ExifIter`].
82///
83/// All entries are lazy-parsed. That is, only when you iterate over
84/// [`ExifIter`] will the IFD entries be parsed one by one.
85///
86/// The one exception is the time zone entries. The method will try to find
87/// and parse the time zone data first, so we can correctly parse all time
88/// information in subsequent iterates.
89#[tracing::instrument]
90pub(crate) fn input_into_iter(
91    input: impl Into<bytes::Bytes> + Debug,
92    state: Option<TiffHeader>,
93) -> crate::Result<ExifIter> {
94    let input: bytes::Bytes = input.into();
95    let header = match state {
96        // header has been parsed, and header has been skipped, input data
97        // is the IFD data
98        Some(header) => header,
99        _ => {
100            // header has not been parsed, input data includes IFD header
101            let (_, header) = TiffHeader::parse(&input[..])?;
102
103            tracing::debug!(
104                ?header,
105                data_len = format!("{:#x}", input.len()),
106                "TIFF header parsed"
107            );
108            header
109        }
110    };
111
112    let start = header.ifd0_offset as usize;
113    if start > input.len() {
114        return Err(crate::Error::UnexpectedEof {
115            context: "exif iter init",
116        });
117    }
118    tracing::debug!(?header, offset = start);
119
120    let mut ifd0 = IfdIter::try_new(0, input.clone(), header.to_owned(), start, None)?;
121
122    let tz = ifd0.find_tz_offset();
123    ifd0.tz = tz.clone();
124    let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
125
126    tracing::debug!(?iter, "got IFD0");
127
128    Ok(iter)
129}
130
131/// An iterator version of [`Exif`](crate::Exif). Use [`ExifIterEntry`] as
132/// iterator items.
133///
134/// Clone an `ExifIter` is very cheap; the underlying data is shared
135/// via `bytes::Bytes` reference counting.
136///
137/// The new cloned `ExifIter`'s iteration index will be reset to the first one.
138///
139/// If you want to convert an `ExifIter` `into` an [`Exif`](crate::Exif), you probably want
140/// to clone the `ExifIter` and use the new cloned one to do the converting.
141/// Since the original's iteration index may have been modified by
142/// `Iterator::next()` calls.
143pub struct ExifIter {
144    input: Bytes,
145    tiff_header: TiffHeader,
146    tz: Option<String>,
147    ifd0: IfdIter,
148
149    // Iterating status
150    ifds: Vec<IfdIter>,
151    visited_offsets: HashSet<usize>,
152
153    // Multi-block support for CR3 files with multiple CMT boxes
154    /// Additional TIFF data blocks to process after the primary block
155    additional_blocks: Vec<TiffDataBlock>,
156    /// Current block index: 0 = primary block, 1+ = additional blocks
157    current_block_index: usize,
158    /// Tags encountered so far for duplicate filtering (ifd_index, tag_code)
159    encountered_tags: HashSet<(usize, u16)>,
160    has_embedded_media: bool,
161}
162
163impl Debug for ExifIter {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        f.debug_struct("ExifIter")
166            .field("data len", &self.input.len())
167            .field("tiff_header", &self.tiff_header)
168            .field("ifd0", &self.ifd0)
169            .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
170            .field("ifds num", &self.ifds.len())
171            .field("additional_blocks", &self.additional_blocks.len())
172            .field("current_block_index", &self.current_block_index)
173            .finish_non_exhaustive()
174    }
175}
176
177impl Clone for ExifIter {
178    fn clone(&self) -> Self {
179        self.clone_rewound()
180    }
181}
182
183impl ExifIter {
184    pub(crate) fn new(
185        input: bytes::Bytes,
186        tiff_header: TiffHeader,
187        tz: Option<String>,
188        ifd0: IfdIter,
189    ) -> ExifIter {
190        let ifds = vec![ifd0.clone()];
191        ExifIter {
192            input,
193            tiff_header,
194            tz,
195            ifd0,
196            ifds,
197            visited_offsets: HashSet::new(),
198            additional_blocks: Vec::new(),
199            current_block_index: 0,
200            encountered_tags: HashSet::new(),
201            has_embedded_media: false,
202        }
203    }
204
205    /// Clone with iteration state reset to entry 0.
206    ///
207    /// Cheap: `ExifIter` shares its underlying `bytes::Bytes` via refcount.
208    pub fn clone_rewound(&self) -> Self {
209        let ifd0 = self.ifd0.clone_and_rewind();
210        let ifds = vec![ifd0.clone()];
211        Self {
212            input: self.input.clone(),
213            tiff_header: self.tiff_header.clone(),
214            tz: self.tz.clone(),
215            ifd0,
216            ifds,
217            visited_offsets: HashSet::new(),
218            additional_blocks: self.additional_blocks.clone(),
219            current_block_index: 0,
220            encountered_tags: HashSet::new(),
221            has_embedded_media: self.has_embedded_media,
222        }
223    }
224
225    /// Reset iteration to the first entry (in-place). After this call,
226    /// `next()` yields entries starting from IFD0 entry 0 again.
227    pub fn rewind(&mut self) {
228        let ifd0 = self.ifd0.clone_and_rewind();
229        self.ifds = vec![ifd0.clone()];
230        self.ifd0 = ifd0;
231        self.visited_offsets.clear();
232        self.current_block_index = 0;
233        self.encountered_tags.clear();
234    }
235
236    /// Try to find and parse GPS information.
237    ///
238    /// Calling this method won't affect the iterator's state.
239    ///
240    /// Returns:
241    ///
242    /// - An `Ok<Some<GPSInfo>>` if gps info is found and parsed successfully.
243    /// - An `Ok<None>` if gps info is not found.
244    /// - An `Err` if gps info is found but parsing failed.
245    #[tracing::instrument(skip_all)]
246    pub fn parse_gps(&self) -> crate::Result<Option<GPSInfo>> {
247        let mut iter = self.clone_rewound();
248        let Some(gps) = iter.find(|x| {
249            tracing::info!(?x, "find");
250            x.tag().tag().is_some_and(|t| t == ExifTag::GPSInfo)
251        }) else {
252            tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
253            return Ok(None);
254        };
255
256        let offset = match gps.result() {
257            Ok(v) => {
258                if let Some(offset) = v.as_u32() {
259                    offset
260                } else {
261                    return Err(EntryError::InvalidValue("invalid gps offset").into());
262                }
263            }
264            Err(e) => return Err(e.clone().into()),
265        };
266        if offset as usize >= iter.input.len() {
267            return Err(crate::Error::Malformed {
268                kind: crate::error::MalformedKind::IfdEntry,
269                message: "GPSInfo offset out of range".into(),
270            });
271        }
272
273        let mut gps_subifd = match IfdIter::try_new(
274            gps.ifd().as_usize(),
275            iter.input.clone(),
276            iter.tiff_header,
277            offset as usize,
278            iter.tz.clone(),
279        ) {
280            Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
281            Err(e) => return Err(e),
282        };
283        Ok(gps_subifd.parse_gps_info())
284    }
285
286    /// Add an additional TIFF data block to be iterated after the current block.
287    /// Used internally for CR3 files with multiple CMT boxes.
288    ///
289    /// # Arguments
290    /// * `block_id` - Identifier for this TIFF block (e.g., "CMT2", "CMT3")
291    /// * `data` - Pre-sliced `Bytes` view containing this block's TIFF data
292    /// * `header` - Optional TIFF header if already parsed
293    pub(crate) fn add_tiff_block(
294        &mut self,
295        block_id: String,
296        data: bytes::Bytes,
297        header: Option<TiffHeader>,
298    ) {
299        self.additional_blocks.push(TiffDataBlock {
300            block_id,
301            data,
302            header,
303        });
304    }
305
306    /// Internal-only setter used by [`crate::MediaParser::parse_exif`] to
307    /// stamp the iterator with format-derived embedded-media information.
308    pub(crate) fn set_has_embedded_media(&mut self, v: bool) {
309        self.has_embedded_media = v;
310    }
311
312    /// Whether the source file carries additional embedded media that this
313    /// parse path does *not* extract — e.g. HEIC Live Photo MOV, RAF JPEG
314    /// preview. Computed from the file format alone (no iteration required).
315    pub fn has_embedded_media(&self) -> bool {
316        self.has_embedded_media
317    }
318}
319
320/// Lazy yield from [`ExifIter`]. Carries a *value xor error* invariant —
321/// every entry holds exactly one of [`Self::value`] or [`Self::error`].
322///
323/// # Why private fields?
324///
325/// Public fields would let callers construct nonsense like `value=Some,
326/// error=Some`. Private fields + getters preserve the invariant while
327/// exposing the natural API: [`Self::result`] for borrowed access,
328/// [`Self::into_result`] for ownership transfer (consumes `self`, no panic
329/// path).
330#[derive(Clone)]
331pub struct ExifIterEntry {
332    ifd: IfdIndex,
333    tag: TagOrCode,
334    res: Result<EntryValue, crate::error::EntryError>,
335}
336
337impl ExifIterEntry {
338    /// IFD this entry was found in (`IfdIndex::MAIN` for the primary image).
339    pub fn ifd(&self) -> IfdIndex {
340        self.ifd
341    }
342
343    /// Recognized tag, or raw `u16` code if not in [`ExifTag`].
344    pub fn tag(&self) -> TagOrCode {
345        self.tag
346    }
347
348    /// Borrow the value. `None` iff this entry hit a parse error.
349    pub fn value(&self) -> Option<&EntryValue> {
350        self.res.as_ref().ok()
351    }
352
353    /// Borrow the error. `None` iff this entry parsed successfully.
354    pub fn error(&self) -> Option<&crate::error::EntryError> {
355        self.res.as_ref().err()
356    }
357
358    /// Borrow either value or error, mirroring the underlying invariant.
359    pub fn result(&self) -> Result<&EntryValue, &crate::error::EntryError> {
360        self.res.as_ref()
361    }
362
363    /// Consume self and return the value or error. No second-call panic
364    /// path (the entry is moved out).
365    pub fn into_result(self) -> Result<EntryValue, crate::error::EntryError> {
366        self.res
367    }
368
369    pub(crate) fn make_ok(ifd: usize, tag: TagOrCode, v: EntryValue) -> Self {
370        Self {
371            ifd: IfdIndex::new(ifd),
372            tag,
373            res: Ok(v),
374        }
375    }
376}
377
378impl std::fmt::Debug for ExifIterEntry {
379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        let value = match &self.res {
381            Ok(v) => format!("{v}"),
382            Err(e) => format!("{e:?}"),
383        };
384        f.debug_struct("ExifIterEntry")
385            .field("ifd", &self.ifd)
386            .field("tag", &self.tag)
387            .field("value", &value)
388            .finish()
389    }
390}
391
392const MAX_IFD_DEPTH: usize = 8;
393
394impl ExifIter {
395    /// Attempt to load and start iterating the next additional TIFF block.
396    /// Returns true if a new block was successfully loaded, false if no more blocks.
397    fn load_next_block(&mut self) -> bool {
398        // Move to the next additional block
399        let block_index = self.current_block_index;
400        if block_index >= self.additional_blocks.len() {
401            return false;
402        }
403
404        let block = &self.additional_blocks[block_index];
405        tracing::debug!(
406            block_id = block.block_id,
407            block_index,
408            "Loading additional TIFF block"
409        );
410
411        // Get the data for this block from the shared input
412        let block_data = block.data.clone();
413        let header = block.header.clone();
414
415        // Try to create an ExifIter for this block
416        match input_into_iter(block_data, header) {
417            Ok(iter) => {
418                // Update our state with the new block's data
419                self.ifd0 = iter.ifd0;
420                self.ifds = vec![self.ifd0.clone()];
421                self.visited_offsets.clear();
422                self.current_block_index += 1;
423
424                tracing::debug!(block_index, "Successfully loaded additional TIFF block");
425                true
426            }
427            Err(e) => {
428                tracing::warn!(
429                    block_index,
430                    error = %e,
431                    "Failed to load additional TIFF block, skipping"
432                );
433                // Move to next block and try again
434                self.current_block_index += 1;
435                self.load_next_block()
436            }
437        }
438    }
439
440    /// Check if a tag should be included based on duplicate filtering.
441    /// Returns true if the tag should be included, false if it's a duplicate.
442    fn should_include_tag(&mut self, ifd_index: usize, tag_code: u16) -> bool {
443        let tag_key = (ifd_index, tag_code);
444        if self.encountered_tags.contains(&tag_key) {
445            tracing::debug!(ifd_index, tag_code, "Skipping duplicate tag");
446            false
447        } else {
448            self.encountered_tags.insert(tag_key);
449            true
450        }
451    }
452}
453
454impl Iterator for ExifIter {
455    type Item = ExifIterEntry;
456
457    #[tracing::instrument(skip_all)]
458    fn next(&mut self) -> Option<Self::Item> {
459        loop {
460            if self.ifds.is_empty() {
461                // Current block exhausted, try to load next additional block
462                if !self.load_next_block() {
463                    tracing::debug!(?self, "all IFDs and blocks have been parsed");
464                    return None;
465                }
466                // Continue with the newly loaded block
467                continue;
468            }
469
470            if self.ifds.len() > MAX_IFD_DEPTH {
471                let depth = self.ifds.len();
472                self.ifds.clear();
473                tracing::error!(
474                    ifds_depth = depth,
475                    "ifd depth is too deep, just go back to ifd0"
476                );
477                self.ifds.push(self.ifd0.clone_with_state());
478            }
479
480            let mut ifd = self.ifds.pop()?;
481            let cur_ifd_idx = ifd.ifd_idx;
482            match ifd.next() {
483                Some((tag_code, entry)) => {
484                    tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
485
486                    match entry {
487                        IfdEntry::IfdNew(new_ifd) => {
488                            if new_ifd.offset > 0 {
489                                if self.visited_offsets.contains(&new_ifd.offset) {
490                                    // Ignore repeated ifd parsing to avoid dead looping
491                                    continue;
492                                }
493                                self.visited_offsets.insert(new_ifd.offset);
494                            }
495
496                            let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
497                                // Push the current ifd before enter sub-ifd.
498                                self.ifds.push(ifd);
499                                tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
500                                true
501                            } else {
502                                // Otherwise this is a next ifd. It means that the
503                                // current ifd has been parsed, so we don't need to
504                                // push it.
505                                tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
506                                tracing::debug!(?new_ifd, "got new IFD");
507                                false
508                            };
509
510                            let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
511                            self.ifds.push(new_ifd);
512
513                            if is_subifd {
514                                // Check for duplicates before returning sub-ifd entry
515                                let tc = tag_code.unwrap();
516                                if !self.should_include_tag(ifd_idx, tc.code()) {
517                                    continue;
518                                }
519                                // Return sub-ifd as an entry
520                                return Some(ExifIterEntry::make_ok(
521                                    ifd_idx,
522                                    tc,
523                                    EntryValue::U32(offset as u32),
524                                ));
525                            }
526                        }
527                        IfdEntry::Entry(v) => {
528                            let tc = tag_code.unwrap();
529                            // Check for duplicates before returning entry
530                            if !self.should_include_tag(ifd.ifd_idx, tc.code()) {
531                                self.ifds.push(ifd);
532                                continue;
533                            }
534                            let res = Some(ExifIterEntry::make_ok(ifd.ifd_idx, tc, v));
535                            self.ifds.push(ifd);
536                            return res;
537                        }
538                        IfdEntry::Err(e) => {
539                            tracing::warn!(?tag_code, ?e, "parse ifd entry error");
540                            self.ifds.push(ifd);
541                            continue;
542                        }
543                    }
544                }
545                None => continue,
546            }
547        }
548    }
549}
550
551#[derive(Clone)]
552pub(crate) struct IfdIter {
553    ifd_idx: usize,
554    tag_code: Option<TagOrCode>,
555
556    // starts from TIFF header
557    input: Bytes,
558
559    // ifd data offset
560    offset: usize,
561
562    header: TiffHeader,
563    entry_num: u16,
564
565    pub tz: Option<String>,
566
567    // Iterating status
568    index: u16,
569    pos: usize,
570}
571
572impl Debug for IfdIter {
573    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
574        f.debug_struct("IfdIter")
575            .field("ifd_idx", &self.ifd_idx)
576            .field("tag", &self.tag_code)
577            .field("data len", &self.input.len())
578            .field("tz", &self.tz)
579            .field("header", &self.header)
580            .field("entry_num", &self.entry_num)
581            .field("index", &self.index)
582            .field("pos", &self.pos)
583            .finish()
584    }
585}
586
587impl IfdIter {
588    pub fn rewind(&mut self) {
589        self.index = 0;
590        // Skip the first two bytes, which is the entry num
591        self.pos = self.offset + 2;
592    }
593
594    pub fn clone_and_rewind(&self) -> Self {
595        let mut it = self.clone();
596        it.rewind();
597        it
598    }
599
600    pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
601        self.tag_code = code.map(|x| x.into());
602        self
603    }
604
605    pub fn tag_code(mut self, code: u16) -> Self {
606        self.tag_code = Some(code.into());
607        self
608    }
609
610    #[allow(unused)]
611    pub fn tag(mut self, tag: TagOrCode) -> Self {
612        self.tag_code = Some(tag);
613        self
614    }
615
616    #[tracing::instrument(skip(input))]
617    pub fn try_new(
618        ifd_idx: usize,
619        input: Bytes,
620        header: TiffHeader,
621        offset: usize,
622        tz: Option<String>,
623    ) -> crate::Result<Self> {
624        if input.len() < 2 {
625            return Err(crate::Error::Malformed {
626                kind: crate::error::MalformedKind::TiffHeader,
627                message: "ifd data too small to decode entry num".into(),
628            });
629        }
630        // should use the complete header data to parse ifd entry num
631        assert!(offset <= input.len());
632        let ifd_data = input.slice(offset..);
633        let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian)?;
634
635        Ok(Self {
636            ifd_idx,
637            tag_code: None,
638            input,
639            offset,
640            header,
641            entry_num,
642            tz,
643            // Skip the first two bytes, which is the entry num
644            pos: offset + 2,
645            index: 0,
646        })
647    }
648
649    fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
650        let endian = self.header.endian;
651        let (_, (tag, data_format, components_num, value_or_offset)) = (
652            complete::u16::<_, nom::error::Error<_>>(endian),
653            complete::u16(endian),
654            complete::u32(endian),
655            complete::u32(endian),
656        )
657            .parse(entry_data)
658            .ok()?;
659
660        if tag == 0 {
661            return None;
662        }
663
664        let df: DataFormat = match DataFormat::try_from(data_format) {
665            Ok(df) => df,
666            Err(bad) => {
667                let t: TagOrCode = tag.into();
668                tracing::warn!(tag = ?t, format = bad, "invalid entry data format");
669                return Some((
670                    tag,
671                    IfdEntry::Err(EntryError::InvalidShape {
672                        format: bad,
673                        count: components_num,
674                    }),
675                ));
676            }
677        };
678        let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
679        Some((tag, res))
680    }
681
682    fn get_data_pos(&self, value_or_offset: u32) -> usize {
683        // value_or_offset.saturating_sub(self.offset)
684        value_or_offset as usize
685    }
686
687    fn parse_entry(
688        &self,
689        tag: u16,
690        data_format: DataFormat,
691        components_num: u32,
692        entry_data: &[u8],
693        value_or_offset: u32,
694    ) -> (u16, IfdEntry) {
695        // get component_size according to data format
696        let component_size = data_format.component_size();
697
698        // get entry data
699        let size = components_num as usize * component_size;
700        let data = if size <= 4 {
701            &entry_data[8..8 + size] // Safe-slice
702        } else {
703            let start = self.get_data_pos(value_or_offset);
704            let end = start + size;
705            let Some(data) = self.input.slice_checked(start..end) else {
706                tracing::warn!(
707                    "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
708                    tag,
709                    start,
710                    end,
711                    self.input.len(),
712                );
713                return (
714                    tag,
715                    IfdEntry::Err(EntryError::Truncated {
716                        needed: size,
717                        available: self.input.len().saturating_sub(start),
718                    }),
719                );
720            };
721
722            data
723        };
724
725        if SUBIFD_TAGS.contains(&tag) {
726            if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
727                return (tag, value);
728            }
729        }
730
731        let entry = EntryData {
732            endian: self.header.endian,
733            tag,
734            data,
735            data_format,
736            components_num,
737        };
738        match EntryValue::parse(&entry, &self.tz) {
739            Ok(v) => (tag, IfdEntry::Entry(v)),
740            Err(e) => (tag, IfdEntry::Err(e)),
741        }
742    }
743
744    fn new_ifd_iter(
745        &self,
746        ifd_idx: usize,
747        value_or_offset: u32,
748        tag: Option<u16>,
749    ) -> Option<IfdEntry> {
750        let offset = self.get_data_pos(value_or_offset);
751        if offset < self.input.len() {
752            match IfdIter::try_new(
753                ifd_idx,
754                self.input.clone(),
755                self.header.to_owned(),
756                offset,
757                self.tz.clone(),
758            ) {
759                Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
760                Err(e) => {
761                    tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
762                }
763            }
764            // return (
765            //     tag,
766            //     // IfdEntry::Ifd {
767            //     //     idx: self.ifd_idx,
768            //     //     offset: value_or_offset,
769            //     // },
770            //     IfdEntry::IfdNew(),
771            // );
772        }
773        None
774    }
775
776    pub fn find_exif_iter(&self) -> Option<IfdIter> {
777        let endian = self.header.endian;
778        // find ExifOffset
779        for i in 0..self.entry_num {
780            let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
781            let (_, tag) =
782                complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
783            if tag == ExifTag::ExifOffset.code() {
784                let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
785                let (_, entry) = self.parse_tag_entry(entry_data)?;
786                match entry {
787                    IfdEntry::IfdNew(iter) => return Some(iter),
788                    IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
789                }
790            }
791        }
792        None
793    }
794
795    pub fn find_tz_offset(&self) -> Option<String> {
796        let iter = self.find_exif_iter()?;
797        let mut offset = None;
798        for entry in iter {
799            let Some(tag) = entry.0 else {
800                continue;
801            };
802            if tag.code() == ExifTag::OffsetTimeOriginal.code()
803                || tag.code() == ExifTag::OffsetTimeDigitized.code()
804            {
805                return entry.1.as_str().map(|x| x.to_owned());
806            } else if tag.code() == ExifTag::OffsetTime.code() {
807                offset = entry.1.as_str().map(|x| x.to_owned());
808            }
809        }
810
811        offset
812    }
813
814    // Assume the current ifd is GPSInfo subifd.
815    pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
816        use crate::exif::gps::{Altitude, LatRef, LonRef, Speed, SpeedUnit};
817
818        let mut latitude_ref = None;
819        let mut latitude = None;
820        let mut longitude_ref = None;
821        let mut longitude = None;
822        let mut altitude_ref = None;
823        let mut altitude_value = None;
824        let mut speed_unit = None;
825        let mut speed_value = None;
826        let mut has_data = false;
827
828        for (tag, entry) in self {
829            let Some(tag) = tag.and_then(|x| x.tag()) else {
830                continue;
831            };
832            has_data = true;
833            match tag {
834                ExifTag::GPSLatitudeRef => {
835                    latitude_ref = entry.as_char().and_then(LatRef::from_char);
836                }
837                ExifTag::GPSLongitudeRef => {
838                    longitude_ref = entry.as_char().and_then(LonRef::from_char);
839                }
840                ExifTag::GPSAltitudeRef => {
841                    altitude_ref = entry.as_u8();
842                }
843                ExifTag::GPSLatitude => {
844                    if let Some(v) = entry.as_urational_slice() {
845                        latitude = LatLng::try_from(v).ok();
846                    } else if let Some(v) = entry.as_irational_slice() {
847                        latitude = LatLng::try_from(v).ok();
848                    }
849                }
850                ExifTag::GPSLongitude => {
851                    if let Some(v) = entry.as_urational_slice() {
852                        longitude = LatLng::try_from(v).ok();
853                    } else if let Some(v) = entry.as_irational_slice() {
854                        longitude = LatLng::try_from(v).ok();
855                    }
856                }
857                ExifTag::GPSAltitude => {
858                    if let Some(v) = entry.as_urational() {
859                        altitude_value = Some(*v);
860                    } else if let Some(v) = entry.as_irational() {
861                        if let Ok(u) = URational::try_from(*v) {
862                            altitude_value = Some(u);
863                        }
864                    }
865                }
866                ExifTag::GPSSpeedRef => {
867                    speed_unit = entry.as_char().and_then(SpeedUnit::from_char);
868                }
869                ExifTag::GPSSpeed => {
870                    if let Some(v) = entry.as_urational() {
871                        speed_value = Some(*v);
872                    } else if let Some(v) = entry.as_irational() {
873                        if let Ok(u) = URational::try_from(*v) {
874                            speed_value = Some(u);
875                        }
876                    }
877                }
878                _ => (),
879            }
880        }
881
882        if !has_data {
883            tracing::warn!("GPSInfo data not found");
884            return None;
885        }
886
887        let altitude = match (altitude_ref, altitude_value) {
888            (Some(0), Some(v)) => Altitude::AboveSeaLevel(v),
889            (Some(1), Some(v)) => Altitude::BelowSeaLevel(v),
890            _ => Altitude::Unknown,
891        };
892
893        let speed = match (speed_unit, speed_value) {
894            (Some(unit), Some(value)) => Some(Speed { unit, value }),
895            _ => None,
896        };
897
898        Some(GPSInfo {
899            latitude_ref: latitude_ref.unwrap_or(LatRef::North),
900            latitude: latitude.unwrap_or_default(),
901            longitude_ref: longitude_ref.unwrap_or(LonRef::East),
902            longitude: longitude.unwrap_or_default(),
903            altitude,
904            speed,
905        })
906    }
907
908    fn clone_with_state(&self) -> IfdIter {
909        let mut it = self.clone();
910        it.index = self.index;
911        it.pos = self.pos;
912        it
913    }
914}
915
916#[derive(Debug)]
917pub(crate) enum IfdEntry {
918    IfdNew(IfdIter), // ifd index
919    Entry(EntryValue),
920    Err(EntryError),
921}
922
923impl IfdEntry {
924    pub fn as_u8(&self) -> Option<u8> {
925        if let IfdEntry::Entry(EntryValue::U8(v)) = self {
926            Some(*v)
927        } else {
928            None
929        }
930    }
931
932    pub fn as_char(&self) -> Option<char> {
933        if let IfdEntry::Entry(EntryValue::Text(s)) = self {
934            s.chars().next()
935        } else {
936            None
937        }
938    }
939
940    fn as_irational(&self) -> Option<&IRational> {
941        if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
942            Some(v)
943        } else {
944            None
945        }
946    }
947
948    fn as_irational_slice(&self) -> Option<&Vec<IRational>> {
949        if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
950            Some(v)
951        } else {
952            None
953        }
954    }
955
956    fn as_urational(&self) -> Option<&URational> {
957        if let IfdEntry::Entry(EntryValue::URational(v)) = self {
958            Some(v)
959        } else {
960            None
961        }
962    }
963
964    fn as_urational_slice(&self) -> Option<&Vec<URational>> {
965        if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
966            Some(v)
967        } else {
968            None
969        }
970    }
971
972    fn as_str(&self) -> Option<&str> {
973        if let IfdEntry::Entry(e) = self {
974            e.as_str()
975        } else {
976            None
977        }
978    }
979}
980
981pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
982
983impl Iterator for IfdIter {
984    type Item = (Option<TagOrCode>, IfdEntry);
985
986    #[tracing::instrument(skip(self))]
987    fn next(&mut self) -> Option<Self::Item> {
988        tracing::debug!(
989            ifd = self.ifd_idx,
990            index = self.index,
991            entry_num = self.entry_num,
992            offset = format!("{:08x}", self.offset),
993            pos = format!("{:08x}", self.pos),
994            "next IFD entry"
995        );
996        if self.input.len() < self.pos + IFD_ENTRY_SIZE {
997            return None;
998        }
999
1000        let endian = self.header.endian;
1001        if self.index > self.entry_num {
1002            return None;
1003        }
1004        if self.index == self.entry_num {
1005            tracing::debug!(
1006                self.ifd_idx,
1007                self.index,
1008                pos = self.pos,
1009                "try to get next ifd"
1010            );
1011            self.index += 1;
1012
1013            // next IFD offset
1014            let (_, offset) =
1015                complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
1016
1017            if offset == 0 {
1018                // IFD parsing completed
1019                tracing::debug!(?self, "IFD parsing completed");
1020                return None;
1021            }
1022
1023            return self
1024                .new_ifd_iter(self.ifd_idx + 1, offset, None)
1025                .map(|x| (None, x));
1026        }
1027
1028        let entry_data = self
1029            .input
1030            .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1031        self.index += 1;
1032        self.pos += IFD_ENTRY_SIZE;
1033
1034        let (tag, res) = self.parse_tag_entry(entry_data)?;
1035
1036        Some((Some(tag.into()), res)) // Safe-slice
1037    }
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042
1043    use crate::exif::extract_exif_with_mime;
1044    use crate::exif::input_into_iter;
1045    use crate::file::MediaMimeImage;
1046    use crate::slice::SubsliceRange;
1047    use crate::testkit::read_sample;
1048    use crate::Exif;
1049    use test_case::test_case;
1050
1051    #[test_case(
1052        "exif.jpg",
1053        "+08:00",
1054        "2023-07-09T20:36:33+08:00",
1055        MediaMimeImage::Jpeg
1056    )]
1057    #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MediaMimeImage::Jpeg)]
1058    #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MediaMimeImage::Jpeg)]
1059    #[test_case(
1060        "exif.heic",
1061        "+08:00",
1062        "2022-07-22T21:26:32+08:00",
1063        MediaMimeImage::Heic
1064    )]
1065    #[test_case("tif.tif", "-", "-", MediaMimeImage::Tiff)]
1066    #[test_case(
1067        "fujifilm_x_t1_01.raf.meta",
1068        "-",
1069        "2014-01-30 12:49:13",
1070        MediaMimeImage::Raf
1071    )]
1072    fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MediaMimeImage) {
1073        let buf = read_sample(path).unwrap();
1074        let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1075        let range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1076        let iter = input_into_iter(bytes::Bytes::from(buf).slice(range), None).unwrap();
1077        let expect = if tz == "-" {
1078            None
1079        } else {
1080            Some(tz.to_string())
1081        };
1082        assert_eq!(iter.tz, expect);
1083        let exif: Exif = iter.into();
1084        let value = exif.get(crate::ExifTag::DateTimeOriginal);
1085        if time == "-" {
1086            assert!(value.is_none());
1087        } else {
1088            let value = value.unwrap();
1089            assert_eq!(value.to_string(), time);
1090        }
1091    }
1092
1093    #[test]
1094    fn ifd_index_constants() {
1095        use crate::IfdIndex;
1096        assert_eq!(IfdIndex::MAIN.as_usize(), 0);
1097        assert_eq!(IfdIndex::THUMBNAIL.as_usize(), 1);
1098    }
1099
1100    #[test]
1101    fn ifd_index_roundtrip_via_new_and_as_usize() {
1102        use crate::IfdIndex;
1103        for raw in [0, 1, 2, 3, 7, 99] {
1104            assert_eq!(IfdIndex::new(raw).as_usize(), raw);
1105        }
1106    }
1107
1108    #[test]
1109    fn ifd_index_equality_and_hash() {
1110        use crate::IfdIndex;
1111        use std::collections::HashSet;
1112        let mut set: HashSet<IfdIndex> = HashSet::new();
1113        set.insert(IfdIndex::MAIN);
1114        set.insert(IfdIndex::new(0)); // duplicate
1115        set.insert(IfdIndex::THUMBNAIL);
1116        assert_eq!(set.len(), 2);
1117    }
1118
1119    #[test]
1120    fn ifd_index_display_format() {
1121        use crate::IfdIndex;
1122        assert_eq!(format!("{}", IfdIndex::MAIN), "ifd0");
1123        assert_eq!(format!("{}", IfdIndex::new(7)), "ifd7");
1124    }
1125
1126    #[test]
1127    fn tag_or_code_for_known_tag_resolves_to_tag_variant() {
1128        use crate::{ExifTag, TagOrCode};
1129        let t: TagOrCode = ExifTag::Make.code().into();
1130        assert_eq!(t, TagOrCode::Tag(ExifTag::Make));
1131        assert_eq!(t.code(), ExifTag::Make.code());
1132    }
1133
1134    #[test]
1135    fn tag_or_code_for_unknown_tag_resolves_to_unknown_variant() {
1136        use crate::TagOrCode;
1137        let t: TagOrCode = 0xffff_u16.into();
1138        assert_eq!(t, TagOrCode::Unknown(0xffff));
1139        assert_eq!(t.code(), 0xffff);
1140    }
1141
1142    #[test]
1143    fn exif_entry_pub_fields_construct_and_destructure() {
1144        use crate::{EntryValue, ExifEntry, ExifTag, IfdIndex, TagOrCode};
1145        let val = EntryValue::Text("vivo X90 Pro+".into());
1146        let e = ExifEntry {
1147            ifd: IfdIndex::MAIN,
1148            tag: TagOrCode::Tag(ExifTag::Model),
1149            value: &val,
1150        };
1151        // Pub fields: just match.
1152        let ExifEntry { ifd, tag, value } = e;
1153        assert_eq!(ifd, IfdIndex::MAIN);
1154        assert_eq!(tag.code(), ExifTag::Model.code());
1155        assert!(matches!(value, EntryValue::Text(_)));
1156        // Copy works because EntryValue is borrowed.
1157        let _e2 = e;
1158        let _e3 = e;
1159    }
1160
1161    #[test]
1162    fn exif_iter_entry_value_xor_error_invariant() {
1163        use crate::{MediaParser, MediaSource};
1164        let mut parser = MediaParser::new();
1165        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1166        for entry in parser.parse_exif(ms).unwrap() {
1167            // Exactly one of value / error is Some.
1168            let has_v = entry.value().is_some();
1169            let has_e = entry.error().is_some();
1170            assert!(has_v ^ has_e, "entry must be value xor error");
1171            // result() agrees with value()/error().
1172            match entry.result() {
1173                Ok(v) => assert_eq!(Some(v), entry.value()),
1174                Err(e) => assert_eq!(Some(e), entry.error()),
1175            }
1176        }
1177    }
1178
1179    #[test]
1180    fn exif_iter_entry_into_result_consumes_self() {
1181        use crate::{MediaParser, MediaSource};
1182        let mut parser = MediaParser::new();
1183        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1184        let mut count_ok = 0usize;
1185        for entry in parser.parse_exif(ms).unwrap() {
1186            // into_result consumes; once consumed, we can't call any other
1187            // method (the entry is gone). This is the spec's panic-free
1188            // replacement for v2's take_result.
1189            if entry.into_result().is_ok() {
1190                count_ok += 1;
1191            }
1192        }
1193        assert!(count_ok > 0);
1194    }
1195
1196    #[test]
1197    fn exif_iter_entry_tag_returns_tag_or_code() {
1198        use crate::{ExifTag, MediaParser, MediaSource, TagOrCode};
1199        let mut parser = MediaParser::new();
1200        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1201        let make_present = parser
1202            .parse_exif(ms)
1203            .unwrap()
1204            .any(|e| matches!(e.tag(), TagOrCode::Tag(ExifTag::Make)));
1205        assert!(make_present);
1206    }
1207
1208    #[test]
1209    fn exif_iter_rewind_resets_iteration_state() {
1210        use crate::{MediaParser, MediaSource};
1211        let mut parser = MediaParser::new();
1212        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1213        let mut iter = parser.parse_exif(ms).unwrap();
1214        let first_count = iter.by_ref().count();
1215        assert!(first_count > 0);
1216        // Already exhausted.
1217        assert_eq!(iter.by_ref().count(), 0);
1218        iter.rewind();
1219        let after_rewind = iter.count();
1220        assert_eq!(first_count, after_rewind);
1221    }
1222
1223    #[test]
1224    fn exif_iter_clone_rewound_yields_independent_full_iter() {
1225        use crate::{MediaParser, MediaSource};
1226        let mut parser = MediaParser::new();
1227        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1228        let mut iter = parser.parse_exif(ms).unwrap();
1229        let _consumed = iter.by_ref().take(2).count();
1230        let cloned = iter.clone_rewound();
1231        // cloned starts from entry 0 even though `iter` consumed 2 entries.
1232        let cloned_total = cloned.count();
1233        let remaining = iter.count();
1234        assert!(cloned_total > remaining);
1235    }
1236
1237    #[test]
1238    fn exif_iter_parse_gps_returns_option_no_iteration_advance() {
1239        use crate::{MediaParser, MediaSource};
1240        let mut parser = MediaParser::new();
1241        let ms = MediaSource::open("testdata/exif.jpg").unwrap();
1242        let iter = parser.parse_exif(ms).unwrap();
1243        let gps = iter.parse_gps().unwrap();
1244        assert!(gps.is_some());
1245        // parse_gps doesn't drive the outer iterator.
1246        let count = iter.count();
1247        assert!(count > 0);
1248    }
1249}