// 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 ( "fmt" "net/http" "net/url" "regexp" "runtime" ) // ValidationError simple struct to store the Message & Key of a validation error type ValidationError struct { Message, Key string } // String returns the Message field of the ValidationError struct. func (e *ValidationError) String() string { if e == nil { return "" } return e.Message } // Validation context manages data validation and error messages. type Validation struct { Errors []*ValidationError Request *Request Translator func(locale, message string, args ...interface{}) string keep bool } // Keep tells revel to set a flash cookie on the client to make the validation // errors available for the next request. // This is helpful when redirecting the client after the validation failed. // It is good practice to always redirect upon a HTTP POST request. Thus // one should use this method when HTTP POST validation failed and redirect // the user back to the form. func (v *Validation) Keep() { v.keep = true } // Clear *all* ValidationErrors func (v *Validation) Clear() { v.Errors = []*ValidationError{} } // HasErrors returns true if there are any (ie > 0) errors. False otherwise. func (v *Validation) HasErrors() bool { return len(v.Errors) > 0 } // ErrorMap returns the errors mapped by key. // If there are multiple validation errors associated with a single key, the // first one "wins". (Typically the first validation will be the more basic). func (v *Validation) ErrorMap() map[string]*ValidationError { m := map[string]*ValidationError{} for _, e := range v.Errors { if _, ok := m[e.Key]; !ok { m[e.Key] = e } } return m } // Error adds an error to the validation context. func (v *Validation) Error(message string, args ...interface{}) *ValidationResult { result := v.ValidationResult(false).Message(message, args...) v.Errors = append(v.Errors, result.Error) return result } // Error adds an error to the validation context. func (v *Validation) ErrorKey(message string, args ...interface{}) *ValidationResult { result := v.ValidationResult(false).MessageKey(message, args...) v.Errors = append(v.Errors, result.Error) return result } // Error adds an error to the validation context. func (v *Validation) ValidationResult(ok bool) *ValidationResult { if ok { return &ValidationResult{Ok: ok} } else { return &ValidationResult{Ok: ok, Error: &ValidationError{}, Locale: v.Request.Locale, Translator: v.Translator} } } // ValidationResult is returned from every validation method. // It provides an indication of success, and a pointer to the Error (if any). type ValidationResult struct { Error *ValidationError Ok bool Locale string Translator func(locale, message string, args ...interface{}) string } // Key sets the ValidationResult's Error "key" and returns itself for chaining func (r *ValidationResult) Key(key string) *ValidationResult { if r.Error != nil { r.Error.Key = key } return r } // Message sets the error message for a ValidationResult. Returns itself to // allow chaining. Allows Sprintf() type calling with multiple parameters func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult { if r.Error != nil { if len(args) == 0 { r.Error.Message = message } else { r.Error.Message = fmt.Sprintf(message, args...) } } return r } // Allow a message key to be passed into the validation result. The Validation has already // setup the translator to translate the message key func (r *ValidationResult) MessageKey(message string, args ...interface{}) *ValidationResult { if r.Error == nil { return r } // If translator found, use that to create the message, otherwise call Message method if r.Translator != nil { r.Error.Message = r.Translator(r.Locale, message, args...) } else { r.Message(message, args...) } return r } // Required tests that the argument is non-nil and non-empty (if string or list) func (v *Validation) Required(obj interface{}) *ValidationResult { return v.apply(Required{}, obj) } func (v *Validation) Min(n int, min int) *ValidationResult { return v.MinFloat(float64(n), float64(min)) } func (v *Validation) MinFloat(n float64, min float64) *ValidationResult { return v.apply(Min{min}, n) } func (v *Validation) Max(n int, max int) *ValidationResult { return v.MaxFloat(float64(n), float64(max)) } func (v *Validation) MaxFloat(n float64, max float64) *ValidationResult { return v.apply(Max{max}, n) } func (v *Validation) Range(n, min, max int) *ValidationResult { return v.RangeFloat(float64(n), float64(min), float64(max)) } func (v *Validation) RangeFloat(n, min, max float64) *ValidationResult { return v.apply(Range{Min{min}, Max{max}}, n) } func (v *Validation) MinSize(obj interface{}, min int) *ValidationResult { return v.apply(MinSize{min}, obj) } func (v *Validation) MaxSize(obj interface{}, max int) *ValidationResult { return v.apply(MaxSize{max}, obj) } func (v *Validation) Length(obj interface{}, n int) *ValidationResult { return v.apply(Length{n}, obj) } func (v *Validation) Match(str string, regex *regexp.Regexp) *ValidationResult { return v.apply(Match{regex}, str) } func (v *Validation) Email(str string) *ValidationResult { return v.apply(Email{Match{emailPattern}}, str) } func (v *Validation) IPAddr(str string, cktype ...int) *ValidationResult { return v.apply(IPAddr{cktype}, str) } func (v *Validation) MacAddr(str string) *ValidationResult { return v.apply(IPAddr{}, str) } func (v *Validation) Domain(str string) *ValidationResult { return v.apply(Domain{}, str) } func (v *Validation) URL(str string) *ValidationResult { return v.apply(URL{}, str) } func (v *Validation) PureText(str string, m int) *ValidationResult { return v.apply(PureText{m}, str) } func (v *Validation) FilePath(str string, m int) *ValidationResult { return v.apply(FilePath{m}, str) } func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult { if chk.IsSatisfied(obj) { return v.ValidationResult(true) } // Get the default key. var key string if pc, _, line, ok := runtime.Caller(2); ok { f := runtime.FuncForPC(pc) if defaultKeys, ok := DefaultValidationKeys[f.Name()]; ok { key = defaultKeys[line] } } else { utilLog.Error("Validation: Failed to get Caller information to look up Validation key") } // Add the error to the validation context. err := &ValidationError{ Message: chk.DefaultMessage(), Key: key, } v.Errors = append(v.Errors, err) // Also return it in the result. vr := v.ValidationResult(false) vr.Error = err return vr } // Check applies a group of validators to a field, in order, and return the // ValidationResult from the first one that fails, or the last one that // succeeds. func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult { var result *ValidationResult for _, check := range checks { result = v.apply(check, obj) if !result.Ok { return result } } return result } // ValidationFilter revel Filter function to be hooked into the filter chain. func ValidationFilter(c *Controller, fc []Filter) { // If json request, we shall assume json response is intended, // as such no validation cookies should be tied response if c.Params != nil && c.Params.JSON != nil { c.Validation = &Validation{Request: c.Request, Translator: MessageFunc} fc[0](c, fc[1:]) } else { errors, err := restoreValidationErrors(c.Request) c.Validation = &Validation{ Errors: errors, keep: false, Request: c.Request, Translator: MessageFunc, } hasCookie := (err != http.ErrNoCookie) fc[0](c, fc[1:]) // Add Validation errors to ViewArgs. c.ViewArgs["errors"] = c.Validation.ErrorMap() // Store the Validation errors var errorsValue string if c.Validation.keep { for _, err := range c.Validation.Errors { if err.Message != "" { errorsValue += "\x00" + err.Key + ":" + err.Message + "\x00" } } } // When there are errors from Validation and Keep() has been called, store the // values in a cookie. If there previously was a cookie but no errors, remove // the cookie. if errorsValue != "" { c.SetCookie(&http.Cookie{ Name: CookiePrefix + "_ERRORS", Value: url.QueryEscape(errorsValue), Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, }) } else if hasCookie { c.SetCookie(&http.Cookie{ Name: CookiePrefix + "_ERRORS", MaxAge: -1, Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, }) } } } // Restore Validation.Errors from a request. func restoreValidationErrors(req *Request) ([]*ValidationError, error) { var ( err error cookie ServerCookie errors = make([]*ValidationError, 0, 5) ) if cookie, err = req.Cookie(CookiePrefix + "_ERRORS"); err == nil { ParseKeyValueCookie(cookie.GetValue(), func(key, val string) { errors = append(errors, &ValidationError{ Key: key, Message: val, }) }) } return errors, err } // DefaultValidationKeys register default validation keys for all calls to Controller.Validation.Func(). // Map from (package).func => (line => name of first arg to Validation func) // E.g. "myapp/controllers.helper" or "myapp/controllers.(*Application).Action" // This is set on initialization in the generated main.go file. var DefaultValidationKeys map[string]map[int]string