1use std::{collections::HashSet, fmt::Debug, ops::Range, sync::Arc};
2
3use nom::{number::complete, sequence::tuple};
4use thiserror::Error;
5
6use crate::{
7 partial_vec::{AssociatedInput, PartialVec},
8 slice::SliceChecked,
9 values::{DataFormat, EntryData, IRational, ParseEntryError, URational},
10 EntryValue, ExifTag,
11};
12
13use super::{exif_exif::IFD_ENTRY_SIZE, tags::ExifTagCode, GPSInfo, TiffHeader};
14
15#[derive(Clone)]
18pub(crate) struct TiffDataBlock {
19 #[allow(dead_code)]
21 pub block_id: String,
22 pub data_range: Range<usize>,
24 pub header: Option<TiffHeader>,
26}
27
28#[tracing::instrument]
37pub(crate) fn input_into_iter(
38 input: impl Into<PartialVec> + Debug,
39 state: Option<TiffHeader>,
40) -> crate::Result<ExifIter> {
41 let input: PartialVec = input.into();
42 let header = match state {
43 Some(header) => header,
46 _ => {
47 let (_, header) = TiffHeader::parse(&input[..])?;
49
50 tracing::debug!(
51 ?header,
52 data_len = format!("{:#x}", input.len()),
53 "TIFF header parsed"
54 );
55 header
56 }
57 };
58
59 let start = header.ifd0_offset as usize;
60 if start > input.len() {
61 return Err(crate::Error::ParseFailed("no enough bytes".into()));
62 }
63 tracing::debug!(?header, offset = start);
64
65 let mut ifd0 = IfdIter::try_new(0, input.to_owned(), header.to_owned(), start, None)?;
66
67 let tz = ifd0.find_tz_offset();
68 ifd0.tz = tz.clone();
69 let iter: ExifIter = ExifIter::new(input, header, tz, ifd0);
70
71 tracing::debug!(?iter, "got IFD0");
72
73 Ok(iter)
74}
75
76pub struct ExifIter {
89 input: Arc<PartialVec>,
91 tiff_header: TiffHeader,
92 tz: Option<String>,
93 ifd0: IfdIter,
94
95 ifds: Vec<IfdIter>,
97 visited_offsets: HashSet<usize>,
98
99 additional_blocks: Vec<TiffDataBlock>,
102 current_block_index: usize,
104 encountered_tags: HashSet<(usize, u16)>,
106}
107
108impl Debug for ExifIter {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("ExifIter")
111 .field("data len", &self.input.len())
112 .field("tiff_header", &self.tiff_header)
113 .field("ifd0", &self.ifd0)
114 .field("state", &self.ifds.first().map(|x| (x.index, x.pos)))
115 .field("ifds num", &self.ifds.len())
116 .field("additional_blocks", &self.additional_blocks.len())
117 .field("current_block_index", &self.current_block_index)
118 .finish_non_exhaustive()
119 }
120}
121
122impl Clone for ExifIter {
123 fn clone(&self) -> Self {
124 self.clone_and_rewind()
125 }
126}
127
128impl ExifIter {
129 pub(crate) fn new(
130 input: impl Into<PartialVec>,
131 tiff_header: TiffHeader,
132 tz: Option<String>,
133 ifd0: IfdIter,
134 ) -> ExifIter {
135 let ifds = vec![ifd0.clone()];
136 ExifIter {
137 input: Arc::new(input.into()),
138 tiff_header,
139 tz,
140 ifd0,
141 ifds,
142 visited_offsets: HashSet::new(),
143 additional_blocks: Vec::new(),
144 current_block_index: 0,
145 encountered_tags: HashSet::new(),
146 }
147 }
148
149 pub fn clone_and_rewind(&self) -> Self {
154 let ifd0 = self.ifd0.clone_and_rewind();
155 let ifds = vec![ifd0.clone()];
156 Self {
157 input: self.input.clone(),
158 tiff_header: self.tiff_header.clone(),
159 tz: self.tz.clone(),
160 ifd0,
161 ifds,
162 visited_offsets: HashSet::new(),
163 additional_blocks: self.additional_blocks.clone(),
164 current_block_index: 0,
165 encountered_tags: HashSet::new(),
166 }
167 }
168
169 #[tracing::instrument(skip_all)]
179 pub fn parse_gps_info(&self) -> crate::Result<Option<GPSInfo>> {
180 let mut iter = self.clone_and_rewind();
181 let Some(gps) = iter.find(|x| {
182 tracing::info!(?x, "find");
183 x.tag.tag().is_some_and(|t| t == ExifTag::GPSInfo)
184 }) else {
185 tracing::warn!(ifd0 = ?iter.ifds.first(), "GPSInfo not found");
186 return Ok(None);
187 };
188
189 let offset = match gps.get_result() {
190 Ok(v) => {
191 if let Some(offset) = v.as_u32() {
192 offset
193 } else {
194 return Err(EntryError(ParseEntryError::InvalidData(
195 "invalid gps offset".into(),
196 ))
197 .into());
198 }
199 }
200 Err(e) => return Err(e.clone().into()),
201 };
202 if offset as usize >= iter.input.len() {
203 return Err(crate::Error::ParseFailed(
204 "GPSInfo offset is out of range".into(),
205 ));
206 }
207
208 let mut gps_subifd = match IfdIter::try_new(
209 gps.ifd,
210 iter.input.partial(&iter.input[..]),
211 iter.tiff_header,
212 offset as usize,
213 iter.tz.clone(),
214 ) {
215 Ok(ifd0) => ifd0.tag_code(ExifTag::GPSInfo.code()),
216 Err(e) => return Err(e),
217 };
218 Ok(gps_subifd.parse_gps_info())
219 }
220
221 pub(crate) fn to_owned(&self) -> ExifIter {
222 let mut iter = ExifIter::new(
223 self.input.to_vec(),
224 self.tiff_header.clone(),
225 self.tz.clone(),
226 self.ifd0.clone_and_rewind(),
227 );
228 iter.additional_blocks = self.additional_blocks.clone();
229 iter
230 }
231
232 pub(crate) fn add_tiff_block(
240 &mut self,
241 block_id: String,
242 data_range: Range<usize>,
243 header: Option<TiffHeader>,
244 ) {
245 self.additional_blocks.push(TiffDataBlock {
246 block_id,
247 data_range,
248 header,
249 });
250 }
251}
252
253#[derive(Debug, Clone, Error)]
254#[error("ifd entry error: {0}")]
255pub struct EntryError(ParseEntryError);
256
257impl From<EntryError> for crate::Error {
258 fn from(value: EntryError) -> Self {
259 Self::ParseFailed(value.into())
260 }
261}
262
263#[derive(Clone)]
265pub struct ParsedExifEntry {
266 ifd: usize,
268 tag: ExifTagCode,
269 res: Option<Result<EntryValue, EntryError>>,
270}
271
272impl ParsedExifEntry {
273 pub fn ifd_index(&self) -> usize {
277 self.ifd
278 }
279
280 pub fn tag(&self) -> Option<ExifTag> {
289 match self.tag {
290 ExifTagCode::Tag(t) => Some(t),
291 ExifTagCode::Code(_) => None,
292 }
293 }
294
295 pub fn tag_code(&self) -> u16 {
301 self.tag.code()
302 }
303
304 pub fn has_value(&self) -> bool {
311 self.res.as_ref().map(|e| e.is_ok()).is_some_and(|b| b)
312 }
313
314 pub fn get_value(&self) -> Option<&EntryValue> {
316 match self.res.as_ref() {
317 Some(Ok(v)) => Some(v),
318 Some(Err(_)) | None => None,
319 }
320 }
321
322 pub fn take_value(&mut self) -> Option<EntryValue> {
331 match self.res.take() {
332 Some(v) => v.ok(),
333 None => None,
334 }
335 }
336
337 #[allow(rustdoc::private_intra_doc_links)]
346 pub fn get_result(&self) -> Result<&EntryValue, &EntryError> {
347 match self.res {
348 Some(ref v) => v.as_ref(),
349 None => panic!("take result of entry twice"),
350 }
351 }
352
353 pub fn take_result(&mut self) -> Result<EntryValue, EntryError> {
368 match self.res.take() {
369 Some(v) => v,
370 None => panic!("take result of entry twice"),
371 }
372 }
373
374 fn make_ok(ifd: usize, tag: ExifTagCode, v: EntryValue) -> Self {
375 Self {
376 ifd,
377 tag,
378 res: Some(Ok(v)),
379 }
380 }
381
382 }
390
391impl Debug for ParsedExifEntry {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 let value = match self.get_result() {
394 Ok(v) => format!("{v}"),
395 Err(e) => format!("{e:?}"),
396 };
397 f.debug_struct("IfdEntryResult")
398 .field("ifd", &format!("ifd{}", self.ifd))
399 .field("tag", &self.tag)
400 .field("value", &value)
401 .finish()
402 }
403}
404
405const MAX_IFD_DEPTH: usize = 8;
406
407impl ExifIter {
408 fn load_next_block(&mut self) -> bool {
411 let block_index = self.current_block_index;
413 if block_index >= self.additional_blocks.len() {
414 return false;
415 }
416
417 let block = &self.additional_blocks[block_index];
418 tracing::debug!(
419 block_id = block.block_id,
420 block_index,
421 "Loading additional TIFF block"
422 );
423
424 let data_range = block.data_range.clone();
426 let header = block.header.clone();
427
428 let block_data = PartialVec::new(self.input.data.clone(), data_range);
430
431 match input_into_iter(block_data, header) {
433 Ok(iter) => {
434 self.ifd0 = iter.ifd0;
436 self.ifds = vec![self.ifd0.clone()];
437 self.visited_offsets.clear();
438 self.current_block_index += 1;
439
440 tracing::debug!(block_index, "Successfully loaded additional TIFF block");
441 true
442 }
443 Err(e) => {
444 tracing::warn!(
445 block_index,
446 error = %e,
447 "Failed to load additional TIFF block, skipping"
448 );
449 self.current_block_index += 1;
451 self.load_next_block()
452 }
453 }
454 }
455
456 fn should_include_tag(&mut self, ifd_index: usize, tag_code: u16) -> bool {
459 let tag_key = (ifd_index, tag_code);
460 if self.encountered_tags.contains(&tag_key) {
461 tracing::debug!(ifd_index, tag_code, "Skipping duplicate tag");
462 false
463 } else {
464 self.encountered_tags.insert(tag_key);
465 true
466 }
467 }
468}
469
470impl Iterator for ExifIter {
471 type Item = ParsedExifEntry;
472
473 #[tracing::instrument(skip_all)]
474 fn next(&mut self) -> Option<Self::Item> {
475 loop {
476 if self.ifds.is_empty() {
477 if !self.load_next_block() {
479 tracing::debug!(?self, "all IFDs and blocks have been parsed");
480 return None;
481 }
482 continue;
484 }
485
486 if self.ifds.len() > MAX_IFD_DEPTH {
487 self.ifds.clear();
488 tracing::error!(
489 ifds_depth = self.ifds.len(),
490 "ifd depth is too deep, just go back to ifd0"
491 );
492 self.ifds.push(self.ifd0.clone_with_state());
493 }
494
495 let mut ifd = self.ifds.pop()?;
496 let cur_ifd_idx = ifd.ifd_idx;
497 match ifd.next() {
498 Some((tag_code, entry)) => {
499 tracing::debug!(ifd = ifd.ifd_idx, ?tag_code, "next tag entry");
500
501 match entry {
502 IfdEntry::IfdNew(new_ifd) => {
503 if new_ifd.offset > 0 {
504 if self.visited_offsets.contains(&new_ifd.offset) {
505 continue;
507 }
508 self.visited_offsets.insert(new_ifd.offset);
509 }
510
511 let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
512 self.ifds.push(ifd);
514 tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
515 true
516 } else {
517 tracing::debug!("IFD{} parsing completed", cur_ifd_idx);
521 tracing::debug!(?new_ifd, "got new IFD");
522 false
523 };
524
525 let (ifd_idx, offset) = (new_ifd.ifd_idx, new_ifd.offset);
526 self.ifds.push(new_ifd);
527
528 if is_subifd {
529 let tc = tag_code.unwrap();
531 if !self.should_include_tag(ifd_idx, tc.code()) {
532 continue;
533 }
534 return Some(ParsedExifEntry::make_ok(
536 ifd_idx,
537 tc,
538 EntryValue::U32(offset as u32),
539 ));
540 }
541 }
542 IfdEntry::Entry(v) => {
543 let tc = tag_code.unwrap();
544 if !self.should_include_tag(ifd.ifd_idx, tc.code()) {
546 self.ifds.push(ifd);
547 continue;
548 }
549 let res = Some(ParsedExifEntry::make_ok(ifd.ifd_idx, tc, v));
550 self.ifds.push(ifd);
551 return res;
552 }
553 IfdEntry::Err(e) => {
554 tracing::warn!(?tag_code, ?e, "parse ifd entry error");
555 self.ifds.push(ifd);
556 continue;
557 }
558 }
559 }
560 None => continue,
561 }
562 }
563 }
564}
565
566#[derive(Clone)]
567pub(crate) struct IfdIter {
568 ifd_idx: usize,
569 tag_code: Option<ExifTagCode>,
570
571 input: AssociatedInput,
573
574 offset: usize,
576
577 header: TiffHeader,
578 entry_num: u16,
579
580 pub tz: Option<String>,
581
582 index: u16,
584 pos: usize,
585}
586
587impl Debug for IfdIter {
588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589 f.debug_struct("IfdIter")
590 .field("ifd_idx", &self.ifd_idx)
591 .field("tag", &self.tag_code)
592 .field("data len", &self.input.len())
593 .field("tz", &self.tz)
594 .field("header", &self.header)
595 .field("entry_num", &self.entry_num)
596 .field("index", &self.index)
597 .field("pos", &self.pos)
598 .finish()
599 }
600}
601
602impl IfdIter {
603 pub fn rewind(&mut self) {
604 self.index = 0;
605 self.pos = self.offset + 2;
607 }
608
609 pub fn clone_and_rewind(&self) -> Self {
610 let mut it = self.clone();
611 it.rewind();
612 it
613 }
614
615 pub fn tag_code_maybe(mut self, code: Option<u16>) -> Self {
616 self.tag_code = code.map(|x| x.into());
617 self
618 }
619
620 pub fn tag_code(mut self, code: u16) -> Self {
621 self.tag_code = Some(code.into());
622 self
623 }
624
625 #[allow(unused)]
626 pub fn tag(mut self, tag: ExifTagCode) -> Self {
627 self.tag_code = Some(tag);
628 self
629 }
630
631 #[tracing::instrument(skip(input))]
632 pub fn try_new(
633 ifd_idx: usize,
634 input: AssociatedInput,
635 header: TiffHeader,
636 offset: usize,
637 tz: Option<String>,
638 ) -> crate::Result<Self> {
639 if input.len() < 2 {
640 return Err(crate::Error::ParseFailed(
641 "ifd data is too small to decode entry num".into(),
642 ));
643 }
644 assert!(offset <= input.len());
646 let ifd_data = input.partial(&input[offset..]);
647 let (_, entry_num) = TiffHeader::parse_ifd_entry_num(&ifd_data, header.endian)?;
648
649 Ok(Self {
650 ifd_idx,
651 tag_code: None,
652 input,
653 offset,
654 header,
655 entry_num,
656 tz,
657 pos: offset + 2,
659 index: 0,
660 })
661 }
662
663 fn parse_tag_entry(&self, entry_data: &[u8]) -> Option<(u16, IfdEntry)> {
664 let endian = self.header.endian;
665 let (_, (tag, data_format, components_num, value_or_offset)) = tuple((
666 complete::u16::<_, nom::error::Error<_>>(endian),
667 complete::u16(endian),
668 complete::u32(endian),
669 complete::u32(endian),
670 ))(entry_data)
671 .ok()?;
672
673 if tag == 0 {
674 return None;
675 }
676
677 let df: DataFormat = match data_format.try_into() {
678 Ok(df) => df,
679 Err(e) => {
680 let t: ExifTagCode = tag.into();
681 tracing::warn!(tag = ?t, ?e, "invalid entry data format");
682 return Some((tag, IfdEntry::Err(e)));
683 }
684 };
685 let (tag, res) = self.parse_entry(tag, df, components_num, entry_data, value_or_offset);
686 Some((tag, res))
687 }
688
689 fn get_data_pos(&self, value_or_offset: u32) -> usize {
690 value_or_offset as usize
692 }
693
694 fn parse_entry(
695 &self,
696 tag: u16,
697 data_format: DataFormat,
698 components_num: u32,
699 entry_data: &[u8],
700 value_or_offset: u32,
701 ) -> (u16, IfdEntry) {
702 let component_size = data_format.component_size();
704
705 let size = components_num as usize * component_size;
707 let data = if size <= 4 {
708 &entry_data[8..8 + size] } else {
710 let start = self.get_data_pos(value_or_offset);
711 let end = start + size;
712 let Some(data) = self.input.slice_checked(start..end) else {
713 tracing::warn!(
714 "entry data overflow, tag: {:04x} start: {:08x} end: {:08x} ifd data len {:08x}",
715 tag,
716 start,
717 end,
718 self.input.len(),
719 );
720 return (tag, IfdEntry::Err(ParseEntryError::EntrySizeTooBig));
721 };
722
723 data
724 };
725
726 if SUBIFD_TAGS.contains(&tag) {
727 if let Some(value) = self.new_ifd_iter(self.ifd_idx, value_or_offset, Some(tag)) {
728 return (tag, value);
729 }
730 }
731
732 let entry = EntryData {
733 endian: self.header.endian,
734 tag,
735 data,
736 data_format,
737 components_num,
738 };
739 match EntryValue::parse(&entry, &self.tz) {
740 Ok(v) => (tag, IfdEntry::Entry(v)),
741 Err(e) => (tag, IfdEntry::Err(e)),
742 }
743 }
744
745 fn new_ifd_iter(
746 &self,
747 ifd_idx: usize,
748 value_or_offset: u32,
749 tag: Option<u16>,
750 ) -> Option<IfdEntry> {
751 let offset = self.get_data_pos(value_or_offset);
752 if offset < self.input.len() {
753 match IfdIter::try_new(
754 ifd_idx,
755 self.input.partial(&self.input[..]),
756 self.header.to_owned(),
757 offset,
758 self.tz.clone(),
759 ) {
760 Ok(iter) => return Some(IfdEntry::IfdNew(iter.tag_code_maybe(tag))),
761 Err(e) => {
762 tracing::warn!(?tag, ?e, "Create next/sub IFD failed");
763 }
764 }
765 }
774 None
775 }
776
777 pub fn find_exif_iter(&self) -> Option<IfdIter> {
778 let endian = self.header.endian;
779 for i in 0..self.entry_num {
781 let pos = self.pos + i as usize * IFD_ENTRY_SIZE;
782 let (_, tag) =
783 complete::u16::<_, nom::error::Error<_>>(endian)(&self.input[pos..]).ok()?;
784 if tag == ExifTag::ExifOffset.code() {
785 let entry_data = self.input.slice_checked(pos..pos + IFD_ENTRY_SIZE)?;
786 let (_, entry) = self.parse_tag_entry(entry_data)?;
787 match entry {
788 IfdEntry::IfdNew(iter) => return Some(iter),
789 IfdEntry::Entry(_) | IfdEntry::Err(_) => return None,
790 }
791 }
792 }
793 None
794 }
795
796 pub fn find_tz_offset(&self) -> Option<String> {
797 let iter = self.find_exif_iter()?;
798 let mut offset = None;
799 for entry in iter {
800 let Some(tag) = entry.0 else {
801 continue;
802 };
803 if tag.code() == ExifTag::OffsetTimeOriginal.code()
804 || tag.code() == ExifTag::OffsetTimeDigitized.code()
805 {
806 return entry.1.as_str().map(|x| x.to_owned());
807 } else if tag.code() == ExifTag::OffsetTime.code() {
808 offset = entry.1.as_str().map(|x| x.to_owned());
809 }
810 }
811
812 offset
813 }
814
815 pub fn parse_gps_info(&mut self) -> Option<GPSInfo> {
817 let mut gps = GPSInfo::default();
818 let mut has_data = false;
819 for (tag, entry) in self {
820 let Some(tag) = tag.and_then(|x| x.tag()) else {
821 continue;
822 };
823 has_data = true;
824 match tag {
825 ExifTag::GPSLatitudeRef => {
826 if let Some(c) = entry.as_char() {
827 gps.latitude_ref = c;
828 }
829 }
830 ExifTag::GPSLongitudeRef => {
831 if let Some(c) = entry.as_char() {
832 gps.longitude_ref = c;
833 }
834 }
835 ExifTag::GPSAltitudeRef => {
836 if let Some(c) = entry.as_u8() {
837 gps.altitude_ref = c;
838 }
839 }
840 ExifTag::GPSLatitude => {
841 if let Some(v) = entry.as_urational_array() {
842 gps.latitude = v.try_into().ok()?;
843 } else if let Some(v) = entry.as_irational_array() {
844 gps.latitude = v.try_into().ok()?;
845 }
846 }
847 ExifTag::GPSLongitude => {
848 if let Some(v) = entry.as_urational_array() {
849 gps.longitude = v.try_into().ok()?;
850 } else if let Some(v) = entry.as_irational_array() {
851 gps.longitude = v.try_into().ok()?;
852 }
853 }
854 ExifTag::GPSAltitude => {
855 if let Some(v) = entry.as_urational() {
856 gps.altitude = *v;
857 } else if let Some(v) = entry.as_irational() {
858 gps.altitude = (*v).into();
859 }
860 }
861 ExifTag::GPSSpeedRef => {
862 if let Some(c) = entry.as_char() {
863 gps.speed_ref = Some(c);
864 }
865 }
866 ExifTag::GPSSpeed => {
867 if let Some(v) = entry.as_urational() {
868 gps.speed = Some(*v);
869 } else if let Some(v) = entry.as_irational() {
870 gps.speed = Some((*v).into());
871 }
872 }
873 _ => (),
874 }
875 }
876
877 if has_data {
878 Some(gps)
879 } else {
880 tracing::warn!("GPSInfo data not found");
881 None
882 }
883 }
884
885 fn clone_with_state(&self) -> IfdIter {
886 let mut it = self.clone();
887 it.index = self.index;
888 it.pos = self.pos;
889 it
890 }
891}
892
893#[derive(Debug)]
894pub(crate) enum IfdEntry {
895 IfdNew(IfdIter), Entry(EntryValue),
897 Err(ParseEntryError),
898}
899
900impl IfdEntry {
901 pub fn as_u8(&self) -> Option<u8> {
902 if let IfdEntry::Entry(EntryValue::U8(v)) = self {
903 Some(*v)
904 } else {
905 None
906 }
907 }
908
909 pub fn as_char(&self) -> Option<char> {
910 if let IfdEntry::Entry(EntryValue::Text(s)) = self {
911 s.chars().next()
912 } else {
913 None
914 }
915 }
916
917 fn as_irational(&self) -> Option<&IRational> {
918 if let IfdEntry::Entry(EntryValue::IRational(v)) = self {
919 Some(v)
920 } else {
921 None
922 }
923 }
924
925 fn as_irational_array(&self) -> Option<&Vec<IRational>> {
926 if let IfdEntry::Entry(EntryValue::IRationalArray(v)) = self {
927 Some(v)
928 } else {
929 None
930 }
931 }
932
933 fn as_urational(&self) -> Option<&URational> {
934 if let IfdEntry::Entry(EntryValue::URational(v)) = self {
935 Some(v)
936 } else {
937 None
938 }
939 }
940
941 fn as_urational_array(&self) -> Option<&Vec<URational>> {
942 if let IfdEntry::Entry(EntryValue::URationalArray(v)) = self {
943 Some(v)
944 } else {
945 None
946 }
947 }
948
949 fn as_str(&self) -> Option<&str> {
950 if let IfdEntry::Entry(e) = self {
951 e.as_str()
952 } else {
953 None
954 }
955 }
956}
957
958pub(crate) const SUBIFD_TAGS: &[u16] = &[ExifTag::ExifOffset.code(), ExifTag::GPSInfo.code()];
959
960impl Iterator for IfdIter {
961 type Item = (Option<ExifTagCode>, IfdEntry);
962
963 #[tracing::instrument(skip(self))]
964 fn next(&mut self) -> Option<Self::Item> {
965 tracing::debug!(
966 ifd = self.ifd_idx,
967 index = self.index,
968 entry_num = self.entry_num,
969 offset = format!("{:08x}", self.offset),
970 pos = format!("{:08x}", self.pos),
971 "next IFD entry"
972 );
973 if self.input.len() < self.pos + IFD_ENTRY_SIZE {
974 return None;
975 }
976
977 let endian = self.header.endian;
978 if self.index > self.entry_num {
979 return None;
980 }
981 if self.index == self.entry_num {
982 tracing::debug!(
983 self.ifd_idx,
984 self.index,
985 pos = self.pos,
986 "try to get next ifd"
987 );
988 self.index += 1;
989
990 let (_, offset) =
992 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
993
994 if offset == 0 {
995 tracing::debug!(?self, "IFD parsing completed");
997 return None;
998 }
999
1000 return self
1001 .new_ifd_iter(self.ifd_idx + 1, offset, None)
1002 .map(|x| (None, x));
1003 }
1004
1005 let entry_data = self
1006 .input
1007 .slice_checked(self.pos..self.pos + IFD_ENTRY_SIZE)?;
1008 self.index += 1;
1009 self.pos += IFD_ENTRY_SIZE;
1010
1011 let (tag, res) = self.parse_tag_entry(entry_data)?;
1012
1013 Some((Some(tag.into()), res)) }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019
1020 use crate::exif::extract_exif_with_mime;
1021 use crate::exif::input_into_iter;
1022 use crate::file::MimeImage;
1023 use crate::slice::SubsliceRange;
1024 use crate::testkit::read_sample;
1025 use crate::Exif;
1026 use test_case::test_case;
1027
1028 #[test_case("exif.jpg", "+08:00", "2023-07-09T20:36:33+08:00", MimeImage::Jpeg)]
1029 #[test_case("exif-no-tz.jpg", "", "2023-07-09 20:36:33", MimeImage::Jpeg)]
1030 #[test_case("broken.jpg", "-", "2014-09-21 15:51:22", MimeImage::Jpeg)]
1031 #[test_case("exif.heic", "+08:00", "2022-07-22T21:26:32+08:00", MimeImage::Heic)]
1032 #[test_case("tif.tif", "-", "-", MimeImage::Tiff)]
1033 #[test_case(
1034 "fujifilm_x_t1_01.raf.meta",
1035 "-",
1036 "2014-01-30 12:49:13",
1037 MimeImage::Raf
1038 )]
1039 fn exif_iter_tz(path: &str, tz: &str, time: &str, img_type: MimeImage) {
1040 let buf = read_sample(path).unwrap();
1041 let (data, _) = extract_exif_with_mime(img_type, &buf, None).unwrap();
1042 let subslice_in_range = data.and_then(|x| buf.subslice_in_range(x)).unwrap();
1043 let iter = input_into_iter((buf, subslice_in_range), None).unwrap();
1044 let expect = if tz == "-" {
1045 None
1046 } else {
1047 Some(tz.to_string())
1048 };
1049 assert_eq!(iter.tz, expect);
1050 let exif: Exif = iter.into();
1051 let value = exif.get(crate::ExifTag::DateTimeOriginal);
1052 if time == "-" {
1053 assert!(value.is_none());
1054 } else {
1055 let value = value.unwrap();
1056 assert_eq!(value.to_string(), time);
1057 }
1058 }
1059}