413 lines
11 KiB
Go
413 lines
11 KiB
Go
package exifcommon
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"encoding/binary"
|
|
|
|
"github.com/dsoprea/go-logging"
|
|
)
|
|
|
|
var (
|
|
parser *Parser
|
|
)
|
|
|
|
var (
|
|
// ErrNotFarValue indicates that an offset-based lookup was attempted for a
|
|
// non-offset-based (embedded) value.
|
|
ErrNotFarValue = errors.New("not a far value")
|
|
)
|
|
|
|
// ValueContext embeds all of the parameters required to find and extract the
|
|
// actual tag value.
|
|
type ValueContext struct {
|
|
unitCount uint32
|
|
valueOffset uint32
|
|
rawValueOffset []byte
|
|
addressableData []byte
|
|
|
|
tagType TagTypePrimitive
|
|
byteOrder binary.ByteOrder
|
|
|
|
// undefinedValueTagType is the effective type to use if this is an
|
|
// "undefined" value.
|
|
undefinedValueTagType TagTypePrimitive
|
|
|
|
ifdPath string
|
|
tagId uint16
|
|
}
|
|
|
|
// TODO(dustin): We can update newValueContext() to derive `valueOffset` itself (from `rawValueOffset`).
|
|
|
|
// NewValueContext returns a new ValueContext struct.
|
|
func NewValueContext(ifdPath string, tagId uint16, unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext {
|
|
return &ValueContext{
|
|
unitCount: unitCount,
|
|
valueOffset: valueOffset,
|
|
rawValueOffset: rawValueOffset,
|
|
addressableData: addressableData,
|
|
|
|
tagType: tagType,
|
|
byteOrder: byteOrder,
|
|
|
|
ifdPath: ifdPath,
|
|
tagId: tagId,
|
|
}
|
|
}
|
|
|
|
// SetUndefinedValueType sets the effective type if this is an unknown-type tag.
|
|
func (vc *ValueContext) SetUndefinedValueType(tagType TagTypePrimitive) {
|
|
if vc.tagType != TypeUndefined {
|
|
log.Panicf("can not set effective type for unknown-type tag because this is *not* an unknown-type tag")
|
|
}
|
|
|
|
vc.undefinedValueTagType = tagType
|
|
}
|
|
|
|
// UnitCount returns the embedded unit-count.
|
|
func (vc *ValueContext) UnitCount() uint32 {
|
|
return vc.unitCount
|
|
}
|
|
|
|
// ValueOffset returns the value-offset decoded as a `uint32`.
|
|
func (vc *ValueContext) ValueOffset() uint32 {
|
|
return vc.valueOffset
|
|
}
|
|
|
|
// RawValueOffset returns the uninterpreted value-offset. This is used for
|
|
// embedded values (values small enough to fit within the offset bytes rather
|
|
// than needing to be stored elsewhere and referred to by an actual offset).
|
|
func (vc *ValueContext) RawValueOffset() []byte {
|
|
return vc.rawValueOffset
|
|
}
|
|
|
|
// AddressableData returns the block of data that we can dereference into.
|
|
func (vc *ValueContext) AddressableData() []byte {
|
|
return vc.addressableData
|
|
}
|
|
|
|
// ByteOrder returns the byte-order of numbers.
|
|
func (vc *ValueContext) ByteOrder() binary.ByteOrder {
|
|
return vc.byteOrder
|
|
}
|
|
|
|
// IfdPath returns the path of the IFD containing this tag.
|
|
func (vc *ValueContext) IfdPath() string {
|
|
return vc.ifdPath
|
|
}
|
|
|
|
// TagId returns the ID of the tag that we represent.
|
|
func (vc *ValueContext) TagId() uint16 {
|
|
return vc.tagId
|
|
}
|
|
|
|
// isEmbedded returns whether the value is embedded or a reference. This can't
|
|
// be precalculated since the size is not defined for all types (namely the
|
|
// "undefined" types).
|
|
func (vc *ValueContext) isEmbedded() bool {
|
|
tagType := vc.effectiveValueType()
|
|
|
|
return (tagType.Size() * int(vc.unitCount)) <= 4
|
|
}
|
|
|
|
// SizeInBytes returns the number of bytes that this value requires. The
|
|
// underlying call will panic if the type is UNDEFINED. It is the
|
|
// responsibility of the caller to preemptively check that.
|
|
func (vc *ValueContext) SizeInBytes() int {
|
|
tagType := vc.effectiveValueType()
|
|
|
|
return tagType.Size() * int(vc.unitCount)
|
|
}
|
|
|
|
// effectiveValueType returns the effective type of the unknown-type tag or, if
|
|
// not unknown, the actual type.
|
|
func (vc *ValueContext) effectiveValueType() (tagType TagTypePrimitive) {
|
|
if vc.tagType == TypeUndefined {
|
|
tagType = vc.undefinedValueTagType
|
|
|
|
if tagType == 0 {
|
|
log.Panicf("undefined-value type not set")
|
|
}
|
|
} else {
|
|
tagType = vc.tagType
|
|
}
|
|
|
|
return tagType
|
|
}
|
|
|
|
// readRawEncoded returns the encoded bytes for the value that we represent.
|
|
func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
tagType := vc.effectiveValueType()
|
|
|
|
unitSizeRaw := uint32(tagType.Size())
|
|
|
|
if vc.isEmbedded() == true {
|
|
byteLength := unitSizeRaw * vc.unitCount
|
|
return vc.rawValueOffset[:byteLength], nil
|
|
}
|
|
|
|
return vc.addressableData[vc.valueOffset : vc.valueOffset+vc.unitCount*unitSizeRaw], nil
|
|
}
|
|
|
|
// GetFarOffset returns the offset if the value is not embedded [within the
|
|
// pointer itself] or an error if an embedded value.
|
|
func (vc *ValueContext) GetFarOffset() (offset uint32, err error) {
|
|
if vc.isEmbedded() == true {
|
|
return 0, ErrNotFarValue
|
|
}
|
|
|
|
return vc.valueOffset, nil
|
|
}
|
|
|
|
// ReadRawEncoded returns the encoded bytes for the value that we represent.
|
|
func (vc *ValueContext) ReadRawEncoded() (rawBytes []byte, err error) {
|
|
|
|
// TODO(dustin): Remove this method and rename readRawEncoded in its place.
|
|
|
|
return vc.readRawEncoded()
|
|
}
|
|
|
|
// Format returns a string representation for the value.
|
|
//
|
|
// Where the type is not ASCII, `justFirst` indicates whether to just stringify
|
|
// the first item in the slice (or return an empty string if the slice is
|
|
// empty).
|
|
//
|
|
// Since this method lacks the information to process undefined-type tags (e.g.
|
|
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
|
|
// `Undefined()`.
|
|
func (vc *ValueContext) Format() (value string, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawBytes, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
phrase, err := FormatFromBytes(rawBytes, vc.effectiveValueType(), false, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return phrase, nil
|
|
}
|
|
|
|
// FormatFirst is similar to `Format` but only gets and stringifies the first
|
|
// item.
|
|
func (vc *ValueContext) FormatFirst() (value string, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawBytes, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
phrase, err := FormatFromBytes(rawBytes, vc.tagType, true, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return phrase, nil
|
|
}
|
|
|
|
// ReadBytes parses the encoded byte-array from the value-context.
|
|
func (vc *ValueContext) ReadBytes() (value []byte, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseBytes(rawValue, vc.unitCount)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadAscii parses the encoded NUL-terminated ASCII string from the value-
|
|
// context.
|
|
func (vc *ValueContext) ReadAscii() (value string, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseAscii(rawValue, vc.unitCount)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadAsciiNoNul parses the non-NUL-terminated encoded ASCII string from the
|
|
// value-context.
|
|
func (vc *ValueContext) ReadAsciiNoNul() (value string, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseAsciiNoNul(rawValue, vc.unitCount)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadShorts parses the list of encoded shorts from the value-context.
|
|
func (vc *ValueContext) ReadShorts() (value []uint16, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseShorts(rawValue, vc.unitCount, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadLongs parses the list of encoded, unsigned longs from the value-context.
|
|
func (vc *ValueContext) ReadLongs() (value []uint32, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseLongs(rawValue, vc.unitCount, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadRationals parses the list of encoded, unsigned rationals from the value-
|
|
// context.
|
|
func (vc *ValueContext) ReadRationals() (value []Rational, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseRationals(rawValue, vc.unitCount, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadSignedLongs parses the list of encoded, signed longs from the value-context.
|
|
func (vc *ValueContext) ReadSignedLongs() (value []int32, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseSignedLongs(rawValue, vc.unitCount, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// ReadSignedRationals parses the list of encoded, signed rationals from the
|
|
// value-context.
|
|
func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
rawValue, err := vc.readRawEncoded()
|
|
log.PanicIf(err)
|
|
|
|
value, err = parser.ParseSignedRationals(rawValue, vc.unitCount, vc.byteOrder)
|
|
log.PanicIf(err)
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// Values knows how to resolve the given value. This value is always a list
|
|
// (undefined-values aside), so we're named accordingly.
|
|
//
|
|
// Since this method lacks the information to process unknown-type tags (e.g.
|
|
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
|
|
// `Undefined()`.
|
|
func (vc *ValueContext) Values() (values interface{}, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
if vc.tagType == TypeByte {
|
|
values, err = vc.ReadBytes()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeAscii {
|
|
values, err = vc.ReadAscii()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeAsciiNoNul {
|
|
values, err = vc.ReadAsciiNoNul()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeShort {
|
|
values, err = vc.ReadShorts()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeLong {
|
|
values, err = vc.ReadLongs()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeRational {
|
|
values, err = vc.ReadRationals()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeSignedLong {
|
|
values, err = vc.ReadSignedLongs()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeSignedRational {
|
|
values, err = vc.ReadSignedRationals()
|
|
log.PanicIf(err)
|
|
} else if vc.tagType == TypeUndefined {
|
|
log.Panicf("will not parse undefined-type value")
|
|
|
|
// Never called.
|
|
return nil, nil
|
|
} else {
|
|
log.Panicf("value of type [%s] is unparseable", vc.tagType)
|
|
// Never called.
|
|
return nil, nil
|
|
}
|
|
|
|
return values, nil
|
|
}
|
|
|
|
func init() {
|
|
parser = new(Parser)
|
|
}
|