Permalink
Fetching contributors…
Cannot retrieve contributors at this time
544 lines (479 sloc) 16 KB
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
// Revel Framework source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.
package revel
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"os"
"reflect"
"strconv"
"strings"
"time"
)
// A Binder translates between string parameters and Go data structures.
type Binder struct {
// Bind takes the name and type of the desired parameter and constructs it
// from one or more values from Params.
//
// Example
//
// Request:
// url?id=123&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=rob
//
// Action:
// Example.Action(id int, ol []int, ul []string, user User)
//
// Calls:
// Bind(params, "id", int): 123
// Bind(params, "ol", []int): {1, 2}
// Bind(params, "ul", []string): {"str", "array"}
// Bind(params, "user", User): User{Name:"rob"}
//
// Note that only exported struct fields may be bound.
Bind func(params *Params, name string, typ reflect.Type) reflect.Value
// Unbind serializes a given value to one or more URL parameters of the given
// name.
Unbind func(output map[string]string, name string, val interface{})
}
var binderLog = RevelLog.New("section", "binder")
// ValueBinder is adapter for easily making one-key-value binders.
func ValueBinder(f func(value string, typ reflect.Type) reflect.Value) func(*Params, string, reflect.Type) reflect.Value {
return func(params *Params, name string, typ reflect.Type) reflect.Value {
vals, ok := params.Values[name]
if !ok || len(vals) == 0 {
return reflect.Zero(typ)
}
return f(vals[0], typ)
}
}
// Revel's default date and time constants
const (
DefaultDateFormat = "2006-01-02"
DefaultDateTimeFormat = "2006-01-02 15:04"
)
// Binders type and kind definition
var (
// These are the lookups to find a Binder for any type of data.
// The most specific binder found will be used (Type before Kind)
TypeBinders = make(map[reflect.Type]Binder)
KindBinders = make(map[reflect.Kind]Binder)
// Applications can add custom time formats to this array, and they will be
// automatically attempted when binding a time.Time.
TimeFormats = []string{}
DateFormat string
DateTimeFormat string
TimeZone = time.UTC
IntBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
if len(val) == 0 {
return reflect.Zero(typ)
}
intValue, err := strconv.ParseInt(val, 10, 64)
if err != nil {
binderLog.Warn("IntBinder Conversion Error", "error", err)
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetInt(intValue)
return pValue.Elem()
}),
Unbind: func(output map[string]string, key string, val interface{}) {
output[key] = fmt.Sprintf("%d", val)
},
}
UintBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
if len(val) == 0 {
return reflect.Zero(typ)
}
uintValue, err := strconv.ParseUint(val, 10, 64)
if err != nil {
binderLog.Warn("UintBinder Conversion Error", "error", err)
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetUint(uintValue)
return pValue.Elem()
}),
Unbind: func(output map[string]string, key string, val interface{}) {
output[key] = fmt.Sprintf("%d", val)
},
}
FloatBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
if len(val) == 0 {
return reflect.Zero(typ)
}
floatValue, err := strconv.ParseFloat(val, 64)
if err != nil {
binderLog.Warn("FloatBinder Conversion Error", "error", err)
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetFloat(floatValue)
return pValue.Elem()
}),
Unbind: func(output map[string]string, key string, val interface{}) {
output[key] = fmt.Sprintf("%f", val)
},
}
StringBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
return reflect.ValueOf(val)
}),
Unbind: func(output map[string]string, name string, val interface{}) {
output[name] = val.(string)
},
}
// Booleans support a various value formats,
// refer `revel.Atob` method.
BoolBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
return reflect.ValueOf(Atob(val))
}),
Unbind: func(output map[string]string, name string, val interface{}) {
output[name] = fmt.Sprintf("%t", val)
},
}
PointerBinder = Binder{
Bind: func(params *Params, name string, typ reflect.Type) reflect.Value {
v := Bind(params, name, typ.Elem())
if v.CanAddr() {
return v.Addr()
}
return v
},
Unbind: func(output map[string]string, name string, val interface{}) {
Unbind(output, name, reflect.ValueOf(val).Elem().Interface())
},
}
TimeBinder = Binder{
Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value {
for _, f := range TimeFormats {
if r, err := time.ParseInLocation(f, val, TimeZone); err == nil {
return reflect.ValueOf(r)
}
}
return reflect.Zero(typ)
}),
Unbind: func(output map[string]string, name string, val interface{}) {
var (
t = val.(time.Time)
format = DateTimeFormat
h, m, s = t.Clock()
)
if h == 0 && m == 0 && s == 0 {
format = DateFormat
}
output[name] = t.Format(format)
},
}
MapBinder = Binder{
Bind: bindMap,
Unbind: unbindMap,
}
)
// Used to keep track of the index for individual keyvalues.
type sliceValue struct {
index int // Index extracted from brackets. If -1, no index was provided.
value reflect.Value // the bound value for this slice element.
}
// This function creates a slice of the given type, Binds each of the individual
// elements, and then sets them to their appropriate location in the slice.
// If elements are provided without an explicit index, they are added (in
// unspecified order) to the end of the slice.
func bindSlice(params *Params, name string, typ reflect.Type) reflect.Value {
// Collect an array of slice elements with their indexes (and the max index).
maxIndex := -1
numNoIndex := 0
sliceValues := []sliceValue{}
// Factor out the common slice logic (between form values and files).
processElement := func(key string, vals []string, files []*multipart.FileHeader) {
if !strings.HasPrefix(key, name+"[") {
return
}
// Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey)
index := -1
leftBracket, rightBracket := len(name), strings.Index(key[len(name):], "]")+len(name)
if rightBracket > leftBracket+1 {
index, _ = strconv.Atoi(key[leftBracket+1 : rightBracket])
}
subKeyIndex := rightBracket + 1
// Handle the indexed case.
if index > -1 {
if index > maxIndex {
maxIndex = index
}
sliceValues = append(sliceValues, sliceValue{
index: index,
value: Bind(params, key[:subKeyIndex], typ.Elem()),
})
return
}
// It's an un-indexed element. (e.g. element[])
numNoIndex += len(vals) + len(files)
for _, val := range vals {
// Unindexed values can only be direct-bound.
sliceValues = append(sliceValues, sliceValue{
index: -1,
value: BindValue(val, typ.Elem()),
})
}
for _, fileHeader := range files {
sliceValues = append(sliceValues, sliceValue{
index: -1,
value: BindFile(fileHeader, typ.Elem()),
})
}
}
for key, vals := range params.Values {
processElement(key, vals, nil)
}
for key, fileHeaders := range params.Files {
processElement(key, nil, fileHeaders)
}
resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex)
for _, sv := range sliceValues {
if sv.index != -1 {
resultArray.Index(sv.index).Set(sv.value)
} else {
resultArray = reflect.Append(resultArray, sv.value)
}
}
return resultArray
}
// Break on dots and brackets.
// e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar"
func nextKey(key string) string {
fieldLen := strings.IndexAny(key, ".[")
if fieldLen == -1 {
return key
}
return key[:fieldLen]
}
func unbindSlice(output map[string]string, name string, val interface{}) {
v := reflect.ValueOf(val)
for i := 0; i < v.Len(); i++ {
Unbind(output, fmt.Sprintf("%s[%d]", name, i), v.Index(i).Interface())
}
}
func bindStruct(params *Params, name string, typ reflect.Type) reflect.Value {
resultPointer := reflect.New(typ)
result := resultPointer.Elem()
if params.JSON != nil {
// Try to inject the response as a json into the created result
if err := json.Unmarshal(params.JSON, resultPointer.Interface()); err != nil {
binderLog.Error("bindStruct Unable to unmarshal request", "name", name, "error", err, "data", string(params.JSON))
}
return result
}
fieldValues := make(map[string]reflect.Value)
for key := range params.Values {
if !strings.HasPrefix(key, name+".") {
continue
}
// Get the name of the struct property.
// Strip off the prefix. e.g. foo.bar.baz => bar.baz
suffix := key[len(name)+1:]
fieldName := nextKey(suffix)
fieldLen := len(fieldName)
if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it.
fieldValue := result.FieldByName(fieldName)
if !fieldValue.IsValid() {
binderLog.Warn("bindStruct Field not found", "name", fieldName)
continue
}
if !fieldValue.CanSet() {
binderLog.Warn("bindStruct Field not settable", "name", fieldName)
continue
}
boundVal := Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type())
fieldValue.Set(boundVal)
fieldValues[fieldName] = boundVal
}
}
return result
}
func unbindStruct(output map[string]string, name string, iface interface{}) {
val := reflect.ValueOf(iface)
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
structField := typ.Field(i)
fieldValue := val.Field(i)
// PkgPath is specified to be empty exactly for exported fields.
if structField.PkgPath == "" {
Unbind(output, fmt.Sprintf("%s.%s", name, structField.Name), fieldValue.Interface())
}
}
}
// Helper that returns an upload of the given name, or nil.
func getMultipartFile(params *Params, name string) multipart.File {
for _, fileHeader := range params.Files[name] {
file, err := fileHeader.Open()
if err == nil {
return file
}
binderLog.Warn("getMultipartFile: Failed to open uploaded file", "name", name, "error", err)
}
return nil
}
func bindFile(params *Params, name string, typ reflect.Type) reflect.Value {
reader := getMultipartFile(params, name)
if reader == nil {
return reflect.Zero(typ)
}
// If it's already stored in a temp file, just return that.
if osFile, ok := reader.(*os.File); ok {
return reflect.ValueOf(osFile)
}
// Otherwise, have to store it.
tmpFile, err := ioutil.TempFile("", "revel-upload")
if err != nil {
binderLog.Warn("bindFile: Failed to create a temp file to store upload", "name", name, "error", err)
return reflect.Zero(typ)
}
// Register it to be deleted after the request is done.
params.tmpFiles = append(params.tmpFiles, tmpFile)
_, err = io.Copy(tmpFile, reader)
if err != nil {
binderLog.Warn("bindFile: Failed to copy upload to temp file", "name", name, "error", err)
return reflect.Zero(typ)
}
_, err = tmpFile.Seek(0, 0)
if err != nil {
binderLog.Warn("bindFile: Failed to seek to beginning of temp file", "name", name, "error", err)
return reflect.Zero(typ)
}
return reflect.ValueOf(tmpFile)
}
func bindByteArray(params *Params, name string, typ reflect.Type) reflect.Value {
if reader := getMultipartFile(params, name); reader != nil {
b, err := ioutil.ReadAll(reader)
if err == nil {
return reflect.ValueOf(b)
}
binderLog.Warn("bindByteArray: Error reading uploaded file contents", "name", name, "error", err)
}
return reflect.Zero(typ)
}
func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value {
if reader := getMultipartFile(params, name); reader != nil {
return reflect.ValueOf(reader.(io.ReadSeeker))
}
return reflect.Zero(typ)
}
// bindMap converts parameters using map syntax into the corresponding map. e.g.:
// params["a[5]"]=foo, name="a", typ=map[int]string => map[int]string{5: "foo"}
func bindMap(params *Params, name string, typ reflect.Type) reflect.Value {
var (
keyType = typ.Key()
valueType = typ.Elem()
resultPtr = reflect.New(reflect.MapOf(keyType, valueType))
result = resultPtr.Elem()
)
result.Set(reflect.MakeMap(typ))
if params.JSON != nil {
// Try to inject the response as a json into the created result
if err := json.Unmarshal(params.JSON, resultPtr.Interface()); err != nil {
binderLog.Warn("bindMap: Unable to unmarshal request", "name", name, "error", err)
}
return result
}
for paramName, _ := range params.Values {
// The paramName string must start with the value in the "name" parameter,
// otherwise there is no way the parameter is part of the map
if !strings.HasPrefix(paramName, name) {
continue
}
suffix := paramName[len(name)+1:]
fieldName := nextKey(suffix)
if fieldName != "" {
fieldName = fieldName[:len(fieldName)-1]
}
if !strings.HasPrefix(paramName, name+"["+fieldName+"]") {
continue
}
result.SetMapIndex(BindValue(fieldName, keyType), Bind(params, name+"["+fieldName+"]", valueType))
}
return result
}
func unbindMap(output map[string]string, name string, iface interface{}) {
mapValue := reflect.ValueOf(iface)
for _, key := range mapValue.MapKeys() {
Unbind(output, name+"["+fmt.Sprintf("%v", key.Interface())+"]",
mapValue.MapIndex(key).Interface())
}
}
// Bind takes the name and type of the desired parameter and constructs it
// from one or more values from Params.
// Returns the zero value of the type upon any sort of failure.
func Bind(params *Params, name string, typ reflect.Type) reflect.Value {
if binder, found := binderForType(typ); found {
return binder.Bind(params, name, typ)
}
return reflect.Zero(typ)
}
func BindValue(val string, typ reflect.Type) reflect.Value {
return Bind(&Params{Values: map[string][]string{"": {val}}}, "", typ)
}
func BindFile(fileHeader *multipart.FileHeader, typ reflect.Type) reflect.Value {
return Bind(&Params{Files: map[string][]*multipart.FileHeader{"": {fileHeader}}}, "", typ)
}
func Unbind(output map[string]string, name string, val interface{}) {
if binder, found := binderForType(reflect.TypeOf(val)); found {
if binder.Unbind != nil {
binder.Unbind(output, name, val)
} else {
binderLog.Error("Unbind: Unable to unmarshal request", "name", name, "value", val)
}
}
}
func binderForType(typ reflect.Type) (Binder, bool) {
binder, ok := TypeBinders[typ]
if !ok {
binder, ok = KindBinders[typ.Kind()]
if !ok {
binderLog.Error("binderForType: no binder for type", "type", typ)
return Binder{}, false
}
}
return binder, true
}
// Sadly, the binder lookups can not be declared initialized -- that results in
// an "initialization loop" compile error.
func init() {
KindBinders[reflect.Int] = IntBinder
KindBinders[reflect.Int8] = IntBinder
KindBinders[reflect.Int16] = IntBinder
KindBinders[reflect.Int32] = IntBinder
KindBinders[reflect.Int64] = IntBinder
KindBinders[reflect.Uint] = UintBinder
KindBinders[reflect.Uint8] = UintBinder
KindBinders[reflect.Uint16] = UintBinder
KindBinders[reflect.Uint32] = UintBinder
KindBinders[reflect.Uint64] = UintBinder
KindBinders[reflect.Float32] = FloatBinder
KindBinders[reflect.Float64] = FloatBinder
KindBinders[reflect.String] = StringBinder
KindBinders[reflect.Bool] = BoolBinder
KindBinders[reflect.Slice] = Binder{bindSlice, unbindSlice}
KindBinders[reflect.Struct] = Binder{bindStruct, unbindStruct}
KindBinders[reflect.Ptr] = PointerBinder
KindBinders[reflect.Map] = MapBinder
TypeBinders[reflect.TypeOf(time.Time{})] = TimeBinder
// Uploads
TypeBinders[reflect.TypeOf(&os.File{})] = Binder{bindFile, nil}
TypeBinders[reflect.TypeOf([]byte{})] = Binder{bindByteArray, nil}
TypeBinders[reflect.TypeOf((*io.Reader)(nil)).Elem()] = Binder{bindReadSeeker, nil}
TypeBinders[reflect.TypeOf((*io.ReadSeeker)(nil)).Elem()] = Binder{bindReadSeeker, nil}
OnAppStart(func() {
DateTimeFormat = Config.StringDefault("format.datetime", DefaultDateTimeFormat)
DateFormat = Config.StringDefault("format.date", DefaultDateFormat)
TimeFormats = append(TimeFormats, DateTimeFormat, DateFormat)
})
}