2014-04-13 01:57:42 -04:00
// Copyright 2013 The Martini Contrib Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-05-05 02:42:52 -04:00
package binding
2014-04-13 01:57:42 -04:00
import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"regexp"
"strconv"
"strings"
"unicode/utf8"
2014-07-26 00:24:27 -04:00
"github.com/Unknwon/macaron"
"github.com/macaron-contrib/i18n"
2014-04-13 01:57:42 -04:00
)
/ *
To the land of Middle - ware Earth :
One func to rule them all ,
One func to find them ,
One func to bring them all ,
And in this package BIND them .
* /
// Bind accepts a copy of an empty struct and populates it with
// values from the request (if deserialization is successful). It
// wraps up the functionality of the Form and Json middleware
// according to the Content-Type of the request, and it guesses
// if no Content-Type is specified. Bind invokes the ErrorHandler
// middleware to bail out if errors occurred. If you want to perform
// your own error handling, use Form or Json middleware directly.
// An interface pointer can be added as a second argument in order
// to map the struct to a specific interface.
2014-07-26 00:24:27 -04:00
func Bind ( obj interface { } , ifacePtr ... interface { } ) macaron . Handler {
return func ( ctx * macaron . Context ) {
contentType := ctx . Req . Header . Get ( "Content-Type" )
2014-04-13 01:57:42 -04:00
if strings . Contains ( contentType , "form-urlencoded" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Form ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else if strings . Contains ( contentType , "multipart/form-data" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( MultipartForm ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else if strings . Contains ( contentType , "json" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Json ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Json ( obj , ifacePtr ... ) )
if getErrors ( ctx ) . Count ( ) > 0 {
ctx . Invoke ( Form ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
}
}
2014-07-26 00:24:27 -04:00
ctx . Invoke ( ErrorHandler )
2014-04-13 01:57:42 -04:00
}
}
// BindIgnErr will do the exactly same thing as Bind but without any
// error handling, which user has freedom to deal with them.
// This allows user take advantages of validation.
2014-07-26 00:24:27 -04:00
func BindIgnErr ( obj interface { } , ifacePtr ... interface { } ) macaron . Handler {
return func ( ctx * macaron . Context , req * http . Request ) {
2014-04-13 01:57:42 -04:00
contentType := req . Header . Get ( "Content-Type" )
if strings . Contains ( contentType , "form-urlencoded" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Form ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else if strings . Contains ( contentType , "multipart/form-data" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( MultipartForm ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else if strings . Contains ( contentType , "json" ) {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Json ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
} else {
2014-07-26 00:24:27 -04:00
ctx . Invoke ( Json ( obj , ifacePtr ... ) )
if getErrors ( ctx ) . Count ( ) > 0 {
ctx . Invoke ( Form ( obj , ifacePtr ... ) )
2014-04-13 01:57:42 -04:00
}
}
}
}
// Form is middleware to deserialize form-urlencoded data from the request.
// It gets data from the form-urlencoded body, if present, or from the
// query string. It uses the http.Request.ParseForm() method
// to perform deserialization, then reflection is used to map each field
// into the struct with the proper type. Structs with primitive slice types
// (bool, float, int, string) can support deserialization of repeated form
// keys, for example: key=val1&key=val2&key=val3
// An interface pointer can be added as a second argument in order
// to map the struct to a specific interface.
2014-07-26 00:24:27 -04:00
func Form ( formStruct interface { } , ifacePtr ... interface { } ) macaron . Handler {
return func ( ctx * macaron . Context ) {
2014-04-13 01:57:42 -04:00
ensureNotPointer ( formStruct )
formStruct := reflect . New ( reflect . TypeOf ( formStruct ) )
errors := newErrors ( )
2014-07-26 00:24:27 -04:00
parseErr := ctx . Req . ParseForm ( )
2014-04-13 01:57:42 -04:00
// Format validation of the request body or the URL would add considerable overhead,
// and ParseForm does not complain when URL encoding is off.
// Because an empty request body or url can also mean absence of all needed values,
// it is not in all cases a bad request, so let's return 422.
if parseErr != nil {
2014-05-05 02:42:52 -04:00
errors . Overall [ BindingDeserializationError ] = parseErr . Error ( )
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
mapForm ( formStruct , ctx . Req . Form , errors )
2014-04-13 01:57:42 -04:00
2014-07-26 00:24:27 -04:00
validateAndMap ( formStruct , ctx , errors , ifacePtr ... )
2014-04-13 01:57:42 -04:00
}
}
2014-07-26 00:24:27 -04:00
func MultipartForm ( formStruct interface { } , ifacePtr ... interface { } ) macaron . Handler {
return func ( ctx * macaron . Context ) {
2014-04-13 01:57:42 -04:00
ensureNotPointer ( formStruct )
formStruct := reflect . New ( reflect . TypeOf ( formStruct ) )
errors := newErrors ( )
// Workaround for multipart forms returning nil instead of an error
// when content is not multipart
// https://code.google.com/p/go/issues/detail?id=6334
2014-07-26 00:24:27 -04:00
multipartReader , err := ctx . Req . MultipartReader ( )
2014-04-13 01:57:42 -04:00
if err != nil {
2014-05-05 02:42:52 -04:00
errors . Overall [ BindingDeserializationError ] = err . Error ( )
2014-04-13 01:57:42 -04:00
} else {
form , parseErr := multipartReader . ReadForm ( MaxMemory )
if parseErr != nil {
2014-05-05 02:42:52 -04:00
errors . Overall [ BindingDeserializationError ] = parseErr . Error ( )
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
ctx . Req . MultipartForm = form
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
mapForm ( formStruct , ctx . Req . MultipartForm . Value , errors )
2014-04-13 01:57:42 -04:00
2014-07-26 00:24:27 -04:00
validateAndMap ( formStruct , ctx , errors , ifacePtr ... )
2014-04-13 01:57:42 -04:00
}
}
// Json is middleware to deserialize a JSON payload from the request
// into the struct that is passed in. The resulting struct is then
// validated, but no error handling is actually performed here.
// An interface pointer can be added as a second argument in order
// to map the struct to a specific interface.
2014-07-26 00:24:27 -04:00
func Json ( jsonStruct interface { } , ifacePtr ... interface { } ) macaron . Handler {
return func ( ctx * macaron . Context ) {
2014-04-13 01:57:42 -04:00
ensureNotPointer ( jsonStruct )
jsonStruct := reflect . New ( reflect . TypeOf ( jsonStruct ) )
errors := newErrors ( )
2014-07-26 00:24:27 -04:00
if ctx . Req . Body != nil {
defer ctx . Req . Body . Close ( )
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
if err := json . NewDecoder ( ctx . Req . Body ) . Decode ( jsonStruct . Interface ( ) ) ; err != nil && err != io . EOF {
2014-05-05 02:42:52 -04:00
errors . Overall [ BindingDeserializationError ] = err . Error ( )
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
validateAndMap ( jsonStruct , ctx , errors , ifacePtr ... )
2014-04-13 01:57:42 -04:00
}
}
// Validate is middleware to enforce required fields. If the struct
// passed in is a Validator, then the user-defined Validate method
// is executed, and its errors are mapped to the context. This middleware
// performs no error handling: it merely detects them and maps them.
2014-07-26 00:24:27 -04:00
func Validate ( obj interface { } ) macaron . Handler {
return func ( ctx * macaron . Context , l i18n . Locale ) {
2014-04-13 01:57:42 -04:00
errors := newErrors ( )
validateStruct ( errors , obj )
if validator , ok := obj . ( Validator ) ; ok {
2014-07-26 00:24:27 -04:00
validator . Validate ( ctx , errors , l )
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
ctx . Map ( * errors )
2014-04-13 01:57:42 -04:00
}
}
var (
2014-05-01 07:35:21 -04:00
alphaDashPattern = regexp . MustCompile ( "[^\\d\\w-_]" )
alphaDashDotPattern = regexp . MustCompile ( "[^\\d\\w-_\\.]" )
emailPattern = regexp . MustCompile ( "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?" )
urlPattern = regexp . MustCompile ( ` (http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])? ` )
2014-04-13 01:57:42 -04:00
)
2014-05-08 22:12:05 -04:00
func validateStruct ( errors * Errors , obj interface { } ) {
2014-04-13 01:57:42 -04:00
typ := reflect . TypeOf ( obj )
val := reflect . ValueOf ( obj )
if typ . Kind ( ) == reflect . Ptr {
typ = typ . Elem ( )
val = val . Elem ( )
}
for i := 0 ; i < typ . NumField ( ) ; i ++ {
field := typ . Field ( i )
// Allow ignored fields in the struct
if field . Tag . Get ( "form" ) == "-" {
continue
}
fieldValue := val . Field ( i ) . Interface ( )
if field . Type . Kind ( ) == reflect . Struct {
validateStruct ( errors , fieldValue )
continue
}
zero := reflect . Zero ( field . Type ) . Interface ( )
// Match rules.
for _ , rule := range strings . Split ( field . Tag . Get ( "binding" ) , ";" ) {
if len ( rule ) == 0 {
continue
}
switch {
case rule == "Required" :
if reflect . DeepEqual ( zero , fieldValue ) {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingRequireError
2014-04-13 01:57:42 -04:00
break
}
case rule == "AlphaDash" :
if alphaDashPattern . MatchString ( fmt . Sprintf ( "%v" , fieldValue ) ) {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingAlphaDashError
2014-04-13 01:57:42 -04:00
break
}
2014-05-01 07:35:21 -04:00
case rule == "AlphaDashDot" :
if alphaDashDotPattern . MatchString ( fmt . Sprintf ( "%v" , fieldValue ) ) {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingAlphaDashDotError
2014-05-01 07:35:21 -04:00
break
}
2014-04-13 01:57:42 -04:00
case strings . HasPrefix ( rule , "MinSize(" ) :
min , err := strconv . Atoi ( rule [ 8 : len ( rule ) - 1 ] )
if err != nil {
errors . Overall [ "MinSize" ] = err . Error ( )
break
}
if str , ok := fieldValue . ( string ) ; ok && utf8 . RuneCountInString ( str ) < min {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingMinSizeError
2014-04-13 01:57:42 -04:00
break
}
v := reflect . ValueOf ( fieldValue )
if v . Kind ( ) == reflect . Slice && v . Len ( ) < min {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingMinSizeError
2014-04-13 01:57:42 -04:00
break
}
case strings . HasPrefix ( rule , "MaxSize(" ) :
max , err := strconv . Atoi ( rule [ 8 : len ( rule ) - 1 ] )
if err != nil {
errors . Overall [ "MaxSize" ] = err . Error ( )
break
}
if str , ok := fieldValue . ( string ) ; ok && utf8 . RuneCountInString ( str ) > max {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingMaxSizeError
2014-04-13 01:57:42 -04:00
break
}
v := reflect . ValueOf ( fieldValue )
if v . Kind ( ) == reflect . Slice && v . Len ( ) > max {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingMinSizeError
2014-04-13 01:57:42 -04:00
break
}
case rule == "Email" :
if ! emailPattern . MatchString ( fmt . Sprintf ( "%v" , fieldValue ) ) {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingEmailError
2014-04-13 01:57:42 -04:00
break
}
case rule == "Url" :
2014-05-05 19:58:13 -04:00
str := fmt . Sprintf ( "%v" , fieldValue )
if len ( str ) == 0 {
continue
} else if ! urlPattern . MatchString ( str ) {
2014-05-05 02:42:52 -04:00
errors . Fields [ field . Name ] = BindingUrlError
2014-04-13 01:57:42 -04:00
break
}
}
}
}
}
2014-05-08 22:12:05 -04:00
func mapForm ( formStruct reflect . Value , form map [ string ] [ ] string , errors * Errors ) {
2014-04-13 01:57:42 -04:00
typ := formStruct . Elem ( ) . Type ( )
for i := 0 ; i < typ . NumField ( ) ; i ++ {
typeField := typ . Field ( i )
if inputFieldName := typeField . Tag . Get ( "form" ) ; inputFieldName != "" {
structField := formStruct . Elem ( ) . Field ( i )
if ! structField . CanSet ( ) {
continue
}
inputValue , exists := form [ inputFieldName ]
if ! exists {
continue
}
numElems := len ( inputValue )
if structField . Kind ( ) == reflect . Slice && numElems > 0 {
sliceOf := structField . Type ( ) . Elem ( ) . Kind ( )
slice := reflect . MakeSlice ( structField . Type ( ) , numElems , numElems )
for i := 0 ; i < numElems ; i ++ {
setWithProperType ( sliceOf , inputValue [ i ] , slice . Index ( i ) , inputFieldName , errors )
}
formStruct . Elem ( ) . Field ( i ) . Set ( slice )
} else {
setWithProperType ( typeField . Type . Kind ( ) , inputValue [ 0 ] , structField , inputFieldName , errors )
}
}
}
}
// ErrorHandler simply counts the number of errors in the
// context and, if more than 0, writes a 400 Bad Request
// response and a JSON payload describing the errors with
// the "Content-Type" set to "application/json".
// Middleware remaining on the stack will not even see the request
// if, by this point, there are any errors.
// This is a "default" handler, of sorts, and you are
// welcome to use your own instead. The Bind middleware
// invokes this automatically for convenience.
2014-05-08 22:12:05 -04:00
func ErrorHandler ( errs Errors , resp http . ResponseWriter ) {
2014-04-13 01:57:42 -04:00
if errs . Count ( ) > 0 {
resp . Header ( ) . Set ( "Content-Type" , "application/json; charset=utf-8" )
2014-05-05 02:42:52 -04:00
if _ , ok := errs . Overall [ BindingDeserializationError ] ; ok {
2014-04-13 01:57:42 -04:00
resp . WriteHeader ( http . StatusBadRequest )
} else {
resp . WriteHeader ( 422 )
}
errOutput , _ := json . Marshal ( errs )
resp . Write ( errOutput )
return
}
}
// This sets the value in a struct of an indeterminate type to the
// matching value from the request (via Form middleware) in the
// same type, so that not all deserialized values have to be strings.
// Supported types are string, int, float, and bool.
2014-05-08 22:12:05 -04:00
func setWithProperType ( valueKind reflect . Kind , val string , structField reflect . Value , nameInTag string , errors * Errors ) {
2014-04-13 01:57:42 -04:00
switch valueKind {
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
if val == "" {
val = "0"
}
intVal , err := strconv . ParseInt ( val , 10 , 64 )
if err != nil {
2014-05-05 02:42:52 -04:00
errors . Fields [ nameInTag ] = BindingIntegerTypeError
2014-04-13 01:57:42 -04:00
} else {
structField . SetInt ( intVal )
}
case reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 :
if val == "" {
val = "0"
}
uintVal , err := strconv . ParseUint ( val , 10 , 64 )
if err != nil {
2014-05-05 02:42:52 -04:00
errors . Fields [ nameInTag ] = BindingIntegerTypeError
2014-04-13 01:57:42 -04:00
} else {
structField . SetUint ( uintVal )
}
case reflect . Bool :
structField . SetBool ( val == "on" )
case reflect . Float32 :
if val == "" {
val = "0.0"
}
floatVal , err := strconv . ParseFloat ( val , 32 )
if err != nil {
2014-05-05 02:42:52 -04:00
errors . Fields [ nameInTag ] = BindingFloatTypeError
2014-04-13 01:57:42 -04:00
} else {
structField . SetFloat ( floatVal )
}
case reflect . Float64 :
if val == "" {
val = "0.0"
}
floatVal , err := strconv . ParseFloat ( val , 64 )
if err != nil {
2014-05-05 02:42:52 -04:00
errors . Fields [ nameInTag ] = BindingFloatTypeError
2014-04-13 01:57:42 -04:00
} else {
structField . SetFloat ( floatVal )
}
case reflect . String :
structField . SetString ( val )
}
}
2014-07-26 00:24:27 -04:00
// Don't pass in pointers to bind to. Can lead to bugs.
2014-04-13 01:57:42 -04:00
func ensureNotPointer ( obj interface { } ) {
if reflect . TypeOf ( obj ) . Kind ( ) == reflect . Ptr {
panic ( "Pointers are not accepted as binding models" )
}
}
// Performs validation and combines errors from validation
// with errors from deserialization, then maps both the
// resulting struct and the errors to the context.
2014-07-26 00:24:27 -04:00
func validateAndMap ( obj reflect . Value , ctx * macaron . Context , errors * Errors , ifacePtr ... interface { } ) {
ctx . Invoke ( Validate ( obj . Interface ( ) ) )
errors . Combine ( getErrors ( ctx ) )
ctx . Map ( * errors )
ctx . Map ( obj . Elem ( ) . Interface ( ) )
2014-04-13 01:57:42 -04:00
if len ( ifacePtr ) > 0 {
2014-07-26 00:24:27 -04:00
ctx . MapTo ( obj . Elem ( ) . Interface ( ) , ifacePtr [ 0 ] )
2014-04-13 01:57:42 -04:00
}
}
2014-05-08 22:12:05 -04:00
func newErrors ( ) * Errors {
return & Errors { make ( map [ string ] string ) , make ( map [ string ] string ) }
2014-04-13 01:57:42 -04:00
}
2014-07-26 00:24:27 -04:00
func getErrors ( ctx * macaron . Context ) Errors {
return ctx . GetVal ( reflect . TypeOf ( Errors { } ) ) . Interface ( ) . ( Errors )
2014-04-13 01:57:42 -04:00
}
type (
// Implement the Validator interface to define your own input
// validation before the request even gets to your application.
// The Validate method will be executed during the validation phase.
Validator interface {
2014-07-26 00:24:27 -04:00
Validate ( * macaron . Context , * Errors , i18n . Locale )
2014-04-13 01:57:42 -04:00
}
)
var (
// Maximum amount of memory to use when parsing a multipart form.
// Set this to whatever value you prefer; default is 10 MB.
MaxMemory = int64 ( 1024 * 1024 * 10 )
)
2014-05-05 02:42:52 -04:00
// Errors represents the contract of the response body when the
// binding step fails before getting to the application.
2014-05-08 22:12:05 -04:00
type Errors struct {
2014-05-05 02:42:52 -04:00
Overall map [ string ] string ` json:"overall" `
Fields map [ string ] string ` json:"fields" `
}
// Total errors is the sum of errors with the request overall
// and errors on individual fields.
2014-05-08 22:12:05 -04:00
func ( err Errors ) Count ( ) int {
2014-05-05 02:42:52 -04:00
return len ( err . Overall ) + len ( err . Fields )
}
2014-05-08 22:12:05 -04:00
func ( this * Errors ) Combine ( other Errors ) {
2014-05-05 02:42:52 -04:00
for key , val := range other . Fields {
if _ , exists := this . Fields [ key ] ; ! exists {
this . Fields [ key ] = val
}
}
for key , val := range other . Overall {
if _ , exists := this . Overall [ key ] ; ! exists {
this . Overall [ key ] = val
}
}
}
const (
BindingRequireError string = "Required"
BindingAlphaDashError string = "AlphaDash"
BindingAlphaDashDotError string = "AlphaDashDot"
BindingMinSizeError string = "MinSize"
BindingMaxSizeError string = "MaxSize"
BindingEmailError string = "Email"
BindingUrlError string = "Url"
BindingDeserializationError string = "DeserializationError"
BindingIntegerTypeError string = "IntegerTypeError"
BindingBooleanTypeError string = "BooleanTypeError"
BindingFloatTypeError string = "FloatTypeError"
)