1 package exif
2
3 import (
4 "bytes"
5 "errors"
6 "fmt"
7 "io"
8 "strconv"
9 "strings"
10 "time"
11
12 "encoding/binary"
13
14 "github.com/dsoprea/go-logging"
15
16 "github.com/dsoprea/go-exif/v3/common"
17 "github.com/dsoprea/go-exif/v3/undefined"
18 )
19
20 var (
21 ifdEnumerateLogger = log.NewLogger("exif.ifd_enumerate")
22 )
23
24 var (
25
26 ErrNoThumbnail = errors.New("no thumbnail")
27
28
29 ErrNoGpsTags = errors.New("no gps tags")
30
31
32 ErrTagTypeNotValid = errors.New("tag type invalid")
33
34
35 ErrOffsetInvalid = errors.New("file offset invalid")
36 )
37
38 var (
39
40 ValidGpsVersions = [][4]byte{
41
42
43
44
45
46
47
48
49
50
51
52 {2, 0, 0, 0},
53
54 {2, 2, 0, 0},
55
56
57
58
59
60
61
62
63
64
65
66 {2, 3, 0, 0},
67 }
68 )
69
70
71
72
73
74
75
76
77 type byteParser struct {
78 byteOrder binary.ByteOrder
79 rs io.ReadSeeker
80 ifdOffset uint32
81 currentOffset uint32
82 }
83
84
85
86
87
88 func newByteParser(rs io.ReadSeeker, byteOrder binary.ByteOrder, initialOffset uint32) (bp *byteParser, err error) {
89
90
91 bp = &byteParser{
92 rs: rs,
93 byteOrder: byteOrder,
94 currentOffset: initialOffset,
95 }
96
97 return bp, nil
98 }
99
100
101
102
103 func (bp *byteParser) getUint16() (value uint16, raw []byte, err error) {
104 defer func() {
105 if state := recover(); state != nil {
106 err = log.Wrap(state.(error))
107 }
108 }()
109
110
111
112 needBytes := 2
113
114 raw = make([]byte, needBytes)
115
116 _, err = io.ReadFull(bp.rs, raw)
117 log.PanicIf(err)
118
119 value = bp.byteOrder.Uint16(raw)
120
121 bp.currentOffset += uint32(needBytes)
122
123 return value, raw, nil
124 }
125
126
127
128
129 func (bp *byteParser) getUint32() (value uint32, raw []byte, err error) {
130 defer func() {
131 if state := recover(); state != nil {
132 err = log.Wrap(state.(error))
133 }
134 }()
135
136
137
138 needBytes := 4
139
140 raw = make([]byte, needBytes)
141
142 _, err = io.ReadFull(bp.rs, raw)
143 log.PanicIf(err)
144
145 value = bp.byteOrder.Uint32(raw)
146
147 bp.currentOffset += uint32(needBytes)
148
149 return value, raw, nil
150 }
151
152
153
154 func (bp *byteParser) CurrentOffset() uint32 {
155 return bp.currentOffset
156 }
157
158
159
160 type IfdEnumerate struct {
161 ebs ExifBlobSeeker
162 byteOrder binary.ByteOrder
163 tagIndex *TagIndex
164 ifdMapping *exifcommon.IfdMapping
165 furthestOffset uint32
166
167 visitedIfdOffsets map[uint32]struct{}
168 }
169
170
171 func NewIfdEnumerate(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ebs ExifBlobSeeker, byteOrder binary.ByteOrder) *IfdEnumerate {
172 return &IfdEnumerate{
173 ebs: ebs,
174 byteOrder: byteOrder,
175 ifdMapping: ifdMapping,
176 tagIndex: tagIndex,
177
178 visitedIfdOffsets: make(map[uint32]struct{}),
179 }
180 }
181
182 func (ie *IfdEnumerate) getByteParser(ifdOffset uint32) (bp *byteParser, err error) {
183 defer func() {
184 if state := recover(); state != nil {
185 err = log.Wrap(state.(error))
186 }
187 }()
188
189 initialOffset := ExifAddressableAreaStart + ifdOffset
190
191 rs, err := ie.ebs.GetReadSeeker(int64(initialOffset))
192 log.PanicIf(err)
193
194 bp, err =
195 newByteParser(
196 rs,
197 ie.byteOrder,
198 initialOffset)
199
200 if err != nil {
201 if err == ErrOffsetInvalid {
202 return nil, err
203 }
204
205 log.Panic(err)
206 }
207
208 return bp, nil
209 }
210
211 func (ie *IfdEnumerate) parseTag(ii *exifcommon.IfdIdentity, tagPosition int, bp *byteParser) (ite *IfdTagEntry, err error) {
212 defer func() {
213 if state := recover(); state != nil {
214 err = log.Wrap(state.(error))
215 }
216 }()
217
218 tagId, _, err := bp.getUint16()
219 log.PanicIf(err)
220
221 tagTypeRaw, _, err := bp.getUint16()
222 log.PanicIf(err)
223
224 tagType := exifcommon.TagTypePrimitive(tagTypeRaw)
225
226 unitCount, _, err := bp.getUint32()
227 log.PanicIf(err)
228
229 valueOffset, rawValueOffset, err := bp.getUint32()
230 log.PanicIf(err)
231
232
233
234 if tagType.IsValid() == false {
235
236
237
238 ifdEnumerateLogger.Warningf(nil,
239 "Tag (0x%04x) in IFD [%s] at position (%d) has invalid type (0x%04x) and will be skipped.",
240 tagId, ii, tagPosition, int(tagType))
241
242 ite = &IfdTagEntry{
243 tagId: tagId,
244 tagType: tagType,
245 }
246
247 return ite, ErrTagTypeNotValid
248 }
249
250
251
252
253 it, err := ie.tagIndex.Get(ii, tagId)
254 if err != nil {
255 if log.Is(err, ErrTagNotFound) == true {
256 ifdEnumerateLogger.Warningf(nil, "Tag (0x%04x) is not known and will be skipped.", tagId)
257
258 ite = &IfdTagEntry{
259 tagId: tagId,
260 }
261
262 return ite, ErrTagNotFound
263 }
264
265 log.Panic(err)
266 }
267
268
269
270
271 if ie.tagIndex.UniversalSearch() == false && it.DoesSupportType(tagType) == false {
272
273
274
275
276
277 ifdEnumerateLogger.Warningf(nil,
278 "Tag (0x%04x) in IFD [%s] at position (%d) has unsupported type (0x%02x) and will be skipped.",
279 tagId, ii, tagPosition, int(tagType))
280
281 return nil, ErrTagTypeNotValid
282 }
283
284
285
286 rs, err := ie.ebs.GetReadSeeker(0)
287 log.PanicIf(err)
288
289 ite = newIfdTagEntry(
290 ii,
291 tagId,
292 tagPosition,
293 tagType,
294 unitCount,
295 valueOffset,
296 rawValueOffset,
297 rs,
298 ie.byteOrder)
299
300 ifdPath := ii.UnindexedString()
301
302
303
304
305 mi, err := ie.ifdMapping.GetChild(ifdPath, tagId)
306 if err == nil {
307 currentIfdTag := ii.IfdTag()
308
309 childIt := exifcommon.NewIfdTag(¤tIfdTag, tagId, mi.Name)
310 iiChild := ii.NewChild(childIt, 0)
311 ite.SetChildIfd(iiChild)
312
313
314
315 } else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
316 log.Panic(err)
317 }
318
319 return ite, nil
320 }
321
322
323 type TagVisitorFn func(ite *IfdTagEntry) (err error)
324
325
326 func (ie *IfdEnumerate) tagPostParse(ite *IfdTagEntry, med *MiscellaneousExifData) (err error) {
327 defer func() {
328 if state := recover(); state != nil {
329 err = log.Wrap(state.(error))
330 }
331 }()
332
333
334
335 ii := ite.IfdIdentity()
336
337 tagId := ite.TagId()
338 tagType := ite.TagType()
339
340 it, err := ie.tagIndex.Get(ii, tagId)
341 if err == nil {
342 ite.setTagName(it.Name)
343 } else {
344 if err != ErrTagNotFound {
345 log.Panic(err)
346 }
347
348
349
350 originalBt := exifcommon.BasicTag{
351 FqIfdPath: ii.String(),
352 IfdPath: ii.UnindexedString(),
353 TagId: tagId,
354 }
355
356 if med != nil {
357 med.unknownTags[originalBt] = exifcommon.BasicTag{}
358 }
359
360 utilityLogger.Debugf(nil,
361 "Tag (0x%04x) is not valid for IFD [%s]. Attempting secondary "+
362 "lookup.", tagId, ii.String())
363
364
365
366
367 it, err = ie.tagIndex.FindFirst(tagId, tagType, nil)
368 if err != nil {
369 if err != ErrTagNotFound {
370 log.Panic(err)
371 }
372
373
374
375
376
377
378
379 utilityLogger.Warningf(nil,
380 "Tag with ID (0x%04x) in IFD [%s] is not recognized and "+
381 "will be ignored.", tagId, ii.String())
382
383 return ErrTagNotFound
384 }
385
386 ite.setTagName(it.Name)
387
388 utilityLogger.Warningf(nil,
389 "Tag with ID (0x%04x) is not valid for IFD [%s], but it *is* "+
390 "valid as tag [%s] under IFD [%s] and has the same type "+
391 "[%s], so we will use that. This EXIF blob was probably "+
392 "written by a buggy implementation.",
393 tagId, ii.UnindexedString(), it.Name, it.IfdPath,
394 tagType)
395
396 if med != nil {
397 med.unknownTags[originalBt] = exifcommon.BasicTag{
398 IfdPath: it.IfdPath,
399 TagId: tagId,
400 }
401 }
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415 if ie.tagIndex.UniversalSearch() == false && it.DoesSupportType(tagType) == false {
416 ifdEnumerateLogger.Warningf(nil,
417 "Skipping tag [%s] (0x%04x) [%s] with an unexpected type: %v ∉ %v",
418 ii.UnindexedString(), tagId, it.Name,
419 tagType, it.SupportedTypes)
420
421 return ErrTagNotFound
422 }
423
424 return nil
425 }
426
427
428
429 func (ie *IfdEnumerate) parseIfd(ii *exifcommon.IfdIdentity, bp *byteParser, visitor TagVisitorFn, doDescend bool, med *MiscellaneousExifData) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
430 defer func() {
431 if state := recover(); state != nil {
432 err = log.Wrap(state.(error))
433 }
434 }()
435
436 tagCount, _, err := bp.getUint16()
437 log.PanicIf(err)
438
439 ifdEnumerateLogger.Debugf(nil, "IFD [%s] tag-count: (%d)", ii.String(), tagCount)
440
441 entries = make([]*IfdTagEntry, 0)
442
443 var enumeratorThumbnailOffset *IfdTagEntry
444 var enumeratorThumbnailSize *IfdTagEntry
445
446 for i := 0; i < int(tagCount); i++ {
447 ite, err := ie.parseTag(ii, i, bp)
448 if err != nil {
449 if log.Is(err, ErrTagNotFound) == true || log.Is(err, ErrTagTypeNotValid) == true {
450
451
452 continue
453 }
454
455 log.Panic(err)
456 }
457
458 err = ie.tagPostParse(ite, med)
459 if err == nil {
460 if err == ErrTagNotFound {
461 continue
462 }
463
464 log.PanicIf(err)
465 }
466
467 tagId := ite.TagId()
468
469 if visitor != nil {
470 err := visitor(ite)
471 log.PanicIf(err)
472 }
473
474 if ite.IsThumbnailOffset() == true {
475 ifdEnumerateLogger.Debugf(nil, "Skipping the thumbnail offset tag (0x%04x). Use accessors to get it or set it.", tagId)
476
477 enumeratorThumbnailOffset = ite
478 entries = append(entries, ite)
479
480 continue
481 } else if ite.IsThumbnailSize() == true {
482 ifdEnumerateLogger.Debugf(nil, "Skipping the thumbnail size tag (0x%04x). Use accessors to get it or set it.", tagId)
483
484 enumeratorThumbnailSize = ite
485 entries = append(entries, ite)
486
487 continue
488 }
489
490 if ite.TagType() != exifcommon.TypeUndefined {
491
492
493
494 vc := ite.getValueContext()
495
496 farOffset, err := vc.GetFarOffset()
497 if err == nil {
498 candidateOffset := farOffset + uint32(vc.SizeInBytes())
499 if candidateOffset > ie.furthestOffset {
500 ie.furthestOffset = candidateOffset
501 }
502 } else if err != exifcommon.ErrNotFarValue {
503 log.PanicIf(err)
504 }
505 }
506
507
508
509
510 if ite.ChildIfdPath() != "" {
511 if doDescend == true {
512 ifdEnumerateLogger.Debugf(nil, "Descending from IFD [%s] to IFD [%s].", ii, ite.ChildIfdPath())
513
514 currentIfdTag := ii.IfdTag()
515
516 childIfdTag :=
517 exifcommon.NewIfdTag(
518 ¤tIfdTag,
519 ite.TagId(),
520 ite.ChildIfdName())
521
522 iiChild := ii.NewChild(childIfdTag, 0)
523
524 err := ie.scan(iiChild, ite.getValueOffset(), visitor, med)
525 log.PanicIf(err)
526
527 ifdEnumerateLogger.Debugf(nil, "Ascending from IFD [%s] to IFD [%s].", ite.ChildIfdPath(), ii)
528 }
529 }
530
531 entries = append(entries, ite)
532 }
533
534 if enumeratorThumbnailOffset != nil && enumeratorThumbnailSize != nil {
535 thumbnailData, err = ie.parseThumbnail(enumeratorThumbnailOffset, enumeratorThumbnailSize)
536 if err != nil {
537 ifdEnumerateLogger.Errorf(
538 nil, err,
539 "We tried to bump our furthest-offset counter but there was an issue first seeking past the thumbnail.")
540 } else {
541
542 offset := enumeratorThumbnailOffset.getValueOffset()
543
544
545 length := enumeratorThumbnailSize.getValueOffset()
546
547 ifdEnumerateLogger.Debugf(nil, "Found thumbnail in IFD [%s]. Its offset is (%d) and is (%d) bytes.", ii, offset, length)
548
549 furthestOffset := offset + length
550
551 if furthestOffset > ie.furthestOffset {
552 ie.furthestOffset = furthestOffset
553 }
554 }
555 }
556
557 nextIfdOffset, _, err = bp.getUint32()
558 log.PanicIf(err)
559
560 _, alreadyVisited := ie.visitedIfdOffsets[nextIfdOffset]
561
562 if alreadyVisited == true {
563 ifdEnumerateLogger.Warningf(nil, "IFD at offset (0x%08x) has been linked-to more than once. There might be a cycle in the IFD chain. Not reparsing.", nextIfdOffset)
564 nextIfdOffset = 0
565 }
566
567 if nextIfdOffset != 0 {
568 ie.visitedIfdOffsets[nextIfdOffset] = struct{}{}
569 ifdEnumerateLogger.Debugf(nil, "[%s] Next IFD at offset: (0x%08x)", ii.String(), nextIfdOffset)
570 } else {
571 ifdEnumerateLogger.Debugf(nil, "[%s] IFD chain has terminated.", ii.String())
572 }
573
574 return nextIfdOffset, entries, thumbnailData, nil
575 }
576
577 func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumbnailData []byte, err error) {
578 defer func() {
579 if state := recover(); state != nil {
580 err = log.Wrap(state.(error))
581 }
582 }()
583
584 vRaw, err := lengthIte.Value()
585 log.PanicIf(err)
586
587 vList := vRaw.([]uint32)
588 if len(vList) != 1 {
589 log.Panicf("not exactly one long: (%d)", len(vList))
590 }
591
592 length := vList[0]
593
594
595 offsetIte.updateTagType(exifcommon.TypeByte)
596 offsetIte.updateUnitCount(length)
597
598 thumbnailData, err = offsetIte.GetRawBytes()
599 log.PanicIf(err)
600
601 return thumbnailData, nil
602 }
603
604
605
606 func (ie *IfdEnumerate) scan(iiGeneral *exifcommon.IfdIdentity, ifdOffset uint32, visitor TagVisitorFn, med *MiscellaneousExifData) (err error) {
607 defer func() {
608 if state := recover(); state != nil {
609 err = log.Wrap(state.(error))
610 }
611 }()
612
613
614
615 for ifdIndex := 0; ; ifdIndex++ {
616 iiSibling := iiGeneral.NewSibling(ifdIndex)
617
618 ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] at offset (0x%04x) (scan).", iiSibling.String(), ifdOffset)
619
620 bp, err := ie.getByteParser(ifdOffset)
621 if err != nil {
622 if err == ErrOffsetInvalid {
623 ifdEnumerateLogger.Errorf(nil, nil, "IFD [%s] at offset (0x%04x) is unreachable. Terminating scan.", iiSibling.String(), ifdOffset)
624 break
625 }
626
627 log.Panic(err)
628 }
629
630 nextIfdOffset, _, _, err := ie.parseIfd(iiSibling, bp, visitor, true, med)
631 log.PanicIf(err)
632
633 currentOffset := bp.CurrentOffset()
634 if currentOffset > ie.furthestOffset {
635 ie.furthestOffset = currentOffset
636 }
637
638 if nextIfdOffset == 0 {
639 break
640 }
641
642 ifdOffset = nextIfdOffset
643 }
644
645 return nil
646 }
647
648
649 type MiscellaneousExifData struct {
650
651
652
653 unknownTags map[exifcommon.BasicTag]exifcommon.BasicTag
654 }
655
656
657 func (med *MiscellaneousExifData) UnknownTags() map[exifcommon.BasicTag]exifcommon.BasicTag {
658 return med.unknownTags
659 }
660
661
662 type ScanOptions struct {
663
664 }
665
666
667
668 func (ie *IfdEnumerate) Scan(iiRoot *exifcommon.IfdIdentity, ifdOffset uint32, visitor TagVisitorFn, so *ScanOptions) (med *MiscellaneousExifData, err error) {
669 defer func() {
670 if state := recover(); state != nil {
671 err = log.Wrap(state.(error))
672 }
673 }()
674
675
676
677 med = &MiscellaneousExifData{
678 unknownTags: make(map[exifcommon.BasicTag]exifcommon.BasicTag),
679 }
680
681 err = ie.scan(iiRoot, ifdOffset, visitor, med)
682 log.PanicIf(err)
683
684 ifdEnumerateLogger.Debugf(nil, "Scan: It looks like the furthest offset that contained EXIF data in the EXIF blob was (%d) (Scan).", ie.FurthestOffset())
685
686 return med, nil
687 }
688
689
690 type Ifd struct {
691 ifdIdentity *exifcommon.IfdIdentity
692
693 ifdMapping *exifcommon.IfdMapping
694 tagIndex *TagIndex
695
696 offset uint32
697 byteOrder binary.ByteOrder
698 id int
699
700 parentIfd *Ifd
701
702
703
704
705 parentTagIndex int
706
707 entries []*IfdTagEntry
708 entriesByTagId map[uint16][]*IfdTagEntry
709
710 children []*Ifd
711 childIfdIndex map[string]*Ifd
712
713 thumbnailData []byte
714
715 nextIfdOffset uint32
716 nextIfd *Ifd
717 }
718
719
720 func (ifd *Ifd) IfdIdentity() *exifcommon.IfdIdentity {
721 return ifd.ifdIdentity
722 }
723
724
725 func (ifd *Ifd) Entries() []*IfdTagEntry {
726
727
728
729 return ifd.entries
730 }
731
732
733 func (ifd *Ifd) EntriesByTagId() map[uint16][]*IfdTagEntry {
734
735
736
737 return ifd.entriesByTagId
738 }
739
740
741 func (ifd *Ifd) Children() []*Ifd {
742
743
744
745 return ifd.children
746 }
747
748
749 func (ifd *Ifd) ChildIfdIndex() map[string]*Ifd {
750
751
752
753 return ifd.childIfdIndex
754 }
755
756
757
758 func (ifd *Ifd) ParentTagIndex() int {
759
760
761
762 return ifd.parentTagIndex
763 }
764
765
766 func (ifd *Ifd) Offset() uint32 {
767
768
769
770 return ifd.offset
771 }
772
773
774 func (ifd *Ifd) ByteOrder() binary.ByteOrder {
775
776
777
778 return ifd.byteOrder
779 }
780
781
782 func (ifd *Ifd) NextIfd() *Ifd {
783
784
785
786 return ifd.nextIfd
787 }
788
789
790
791 func (ifd *Ifd) ChildWithIfdPath(iiChild *exifcommon.IfdIdentity) (childIfd *Ifd, err error) {
792 defer func() {
793 if state := recover(); state != nil {
794 err = log.Wrap(state.(error))
795 }
796 }()
797
798
799 ifdPath := iiChild.UnindexedString()
800
801 for _, childIfd := range ifd.children {
802 if childIfd.ifdIdentity.UnindexedString() == ifdPath {
803 return childIfd, nil
804 }
805 }
806
807 log.Panic(ErrTagNotFound)
808 return nil, nil
809 }
810
811
812
813 func (ifd *Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
814 defer func() {
815 if state := recover(); state != nil {
816 err = log.Wrap(state.(error))
817 }
818 }()
819
820 results, found := ifd.entriesByTagId[tagId]
821 if found != true {
822 log.Panic(ErrTagNotFound)
823 }
824
825 return results, nil
826 }
827
828
829
830 func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error) {
831 defer func() {
832 if state := recover(); state != nil {
833 err = log.Wrap(state.(error))
834 }
835 }()
836
837 it, err := ifd.tagIndex.GetWithName(ifd.ifdIdentity, tagName)
838 if log.Is(err, ErrTagNotFound) == true {
839 log.Panic(ErrTagNotKnown)
840 } else if err != nil {
841 log.Panic(err)
842 }
843
844 results = make([]*IfdTagEntry, 0)
845 for _, ite := range ifd.entries {
846 if ite.TagId() == it.Id {
847 results = append(results, ite)
848 }
849 }
850
851 if len(results) == 0 {
852 log.Panic(ErrTagNotFound)
853 }
854
855 return results, nil
856 }
857
858
859 func (ifd *Ifd) String() string {
860 parentOffset := uint32(0)
861 if ifd.parentIfd != nil {
862 parentOffset = ifd.parentIfd.offset
863 }
864
865 return fmt.Sprintf("Ifd<ID=(%d) IFD-PATH=[%s] INDEX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)>", ifd.id, ifd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index(), len(ifd.entries), ifd.offset, len(ifd.children), parentOffset, ifd.nextIfdOffset)
866 }
867
868
869
870 func (ifd *Ifd) Thumbnail() (data []byte, err error) {
871
872 if ifd.thumbnailData == nil {
873 return nil, ErrNoThumbnail
874 }
875
876 return ifd.thumbnailData, nil
877 }
878
879
880 func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
881 if tags == nil {
882 tags = make([]*IfdTagEntry, 0)
883 }
884
885
886
887 ifdsFoundCount := 0
888
889 for _, ite := range ifd.entries {
890 tags = append(tags, ite)
891
892 childIfdPath := ite.ChildIfdPath()
893 if childIfdPath != "" {
894 ifdsFoundCount++
895
896 childIfd, found := ifd.childIfdIndex[childIfdPath]
897 if found != true {
898 log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
899 }
900
901 tags = childIfd.dumpTags(tags)
902 }
903 }
904
905 if len(ifd.children) != ifdsFoundCount {
906 log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.children), ifdsFoundCount)
907 }
908
909 if ifd.nextIfd != nil {
910 tags = ifd.nextIfd.dumpTags(tags)
911 }
912
913 return tags
914 }
915
916
917 func (ifd *Ifd) DumpTags() []*IfdTagEntry {
918 return ifd.dumpTags(nil)
919 }
920
921 func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink bool) {
922 indent := strings.Repeat(" ", level*2)
923
924 prefix := " "
925 if nextLink {
926 prefix = ">"
927 }
928
929 fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
930
931
932
933 ifdsFoundCount := 0
934
935 for _, ite := range ifd.entries {
936 if ite.ChildIfdPath() != "" {
937 fmt.Printf("%s - TAG: %s\n", indent, ite)
938 } else {
939
940
941 if ite.IsThumbnailOffset() == true || ite.IsThumbnailSize() == true {
942 continue
943 }
944
945 it, err := ifd.tagIndex.Get(ifd.ifdIdentity, ite.TagId())
946
947 tagName := ""
948 if err == nil {
949 tagName = it.Name
950 }
951
952 var valuePhrase string
953 if populateValues == true {
954 var err error
955
956 valuePhrase, err = ite.Format()
957 if err != nil {
958 if log.Is(err, exifcommon.ErrUnhandledUndefinedTypedTag) == true {
959 ifdEnumerateLogger.Warningf(nil, "Skipping non-standard undefined tag: [%s] (%04x)", ifd.ifdIdentity.UnindexedString(), ite.TagId())
960 continue
961 } else if err == exifundefined.ErrUnparseableValue {
962 ifdEnumerateLogger.Warningf(nil, "Skipping unparseable undefined tag: [%s] (%04x) [%s]", ifd.ifdIdentity.UnindexedString(), ite.TagId(), it.Name)
963 continue
964 }
965
966 log.Panic(err)
967 }
968 } else {
969 valuePhrase = "!UNRESOLVED"
970 }
971
972 fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, ite, tagName, valuePhrase)
973 }
974
975 childIfdPath := ite.ChildIfdPath()
976 if childIfdPath != "" {
977 ifdsFoundCount++
978
979 childIfd, found := ifd.childIfdIndex[childIfdPath]
980 if found != true {
981 log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
982 }
983
984 childIfd.printTagTree(populateValues, 0, level+1, false)
985 }
986 }
987
988 if len(ifd.children) != ifdsFoundCount {
989 log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.children), ifdsFoundCount)
990 }
991
992 if ifd.nextIfd != nil {
993 ifd.nextIfd.printTagTree(populateValues, index+1, level, true)
994 }
995 }
996
997
998 func (ifd *Ifd) PrintTagTree(populateValues bool) {
999 ifd.printTagTree(populateValues, 0, 0, false)
1000 }
1001
1002 func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
1003 indent := strings.Repeat(" ", level*2)
1004
1005 prefix := " "
1006 if nextLink {
1007 prefix = ">"
1008 }
1009
1010 fmt.Printf("%s%s%s\n", indent, prefix, ifd)
1011
1012
1013
1014 ifdsFoundCount := 0
1015
1016 for _, ite := range ifd.entries {
1017 childIfdPath := ite.ChildIfdPath()
1018 if childIfdPath != "" {
1019 ifdsFoundCount++
1020
1021 childIfd, found := ifd.childIfdIndex[childIfdPath]
1022 if found != true {
1023 log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
1024 }
1025
1026 childIfd.printIfdTree(level+1, false)
1027 }
1028 }
1029
1030 if len(ifd.children) != ifdsFoundCount {
1031 log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.children), ifdsFoundCount)
1032 }
1033
1034 if ifd.nextIfd != nil {
1035 ifd.nextIfd.printIfdTree(level, true)
1036 }
1037 }
1038
1039
1040 func (ifd *Ifd) PrintIfdTree() {
1041 ifd.printIfdTree(0, false)
1042 }
1043
1044 func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
1045 if tagsDump == nil {
1046 tagsDump = make([]string, 0)
1047 }
1048
1049 indent := strings.Repeat(" ", level*2)
1050
1051 var ifdPhrase string
1052 if ifd.parentIfd != nil {
1053 ifdPhrase = fmt.Sprintf("[%s]->[%s]:(%d)", ifd.parentIfd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index())
1054 } else {
1055 ifdPhrase = fmt.Sprintf("[ROOT]->[%s]:(%d)", ifd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index())
1056 }
1057
1058 startBlurb := fmt.Sprintf("%s> IFD %s TOP", indent, ifdPhrase)
1059 tagsDump = append(tagsDump, startBlurb)
1060
1061 ifdsFoundCount := 0
1062 for _, ite := range ifd.entries {
1063 tagsDump = append(tagsDump, fmt.Sprintf("%s - (0x%04x)", indent, ite.TagId()))
1064
1065 childIfdPath := ite.ChildIfdPath()
1066 if childIfdPath != "" {
1067 ifdsFoundCount++
1068
1069 childIfd, found := ifd.childIfdIndex[childIfdPath]
1070 if found != true {
1071 log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
1072 }
1073
1074 tagsDump = childIfd.dumpTree(tagsDump, level+1)
1075 }
1076 }
1077
1078 if len(ifd.children) != ifdsFoundCount {
1079 log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.children), ifdsFoundCount)
1080 }
1081
1082 finishBlurb := fmt.Sprintf("%s< IFD %s BOTTOM", indent, ifdPhrase)
1083 tagsDump = append(tagsDump, finishBlurb)
1084
1085 if ifd.nextIfd != nil {
1086 siblingBlurb := fmt.Sprintf("%s* LINKING TO SIBLING IFD [%s]:(%d)", indent, ifd.nextIfd.ifdIdentity.UnindexedString(), ifd.nextIfd.ifdIdentity.Index())
1087 tagsDump = append(tagsDump, siblingBlurb)
1088
1089 tagsDump = ifd.nextIfd.dumpTree(tagsDump, level)
1090 }
1091
1092 return tagsDump
1093 }
1094
1095
1096 func (ifd *Ifd) DumpTree() []string {
1097 return ifd.dumpTree(nil, 0)
1098 }
1099
1100
1101
1102 func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
1103 defer func() {
1104 if state := recover(); state != nil {
1105 err = log.Wrap(state.(error))
1106 }
1107 }()
1108
1109 gi = new(GpsInfo)
1110
1111 if ifd.ifdIdentity.Equals(exifcommon.IfdGpsInfoStandardIfdIdentity) == false {
1112 log.Panicf("GPS can only be read on GPS IFD: [%s]", ifd.ifdIdentity.UnindexedString())
1113 }
1114
1115 if tags, found := ifd.entriesByTagId[TagGpsVersionId]; found == false {
1116
1117
1118 ifdEnumerateLogger.Warningf(nil, "No GPS version tag (0x%04x) found.", TagGpsVersionId)
1119 } else {
1120 versionBytes, err := tags[0].GetRawBytes()
1121 log.PanicIf(err)
1122
1123 hit := false
1124 for _, acceptedGpsVersion := range ValidGpsVersions {
1125 if bytes.Compare(versionBytes, acceptedGpsVersion[:]) == 0 {
1126 hit = true
1127 break
1128 }
1129 }
1130
1131 if hit != true {
1132 ifdEnumerateLogger.Warningf(nil, "GPS version not supported: %v", versionBytes)
1133 log.Panic(ErrNoGpsTags)
1134 }
1135 }
1136
1137 tags, found := ifd.entriesByTagId[TagLatitudeId]
1138 if found == false {
1139 ifdEnumerateLogger.Warningf(nil, "latitude not found")
1140 log.Panic(ErrNoGpsTags)
1141 }
1142
1143 latitudeValue, err := tags[0].Value()
1144 log.PanicIf(err)
1145
1146
1147 tags, found = ifd.entriesByTagId[TagLatitudeRefId]
1148 if found == false {
1149 ifdEnumerateLogger.Warningf(nil, "latitude-ref not found")
1150 log.Panic(ErrNoGpsTags)
1151 }
1152
1153 latitudeRefValue, err := tags[0].Value()
1154 log.PanicIf(err)
1155
1156 tags, found = ifd.entriesByTagId[TagLongitudeId]
1157 if found == false {
1158 ifdEnumerateLogger.Warningf(nil, "longitude not found")
1159 log.Panic(ErrNoGpsTags)
1160 }
1161
1162 longitudeValue, err := tags[0].Value()
1163 log.PanicIf(err)
1164
1165
1166 tags, found = ifd.entriesByTagId[TagLongitudeRefId]
1167 if found == false {
1168 ifdEnumerateLogger.Warningf(nil, "longitude-ref not found")
1169 log.Panic(ErrNoGpsTags)
1170 }
1171
1172 longitudeRefValue, err := tags[0].Value()
1173 log.PanicIf(err)
1174
1175
1176
1177 latitudeRaw := latitudeValue.([]exifcommon.Rational)
1178
1179 gi.Latitude, err = NewGpsDegreesFromRationals(latitudeRefValue.(string), latitudeRaw)
1180 log.PanicIf(err)
1181
1182 longitudeRaw := longitudeValue.([]exifcommon.Rational)
1183
1184 gi.Longitude, err = NewGpsDegreesFromRationals(longitudeRefValue.(string), longitudeRaw)
1185 log.PanicIf(err)
1186
1187
1188
1189 altitudeTags, foundAltitude := ifd.entriesByTagId[TagAltitudeId]
1190 altitudeRefTags, foundAltitudeRef := ifd.entriesByTagId[TagAltitudeRefId]
1191
1192 if foundAltitude == true && foundAltitudeRef == true {
1193 altitudePhrase, err := altitudeTags[0].Format()
1194 log.PanicIf(err)
1195
1196 ifdEnumerateLogger.Debugf(nil, "Altitude is [%s].", altitudePhrase)
1197
1198 altitudeValue, err := altitudeTags[0].Value()
1199 log.PanicIf(err)
1200
1201 altitudeRefPhrase, err := altitudeRefTags[0].Format()
1202 log.PanicIf(err)
1203
1204 ifdEnumerateLogger.Debugf(nil, "Altitude-reference is [%s].", altitudeRefPhrase)
1205
1206 altitudeRefValue, err := altitudeRefTags[0].Value()
1207 log.PanicIf(err)
1208
1209 altitudeRaw := altitudeValue.([]exifcommon.Rational)
1210 if altitudeRaw[0].Denominator > 0 {
1211 altitude := int(altitudeRaw[0].Numerator / altitudeRaw[0].Denominator)
1212
1213 if altitudeRefValue.([]byte)[0] == 1 {
1214 altitude *= -1
1215 }
1216
1217 gi.Altitude = altitude
1218 }
1219 }
1220
1221
1222
1223 timestampTags, foundTimestamp := ifd.entriesByTagId[TagTimestampId]
1224 datestampTags, foundDatestamp := ifd.entriesByTagId[TagDatestampId]
1225
1226 if foundTimestamp == true && foundDatestamp == true {
1227 datestampValue, err := datestampTags[0].Value()
1228 log.PanicIf(err)
1229
1230 datePhrase := datestampValue.(string)
1231 ifdEnumerateLogger.Debugf(nil, "Date tag value is [%s].", datePhrase)
1232
1233
1234 datePhrase = strings.ReplaceAll(datePhrase, "-", ":")
1235
1236 dateParts := strings.Split(datePhrase, ":")
1237
1238 year, err1 := strconv.ParseUint(dateParts[0], 10, 16)
1239 month, err2 := strconv.ParseUint(dateParts[1], 10, 8)
1240 day, err3 := strconv.ParseUint(dateParts[2], 10, 8)
1241
1242 if err1 == nil && err2 == nil && err3 == nil {
1243 timestampValue, err := timestampTags[0].Value()
1244 log.PanicIf(err)
1245
1246 timePhrase, err := timestampTags[0].Format()
1247 log.PanicIf(err)
1248
1249 ifdEnumerateLogger.Debugf(nil, "Time tag value is [%s].", timePhrase)
1250
1251 timestampRaw := timestampValue.([]exifcommon.Rational)
1252
1253 hour := int(timestampRaw[0].Numerator / timestampRaw[0].Denominator)
1254 minute := int(timestampRaw[1].Numerator / timestampRaw[1].Denominator)
1255 second := int(timestampRaw[2].Numerator / timestampRaw[2].Denominator)
1256
1257 gi.Timestamp = time.Date(int(year), time.Month(month), int(day), hour, minute, second, 0, time.UTC)
1258 }
1259 }
1260
1261 return gi, nil
1262 }
1263
1264
1265
1266 type ParsedTagVisitor func(*Ifd, *IfdTagEntry) error
1267
1268
1269
1270 func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error) {
1271 defer func() {
1272 if state := recover(); state != nil {
1273 err = log.Wrap(state.(error))
1274 }
1275 }()
1276
1277 for ptr := ifd; ptr != nil; ptr = ptr.nextIfd {
1278 for _, ite := range ifd.entries {
1279 childIfdPath := ite.ChildIfdPath()
1280 if childIfdPath != "" {
1281 childIfd := ifd.childIfdIndex[childIfdPath]
1282
1283 err := childIfd.EnumerateTagsRecursively(visitor)
1284 log.PanicIf(err)
1285 } else {
1286 err := visitor(ifd, ite)
1287 log.PanicIf(err)
1288 }
1289 }
1290 }
1291
1292 return nil
1293 }
1294
1295
1296 type QueuedIfd struct {
1297 IfdIdentity *exifcommon.IfdIdentity
1298
1299 Offset uint32
1300 Parent *Ifd
1301
1302
1303
1304
1305 ParentTagIndex int
1306 }
1307
1308
1309
1310 type IfdIndex struct {
1311 RootIfd *Ifd
1312 Ifds []*Ifd
1313 Tree map[int]*Ifd
1314 Lookup map[string]*Ifd
1315 }
1316
1317
1318
1319 func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error) {
1320 defer func() {
1321 if state := recover(); state != nil {
1322 err = log.Wrap(state.(error))
1323 }
1324 }()
1325
1326
1327
1328 tree := make(map[int]*Ifd)
1329 ifds := make([]*Ifd, 0)
1330 lookup := make(map[string]*Ifd)
1331
1332 queue := []QueuedIfd{
1333 {
1334 IfdIdentity: exifcommon.IfdStandardIfdIdentity,
1335 Offset: rootIfdOffset,
1336 },
1337 }
1338
1339 edges := make(map[uint32]*Ifd)
1340
1341 for {
1342 if len(queue) == 0 {
1343 break
1344 }
1345
1346 qi := queue[0]
1347 ii := qi.IfdIdentity
1348
1349 offset := qi.Offset
1350 parentIfd := qi.Parent
1351
1352 queue = queue[1:]
1353
1354 ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (0x%04x) (Collect).", ii.String(), ii.Index(), offset)
1355
1356 bp, err := ie.getByteParser(offset)
1357 if err != nil {
1358 if err == ErrOffsetInvalid {
1359 return index, err
1360 }
1361
1362 log.Panic(err)
1363 }
1364
1365
1366
1367 nextIfdOffset, entries, thumbnailData, err := ie.parseIfd(ii, bp, nil, false, nil)
1368 log.PanicIf(err)
1369
1370 currentOffset := bp.CurrentOffset()
1371 if currentOffset > ie.furthestOffset {
1372 ie.furthestOffset = currentOffset
1373 }
1374
1375 id := len(ifds)
1376
1377 entriesByTagId := make(map[uint16][]*IfdTagEntry)
1378 for _, ite := range entries {
1379 tagId := ite.TagId()
1380
1381 tags, found := entriesByTagId[tagId]
1382 if found == false {
1383 tags = make([]*IfdTagEntry, 0)
1384 }
1385
1386 entriesByTagId[tagId] = append(tags, ite)
1387 }
1388
1389 ifd := &Ifd{
1390 ifdIdentity: ii,
1391
1392 byteOrder: ie.byteOrder,
1393
1394 id: id,
1395
1396 parentIfd: parentIfd,
1397 parentTagIndex: qi.ParentTagIndex,
1398
1399 offset: offset,
1400 entries: entries,
1401 entriesByTagId: entriesByTagId,
1402
1403
1404 children: make([]*Ifd, 0),
1405
1406 nextIfdOffset: nextIfdOffset,
1407 thumbnailData: thumbnailData,
1408
1409 ifdMapping: ie.ifdMapping,
1410 tagIndex: ie.tagIndex,
1411 }
1412
1413
1414 ifds = append(ifds, ifd)
1415
1416
1417 tree[id] = ifd
1418
1419
1420 lookup[ii.String()] = ifd
1421
1422
1423 if previousIfd, found := edges[offset]; found == true {
1424 previousIfd.nextIfd = ifd
1425 }
1426
1427
1428
1429 if parentIfd != nil {
1430 parentIfd.children = append(parentIfd.children, ifd)
1431 }
1432
1433
1434 for i, ite := range entries {
1435 if ite.ChildIfdPath() == "" {
1436 continue
1437 }
1438
1439 tagId := ite.TagId()
1440 childIfdName := ite.ChildIfdName()
1441
1442 currentIfdTag := ii.IfdTag()
1443
1444 childIfdTag :=
1445 exifcommon.NewIfdTag(
1446 ¤tIfdTag,
1447 tagId,
1448 childIfdName)
1449
1450 iiChild := ii.NewChild(childIfdTag, 0)
1451
1452 qi := QueuedIfd{
1453 IfdIdentity: iiChild,
1454
1455 Offset: ite.getValueOffset(),
1456 Parent: ifd,
1457 ParentTagIndex: i,
1458 }
1459
1460 queue = append(queue, qi)
1461 }
1462
1463
1464 if nextIfdOffset != 0 {
1465 iiSibling := ii.NewSibling(ii.Index() + 1)
1466
1467
1468 edges[nextIfdOffset] = ifd
1469
1470 qi := QueuedIfd{
1471 IfdIdentity: iiSibling,
1472 Offset: nextIfdOffset,
1473 }
1474
1475 queue = append(queue, qi)
1476 }
1477 }
1478
1479 index.RootIfd = tree[0]
1480 index.Ifds = ifds
1481 index.Tree = tree
1482 index.Lookup = lookup
1483
1484 err = ie.setChildrenIndex(index.RootIfd)
1485 log.PanicIf(err)
1486
1487 ifdEnumerateLogger.Debugf(nil, "Collect: It looks like the furthest offset that contained EXIF data in the EXIF blob was (%d).", ie.FurthestOffset())
1488
1489 return index, nil
1490 }
1491
1492 func (ie *IfdEnumerate) setChildrenIndex(ifd *Ifd) (err error) {
1493 defer func() {
1494 if state := recover(); state != nil {
1495 err = log.Wrap(state.(error))
1496 }
1497 }()
1498
1499 childIfdIndex := make(map[string]*Ifd)
1500 for _, childIfd := range ifd.children {
1501 childIfdIndex[childIfd.ifdIdentity.UnindexedString()] = childIfd
1502 }
1503
1504 ifd.childIfdIndex = childIfdIndex
1505
1506 for _, childIfd := range ifd.children {
1507 err := ie.setChildrenIndex(childIfd)
1508 log.PanicIf(err)
1509 }
1510
1511 return nil
1512 }
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522 func (ie *IfdEnumerate) FurthestOffset() uint32 {
1523
1524
1525
1526 return ie.furthestOffset
1527 }
1528
1529
1530
1531
1532
1533
1534 func parseOneIfd(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
1535 defer func() {
1536 if state := recover(); state != nil {
1537 err = log.Wrap(state.(error))
1538 }
1539 }()
1540
1541
1542
1543 ebs := NewExifReadSeekerWithBytes(ifdBlock)
1544
1545 rs, err := ebs.GetReadSeeker(0)
1546 log.PanicIf(err)
1547
1548 bp, err := newByteParser(rs, byteOrder, 0)
1549 if err != nil {
1550 if err == ErrOffsetInvalid {
1551 return 0, nil, err
1552 }
1553
1554 log.Panic(err)
1555 }
1556
1557 dummyEbs := NewExifReadSeekerWithBytes([]byte{})
1558 ie := NewIfdEnumerate(ifdMapping, tagIndex, dummyEbs, byteOrder)
1559
1560 nextIfdOffset, entries, _, err = ie.parseIfd(ii, bp, visitor, true, nil)
1561 log.PanicIf(err)
1562
1563 return nextIfdOffset, entries, nil
1564 }
1565
1566
1567 func parseOneTag(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (ite *IfdTagEntry, err error) {
1568 defer func() {
1569 if state := recover(); state != nil {
1570 err = log.Wrap(state.(error))
1571 }
1572 }()
1573
1574
1575
1576 ebs := NewExifReadSeekerWithBytes(tagBlock)
1577
1578 rs, err := ebs.GetReadSeeker(0)
1579 log.PanicIf(err)
1580
1581 bp, err := newByteParser(rs, byteOrder, 0)
1582 if err != nil {
1583 if err == ErrOffsetInvalid {
1584 return nil, err
1585 }
1586
1587 log.Panic(err)
1588 }
1589
1590 dummyEbs := NewExifReadSeekerWithBytes([]byte{})
1591 ie := NewIfdEnumerate(ifdMapping, tagIndex, dummyEbs, byteOrder)
1592
1593 ite, err = ie.parseTag(ii, 0, bp)
1594 log.PanicIf(err)
1595
1596 err = ie.tagPostParse(ite, nil)
1597 if err != nil {
1598 if err == ErrTagNotFound {
1599 return nil, err
1600 }
1601
1602 log.Panic(err)
1603 }
1604
1605 return ite, nil
1606 }
1607
1608
1609
1610 func FindIfdFromRootIfd(rootIfd *Ifd, ifdPath string) (ifd *Ifd, err error) {
1611 defer func() {
1612 if state := recover(); state != nil {
1613 err = log.Wrap(state.(error))
1614 }
1615 }()
1616
1617
1618
1619 lineage, err := rootIfd.ifdMapping.ResolvePath(ifdPath)
1620 log.PanicIf(err)
1621
1622
1623
1624
1625 if len(lineage) == 0 {
1626 log.Panicf("IFD path must be non-empty.")
1627 } else if lineage[0].Name != exifcommon.IfdStandardIfdIdentity.Name() {
1628 log.Panicf("First IFD path item must be [%s].", exifcommon.IfdStandardIfdIdentity.Name())
1629 }
1630
1631 desiredRootIndex := lineage[0].Index
1632 lineage = lineage[1:]
1633
1634
1635
1636 thisIfd := rootIfd
1637 for currentRootIndex := 0; currentRootIndex < desiredRootIndex; currentRootIndex++ {
1638 if thisIfd.nextIfd == nil {
1639 log.Panicf("Root-IFD index (%d) does not exist in the data.", currentRootIndex)
1640 }
1641
1642 thisIfd = thisIfd.nextIfd
1643 }
1644
1645 for _, itii := range lineage {
1646 var hit *Ifd
1647 for _, childIfd := range thisIfd.children {
1648 if childIfd.ifdIdentity.TagId() == itii.TagId {
1649 hit = childIfd
1650 break
1651 }
1652 }
1653
1654
1655 if hit == nil {
1656 log.Panicf("IFD [%s] in [%s] not found: %s", itii.Name, ifdPath, thisIfd.children)
1657 }
1658
1659 thisIfd = hit
1660
1661
1662 for i := 0; i < itii.Index; i++ {
1663 if thisIfd.nextIfd == nil {
1664 log.Panicf("IFD [%s] does not have (%d) occurrences/siblings", thisIfd.ifdIdentity.UnindexedString(), itii.Index)
1665 }
1666
1667 thisIfd = thisIfd.nextIfd
1668 }
1669 }
1670
1671 return thisIfd, nil
1672 }
1673
View as plain text