gotosocial/vendor/github.com/dsoprea/go-exif/type.go
Tobi Smethurst 98263a7de6
Grand test fixup (#138)
* start fixing up tests

* fix up tests + automate with drone

* fiddle with linting

* messing about with drone.yml

* some more fiddling

* hmmm

* add cache

* add vendor directory

* verbose

* ci updates

* update some little things

* update sig
2021-08-12 21:03:24 +02:00

311 lines
7.9 KiB
Go

package exif
import (
"errors"
"fmt"
"strconv"
"strings"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
type TagTypePrimitive uint16
func (typeType TagTypePrimitive) String() string {
return TypeNames[typeType]
}
func (tagType TagTypePrimitive) Size() int {
if tagType == TypeByte {
return 1
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
return 1
} else if tagType == TypeShort {
return 2
} else if tagType == TypeLong {
return 4
} else if tagType == TypeRational {
return 8
} else if tagType == TypeSignedLong {
return 4
} else if tagType == TypeSignedRational {
return 8
} else {
log.Panicf("can not determine tag-value size for type (%d): [%s]", tagType, TypeNames[tagType])
// Never called.
return 0
}
}
const (
TypeByte TagTypePrimitive = 1
TypeAscii TagTypePrimitive = 2
TypeShort TagTypePrimitive = 3
TypeLong TagTypePrimitive = 4
TypeRational TagTypePrimitive = 5
TypeUndefined TagTypePrimitive = 7
TypeSignedLong TagTypePrimitive = 9
TypeSignedRational TagTypePrimitive = 10
// TypeAsciiNoNul is just a pseudo-type, for our own purposes.
TypeAsciiNoNul TagTypePrimitive = 0xf0
)
var (
typeLogger = log.NewLogger("exif.type")
)
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",
TypeAsciiNoNul: "_ASCII_NO_NUL",
}
TypeNamesR = map[string]TagTypePrimitive{}
)
var (
// ErrNotEnoughData is used when there isn't enough data to accomodate 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")
// ErrUnhandledUnknownTag 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).
ErrUnhandledUnknownTypedTag = errors.New("not a standard unknown-typed tag")
)
type Rational struct {
Numerator uint32
Denominator uint32
}
type SignedRational struct {
Numerator int32
Denominator int32
}
func TagTypeSize(tagType TagTypePrimitive) int {
// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly.
return tagType.Size()
}
// Format returns a stringified value for the given bytes. Automatically
// calculates count based on type size.
func Format(rawBytes []byte, tagType TagTypePrimitive, justFirst bool, byteOrder binary.ByteOrder) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add tests
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.
valueSuffix := ""
if justFirst == true && unitCount > 1 && tagType != TypeByte && tagType != TypeAscii && tagType != TypeAsciiNoNul {
unitCount = 1
valueSuffix = "..."
}
if tagType == TypeByte {
items, err := parser.ParseBytes(rawBytes, unitCount)
log.PanicIf(err)
return DumpBytesToString(items), nil
} else if tagType == TypeAscii {
phrase, err := parser.ParseAscii(rawBytes, unitCount)
log.PanicIf(err)
return phrase, nil
} else if tagType == TypeAsciiNoNul {
phrase, err := parser.ParseAsciiNoNul(rawBytes, unitCount)
log.PanicIf(err)
return phrase, nil
} else if tagType == TypeShort {
items, err := parser.ParseShorts(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
if justFirst == true {
return fmt.Sprintf("%v%s", items[0], valueSuffix), nil
} else {
return fmt.Sprintf("%v", items), nil
}
} else {
return "", nil
}
} else if tagType == TypeLong {
items, err := parser.ParseLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
if justFirst == true {
return fmt.Sprintf("%v%s", items[0], valueSuffix), nil
} else {
return fmt.Sprintf("%v", items), nil
}
} else {
return "", nil
}
} else if tagType == TypeRational {
items, err := parser.ParseRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
parts := make([]string, len(items))
for i, r := range items {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if justFirst == true {
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
} else {
return fmt.Sprintf("%v", parts), nil
}
} else {
return "", nil
}
} else if tagType == TypeSignedLong {
items, err := parser.ParseSignedLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
if justFirst == true {
return fmt.Sprintf("%v%s", items[0], valueSuffix), nil
} else {
return fmt.Sprintf("%v", items), nil
}
} else {
return "", nil
}
} else if tagType == TypeSignedRational {
items, err := parser.ParseSignedRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
parts := make([]string, len(items))
for i, r := range items {
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
}
if len(items) > 0 {
if justFirst == true {
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
} else {
return fmt.Sprintf("%v", parts), nil
}
} else {
return "", nil
}
} else {
// Affects only "unknown" values, in general.
log.Panicf("value of type [%s] can not be formatted into string", tagType.String())
// Never called.
return "", nil
}
}
func EncodeStringToBytes(tagType TagTypePrimitive, valueString string) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tagType == TypeUndefined {
// TODO(dustin): Circle back to this.
log.Panicf("undefined-type values are not supported")
}
if tagType == TypeByte {
return []byte(valueString), 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 == 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
}
func init() {
for typeId, typeName := range TypeNames {
TypeNamesR[typeName] = typeId
}
}