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#[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 Some(header) => (header, 0),
36 _ => {
37 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
69pub struct ExifIter {
82 input: Arc<PartialVec>,
84 tiff_header: TiffHeader,
85 tz: Option<String>,
86 ifd0: IfdIter,
87
88 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 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 #[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#[derive(Clone)]
212pub struct ParsedExifEntry {
213 ifd: usize,
215 tag: ExifTagCode,
216 res: Option<Result<EntryValue, EntryError>>,
217}
218
219impl ParsedExifEntry {
220 pub fn ifd_index(&self) -> usize {
224 self.ifd
225 }
226
227 pub fn tag(&self) -> Option<ExifTag> {
236 match self.tag {
237 ExifTagCode::Tag(t) => Some(t),
238 ExifTagCode::Code(_) => None,
239 }
240 }
241
242 pub fn tag_code(&self) -> u16 {
248 self.tag.code()
249 }
250
251 pub fn has_value(&self) -> bool {
258 self.res.as_ref().map(|e| e.is_ok()).is_some_and(|b| b)
259 }
260
261 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 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 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 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 match entry {
380 IfdEntry::IfdNew(new_ifd) => {
381 let is_subifd = if new_ifd.ifd_idx == ifd.ifd_idx {
382 self.ifds.push(ifd);
384 tracing::debug!(?tag_code, ?new_ifd, "got new SUB-IFD");
385 true
386 } else {
387 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 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 input: AssociatedInput,
435
436 offset: u32,
438
439 pub tz: Option<String>,
440 endian: Endianness,
441 entry_num: u16,
442
443 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 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 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 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 let component_size = data_format.component_size();
563
564 let size = components_num as usize * component_size;
566 let data = if size <= 4 {
567 &entry_data[8..8 + size] } 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 }
634 None
635 }
636
637 pub fn find_tz_offset(&self) -> Option<String> {
638 let endian = self.endian;
639 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 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 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), 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 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 let (_, offset) =
860 complete::u32::<_, nom::error::Error<_>>(endian)(&self.input[self.pos..]).ok()?;
861
862 if offset == 0 {
863 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)) }
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}