blob: 63b4b1d5906f57bfe679eb4bc50c268afc37dc87 [file] [log] [blame]
package plist
import (
"encoding"
"fmt"
"reflect"
"runtime"
"time"
)
type incompatibleDecodeTypeError struct {
dest reflect.Type
src string // type name (from cfValue)
}
func (u *incompatibleDecodeTypeError) Error() string {
return fmt.Sprintf("plist: type mismatch: tried to decode plist type `%v' into value of type `%v'", u.src, u.dest)
}
var (
plistUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
uidType = reflect.TypeOf(UID(0))
)
func isEmptyInterface(v reflect.Value) bool {
return v.Kind() == reflect.Interface && v.NumMethod() == 0
}
func (p *Decoder) unmarshalPlistInterface(pval cfValue, unmarshalable Unmarshaler) {
err := unmarshalable.UnmarshalPlist(func(i interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
if _, ok := r.(runtime.Error); ok {
panic(r)
}
err = r.(error)
}
}()
p.unmarshal(pval, reflect.ValueOf(i))
return
})
if err != nil {
panic(err)
}
}
func (p *Decoder) unmarshalTextInterface(pval cfString, unmarshalable encoding.TextUnmarshaler) {
err := unmarshalable.UnmarshalText([]byte(pval))
if err != nil {
panic(err)
}
}
func (p *Decoder) unmarshalTime(pval cfDate, val reflect.Value) {
val.Set(reflect.ValueOf(time.Time(pval)))
}
func (p *Decoder) unmarshalLaxString(s string, val reflect.Value) {
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i := mustParseInt(s, 10, 64)
val.SetInt(i)
return
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i := mustParseUint(s, 10, 64)
val.SetUint(i)
return
case reflect.Float32, reflect.Float64:
f := mustParseFloat(s, 64)
val.SetFloat(f)
return
case reflect.Bool:
b := mustParseBool(s)
val.SetBool(b)
return
case reflect.Struct:
if val.Type() == timeType {
t, err := time.Parse(textPlistTimeLayout, s)
if err != nil {
panic(err)
}
val.Set(reflect.ValueOf(t.In(time.UTC)))
return
}
fallthrough
default:
panic(&incompatibleDecodeTypeError{val.Type(), "string"})
}
}
func (p *Decoder) unmarshal(pval cfValue, val reflect.Value) {
if pval == nil {
return
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
val = val.Elem()
}
if isEmptyInterface(val) {
v := p.valueInterface(pval)
val.Set(reflect.ValueOf(v))
return
}
incompatibleTypeError := &incompatibleDecodeTypeError{val.Type(), pval.typeName()}
// time.Time implements TextMarshaler, but we need to parse it as RFC3339
if date, ok := pval.(cfDate); ok {
if val.Type() == timeType {
p.unmarshalTime(date, val)
return
}
panic(incompatibleTypeError)
}
if receiver, can := implementsInterface(val, plistUnmarshalerType); can {
p.unmarshalPlistInterface(pval, receiver.(Unmarshaler))
return
}
if val.Type() != timeType {
if receiver, can := implementsInterface(val, textUnmarshalerType); can {
if str, ok := pval.(cfString); ok {
p.unmarshalTextInterface(str, receiver.(encoding.TextUnmarshaler))
} else {
panic(incompatibleTypeError)
}
return
}
}
typ := val.Type()
switch pval := pval.(type) {
case cfString:
if val.Kind() == reflect.String {
val.SetString(string(pval))
return
}
if p.lax {
p.unmarshalLaxString(string(pval), val)
return
}
panic(incompatibleTypeError)
case *cfNumber:
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val.SetInt(int64(pval.value))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
val.SetUint(pval.value)
default:
panic(incompatibleTypeError)
}
case *cfReal:
if val.Kind() == reflect.Float32 || val.Kind() == reflect.Float64 {
// TODO: Consider warning on a downcast (storing a 64-bit value in a 32-bit reflect)
val.SetFloat(pval.value)
} else {
panic(incompatibleTypeError)
}
case cfBoolean:
if val.Kind() == reflect.Bool {
val.SetBool(bool(pval))
} else {
panic(incompatibleTypeError)
}
case cfData:
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
panic(incompatibleTypeError)
}
if typ.Elem().Kind() != reflect.Uint8 {
panic(incompatibleTypeError)
}
b := []byte(pval)
switch val.Kind() {
case reflect.Slice:
val.SetBytes(b)
case reflect.Array:
if val.Len() < len(b) {
panic(fmt.Errorf("plist: attempted to unmarshal %d bytes into a byte array of size %d", len(b), val.Len()))
}
sval := reflect.ValueOf(b)
reflect.Copy(val, sval)
}
case cfUID:
if val.Type() == uidType {
val.SetUint(uint64(pval))
} else {
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val.SetInt(int64(pval))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
val.SetUint(uint64(pval))
default:
panic(incompatibleTypeError)
}
}
case *cfArray:
p.unmarshalArray(pval, val)
case *cfDictionary:
p.unmarshalDictionary(pval, val)
}
}
func (p *Decoder) unmarshalArray(a *cfArray, val reflect.Value) {
var n int
if val.Kind() == reflect.Slice {
// Slice of element values.
// Grow slice.
cnt := len(a.values) + val.Len()
if cnt >= val.Cap() {
ncap := 2 * cnt
if ncap < 4 {
ncap = 4
}
new := reflect.MakeSlice(val.Type(), val.Len(), ncap)
reflect.Copy(new, val)
val.Set(new)
}
n = val.Len()
val.SetLen(cnt)
} else if val.Kind() == reflect.Array {
if len(a.values) > val.Cap() {
panic(fmt.Errorf("plist: attempted to unmarshal %d values into an array of size %d", len(a.values), val.Cap()))
}
} else {
panic(&incompatibleDecodeTypeError{val.Type(), a.typeName()})
}
// Recur to read element into slice.
for _, sval := range a.values {
p.unmarshal(sval, val.Index(n))
n++
}
return
}
func (p *Decoder) unmarshalDictionary(dict *cfDictionary, val reflect.Value) {
typ := val.Type()
switch val.Kind() {
case reflect.Struct:
tinfo, err := getTypeInfo(typ)
if err != nil {
panic(err)
}
entries := make(map[string]cfValue, len(dict.keys))
for i, k := range dict.keys {
sval := dict.values[i]
entries[k] = sval
}
for _, finfo := range tinfo.fields {
p.unmarshal(entries[finfo.name], finfo.value(val))
}
case reflect.Map:
if val.IsNil() {
val.Set(reflect.MakeMap(typ))
}
for i, k := range dict.keys {
sval := dict.values[i]
keyv := reflect.ValueOf(k).Convert(typ.Key())
mapElem := reflect.New(typ.Elem()).Elem()
p.unmarshal(sval, mapElem)
val.SetMapIndex(keyv, mapElem)
}
default:
panic(&incompatibleDecodeTypeError{typ, dict.typeName()})
}
}
/* *Interface is modelled after encoding/json */
func (p *Decoder) valueInterface(pval cfValue) interface{} {
switch pval := pval.(type) {
case cfString:
return string(pval)
case *cfNumber:
if pval.signed {
return int64(pval.value)
}
return pval.value
case *cfReal:
if pval.wide {
return pval.value
} else {
return float32(pval.value)
}
case cfBoolean:
return bool(pval)
case *cfArray:
return p.arrayInterface(pval)
case *cfDictionary:
return p.dictionaryInterface(pval)
case cfData:
return []byte(pval)
case cfDate:
return time.Time(pval)
case cfUID:
return UID(pval)
}
return nil
}
func (p *Decoder) arrayInterface(a *cfArray) []interface{} {
out := make([]interface{}, len(a.values))
for i, subv := range a.values {
out[i] = p.valueInterface(subv)
}
return out
}
func (p *Decoder) dictionaryInterface(dict *cfDictionary) map[string]interface{} {
out := make(map[string]interface{})
for i, k := range dict.keys {
subv := dict.values[i]
out[k] = p.valueInterface(subv)
}
return out
}