summaryrefslogtreecommitdiff
path: root/vendor/github.com/dsoprea/go-exif/v3/exif.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/dsoprea/go-exif/v3/exif.go')
-rw-r--r--vendor/github.com/dsoprea/go-exif/v3/exif.go333
1 files changed, 333 insertions, 0 deletions
diff --git a/vendor/github.com/dsoprea/go-exif/v3/exif.go b/vendor/github.com/dsoprea/go-exif/v3/exif.go
new file mode 100644
index 000000000..f66e839d9
--- /dev/null
+++ b/vendor/github.com/dsoprea/go-exif/v3/exif.go
@@ -0,0 +1,333 @@
+package exif
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+
+ "encoding/binary"
+ "io/ioutil"
+
+ "github.com/dsoprea/go-logging"
+
+ "github.com/dsoprea/go-exif/v3/common"
+)
+
+const (
+ // ExifAddressableAreaStart is the absolute offset in the file that all
+ // offsets are relative to.
+ ExifAddressableAreaStart = uint32(0x0)
+
+ // ExifDefaultFirstIfdOffset is essentially the number of bytes in addition
+ // to `ExifAddressableAreaStart` that you have to move in order to escape
+ // the rest of the header and get to the earliest point where we can put
+ // stuff (which has to be the first IFD). This is the size of the header
+ // sequence containing the two-character byte-order, two-character fixed-
+ // bytes, and the four bytes describing the first-IFD offset.
+ ExifDefaultFirstIfdOffset = uint32(2 + 2 + 4)
+)
+
+const (
+ // ExifSignatureLength is the number of bytes in the EXIF signature (which
+ // customarily includes the first IFD offset).
+ ExifSignatureLength = 8
+)
+
+var (
+ exifLogger = log.NewLogger("exif.exif")
+
+ ExifBigEndianSignature = [4]byte{'M', 'M', 0x00, 0x2a}
+ ExifLittleEndianSignature = [4]byte{'I', 'I', 0x2a, 0x00}
+)
+
+var (
+ ErrNoExif = errors.New("no exif data")
+ ErrExifHeaderError = errors.New("exif header error")
+)
+
+// SearchAndExtractExif searches for an EXIF blob in the byte-slice.
+func SearchAndExtractExif(data []byte) (rawExif []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ b := bytes.NewBuffer(data)
+
+ rawExif, err = SearchAndExtractExifWithReader(b)
+ if err != nil {
+ if err == ErrNoExif {
+ return nil, err
+ }
+
+ log.Panic(err)
+ }
+
+ return rawExif, nil
+}
+
+// SearchAndExtractExifN searches for an EXIF blob in the byte-slice, but skips
+// the given number of EXIF blocks first. This is a forensics tool that helps
+// identify multiple EXIF blocks in a file.
+func SearchAndExtractExifN(data []byte, n int) (rawExif []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ skips := 0
+ totalDiscarded := 0
+ for {
+ b := bytes.NewBuffer(data)
+
+ var discarded int
+
+ rawExif, discarded, err = searchAndExtractExifWithReaderWithDiscarded(b)
+ if err != nil {
+ if err == ErrNoExif {
+ return nil, err
+ }
+
+ log.Panic(err)
+ }
+
+ exifLogger.Debugf(nil, "Read EXIF block (%d).", skips)
+
+ totalDiscarded += discarded
+
+ if skips >= n {
+ exifLogger.Debugf(nil, "Reached requested EXIF block (%d).", n)
+ break
+ }
+
+ nextOffset := discarded + 1
+ exifLogger.Debugf(nil, "Skipping EXIF block (%d) by seeking to position (%d).", skips, nextOffset)
+
+ data = data[nextOffset:]
+ skips++
+ }
+
+ exifLogger.Debugf(nil, "Found EXIF blob (%d) bytes from initial position.", totalDiscarded)
+ return rawExif, nil
+}
+
+// searchAndExtractExifWithReaderWithDiscarded searches for an EXIF blob using
+// an `io.Reader`. We can't know how much long the EXIF data is without parsing
+// it, so this will likely grab up a lot of the image-data, too.
+//
+// This function returned the count of preceding bytes.
+func searchAndExtractExifWithReaderWithDiscarded(r io.Reader) (rawExif []byte, discarded int, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // Search for the beginning of the EXIF information. The EXIF is near the
+ // beginning of most JPEGs, so this likely doesn't have a high cost (at
+ // least, again, with JPEGs).
+
+ br := bufio.NewReader(r)
+
+ for {
+ window, err := br.Peek(ExifSignatureLength)
+ if err != nil {
+ if err == io.EOF {
+ return nil, 0, ErrNoExif
+ }
+
+ log.Panic(err)
+ }
+
+ _, err = ParseExifHeader(window)
+ if err != nil {
+ if log.Is(err, ErrNoExif) == true {
+ // No EXIF. Move forward by one byte.
+
+ _, err := br.Discard(1)
+ log.PanicIf(err)
+
+ discarded++
+
+ continue
+ }
+
+ // Some other error.
+ log.Panic(err)
+ }
+
+ break
+ }
+
+ exifLogger.Debugf(nil, "Found EXIF blob (%d) bytes from initial position.", discarded)
+
+ rawExif, err = ioutil.ReadAll(br)
+ log.PanicIf(err)
+
+ return rawExif, discarded, nil
+}
+
+// RELEASE(dustin): We should replace the implementation of SearchAndExtractExifWithReader with searchAndExtractExifWithReaderWithDiscarded and drop the latter.
+
+// SearchAndExtractExifWithReader searches for an EXIF blob using an
+// `io.Reader`. We can't know how much long the EXIF data is without parsing it,
+// so this will likely grab up a lot of the image-data, too.
+func SearchAndExtractExifWithReader(r io.Reader) (rawExif []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ rawExif, _, err = searchAndExtractExifWithReaderWithDiscarded(r)
+ if err != nil {
+ if err == ErrNoExif {
+ return nil, err
+ }
+
+ log.Panic(err)
+ }
+
+ return rawExif, nil
+}
+
+// SearchFileAndExtractExif returns a slice from the beginning of the EXIF data
+// to the end of the file (it's not practical to try and calculate where the
+// data actually ends).
+func SearchFileAndExtractExif(filepath string) (rawExif []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // Open the file.
+
+ f, err := os.Open(filepath)
+ log.PanicIf(err)
+
+ defer f.Close()
+
+ rawExif, err = SearchAndExtractExifWithReader(f)
+ log.PanicIf(err)
+
+ return rawExif, nil
+}
+
+type ExifHeader struct {
+ ByteOrder binary.ByteOrder
+ FirstIfdOffset uint32
+}
+
+func (eh ExifHeader) String() string {
+ return fmt.Sprintf("ExifHeader<BYTE-ORDER=[%v] FIRST-IFD-OFFSET=(0x%02x)>", eh.ByteOrder, eh.FirstIfdOffset)
+}
+
+// ParseExifHeader parses the bytes at the very top of the header.
+//
+// This will panic with ErrNoExif on any data errors so that we can double as
+// an EXIF-detection routine.
+func ParseExifHeader(data []byte) (eh ExifHeader, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // Good reference:
+ //
+ // CIPA DC-008-2016; JEITA CP-3451D
+ // -> http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
+
+ if len(data) < ExifSignatureLength {
+ exifLogger.Warningf(nil, "Not enough data for EXIF header: (%d)", len(data))
+ return eh, ErrNoExif
+ }
+
+ if bytes.Equal(data[:4], ExifBigEndianSignature[:]) == true {
+ exifLogger.Debugf(nil, "Byte-order is big-endian.")
+ eh.ByteOrder = binary.BigEndian
+ } else if bytes.Equal(data[:4], ExifLittleEndianSignature[:]) == true {
+ eh.ByteOrder = binary.LittleEndian
+ exifLogger.Debugf(nil, "Byte-order is little-endian.")
+ } else {
+ return eh, ErrNoExif
+ }
+
+ eh.FirstIfdOffset = eh.ByteOrder.Uint32(data[4:8])
+
+ return eh, nil
+}
+
+// Visit recursively invokes a callback for every tag.
+func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn, so *ScanOptions) (eh ExifHeader, furthestOffset uint32, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ eh, err = ParseExifHeader(exifData)
+ log.PanicIf(err)
+
+ ebs := NewExifReadSeekerWithBytes(exifData)
+ ie := NewIfdEnumerate(ifdMapping, tagIndex, ebs, eh.ByteOrder)
+
+ _, err = ie.Scan(rootIfdIdentity, eh.FirstIfdOffset, visitor, so)
+ log.PanicIf(err)
+
+ furthestOffset = ie.FurthestOffset()
+
+ return eh, furthestOffset, nil
+}
+
+// Collect recursively builds a static structure of all IFDs and tags.
+func Collect(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ eh, err = ParseExifHeader(exifData)
+ log.PanicIf(err)
+
+ ebs := NewExifReadSeekerWithBytes(exifData)
+ ie := NewIfdEnumerate(ifdMapping, tagIndex, ebs, eh.ByteOrder)
+
+ index, err = ie.Collect(eh.FirstIfdOffset)
+ log.PanicIf(err)
+
+ return eh, index, nil
+}
+
+// BuildExifHeader constructs the bytes that go at the front of the stream.
+func BuildExifHeader(byteOrder binary.ByteOrder, firstIfdOffset uint32) (headerBytes []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ b := new(bytes.Buffer)
+
+ var signatureBytes []byte
+ if byteOrder == binary.BigEndian {
+ signatureBytes = ExifBigEndianSignature[:]
+ } else {
+ signatureBytes = ExifLittleEndianSignature[:]
+ }
+
+ _, err = b.Write(signatureBytes)
+ log.PanicIf(err)
+
+ err = binary.Write(b, byteOrder, firstIfdOffset)
+ log.PanicIf(err)
+
+ return b.Bytes(), nil
+}