package exif import ( "bytes" "fmt" "strings" "encoding/binary" "github.com/dsoprea/go-logging" ) var ( ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd") ) // IfdTagEnumerator knows how to decode an IFD and all of the tags it // describes. Note that the IFDs and the actual values floating throughout the // whole EXIF block, but the IFD itself has just a minor header and a set of // repeating, statically-sized records. So, the tags (though not their values) // are fairly simple to enumerate. type IfdTagEnumerator struct { byteOrder binary.ByteOrder addressableData []byte ifdOffset uint32 buffer *bytes.Buffer } func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (ite *IfdTagEnumerator) { ite = &IfdTagEnumerator{ addressableData: addressableData, byteOrder: byteOrder, buffer: bytes.NewBuffer(addressableData[ifdOffset:]), } return ite } // getUint16 reads a uint16 and advances both our current and our current // accumulator (which allows us to know how far to seek to the beginning of the // next IFD when it's time to jump). func (ife *IfdTagEnumerator) getUint16() (value uint16, raw []byte, err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() raw = make([]byte, 2) _, err = ife.buffer.Read(raw) log.PanicIf(err) if ife.byteOrder == binary.BigEndian { value = binary.BigEndian.Uint16(raw) } else { value = binary.LittleEndian.Uint16(raw) } return value, raw, nil } // getUint32 reads a uint32 and advances both our current and our current // accumulator (which allows us to know how far to seek to the beginning of the // next IFD when it's time to jump). func (ife *IfdTagEnumerator) getUint32() (value uint32, raw []byte, err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() raw = make([]byte, 4) _, err = ife.buffer.Read(raw) log.PanicIf(err) if ife.byteOrder == binary.BigEndian { value = binary.BigEndian.Uint32(raw) } else { value = binary.LittleEndian.Uint32(raw) } return value, raw, nil } type IfdEnumerate struct { exifData []byte buffer *bytes.Buffer byteOrder binary.ByteOrder currentOffset uint32 } func NewIfdEnumerate(exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate { // Make it obvious what data we expect and when we don't get it. if IsExif(exifData) == false { log.Panicf("not exif data") } return &IfdEnumerate{ exifData: exifData, buffer: bytes.NewBuffer(exifData), byteOrder: byteOrder, } } // ValueContext describes all of the parameters required to find and extract // the actual tag value. type ValueContext struct { UnitCount uint32 ValueOffset uint32 RawValueOffset []byte AddressableData []byte } func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) { ite = NewIfdTagEnumerator( ie.exifData[ExifAddressableAreaStart:], ie.byteOrder, ifdOffset) return ite } // TagVisitor is an optional callback that can get hit for every tag we parse // through. `addressableData` is the byte array startign after the EXIF header // (where the offsets of all IFDs and values are calculated from). type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error) // ParseIfd decodes the IFD block that we're currently sitting on the first // byte of. func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ifdOffset uint32, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []IfdTagEntry, err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, ifdIndex, ifdOffset) ite := ie.getTagEnumerator(ifdOffset) tagCount, _, err := ite.getUint16() log.PanicIf(err) ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount) entries = make([]IfdTagEntry, tagCount) for i := uint16(0); i < tagCount; i++ { tagId, _, err := ite.getUint16() log.PanicIf(err) tagType, _, err := ite.getUint16() log.PanicIf(err) unitCount, _, err := ite.getUint32() log.PanicIf(err) valueOffset, rawValueOffset, err := ite.getUint32() log.PanicIf(err) if visitor != nil { tt := NewTagType(tagType, ie.byteOrder) vc := ValueContext{ UnitCount: unitCount, ValueOffset: valueOffset, RawValueOffset: rawValueOffset, AddressableData: ie.exifData[ExifAddressableAreaStart:], } err := visitor(ii, ifdIndex, tagId, tt, vc) log.PanicIf(err) } tag := IfdTagEntry{ Ii: ii, TagId: tagId, TagIndex: int(i), TagType: tagType, UnitCount: unitCount, ValueOffset: valueOffset, RawValueOffset: rawValueOffset, } childIfdName, isIfd := IfdTagNameWithId(ii.IfdName, tagId) // If it's an IFD but not a standard one, it'll just be seen as a LONG // (the standard IFD tag type), later, unless we skip it because it's // [likely] not even in the standard list of known tags. if isIfd == true { tag.ChildIfdName = childIfdName if doDescend == true { ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName) childIi, _ := IfdIdOrFail(ii.IfdName, childIfdName) err := ie.scan(childIi, valueOffset, visitor) log.PanicIf(err) } } entries[i] = tag } nextIfdOffset, _, err = ite.getUint32() log.PanicIf(err) ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset) return nextIfdOffset, entries, nil } // Scan enumerates the different EXIF blocks (called IFDs). func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor) (err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() for ifdIndex := 0;; ifdIndex++ { nextIfdOffset, _, err := ie.ParseIfd(ii, ifdIndex, ifdOffset, visitor, true) log.PanicIf(err) if nextIfdOffset == 0 { break } ifdOffset = nextIfdOffset } return nil } // Scan enumerates the different EXIF blocks (called IFDs). func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() ii, _ := IfdIdOrFail("", IfdStandard) err = ie.scan(ii, ifdOffset, visitor) log.PanicIf(err) return nil } type Ifd struct { ByteOrder binary.ByteOrder Id int ParentIfd *Ifd Name string Index int Offset uint32 // TODO(dustin): !! Add a find method. Entries []IfdTagEntry Children []*Ifd NextIfdOffset uint32 NextIfd *Ifd } func (ifd Ifd) String() string { parentOffset := uint32(0) if ifd.ParentIfd != nil { parentOffset = ifd.ParentIfd.Offset } return fmt.Sprintf("IFD