1// Copyright 2014 Martini Authors
2// Copyright 2014 The Macaron Authors
3//
4// Licensed under the Apache License, Version 2.0 (the "License"): you may
5// not use this file except in compliance with the License. You may obtain
6// a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16// Package binding is a middleware that provides request data binding and validation for Macaron.
17package binding
18
19import (
20	"encoding/json"
21	"fmt"
22	"io"
23	"mime/multipart"
24	"net/http"
25	"net/url"
26	"reflect"
27	"regexp"
28	"strconv"
29	"strings"
30	"unicode/utf8"
31
32	"gopkg.in/macaron.v1"
33)
34
35func bind(ctx *macaron.Context, obj interface{}, ifacePtr ...interface{}) {
36	contentType := ctx.Req.Header.Get("Content-Type")
37	if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
38		switch {
39		case strings.Contains(contentType, "form-urlencoded"):
40			_, _ = ctx.Invoke(bindForm(obj, ifacePtr...))
41		case strings.Contains(contentType, "multipart/form-data"):
42			_, _ = ctx.Invoke(bindMultipartForm(obj, ifacePtr...))
43		case strings.Contains(contentType, "json"):
44			_, _ = ctx.Invoke(bindJson(obj, ifacePtr...))
45		default:
46			var errors Errors
47			if contentType == "" {
48				errors.Add([]string{}, ERR_CONTENT_TYPE, "Empty Content-Type")
49			} else {
50				errors.Add([]string{}, ERR_CONTENT_TYPE, "Unsupported Content-Type")
51			}
52			ctx.Map(errors)
53			ctx.Map(obj) // Map a fake struct so handler won't panic.
54		}
55	} else {
56		_, _ = ctx.Invoke(bindForm(obj, ifacePtr...))
57	}
58}
59
60const (
61	_JSON_CONTENT_TYPE          = "application/json; charset=utf-8"
62	STATUS_UNPROCESSABLE_ENTITY = 422
63)
64
65// errorHandler simply counts the number of errors in the
66// context and, if more than 0, writes a response with an
67// error code and a JSON payload describing the errors.
68// The response will have a JSON content-type.
69// Middleware remaining on the stack will not even see the request
70// if, by this point, there are any errors.
71// This is a "default" handler, of sorts, and you are
72// welcome to use your own instead. The Bind middleware
73// invokes this automatically for convenience.
74func errorHandler(errs Errors, rw http.ResponseWriter) {
75	if len(errs) > 0 {
76		rw.Header().Set("Content-Type", _JSON_CONTENT_TYPE)
77		if errs.Has(ERR_DESERIALIZATION) {
78			rw.WriteHeader(http.StatusBadRequest)
79		} else if errs.Has(ERR_CONTENT_TYPE) {
80			rw.WriteHeader(http.StatusUnsupportedMediaType)
81		} else {
82			rw.WriteHeader(STATUS_UNPROCESSABLE_ENTITY)
83		}
84		errOutput, _ := json.Marshal(errs)
85		_, _ = rw.Write(errOutput)
86		return
87	}
88}
89
90// Bind wraps up the functionality of the Form and Json middleware
91// according to the Content-Type and verb of the request.
92// A Content-Type is required for POST and PUT requests.
93// Bind invokes the ErrorHandler middleware to bail out if errors
94// occurred. If you want to perform your own error handling, use
95// Form or Json middleware directly. An interface pointer can
96// be added as a second argument in order to map the struct to
97// a specific interface.
98func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
99	return func(ctx *macaron.Context) {
100		bind(ctx, obj, ifacePtr...)
101		_, _ = ctx.Invoke(errorHandler)
102	}
103}
104
105// bindForm is middleware to deserialize form-urlencoded data from the request.
106// It gets data from the form-urlencoded body, if present, or from the
107// query string. It uses the http.Request.ParseForm() method
108// to perform deserialization, then reflection is used to map each field
109// into the struct with the proper type. Structs with primitive slice types
110// (bool, float, int, string) can support deserialization of repeated form
111// keys, for example: key=val1&key=val2&key=val3
112// An interface pointer can be added as a second argument in order
113// to map the struct to a specific interface.
114func bindForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
115	return func(ctx *macaron.Context) {
116		var errors Errors
117
118		ensureNotPointer(formStruct)
119		formStruct := reflect.New(reflect.TypeOf(formStruct))
120		parseErr := ctx.Req.ParseForm()
121
122		// Format validation of the request body or the URL would add considerable overhead,
123		// and ParseForm does not complain when URL encoding is off.
124		// Because an empty request body or url can also mean absence of all needed values,
125		// it is not in all cases a bad request, so let's return 422.
126		if parseErr != nil {
127			errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
128		}
129		errors = mapForm(formStruct, ctx.Req.Form, nil, errors)
130		validateAndMap(formStruct, ctx, errors, ifacePtr...)
131	}
132}
133
134// Maximum amount of memory to use when parsing a multipart form.
135// Set this to whatever value you prefer; default is 10 MB.
136var MaxMemory = int64(1024 * 1024 * 10)
137
138// bindMultipartForm works much like Form, except it can parse multipart forms
139// and handle file uploads. Like the other deserialization middleware handlers,
140// you can pass in an interface to make the interface available for injection
141// into other handlers later.
142func bindMultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
143	return func(ctx *macaron.Context) {
144		var errors Errors
145		ensureNotPointer(formStruct)
146		formStruct := reflect.New(reflect.TypeOf(formStruct))
147		// This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6
148		if ctx.Req.MultipartForm == nil {
149			// Workaround for multipart forms returning nil instead of an error
150			// when content is not multipart; see https://code.google.com/p/go/issues/detail?id=6334
151			if multipartReader, err := ctx.Req.MultipartReader(); err != nil {
152				errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
153			} else {
154				form, parseErr := multipartReader.ReadForm(MaxMemory)
155				if parseErr != nil {
156					errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
157				}
158
159				if ctx.Req.Form == nil {
160					_ = ctx.Req.ParseForm()
161				}
162				for k, v := range form.Value {
163					ctx.Req.Form[k] = append(ctx.Req.Form[k], v...)
164				}
165
166				ctx.Req.MultipartForm = form
167			}
168		}
169		errors = mapForm(formStruct, ctx.Req.MultipartForm.Value, ctx.Req.MultipartForm.File, errors)
170		validateAndMap(formStruct, ctx, errors, ifacePtr...)
171	}
172}
173
174// bindJson is middleware to deserialize a JSON payload from the request
175// into the struct that is passed in. The resulting struct is then
176// validated, but no error handling is actually performed here.
177// An interface pointer can be added as a second argument in order
178// to map the struct to a specific interface.
179func bindJson(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
180	return func(ctx *macaron.Context) {
181		var errors Errors
182		ensureNotPointer(jsonStruct)
183		jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
184		if ctx.Req.Body != nil {
185			defer ctx.Req.Body.Close()
186			err := json.NewDecoder(ctx.Req.Body).Decode(jsonStruct.Interface())
187			if err != nil && err != io.EOF {
188				errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
189			}
190		}
191		validateAndMap(jsonStruct, ctx, errors, ifacePtr...)
192	}
193}
194
195// validateMiddleware is middleware to enforce required fields. If the struct
196// passed in implements Validator, then the user-defined validateMiddleware method
197// is executed, and its errors are mapped to the context. This middleware
198// performs no error handling: it merely detects errors and maps them.
199func validateMiddleware(obj interface{}) macaron.Handler {
200	return func(ctx *macaron.Context) {
201		var errs Errors
202		v := reflect.ValueOf(obj)
203		k := v.Kind()
204		if k == reflect.Interface || k == reflect.Ptr {
205			v = v.Elem()
206			k = v.Kind()
207		}
208		if k == reflect.Slice || k == reflect.Array {
209			for i := 0; i < v.Len(); i++ {
210				e := v.Index(i).Interface()
211				errs = validateStruct(errs, e)
212				if validator, ok := e.(_Validator); ok {
213					errs = validator.Validate(ctx, errs)
214				}
215			}
216		} else {
217			errs = validateStruct(errs, obj)
218			if validator, ok := obj.(_Validator); ok {
219				errs = validator.Validate(ctx, errs)
220			}
221		}
222		ctx.Map(errs)
223	}
224}
225
226var (
227	alphaDashPattern    = regexp.MustCompile(`[^\d\w-_]`)
228	alphaDashDotPattern = regexp.MustCompile(`[^\d\w-_\.]`)
229	emailPattern        = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
230)
231
232// Copied from github.com/asaskevich/govalidator.
233const _MAX_URL_RUNE_COUNT = 2083
234const _MIN_URL_RUNE_COUNT = 3
235
236var (
237	urlSchemaRx    = `((ftp|tcp|udp|wss?|https?):\/\/)`
238	urlUsernameRx  = `(\S+(:\S*)?@)`
239	urlIPRx        = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
240	ipRx           = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
241	urlSubdomainRx = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
242	urlPortRx      = `(:(\d{1,5}))`
243	urlPathRx      = `((\/|\?|#)[^\s]*)`
244	URLPattern     = regexp.MustCompile(`^` + urlSchemaRx + urlUsernameRx + `?` + `((` + urlIPRx + `|(\[` + ipRx + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + urlSubdomainRx + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + urlPortRx + `?` + urlPathRx + `?$`)
245)
246
247// IsURL check if the string is an URL.
248func isURL(str string) bool {
249	if str == "" || utf8.RuneCountInString(str) >= _MAX_URL_RUNE_COUNT || len(str) <= _MIN_URL_RUNE_COUNT || strings.HasPrefix(str, ".") {
250		return false
251	}
252	u, err := url.Parse(str)
253	if err != nil {
254		return false
255	}
256	if strings.HasPrefix(u.Host, ".") {
257		return false
258	}
259	if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
260		return false
261	}
262	return URLPattern.MatchString(str)
263
264}
265
266type (
267	// rule represents a validation rule.
268	rule struct {
269		// IsMatch checks if rule matches.
270		IsMatch func(string) bool
271		// IsValid applies validation rule to condition.
272		IsValid func(Errors, string, interface{}) (bool, Errors)
273	}
274
275	// paramRule does same thing as Rule but passes rule itself to IsValid method.
276	paramRule struct {
277		// IsMatch checks if rule matches.
278		IsMatch func(string) bool
279		// IsValid applies validation rule to condition.
280		IsValid func(Errors, string, string, interface{}) (bool, Errors)
281	}
282
283	// _RuleMapper and ParamRuleMapper represent validation rule mappers,
284	// it allwos users to add custom validation rules.
285	_RuleMapper      []*rule
286	_ParamRuleMapper []*paramRule
287)
288
289var ruleMapper _RuleMapper
290var paramRuleMapper _ParamRuleMapper
291
292func in(fieldValue interface{}, arr string) bool {
293	val := fmt.Sprintf("%v", fieldValue)
294	vals := strings.Split(arr, ",")
295	isIn := false
296	for _, v := range vals {
297		if v == val {
298			isIn = true
299			break
300		}
301	}
302	return isIn
303}
304
305func parseFormName(raw, actual string) string {
306	if len(actual) > 0 {
307		return actual
308	}
309	return nameMapper(raw)
310}
311
312// Performs required field checking on a struct
313func validateStruct(errors Errors, obj interface{}) Errors {
314	typ := reflect.TypeOf(obj)
315	val := reflect.ValueOf(obj)
316
317	if typ.Kind() == reflect.Ptr {
318		typ = typ.Elem()
319		val = val.Elem()
320	}
321
322	for i := 0; i < typ.NumField(); i++ {
323		field := typ.Field(i)
324
325		// Allow ignored fields in the struct
326		if field.Tag.Get("form") == "-" || !val.Field(i).CanInterface() {
327			continue
328		}
329
330		fieldVal := val.Field(i)
331		fieldValue := fieldVal.Interface()
332		zero := reflect.Zero(field.Type).Interface()
333
334		// Validate nested and embedded structs (if pointer, only do so if not nil)
335		if field.Type.Kind() == reflect.Struct ||
336			(field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue) &&
337				field.Type.Elem().Kind() == reflect.Struct) {
338			errors = validateStruct(errors, fieldValue)
339		}
340		errors = validateField(errors, zero, field, fieldVal, fieldValue)
341	}
342	return errors
343}
344
345func validateField(errors Errors, zero interface{}, field reflect.StructField, fieldVal reflect.Value, fieldValue interface{}) Errors {
346	if fieldVal.Kind() == reflect.Slice {
347		for i := 0; i < fieldVal.Len(); i++ {
348			sliceVal := fieldVal.Index(i)
349			if sliceVal.Kind() == reflect.Ptr {
350				sliceVal = sliceVal.Elem()
351			}
352
353			if sliceVal.Kind() == reflect.Invalid {
354				continue
355			}
356
357			sliceValue := sliceVal.Interface()
358			zero := reflect.Zero(sliceVal.Type()).Interface()
359			if sliceVal.Kind() == reflect.Struct ||
360				(sliceVal.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, sliceValue) &&
361					sliceVal.Elem().Kind() == reflect.Struct) {
362				errors = validateStruct(errors, sliceValue)
363			}
364			/* Apply validation rules to each item in a slice. ISSUE #3
365			else {
366				errors = validateField(errors, zero, field, sliceVal, sliceValue)
367			}*/
368		}
369	}
370
371VALIDATE_RULES:
372	for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
373		if len(rule) == 0 {
374			continue
375		}
376
377		switch {
378		case rule == "OmitEmpty":
379			if reflect.DeepEqual(zero, fieldValue) {
380				break VALIDATE_RULES
381			}
382		case rule == "Required":
383			v := reflect.ValueOf(fieldValue)
384			if v.Kind() == reflect.Slice {
385				if v.Len() == 0 {
386					errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
387					break VALIDATE_RULES
388				}
389
390				continue
391			}
392
393			if reflect.DeepEqual(zero, fieldValue) {
394				errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
395				break VALIDATE_RULES
396			}
397		case rule == "AlphaDash":
398			if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
399				errors.Add([]string{field.Name}, ERR_ALPHA_DASH, "AlphaDash")
400				break VALIDATE_RULES
401			}
402		case rule == "AlphaDashDot":
403			if alphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
404				errors.Add([]string{field.Name}, ERR_ALPHA_DASH_DOT, "AlphaDashDot")
405				break VALIDATE_RULES
406			}
407		case strings.HasPrefix(rule, "Size("):
408			size, _ := strconv.Atoi(rule[5 : len(rule)-1])
409			if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) != size {
410				errors.Add([]string{field.Name}, ERR_SIZE, "Size")
411				break VALIDATE_RULES
412			}
413			v := reflect.ValueOf(fieldValue)
414			if v.Kind() == reflect.Slice && v.Len() != size {
415				errors.Add([]string{field.Name}, ERR_SIZE, "Size")
416				break VALIDATE_RULES
417			}
418		case strings.HasPrefix(rule, "MinSize("):
419			min, _ := strconv.Atoi(rule[8 : len(rule)-1])
420			if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
421				errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
422				break VALIDATE_RULES
423			}
424			v := reflect.ValueOf(fieldValue)
425			if v.Kind() == reflect.Slice && v.Len() < min {
426				errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
427				break VALIDATE_RULES
428			}
429		case strings.HasPrefix(rule, "MaxSize("):
430			max, _ := strconv.Atoi(rule[8 : len(rule)-1])
431			if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
432				errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
433				break VALIDATE_RULES
434			}
435			v := reflect.ValueOf(fieldValue)
436			if v.Kind() == reflect.Slice && v.Len() > max {
437				errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
438				break VALIDATE_RULES
439			}
440		case strings.HasPrefix(rule, "Range("):
441			nums := strings.Split(rule[6:len(rule)-1], ",")
442			if len(nums) != 2 {
443				break VALIDATE_RULES
444			}
445			if min, err := strconv.Atoi(nums[0]); err != nil {
446				errors.Add([]string{field.Name}, ERR_RANGE, "Range")
447				break VALIDATE_RULES
448			} else if max, err := strconv.Atoi(nums[1]); err != nil {
449				errors.Add([]string{field.Name}, ERR_RANGE, "Range")
450				break VALIDATE_RULES
451			} else if val, err := strconv.Atoi(fmt.Sprintf("%v", fieldValue)); err != nil {
452				errors.Add([]string{field.Name}, ERR_RANGE, "Range")
453				break VALIDATE_RULES
454			} else if val < min || val > max {
455				errors.Add([]string{field.Name}, ERR_RANGE, "Range")
456				break VALIDATE_RULES
457			}
458		case rule == "Email":
459			if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
460				errors.Add([]string{field.Name}, ERR_EMAIL, "Email")
461				break VALIDATE_RULES
462			}
463		case rule == "Url":
464			str := fmt.Sprintf("%v", fieldValue)
465			if len(str) == 0 {
466				continue
467			} else if !isURL(str) {
468				errors.Add([]string{field.Name}, ERR_URL, "Url")
469				break VALIDATE_RULES
470			}
471		case strings.HasPrefix(rule, "In("):
472			if !in(fieldValue, rule[3:len(rule)-1]) {
473				errors.Add([]string{field.Name}, ERR_IN, "In")
474				break VALIDATE_RULES
475			}
476		case strings.HasPrefix(rule, "NotIn("):
477			if in(fieldValue, rule[6:len(rule)-1]) {
478				errors.Add([]string{field.Name}, ERR_NOT_INT, "NotIn")
479				break VALIDATE_RULES
480			}
481		case strings.HasPrefix(rule, "Include("):
482			if !strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
483				errors.Add([]string{field.Name}, ERR_INCLUDE, "Include")
484				break VALIDATE_RULES
485			}
486		case strings.HasPrefix(rule, "Exclude("):
487			if strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
488				errors.Add([]string{field.Name}, ERR_EXCLUDE, "Exclude")
489				break VALIDATE_RULES
490			}
491		case strings.HasPrefix(rule, "Default("):
492			if reflect.DeepEqual(zero, fieldValue) {
493				if fieldVal.CanAddr() {
494					errors = setWithProperType(field.Type.Kind(), rule[8:len(rule)-1], fieldVal, field.Tag.Get("form"), errors)
495				} else {
496					errors.Add([]string{field.Name}, ERR_EXCLUDE, "Default")
497					break VALIDATE_RULES
498				}
499			}
500		default:
501			// Apply custom validation rules
502			var isValid bool
503			for i := range ruleMapper {
504				if ruleMapper[i].IsMatch(rule) {
505					isValid, errors = ruleMapper[i].IsValid(errors, field.Name, fieldValue)
506					if !isValid {
507						break VALIDATE_RULES
508					}
509				}
510			}
511			for i := range paramRuleMapper {
512				if paramRuleMapper[i].IsMatch(rule) {
513					isValid, errors = paramRuleMapper[i].IsValid(errors, rule, field.Name, fieldValue)
514					if !isValid {
515						break VALIDATE_RULES
516					}
517				}
518			}
519		}
520	}
521	return errors
522}
523
524var (
525	nameMapper = func(field string) string {
526		newstr := make([]rune, 0, len(field))
527		for i, chr := range field {
528			if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
529				if i > 0 {
530					newstr = append(newstr, '_')
531				}
532				chr -= ('A' - 'a')
533			}
534			newstr = append(newstr, chr)
535		}
536		return string(newstr)
537	}
538)
539
540// Takes values from the form data and puts them into a struct
541func mapForm(formStruct reflect.Value, form map[string][]string,
542	formfile map[string][]*multipart.FileHeader, errors Errors) Errors {
543
544	if formStruct.Kind() == reflect.Ptr {
545		formStruct = formStruct.Elem()
546	}
547	typ := formStruct.Type()
548
549	for i := 0; i < typ.NumField(); i++ {
550		typeField := typ.Field(i)
551		structField := formStruct.Field(i)
552
553		if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous {
554			structField.Set(reflect.New(typeField.Type.Elem()))
555			errors = mapForm(structField.Elem(), form, formfile, errors)
556			if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) {
557				structField.Set(reflect.Zero(structField.Type()))
558			}
559		} else if typeField.Type.Kind() == reflect.Struct {
560			errors = mapForm(structField, form, formfile, errors)
561		}
562
563		inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form"))
564		if len(inputFieldName) == 0 || !structField.CanSet() {
565			continue
566		}
567
568		inputValue, exists := form[inputFieldName]
569		if exists {
570			numElems := len(inputValue)
571			if structField.Kind() == reflect.Slice && numElems > 0 {
572				sliceOf := structField.Type().Elem().Kind()
573				slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
574				for i := 0; i < numElems; i++ {
575					errors = setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
576				}
577				formStruct.Field(i).Set(slice)
578			} else {
579				errors = setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
580			}
581			continue
582		}
583
584		inputFile, exists := formfile[inputFieldName]
585		if !exists {
586			continue
587		}
588		fhType := reflect.TypeOf((*multipart.FileHeader)(nil))
589		numElems := len(inputFile)
590		if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType {
591			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
592			for i := 0; i < numElems; i++ {
593				slice.Index(i).Set(reflect.ValueOf(inputFile[i]))
594			}
595			structField.Set(slice)
596		} else if structField.Type() == fhType {
597			structField.Set(reflect.ValueOf(inputFile[0]))
598		}
599	}
600	return errors
601}
602
603// This sets the value in a struct of an indeterminate type to the
604// matching value from the request (via Form middleware) in the
605// same type, so that not all deserialized values have to be strings.
606// Supported types are string, int, float, and bool.
607func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors Errors) Errors {
608	switch valueKind {
609	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
610		if val == "" {
611			val = "0"
612		}
613		intVal, err := strconv.ParseInt(val, 10, 64)
614		if err != nil {
615			errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as integer")
616		} else {
617			structField.SetInt(intVal)
618		}
619	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
620		if val == "" {
621			val = "0"
622		}
623		uintVal, err := strconv.ParseUint(val, 10, 64)
624		if err != nil {
625			errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as unsigned integer")
626		} else {
627			structField.SetUint(uintVal)
628		}
629	case reflect.Bool:
630		if val == "on" {
631			structField.SetBool(true)
632			break
633		}
634
635		if val == "" {
636			val = "false"
637		}
638		boolVal, err := strconv.ParseBool(val)
639		if err != nil {
640			errors.Add([]string{nameInTag}, ERR_BOOLEAN_TYPE, "Value could not be parsed as boolean")
641		} else if boolVal {
642			structField.SetBool(true)
643		}
644	case reflect.Float32:
645		if val == "" {
646			val = "0.0"
647		}
648		floatVal, err := strconv.ParseFloat(val, 32)
649		if err != nil {
650			errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 32-bit float")
651		} else {
652			structField.SetFloat(floatVal)
653		}
654	case reflect.Float64:
655		if val == "" {
656			val = "0.0"
657		}
658		floatVal, err := strconv.ParseFloat(val, 64)
659		if err != nil {
660			errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 64-bit float")
661		} else {
662			structField.SetFloat(floatVal)
663		}
664	case reflect.String:
665		structField.SetString(val)
666	}
667	return errors
668}
669
670// Don't pass in pointers to bind to. Can lead to bugs.
671func ensureNotPointer(obj interface{}) {
672	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
673		panic("Pointers are not accepted as binding models")
674	}
675}
676
677// Performs validation and combines errors from validation
678// with errors from deserialization, then maps both the
679// resulting struct and the errors to the context.
680func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors Errors, ifacePtr ...interface{}) {
681	_, _ = ctx.Invoke(validateMiddleware(obj.Interface()))
682	errors = append(errors, getErrors(ctx)...)
683	ctx.Map(errors)
684	ctx.Map(obj.Elem().Interface())
685	if len(ifacePtr) > 0 {
686		ctx.MapTo(obj.Elem().Interface(), ifacePtr[0])
687	}
688}
689
690// getErrors simply gets the errors from the context (it's kind of a chore)
691func getErrors(ctx *macaron.Context) Errors {
692	return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors)
693}
694
695type (
696	// _Validator is the interface that handles some rudimentary
697	// request validation logic so your application doesn't have to.
698	_Validator interface {
699		// Validate validates that the request is OK. It is recommended
700		// that validation be limited to checking values for syntax and
701		// semantics, enough to know that you can make sense of the request
702		// in your application. For example, you might verify that a credit
703		// card number matches a valid pattern, but you probably wouldn't
704		// perform an actual credit card authorization here.
705		Validate(*macaron.Context, Errors) Errors
706	}
707)
708