Skip to main content

nom_exif/exif/
exif_iter.rs

1use std::{fmt::Debug, sync::Arc};
2
3use nom::{
4    number::{complete, Endianness},
5    sequence::tuple,
6};
7use thiserror::Error;
8
9use crate::{
10    partial_vec::{AssociatedInput, PartialVec},
11    slice::SliceChecked,
12    values::{DataFormat, EntryData, IRational, ParseEntryError, URational},
13    EntryValue, ExifTag,
14};
15
16use super::{exif_exif::IFD_ENTRY_SIZE, tags::ExifTagCode, GPSInfo, TiffHeader};
17
18/// Parses header from input data, and returns an [`ExifIter`].
19///
20/// All entries are lazy-parsed. That is, only when you iterate over
21/// [`ExifIter`] will the IFD entries be parsed one by one.
22///
23/// The one exception is the time zone entries. The method will try to find
24/// and parse the time zone data first, so we can correctly parse all time
25/// information in subsequent iterates.
26#[tracing::instrument]
27pub(crate) fn input_into_iter(
28    input: impl Into<PartialVec> + Debug,
29    state: Option<TiffHeader>,
30) -> crate::Result<ExifIter> {
31    let input = input.into();
32    let (header, start) = match state {
33        // header has been parsed, and header has been skipped, input data
34        // is the IFD data
35        Some(header) => (header, 0),
36        _ => {
37            // header has not been parsed, input data includes IFD header
38            let (_, header) = TiffHeader::parse(&input[..])?;
39            let start = header.ifd0_offset as usize;
40            if start > input.len() {
41                return Err(crate::Error::ParseFailed("no enough bytes".into()));
42            }
43
44            (header, start)
45        }
46    };
47
48    tracing::debug!(?header, offset = start);
49
50    let data = &input[..];
51
52    let mut ifd0 = IfdIter::try_new(
53        0,
54        input.partial(&data[start..]),
55        header.ifd0_offset,
56        header.endian,
57        None,
58    )?;
59
60    let tz = ifd0.find_tz_offset();
61    ifd0.tz = tz.clone();
62    let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
63
64    tracing::debug!(?iter, "got IFD0");
65
66    Ok(iter)
67}
68
69/// An iterator version of [`Exif`](crate::Exif). Use [`ParsedExifEntry`] as
70/// iterator items.
71///
72/// Clone an `ExifIter` is very cheap, the underlying data is shared
73/// through `Arc`.
74///
75/// The new cloned `ExifIter`'s iteration index will be reset to the first one.
76///
77/// If you want to convert an `ExifIter` `into` an [`Exif`], you probably want
78/// to clone the `ExifIter` and use the new cloned one to do the converting.
79/// Since the original's iteration index may have been modified by
80/// `Iterator::next()` calls.
81pub struct ExifIter {
82    // Use Arc to make sure we won't clone the owned data.
83    input: Arc<PartialVec>,
84    tiff_header: TiffHeader,
85    tz: Option<String>,
86    ifd0: IfdIter,
87
88    // Iterating status
89    ifds: Vec<IfdIter>,
90}
91
92impl Debug for ExifIter {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        f.debug_struct("ExifIter")
95            .field("data len", &self.input.len())
96            .field("tiff_header", &self.tiff_header)
97            .field("ifd0", &self.ifd0)
98            .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
99            .field("ifds num", &self.ifds.len())
100            .finish_non_exhaustive()
101    }
102}
103
104impl Clone for ExifIter {
105    fn clone(&self) -> Self {
106        self.clone_and_rewind()
107    }
108}
109
110impl ExifIter {
111    pub(crate) fn new(
112        input: impl Into<PartialVec>,
113        tiff_header: TiffHeader,
114        tz: Option<String>,
115        ifd0: IfdIter,
116    ) -> ExifIter {
117        let ifds = vec![ifd0.clone()];
118        ExifIter {
119            input: Arc::new(input.into()),
120            tiff_header,
121            tz,
122            ifd0,
123            ifds,
124        }
125    }
126
127    /// Clone and rewind the iterator's index.
128    ///
129    /// Clone an `ExifIter` is very cheap, the underlying data is shared
130    /// through Arc.
131    pub fn clone_and_rewind(&self) -> Self {
132        let ifd0 = self.ifd0.clone_and_rewind();
133        let ifds = vec![ifd0.clone()];
134        Self {
135            input: self.input.clone(),
136            tiff_header: self.tiff_header.clone(),
137            tz: self.tz.clone(),
138            ifd0,
139            ifds,
140        }
141    }
142
143    /// Try to find and parse gps information.
144    ///
145    /// Calling this method won't affect the iterator's state.
146    ///
147    /// Returns:
148    ///
149    /// - An `Ok<Some<GPSInfo>>` if gps info is found and parsed successfully.
150    /// - An `Ok<None>` if gps info is not found.
151    /// - An `Err` if gps info is found but parsing failed.
152    #[tracing::instrument(skip_all)]
153    pub fn parse_gps_info(&self) -> crate::Result<Option<GPSInfo>> {
154        let mut iter = self.clone_and_rewind();
155        let Some(gps) = iter.find(|x| {
156            tracing::info!(?x, "find");
157            x.tag.tag().is_some_and(|t| t == ExifTag::GPSInfo)
158        }) else {
159            tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
160            return Ok(None);
161        };
162
163        let offset = match gps.get_result() {
164            Ok(v) => {
165                if let Some(offset) = v.as_u32() {
166                    offset
167                } else {
168                    return Err(EntryError(ParseEntryError::InvalidData(
169                        "invalid gps offset".into(),
170                    ))
171                    .into());
172                }
173            }
174            Err(e) => return Err(e.clone().into()),
175        };
176
177        let mut gps_subifd = match IfdIter::try_new(
178            gps.ifd,
179            iter.input.partial(&iter.input[offset as usize..]),
180            offset,
181            iter.tiff_header.endian,
182            iter.tz.clone(),
183        ) {
184            Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
185            Err(e) => return Err(e),
186        };
187        Ok(gps_subifd.parse_gps_info())
188    }
189
190    pub(crate) fn to_owned(&self) -> ExifIter {
191        ExifIter::new(
192            self.input.to_vec(),
193            self.tiff_header.clone(),
194            self.tz.clone(),
195            self.ifd0.clone_and_rewind(),
196        )
197    }
198}
199
200#[derive(Debug, Clone, Error)]
201#[error("ifd entry error: {0}")]
202pub struct EntryError(ParseEntryError);
203
204impl From<EntryError> for crate::Error {
205    fn from(value: EntryError) -> Self {
206        Self::ParseFailed(value.into())
207    }
208}
209
210/// Represents a parsed IFD entry. Used as iterator items in [`ExifIter`].
211#[derive(Clone)]
212pub struct ParsedExifEntry {
213    // 0: ifd0, 1: ifd1
214    ifd: usize,
215    tag: ExifTagCode,
216    res: Option<Result<EntryValue, EntryError>>,
217}
218
219impl ParsedExifEntry {
220    /// Get the IFD index value where this entry is located.
221    /// - 0: ifd0 (main image)
222    /// - 1: ifd1 (thumbnail)
223    pub fn ifd_index(&self) -> usize {
224        self.ifd
225    }
226
227    /// Get recognized Exif tag of this entry, maybe return `None` if the tag
228    /// is unrecognized.
229    ///
230    /// If you have any custom defined tag which does not exist in [`ExifTag`],
231    /// then you should use [`Self::tag_code`] to get the raw tag code.
232    ///
233    /// **Note**: You can always get the raw tag code via [`Self::tag_code`],
234    /// no matter if it's recognized.
235    pub fn tag(&self) -> Option<ExifTag> {
236        match self.tag {
237            ExifTagCode::Tag(t) => Some(t),
238            ExifTagCode::Code(_) => None,
239        }
240    }
241
242    /// Get the raw tag code of this entry.
243    ///
244    /// In case you have some custom defined tags which doesn't exist in
245    /// [`ExifTag`], you can use this method to get the raw tag code of this
246    /// entry.
247    pub fn tag_code(&self) -> u16 {
248        self.tag.code()
249    }
250
251    /// Returns true if there is an `EntryValue` in self.
252    ///
253    /// Both of the following situations may cause this method to return false:
254    /// - An error occurred while parsing this entry
255    /// - The value has been taken by calling [`Self::take_value`] or
256    ///   [`Self::take_result`] methods.
257    pub fn has_value(&self) -> bool {
258        self.res.as_ref().map(|e| e.is_ok()).is_some_and(|b| b)
259    }
260
261    /// Get the parsed entry value of this entry.
262    pub fn get_value(&self) -> Option<&EntryValue> {
263        match self.res.as_ref() {
264            Some(Ok(v)) => Some(v),
265            Some(Err(_)) | None => None,
266        }
267    }
268
269    /// Takes out the parsed entry value of this entry.
270    ///
271    /// If you need to convert this `ExifIter` to an [`crate::Exif`], please
272    /// don't call this method! Otherwise the converted `Exif` is incomplete.
273    ///
274    /// **Note**: This method can only be called once! Once it has been called,
275    /// calling it again always returns `None`. You may want to check it by
276    /// calling [`Self::has_value`] before calling this method.
277    pub fn take_value(&mut self) -> Option<EntryValue> {
278        match self.res.take() {
279            Some(v) => v.ok(),
280            None => None,
281        }
282    }
283
284    /// Get the parsed result of this entry.
285    ///
286    /// Returns:
287    ///
288    /// - If any error occurred while parsing this entry, an
289    ///   Err(&[`EntryError`]) is returned.
290    ///
291    /// - Otherwise, an Ok(&[`EntryValue`]) is returned.
292    pub fn get_result(&self) -> Result<&EntryValue, &EntryError> {
293        match self.res {
294            Some(ref v) => v.as_ref(),
295            None => panic!("take result of entry twice"),
296        }
297    }
298
299    /// Takes out the parsed result of this entry.
300    ///
301    /// If you need to convert this `ExifIter` to an [`crate::Exif`], please
302    /// don't call this method! Otherwise the converted `Exif` is incomplete.
303    ///
304    /// Returns:
305    ///
306    /// - If any error occurred while parsing this entry, an
307    ///   Err([`InvalidEntry`](crate::Error::InvalidEntry)) is returned.
308    ///
309    /// - Otherwise, an Ok([`EntryValue`]) is returned.
310    ///
311    /// **Note**: This method can ONLY be called once! If you call it twice, it
312    /// will **panic** directly!
313    pub fn take_result(&mut self) -> Result<EntryValue, EntryError> {
314        match self.res.take() {
315            Some(v) => v,
316            None => panic!("take result of entry twice"),
317        }
318    }
319
320    fn make_ok(ifd: usize, tag: ExifTagCode, v: EntryValue) -> Self {
321        Self {
322            ifd,
323            tag,
324            res: Some(Ok(v)),
325        }
326    }
327
328    fn make_err(ifd: usize, tag: ExifTagCode, e: ParseEntryError) -> Self {
329        Self {
330            ifd,
331            tag,
332            res: Some(Err(EntryError(e))),
333        }
334    }
335}
336
337impl Debug for ParsedExifEntry {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        let value = match self.get_result() {
340            Ok(v) => format!("{v}"),
341            Err(e) => format!("{e:?}"),
342        };
343        f.debug_struct("IfdEntryResult")
344            .field("ifd", &format!("ifd{}", self.ifd))
345            .field("tag", &self.tag)
346            .field("value", &value)
347            .finish()
348    }
349}
350
351const MAX_IFD_DEPTH: usize = 8;
352
353impl Iterator for ExifIter {
354    type Item = ParsedExifEntry;
355
356    #[tracing::instrument(skip_all)]
357    fn next(&mut self) -> Option<Self::Item> {
358        loop {
359            if self.ifds.is_empty() {
360                tracing::debug!(?self, "all IFDs has been parsed");
361                return None;
362            }
363
364            if self.ifds.len() > MAX_IFD_DEPTH {
365                self.ifds.clear();
366                tracing::error!(
367                    ifds_depth = self.ifds.len(),
368                    "ifd depth is too deep, just go back to ifd0"
369                );
370                self.ifds.push(self.ifd0.clone_with_state());
371            }
372
373            let mut ifd = self.ifds.pop()?;
374            let cur_ifd_idx = ifd.ifd_idx;
375            match ifd.next() {
376                Some((tag_code, entry)) => {
377                    // tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, ?entry, "next tag entry");
378
379                    match entry {
380                        IfdEntry::IfdNew(new_ifd) => {
381                            let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
382                                // Push the current ifd before enter sub-ifd.
383                                self.ifds.push(ifd);
384                                tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
385                                true
386                            } else {
387                                // Otherwise this is a next ifd. It means that the
388                                // current ifd has been parsed, so we don't need to
389                                // push it.
390                                tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
391                                tracing::debug!(?new_ifd, "got new IFD");
392                                false
393                            };
394
395                            let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
396                            self.ifds.push(new_ifd);
397
398                            if is_subifd {
399                                // Return sub-ifd as an entry
400                                return Some(ParsedExifEntry::make_ok(
401                                    ifd_idx,
402                                    tag_code.unwrap(),
403                                    EntryValue::U32(offset),
404                                ));
405                            }
406                        }
407                        IfdEntry::Entry(v) => {
408                            let res =
409                                Some(ParsedExifEntry::make_ok(ifd.ifd_idx, tag_code.unwrap(), v));
410                            self.ifds.push(ifd);
411                            return res;
412                        }
413                        IfdEntry::Err(e) => {
414                            tracing::warn!(?tag_code, ?e, "parse ifd entry error");
415                            let res =
416                                Some(ParsedExifEntry::make_err(ifd.ifd_idx, tag_code.unwrap(), e));
417                            self.ifds.push(ifd);
418                            return res;
419                        }
420                    }
421                }
422                None => continue,
423            }
424        }
425    }
426}
427
428#[derive(Clone)]
429pub(crate) struct IfdIter {
430    ifd_idx: usize,
431    tag_code: Option<ExifTagCode>,
432
433    // starts from "ifd/sub-ifd entries" (two bytes of ifd/sub-ifd entry num)
434    input: AssociatedInput,
435
436    // IFD data offset relative to the TIFF header.
437    offset: u32,
438
439    pub tz: Option<String>,
440    endian: Endianness,
441    entry_num: u16,
442
443    // Iterating status
444    index: u16,
445    pos: usize,
446}
447
448impl Debug for IfdIter {
449    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450        f.debug_struct("IfdIter")
451            .field("ifd_idx", &self.ifd_idx)
452            .field("tag", &self.tag_code)
453            .field("data len", &self.input.len())
454            .field("offset", &self.offset)
455            .field("tz", &self.tz)
456            .field("endian", &self.endian)
457            .field("entry_num", &self.entry_num)
458            .field("index", &self.index)
459            .field("pos", &self.pos)
460            .finish()
461    }
462}
463
464impl IfdIter {
465    pub fn rewind(&mut self) {
466        self.index = 0;
467        // Skip the first two bytes, which is the entry num
468        self.pos = 2;
469    }
470
471    pub fn clone_and_rewind(&self) -> Self {
472        let mut it = self.clone();
473        it.rewind();
474        it
475    }
476
477    pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
478        self.tag_code = code.map(|x| x.into());
479        self
480    }
481
482    pub fn tag_code(mut self, code: u16) -> Self {
483        self.tag_code = Some(code.into());
484        self
485    }
486
487    #[allow(unused)]
488    pub fn tag(mut self, tag: ExifTagCode) -> Self {
489        self.tag_code = Some(tag);
490        self
491    }
492
493    #[tracing::instrument(skip(input))]
494    pub fn try_new(
495        ifd_idx: usize,
496        input: AssociatedInput,
497        offset: u32,
498        endian: Endianness,
499        tz: Option<String>,
500    ) -> crate::Result<Self> {
501        if input.len() < 2 {
502            return Err(crate::Error::ParseFailed(
503                "ifd data is too small to decode entry num".into(),
504            ));
505        }
506        // should use the complete header data to parse ifd entry num
507        let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&input[..], endian)?;
508
509        Ok(Self {
510            ifd_idx,
511            tag_code: None,
512            input,
513            offset,
514            entry_num,
515            tz,
516            endian,
517            // Skip the first two bytes, which is the entry num
518            pos: 2,
519            index: 0,
520        })
521    }
522
523    fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
524        let endian = self.endian;
525        let (_, (tag, data_format, components_num, value_or_offset)) = tuple((
526            complete::u16::<_, nom::error::Error<_>>(endian),
527            complete::u16(endian),
528            complete::u32(endian),
529            complete::u32(endian),
530        ))(entry_data)
531        .ok()?;
532
533        if tag == 0 {
534            return None;
535        }
536
537        let df: DataFormat = match data_format.try_into() {
538            Ok(df) => df,
539            Err(e) => {
540                let t: ExifTagCode = tag.into();
541                tracing::warn!(tag = ?t, ?e, "invalid entry data format");
542                return Some((tag, IfdEntry::Err(e)));
543            }
544        };
545        let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
546        Some((tag, res))
547    }
548
549    fn get_data_pos(&self, value_or_offset: u32) -> u32 {
550        value_or_offset.saturating_sub(self.offset)
551    }
552
553    fn parse_entry(
554        &self,
555        tag: u16,
556        data_format: DataFormat,
557        components_num: u32,
558        entry_data: &[u8],
559        value_or_offset: u32,
560    ) -> (u16, IfdEntry) {
561        // get component_size according to data format
562        let component_size = data_format.component_size();
563
564        // get entry data
565        let size = components_num as usize * component_size;
566        let data = if size <= 4 {
567            &entry_data[8..8 + size] // Safe-slice
568        } else {
569            let start = self.get_data_pos(value_or_offset) as usize;
570            let end = start + size;
571            let Some(data) = self.input.slice_checked(start..end) else {
572                tracing::warn!(
573                    "entry data overflow, self.offset: {:08x} tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
574                    self.offset,
575                    tag,
576                    start,
577                    end,
578                    self.input.len(),
579                );
580                return (tag, IfdEntry::Err(ParseEntryError::EntrySizeTooBig));
581            };
582
583            data
584        };
585
586        if SUBIFD_TAGS.contains(&tag) {
587            if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
588                return (tag, value);
589            }
590        }
591
592        let entry = EntryData {
593            endian: self.endian,
594            tag,
595            data,
596            data_format,
597            components_num,
598        };
599        match EntryValue::parse(&entry, &self.tz) {
600            Ok(v) => (tag, IfdEntry::Entry(v)),
601            Err(e) => (tag, IfdEntry::Err(e)),
602        }
603    }
604
605    fn new_ifd_iter(
606        &self,
607        ifd_idx: usize,
608        value_or_offset: u32,
609        tag: Option<u16>,
610    ) -> Option<IfdEntry> {
611        let pos = self.get_data_pos(value_or_offset) as usize;
612        if pos < self.input.len() {
613            match IfdIter::try_new(
614                ifd_idx,
615                self.input.partial(&self.input[pos..]),
616                value_or_offset,
617                self.endian,
618                self.tz.clone(),
619            ) {
620                Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
621                Err(e) => {
622                    tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
623                }
624            }
625            // return (
626            //     tag,
627            //     // IfdEntry::Ifd {
628            //     //     idx: self.ifd_idx,
629            //     //     offset: value_or_offset,
630            //     // },
631            //     IfdEntry::IfdNew(),
632            // );
633        }
634        None
635    }
636
637    pub fn find_tz_offset(&self) -> Option<String> {
638        let endian = self.endian;
639        // find ExifOffset
640        for i in 0..self.entry_num {
641            let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
642            let (remain, tag) =
643                complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
644            if tag == ExifTag::ExifOffset.code() {
645                let (_, (_, _, offset)) = tuple((
646                    complete::u16::<_, nom::error::Error<_>>(endian),
647                    complete::u32(endian),
648                    complete::u32(endian),
649                ))(remain)
650                .ok()?;
651
652                // find tz offset
653                return self.find_tz_offset_in_exif_subifd(offset);
654            }
655        }
656        None
657    }
658
659    fn find_tz_offset_in_exif_subifd(&self, offset: u32) -> Option<String> {
660        let num_entries = self.entry_num;
661        let pos = self.get_data_pos(offset + 2) as usize;
662        for i in 0..num_entries {
663            let pos = pos + i as usize * IFD_ENTRY_SIZE;
664            let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
665            let (tag, res) = self.parse_tag_entry(entry_data)?;
666
667            if TZ_OFFSET_TAGS.contains(&tag) {
668                return match res {
669                    IfdEntry::IfdNew(_) => {
670                        tracing::warn!("got ifd when parsing tz offset");
671                        continue;
672                    }
673                    IfdEntry::Entry(v) => match v {
674                        EntryValue::Text(v) => return Some(v),
675                        _ => {
676                            tracing::warn!("tz offset is not a text");
677                            continue;
678                        }
679                    },
680                    IfdEntry::Err(_) => None,
681                };
682            }
683        }
684        None
685    }
686
687    // Assume the current ifd is GPSInfo subifd.
688    pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
689        let mut gps = GPSInfo::default();
690        let mut has_data = false;
691        for (tag, entry) in self {
692            let Some(tag) = tag.and_then(|x| x.tag()) else {
693                continue;
694            };
695            has_data = true;
696            match tag {
697                ExifTag::GPSLatitudeRef => {
698                    if let Some(c) = entry.as_char() {
699                        gps.latitude_ref = c;
700                    }
701                }
702                ExifTag::GPSLongitudeRef => {
703                    if let Some(c) = entry.as_char() {
704                        gps.longitude_ref = c;
705                    }
706                }
707                ExifTag::GPSAltitudeRef => {
708                    if let Some(c) = entry.as_u8() {
709                        gps.altitude_ref = c;
710                    }
711                }
712                ExifTag::GPSLatitude => {
713                    if let Some(v) = entry.as_urational_array() {
714                        gps.latitude = v.iter().collect();
715                    } else if let Some(v) = entry.as_irational_array() {
716                        gps.latitude = v.iter().collect();
717                    }
718                }
719                ExifTag::GPSLongitude => {
720                    if let Some(v) = entry.as_urational_array() {
721                        gps.longitude = v.iter().collect();
722                    } else if let Some(v) = entry.as_irational_array() {
723                        gps.longitude = v.iter().collect();
724                    }
725                }
726                ExifTag::GPSAltitude => {
727                    if let Some(v) = entry.as_urational() {
728                        gps.altitude = *v;
729                    } else if let Some(v) = entry.as_irational() {
730                        gps.altitude = (*v).into();
731                    }
732                }
733                ExifTag::GPSSpeedRef => {
734                    if let Some(c) = entry.as_char() {
735                        gps.speed_ref = Some(c);
736                    }
737                }
738                ExifTag::GPSSpeed => {
739                    if let Some(v) = entry.as_urational() {
740                        gps.speed = Some(*v);
741                    } else if let Some(v) = entry.as_irational() {
742                        gps.speed = Some((*v).into());
743                    }
744                }
745                _ => (),
746            }
747        }
748
749        if has_data {
750            Some(gps)
751        } else {
752            tracing::warn!("GPSInfo data not found");
753            None
754        }
755    }
756
757    fn clone_with_state(&self) -> IfdIter {
758        let mut it = self.clone();
759        it.index = self.index;
760        it.pos = self.pos;
761        it
762    }
763}
764
765#[derive(Debug)]
766pub(crate) enum IfdEntry {
767    IfdNew(IfdIter), // ifd index
768    Entry(EntryValue),
769    Err(ParseEntryError),
770}
771
772impl IfdEntry {
773    pub fn as_u8(&self) -> Option<u8> {
774        if let IfdEntry::Entry(EntryValue::U8(v)) = self {
775            Some(*v)
776        } else {
777            None
778        }
779    }
780
781    pub fn as_char(&self) -> Option<char> {
782        if let IfdEntry::Entry(EntryValue::Text(s)) = self {
783            s.chars().next()
784        } else {
785            None
786        }
787    }
788
789    fn as_irational(&self) -> Option<&IRational> {
790        if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
791            Some(v)
792        } else {
793            None
794        }
795    }
796
797    fn as_irational_array(&self) -> Option<&Vec<IRational>> {
798        if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
799            Some(v)
800        } else {
801            None
802        }
803    }
804
805    fn as_urational(&self) -> Option<&URational> {
806        if let IfdEntry::Entry(EntryValue::URational(v)) = self {
807            Some(v)
808        } else {
809            None
810        }
811    }
812
813    fn as_urational_array(&self) -> Option<&Vec<URational>> {
814        if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
815            Some(v)
816        } else {
817            None
818        }
819    }
820}
821
822pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
823const TZ_OFFSET_TAGS: &[u16] = &[
824    ExifTag::OffsetTimeOriginal.code(),
825    ExifTag::OffsetTimeDigitized.code(),
826    ExifTag::OffsetTime.code(),
827];
828
829impl Iterator for IfdIter {
830    type Item = (Option<ExifTagCode>, IfdEntry);
831
832    #[tracing::instrument(skip(self))]
833    fn next(&mut self) -> Option<Self::Item> {
834        // tracing::debug!(
835        //     ifd = self.ifd_idx,
836        //     index = self.index,
837        //     entry_num = self.entry_num,
838        //     pos = format!("{:08x}", self.pos),
839        //     "next IFD entry"
840        // );
841        if self.input.len() < self.pos + IFD_ENTRY_SIZE {
842            return None;
843        }
844
845        let endian = self.endian;
846        if self.index > self.entry_num {
847            return None;
848        }
849        if self.index == self.entry_num {
850            tracing::debug!(
851                self.ifd_idx,
852                self.index,
853                pos = self.pos,
854                "try to get next ifd"
855            );
856            self.index += 1;
857
858            // next IFD offset
859            let (_, offset) =
860                complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
861
862            if offset == 0 {
863                // IFD parsing completed
864                tracing::debug!(?self, "IFD parsing completed");
865                return None;
866            }
867
868            return self
869                .new_ifd_iter(self.ifd_idx + 1, offset, None)
870                .map(|x| (None, x));
871        }
872
873        let entry_data = self
874            .input
875            .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
876        self.index += 1;
877        self.pos += IFD_ENTRY_SIZE;
878
879        let (tag, res) = self.parse_tag_entry(entry_data)?;
880
881        Some((Some(tag.into()), res)) // Safe-slice
882    }
883}
884
885#[cfg(test)]
886mod tests {
887
888    use crate::exif::extract_exif_with_mime;
889    use crate::exif::input_into_iter;
890    use crate::file::MimeImage;
891    use crate::slice::SubsliceRange;
892    use crate::testkit::read_sample;
893    use test_case::test_case;
894
895    #[test_case("exif.jpg", "+08:00", MimeImage::Jpeg)]
896    #[test_case("broken.jpg", "", MimeImage::Jpeg)]
897    #[test_case("exif.heic", "+08:00", MimeImage::Heic)]
898    #[test_case("tif.tif", "", MimeImage::Tiff)]
899    fn exif_iter_tz(path: &str, tz: &str, img_type: MimeImage) {
900        let buf = read_sample(path).unwrap();
901        let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
902        let subslice_range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
903        let iter = input_into_iter((buf, subslice_range), None).unwrap();
904        let expect = if tz.is_empty() {
905            None
906        } else {
907            Some(tz.to_string())
908        };
909        assert_eq!(iter.tz, expect);
910    }
911}