gotosocial/vendor/github.com/dsoprea/go-exif/v3/common/type.go
2022-01-23 14:41:31 +01:00

483 lines
12 KiB
Go

package exifcommon
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"unicode"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
var (
typeLogger = log.NewLogger("exif.type")
)
var (
// ErrNotEnoughData is used when there isn't enough data to accommodate what
// we're trying to parse (sizeof(type) * unit_count).
ErrNotEnoughData = errors.New("not enough data for type")
// ErrWrongType is used when we try to parse anything other than the
// current type.
ErrWrongType = errors.New("wrong type, can not parse")
// ErrUnhandledUndefinedTypedTag is used when we try to parse a tag that's
// recorded as an "unknown" type but not a documented tag (therefore
// leaving us not knowning how to read it).
ErrUnhandledUndefinedTypedTag = errors.New("not a standard unknown-typed tag")
)
// TagTypePrimitive is a type-alias that let's us easily lookup type properties.
type TagTypePrimitive uint16
const (
// TypeByte describes an encoded list of bytes.
TypeByte TagTypePrimitive = 1
// TypeAscii describes an encoded list of characters that is terminated
// with a NUL in its encoded form.
TypeAscii TagTypePrimitive = 2
// TypeShort describes an encoded list of shorts.
TypeShort TagTypePrimitive = 3
// TypeLong describes an encoded list of longs.
TypeLong TagTypePrimitive = 4
// TypeRational describes an encoded list of rationals.
TypeRational TagTypePrimitive = 5
// TypeUndefined describes an encoded value that has a complex/non-clearcut
// interpretation.
TypeUndefined TagTypePrimitive = 7
// We've seen type-8, but have no documentation on it.
// TypeSignedLong describes an encoded list of signed longs.
TypeSignedLong TagTypePrimitive = 9
// TypeSignedRational describes an encoded list of signed rationals.
TypeSignedRational TagTypePrimitive = 10
// TypeFloat describes an encoded list of floats
TypeFloat TagTypePrimitive = 11
// TypeDouble describes an encoded list of doubles.
TypeDouble TagTypePrimitive = 12
// TypeAsciiNoNul is just a pseudo-type, for our own purposes.
TypeAsciiNoNul TagTypePrimitive = 0xf0
)
// String returns the name of the type
func (typeType TagTypePrimitive) String() string {
return TypeNames[typeType]
}
// Size returns the size of one atomic unit of the type.
func (tagType TagTypePrimitive) Size() int {
switch tagType {
case TypeByte, TypeAscii, TypeAsciiNoNul:
return 1
case TypeShort:
return 2
case TypeLong, TypeSignedLong, TypeFloat:
return 4
case TypeRational, TypeSignedRational, TypeDouble:
return 8
default:
log.Panicf("can not determine tag-value size for type (%d): [%s]",
tagType,
TypeNames[tagType])
// Never called.
return 0
}
}
// IsValid returns true if tagType is a valid type.
func (tagType TagTypePrimitive) IsValid() bool {
// TODO(dustin): Add test
return tagType == TypeByte ||
tagType == TypeAscii ||
tagType == TypeAsciiNoNul ||
tagType == TypeShort ||
tagType == TypeLong ||
tagType == TypeRational ||
tagType == TypeSignedLong ||
tagType == TypeSignedRational ||
tagType == TypeFloat ||
tagType == TypeDouble ||
tagType == TypeUndefined
}
var (
// TODO(dustin): Rename TypeNames() to typeNames() and add getter.
TypeNames = map[TagTypePrimitive]string{
TypeByte: "BYTE",
TypeAscii: "ASCII",
TypeShort: "SHORT",
TypeLong: "LONG",
TypeRational: "RATIONAL",
TypeUndefined: "UNDEFINED",
TypeSignedLong: "SLONG",
TypeSignedRational: "SRATIONAL",
TypeFloat: "FLOAT",
TypeDouble: "DOUBLE",
TypeAsciiNoNul: "_ASCII_NO_NUL",
}
typeNamesR = map[string]TagTypePrimitive{}
)
// Rational describes an unsigned rational value.
type Rational struct {
// Numerator is the numerator of the rational value.
Numerator uint32
// Denominator is the numerator of the rational value.
Denominator uint32
}
// SignedRational describes a signed rational value.
type SignedRational struct {
// Numerator is the numerator of the rational value.
Numerator int32
// Denominator is the numerator of the rational value.
Denominator int32
}
func isPrintableText(s string) bool {
for _, c := range s {
// unicode.IsPrint() returns false for newline characters.
if c == 0x0d || c == 0x0a {
continue
} else if unicode.IsPrint(rune(c)) == false {
return false
}
}
return true
}
// Format returns a stringified value for the given encoding. Automatically
// parses. Automatically calculates count based on type size. This function
// also supports undefined-type values (the ones that we support, anyway) by
// way of the String() method that they all require. We can't be more specific
// because we're a base package and we can't refer to it.
func FormatFromType(value interface{}, justFirst bool) (phrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test
switch t := value.(type) {
case []byte:
return DumpBytesToString(t), nil
case string:
for i, c := range t {
if c == 0 {
t = t[:i]
break
}
}
if isPrintableText(t) == false {
phrase = fmt.Sprintf("string with binary data (%d bytes)", len(t))
return phrase, nil
}
return t, nil
case []uint16, []uint32, []int32, []float64, []float32:
val := reflect.ValueOf(t)
if val.Len() == 0 {
return "", nil
}
if justFirst == true {
var valueSuffix string
if val.Len() > 1 {
valueSuffix = "..."
}
return fmt.Sprintf("%v%s", val.Index(0), valueSuffix), nil
}
return fmt.Sprintf("%v", val), nil
case []Rational:
if len(t) == 0 {
return "", nil
}
parts := make([]string, len(t))
for i, r := range t {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
if justFirst == true {
break
}
}
if justFirst == true {
var valueSuffix string
if len(t) > 1 {
valueSuffix = "..."
}
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
}
return fmt.Sprintf("%v", parts), nil
case []SignedRational:
if len(t) == 0 {
return "", nil
}
parts := make([]string, len(t))
for i, r := range t {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
if justFirst == true {
break
}
}
if justFirst == true {
var valueSuffix string
if len(t) > 1 {
valueSuffix = "..."
}
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
}
return fmt.Sprintf("%v", parts), nil
case fmt.Stringer:
s := t.String()
if isPrintableText(s) == false {
phrase = fmt.Sprintf("stringable with binary data (%d bytes)", len(s))
return phrase, nil
}
// An undefined value that is documented (or that we otherwise support).
return s, nil
default:
// Affects only "unknown" values, in general.
log.Panicf("type can not be formatted into string: %v", reflect.TypeOf(value).Name())
// Never called.
return "", nil
}
}
// Format returns a stringified value for the given encoding. Automatically
// parses. Automatically calculates count based on type size.
func FormatFromBytes(rawBytes []byte, tagType TagTypePrimitive, justFirst bool, byteOrder binary.ByteOrder) (phrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test
typeSize := tagType.Size()
if len(rawBytes)%typeSize != 0 {
log.Panicf("byte-count (%d) does not align for [%s] type with a size of (%d) bytes", len(rawBytes), TypeNames[tagType], typeSize)
}
// unitCount is the calculated unit-count. This should equal the original
// value from the tag (pre-resolution).
unitCount := uint32(len(rawBytes) / typeSize)
// Truncate the items if it's not bytes or a string and we just want the first.
var value interface{}
switch tagType {
case TypeByte:
var err error
value, err = parser.ParseBytes(rawBytes, unitCount)
log.PanicIf(err)
case TypeAscii:
var err error
value, err = parser.ParseAscii(rawBytes, unitCount)
log.PanicIf(err)
case TypeAsciiNoNul:
var err error
value, err = parser.ParseAsciiNoNul(rawBytes, unitCount)
log.PanicIf(err)
case TypeShort:
var err error
value, err = parser.ParseShorts(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeLong:
var err error
value, err = parser.ParseLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeFloat:
var err error
value, err = parser.ParseFloats(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeDouble:
var err error
value, err = parser.ParseDoubles(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeRational:
var err error
value, err = parser.ParseRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeSignedLong:
var err error
value, err = parser.ParseSignedLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
case TypeSignedRational:
var err error
value, err = parser.ParseSignedRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
default:
// Affects only "unknown" values, in general.
log.Panicf("value of type [%s] can not be formatted into string", tagType.String())
// Never called.
return "", nil
}
phrase, err = FormatFromType(value, justFirst)
log.PanicIf(err)
return phrase, nil
}
// TranslateStringToType converts user-provided strings to properly-typed
// values. If a string, returns a string. Else, assumes that it's a single
// number. If a list needs to be processed, it is the caller's responsibility to
// split it (according to whichever convention has been established).
func TranslateStringToType(tagType TagTypePrimitive, valueString string) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tagType == TypeUndefined {
// The caller should just call String() on the decoded type.
log.Panicf("undefined-type values are not supported")
}
if tagType == TypeByte {
wide, err := strconv.ParseInt(valueString, 16, 8)
log.PanicIf(err)
return byte(wide), nil
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
// Whether or not we're putting an NUL on the end is only relevant for
// byte-level encoding. This function really just supports a user
// interface.
return valueString, nil
} else if tagType == TypeShort {
n, err := strconv.ParseUint(valueString, 10, 16)
log.PanicIf(err)
return uint16(n), nil
} else if tagType == TypeLong {
n, err := strconv.ParseUint(valueString, 10, 32)
log.PanicIf(err)
return uint32(n), nil
} else if tagType == TypeRational {
parts := strings.SplitN(valueString, "/", 2)
numerator, err := strconv.ParseUint(parts[0], 10, 32)
log.PanicIf(err)
denominator, err := strconv.ParseUint(parts[1], 10, 32)
log.PanicIf(err)
return Rational{
Numerator: uint32(numerator),
Denominator: uint32(denominator),
}, nil
} else if tagType == TypeSignedLong {
n, err := strconv.ParseInt(valueString, 10, 32)
log.PanicIf(err)
return int32(n), nil
} else if tagType == TypeFloat {
n, err := strconv.ParseFloat(valueString, 32)
log.PanicIf(err)
return float32(n), nil
} else if tagType == TypeDouble {
n, err := strconv.ParseFloat(valueString, 64)
log.PanicIf(err)
return float64(n), nil
} else if tagType == TypeSignedRational {
parts := strings.SplitN(valueString, "/", 2)
numerator, err := strconv.ParseInt(parts[0], 10, 32)
log.PanicIf(err)
denominator, err := strconv.ParseInt(parts[1], 10, 32)
log.PanicIf(err)
return SignedRational{
Numerator: int32(numerator),
Denominator: int32(denominator),
}, nil
}
log.Panicf("from-string encoding for type not supported; this shouldn't happen: [%s]", tagType.String())
return nil, nil
}
// GetTypeByName returns the `TagTypePrimitive` for the given type name.
// Returns (0) if not valid.
func GetTypeByName(typeName string) (tagType TagTypePrimitive, found bool) {
tagType, found = typeNamesR[typeName]
return tagType, found
}
// BasicTag describes a single tag for any purpose.
type BasicTag struct {
// FqIfdPath is the fully-qualified IFD-path.
FqIfdPath string
// IfdPath is the unindexed IFD-path.
IfdPath string
// TagId is the tag-ID.
TagId uint16
}
func init() {
for typeId, typeName := range TypeNames {
typeNamesR[typeName] = typeId
}
}