1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
20 type Validator interface {
21 IsSatisfied(interface{}) bool
22 DefaultMessage() string
25 type Required struct{}
27 func ValidRequired() Required {
31 func (r Required) IsSatisfied(obj interface{}) bool {
35 switch v := reflect.ValueOf(obj); v.Kind() {
36 case reflect.Array, reflect.Slice, reflect.Map, reflect.String, reflect.Chan:
41 return r.IsSatisfied(reflect.Indirect(v).Interface())
43 return !reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface())
46 func (r Required) DefaultMessage() string {
47 return fmt.Sprintln("Required")
54 func ValidMin(min int) Min {
55 return ValidMinFloat(float64(min))
58 func ValidMinFloat(min float64) Min {
62 func (m Min) IsSatisfied(obj interface{}) bool {
67 switch reflect.TypeOf(obj).Kind() {
69 num, ok = obj.(float64)
72 num = float64(obj.(float32))
75 num = float64(obj.(int))
84 func (m Min) DefaultMessage() string {
85 return fmt.Sprintln("Minimum is", m.Min)
92 func ValidMax(max int) Max {
93 return ValidMaxFloat(float64(max))
96 func ValidMaxFloat(max float64) Max {
100 func (m Max) IsSatisfied(obj interface{}) bool {
105 switch reflect.TypeOf(obj).Kind() {
106 case reflect.Float64:
107 num, ok = obj.(float64)
108 case reflect.Float32:
110 num = float64(obj.(float32))
113 num = float64(obj.(int))
122 func (m Max) DefaultMessage() string {
123 return fmt.Sprintln("Maximum is", m.Max)
126 // Range requires an integer to be within Min, Max inclusive.
132 func ValidRange(min, max int) Range {
133 return ValidRangeFloat(float64(min), float64(max))
136 func ValidRangeFloat(min, max float64) Range {
137 return Range{Min{min}, Max{max}}
140 func (r Range) IsSatisfied(obj interface{}) bool {
141 return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
144 func (r Range) DefaultMessage() string {
145 return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max)
148 // MinSize requires an array or string to be at least a given length.
149 type MinSize struct {
153 func ValidMinSize(min int) MinSize {
157 func (m MinSize) IsSatisfied(obj interface{}) bool {
158 if str, ok := obj.(string); ok {
159 return utf8.RuneCountInString(str) >= m.Min
161 v := reflect.ValueOf(obj)
162 if v.Kind() == reflect.Slice {
163 return v.Len() >= m.Min
168 func (m MinSize) DefaultMessage() string {
169 return fmt.Sprintln("Minimum size is", m.Min)
172 // MaxSize requires an array or string to be at most a given length.
173 type MaxSize struct {
177 func ValidMaxSize(max int) MaxSize {
181 func (m MaxSize) IsSatisfied(obj interface{}) bool {
182 if str, ok := obj.(string); ok {
183 return utf8.RuneCountInString(str) <= m.Max
185 v := reflect.ValueOf(obj)
186 if v.Kind() == reflect.Slice {
187 return v.Len() <= m.Max
192 func (m MaxSize) DefaultMessage() string {
193 return fmt.Sprintln("Maximum size is", m.Max)
196 // Length requires an array or string to be exactly a given length.
201 func ValidLength(n int) Length {
205 func (s Length) IsSatisfied(obj interface{}) bool {
206 if str, ok := obj.(string); ok {
207 return utf8.RuneCountInString(str) == s.N
209 v := reflect.ValueOf(obj)
210 if v.Kind() == reflect.Slice {
211 return v.Len() == s.N
216 func (s Length) DefaultMessage() string {
217 return fmt.Sprintln("Required length is", s.N)
220 // Match requires a string to match a given regex.
222 Regexp *regexp.Regexp
225 func ValidMatch(regex *regexp.Regexp) Match {
229 func (m Match) IsSatisfied(obj interface{}) bool {
231 return m.Regexp.MatchString(str)
234 func (m Match) DefaultMessage() string {
235 return fmt.Sprintln("Must match", m.Regexp)
238 var emailPattern = regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$")
244 func ValidEmail() Email {
245 return Email{Match{emailPattern}}
248 func (e Email) DefaultMessage() string {
249 return fmt.Sprintln("Must be a valid email address")
255 IPv4 = 32 // IPv4 (32 chars)
256 IPv6 = 39 // IPv6(39 chars)
257 IPv4MappedIPv6 = 45 // IP4-mapped IPv6 (45 chars) , Ex) ::FFFF:129.144.52.38
260 IPv4MappedIPv6CIDR = IPv4MappedIPv6 + 3
263 // Requires a string(IP Address) to be within IP Pattern type inclusive.
268 // Requires an IP Address string to be exactly a given validation type (IPv4, IPv6, IPv4MappedIPv6, IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR OR IPAny)
269 func ValidIPAddr(cktypes ...int) IPAddr {
271 for _, cktype := range cktypes {
273 if cktype != IPAny && cktype != IPv4 && cktype != IPv6 && cktype != IPv4MappedIPv6 && cktype != IPv4CIDR && cktype != IPv6CIDR && cktype != IPv4MappedIPv6CIDR {
274 return IPAddr{Vaildtypes: []int{None}}
278 return IPAddr{Vaildtypes: cktypes}
281 func isWithCIDR(str string, l int) bool {
283 if str[l-3] == '/' || str[l-2] == '/' {
285 cidr_bit := strings.Split(str, "/")
286 if 2 == len(cidr_bit) {
287 bit, err := strconv.Atoi(cidr_bit[1])
288 //IPv4 : 0~32, IPv6 : 0 ~ 128
289 if err == nil && bit >= 0 && bit <= 128 {
298 func getIPType(str string, l int) int {
300 if l < 3 { //least 3 chars (::F)
304 has_dot := strings.Index(str[2:], ".")
305 has_colon := strings.Index(str[2:], ":")
308 case has_dot > -1 && has_colon == -1 && l >= 7 && l <= IPv4CIDR:
309 if isWithCIDR(str, l) == true {
314 case has_dot == -1 && has_colon > -1 && l >= 6 && l <= IPv6CIDR:
315 if isWithCIDR(str, l) == true {
321 case has_dot > -1 && has_colon > -1 && l >= 14 && l <= IPv4MappedIPv6:
322 if isWithCIDR(str, l) == true {
323 return IPv4MappedIPv6CIDR
325 return IPv4MappedIPv6
332 func (i IPAddr) IsSatisfied(obj interface{}) bool {
334 if str, ok := obj.(string); ok {
337 ret := getIPType(str, l)
339 for _, ck := range i.Vaildtypes {
341 if ret != None && (ck == ret || ck == IPAny) {
344 case IPv4, IPv6, IPv4MappedIPv6:
345 ip := net.ParseIP(str)
351 case IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR:
352 _, _, err := net.ParseCIDR(str)
364 func (i IPAddr) DefaultMessage() string {
365 return fmt.Sprintln("Must be a vaild IP address")
368 // Requires a MAC Address string to be exactly
369 type MacAddr struct{}
371 func ValidMacAddr() MacAddr {
376 func (m MacAddr) IsSatisfied(obj interface{}) bool {
378 if str, ok := obj.(string); ok {
379 if _, err := net.ParseMAC(str); err == nil {
387 func (m MacAddr) DefaultMessage() string {
388 return fmt.Sprintln("Must be a vaild MAC address")
391 var domainPattern = regexp.MustCompile(`^(([a-zA-Z0-9-\p{L}]{1,63}\.)?(xn--)?[a-zA-Z0-9\p{L}]+(-[a-zA-Z0-9\p{L}]+)*\.)+[a-zA-Z\p{L}]{2,63}$`)
393 // Requires a Domain string to be exactly
395 Regexp *regexp.Regexp
398 func ValidDomain() Domain {
399 return Domain{domainPattern}
402 func (d Domain) IsSatisfied(obj interface{}) bool {
404 if str, ok := obj.(string); ok {
407 //can't exceed 253 chars.
412 //first and last char must be alphanumeric
413 if str[l-1] == 46 || str[0] == 46 {
417 return domainPattern.MatchString(str)
423 func (d Domain) DefaultMessage() string {
424 return fmt.Sprintln("Must be a vaild domain address")
427 var urlPattern = regexp.MustCompile(`^((((https?|ftps?|gopher|telnet|nntp)://)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-()_.!~*';/?:@#&=+$,A-Za-z0-9\p{L}])+)([).!';/?:,][[:blank:]])?$`)
433 func ValidURL() URL {
434 return URL{Domain: ValidDomain()}
437 func (u URL) IsSatisfied(obj interface{}) bool {
439 if str, ok := obj.(string); ok {
441 // TODO : Required lot of testing
442 return urlPattern.MatchString(str)
448 func (u URL) DefaultMessage() string {
449 return fmt.Sprintln("Must be a vaild URL address")
453 NORMAL BenchmarkRegex-8 2000000000 0.24 ns/op
454 STRICT BenchmarkLoop-8 2000000000 0.01 ns/op
461 // Requires a string to be without invisible characters
462 type PureText struct {
466 func ValidPureText(m int) PureText {
467 if m != NORMAL && m != STRICT { // Q:required fatal error
473 func isPureTextStrict(str string) (bool, error) {
477 for i := 0; i < l; i++ {
481 // deny : control char (00-31 without 9(TAB) and Single 10(LF),13(CR)
482 if c >= 0 && c <= 31 && c != 9 && c != 10 && c != 13 {
483 return false, errors.New("detect control character")
486 // deny : control char (DEL)
488 return false, errors.New("detect control character (DEL)")
491 //deny : html tag (< ~ >)
495 for n := i; n < l; n++ {
497 // 60 (<) , 47(/) | 33(!) | 63(?)
498 if str[n] == 60 && n+1 <= l && (str[n+1] == 47 || str[n+1] == 33 || str[n+1] == 63) {
500 n += 3 //jump to next char
504 if ds == 1 && str[n] == 62 {
505 return false, errors.New("detect tag (<[!|?]~>)")
510 //deby : html encoded tag (&xxx;)
511 if c == 38 && i+1 <= l && str[i+1] != 35 {
517 for n := i; n < max; n++ {
519 return false, errors.New("detect html encoded ta (&XXX;)")
528 // Requires a string to match a given html tag elements regex pattern
529 // referrer : http://www.w3schools.com/Tags/
530 var elementPattern = regexp.MustCompile(`(?im)<(?P<tag>(/*\s*|\?*|\!*)(figcaption|expression|blockquote|plaintext|textarea|progress|optgroup|noscript|noframes|menuitem|frameset|fieldset|!DOCTYPE|datalist|colgroup|behavior|basefont|summary|section|isindex|details|caption|bgsound|article|address|acronym|strong|strike|source|select|script|output|option|object|legend|keygen|ilayer|iframe|header|footer|figure|dialog|center|canvas|button|applet|video|track|title|thead|tfoot|tbody|table|style|small|param|meter|layer|label|input|frame|embed|blink|audio|aside|alert|time|span|samp|ruby|meta|menu|mark|main|link|html|head|form|font|code|cite|body|base|area|abbr|xss|xml|wbr|var|svg|sup|sub|pre|nav|map|kbd|ins|img|div|dir|dfn|del|col|big|bdo|bdi|!--|ul|tt|tr|th|td|rt|rp|ol|li|hr|em|dt|dl|dd|br|u|s|q|p|i|b|a|(h[0-9]+)))([^><]*)([><]*)`)
532 // Requires a string to match a given urlencoded regex pattern
533 var urlencodedPattern = regexp.MustCompile(`(?im)(\%[0-9a-fA-F]{1,})`)
535 // Requires a string to match a given control characters regex pattern (ASCII : 00-08, 11, 12, 14, 15-31)
536 var controlcharPattern = regexp.MustCompile(`(?im)([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)`)
538 func isPureTextNormal(str string) (bool, error) {
540 decoded_str := html.UnescapeString(str)
542 matched_urlencoded := urlencodedPattern.MatchString(decoded_str)
543 if matched_urlencoded == true {
544 temp_buf, err := url.QueryUnescape(decoded_str)
546 decoded_str = temp_buf
550 matched_element := elementPattern.MatchString(decoded_str)
551 if matched_element == true {
552 return false, errors.New("detect html element")
555 matched_cc := controlcharPattern.MatchString(decoded_str)
556 if matched_cc == true {
557 return false, errors.New("detect control character")
563 func (p PureText) IsSatisfied(obj interface{}) bool {
565 if str, ok := obj.(string); ok {
570 ret, _ = isPureTextStrict(str)
572 ret, _ = isPureTextStrict(str)
580 func (p PureText) DefaultMessage() string {
581 return fmt.Sprintln("Must be a vaild Text")
586 ALLOW_RELATIVE_PATH = 1
589 const regexDenyFileNameCharList = `[\x00-\x1f|\x21-\x2c|\x3b-\x40|\x5b-\x5e|\x60|\x7b-\x7f]+`
590 const regexDenyFileName = `|\x2e\x2e\x2f+`
592 var checkAllowRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + `)`)
593 var checkDenyRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + regexDenyFileName + `)`)
595 // Requires an string to be sanitary file path
596 type FilePath struct {
600 func ValidFilePath(m int) FilePath {
602 if m != ONLY_FILENAME && m != ALLOW_RELATIVE_PATH {
608 func (f FilePath) IsSatisfied(obj interface{}) bool {
610 if str, ok := obj.(string); ok {
615 case ALLOW_RELATIVE_PATH:
616 ret = checkAllowRelativePath.MatchString(str)
620 default: //ONLY_FILENAME
621 ret = checkDenyRelativePath.MatchString(str)
631 func (f FilePath) DefaultMessage() string {
632 return fmt.Sprintln("Must be a unsanitary string")