X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Fvalidators.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Fvalidators.go;h=31aef428930cb71c250209fd188055f9db39b10e;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/validators.go b/src/foundation/api/revel/validators.go new file mode 100644 index 0000000..31aef42 --- /dev/null +++ b/src/foundation/api/revel/validators.go @@ -0,0 +1,633 @@ +// 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 ( + "errors" + "fmt" + "html" + "net" + "net/url" + "reflect" + "regexp" + "strconv" + "strings" + "unicode/utf8" +) + +type Validator interface { + IsSatisfied(interface{}) bool + DefaultMessage() string +} + +type Required struct{} + +func ValidRequired() Required { + return Required{} +} + +func (r Required) IsSatisfied(obj interface{}) bool { + if obj == nil { + return false + } + switch v := reflect.ValueOf(obj); v.Kind() { + case reflect.Array, reflect.Slice, reflect.Map, reflect.String, reflect.Chan: + if v.Len() == 0 { + return false + } + case reflect.Ptr: + return r.IsSatisfied(reflect.Indirect(v).Interface()) + } + return !reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface()) +} + +func (r Required) DefaultMessage() string { + return fmt.Sprintln("Required") +} + +type Min struct { + Min float64 +} + +func ValidMin(min int) Min { + return ValidMinFloat(float64(min)) +} + +func ValidMinFloat(min float64) Min { + return Min{min} +} + +func (m Min) IsSatisfied(obj interface{}) bool { + var ( + num float64 + ok bool + ) + switch reflect.TypeOf(obj).Kind() { + case reflect.Float64: + num, ok = obj.(float64) + case reflect.Float32: + ok = true + num = float64(obj.(float32)) + case reflect.Int: + ok = true + num = float64(obj.(int)) + } + + if ok { + return num >= m.Min + } + return false +} + +func (m Min) DefaultMessage() string { + return fmt.Sprintln("Minimum is", m.Min) +} + +type Max struct { + Max float64 +} + +func ValidMax(max int) Max { + return ValidMaxFloat(float64(max)) +} + +func ValidMaxFloat(max float64) Max { + return Max{max} +} + +func (m Max) IsSatisfied(obj interface{}) bool { + var ( + num float64 + ok bool + ) + switch reflect.TypeOf(obj).Kind() { + case reflect.Float64: + num, ok = obj.(float64) + case reflect.Float32: + ok = true + num = float64(obj.(float32)) + case reflect.Int: + ok = true + num = float64(obj.(int)) + } + + if ok { + return num <= m.Max + } + return false +} + +func (m Max) DefaultMessage() string { + return fmt.Sprintln("Maximum is", m.Max) +} + +// Range requires an integer to be within Min, Max inclusive. +type Range struct { + Min + Max +} + +func ValidRange(min, max int) Range { + return ValidRangeFloat(float64(min), float64(max)) +} + +func ValidRangeFloat(min, max float64) Range { + return Range{Min{min}, Max{max}} +} + +func (r Range) IsSatisfied(obj interface{}) bool { + return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj) +} + +func (r Range) DefaultMessage() string { + return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max) +} + +// MinSize requires an array or string to be at least a given length. +type MinSize struct { + Min int +} + +func ValidMinSize(min int) MinSize { + return MinSize{min} +} + +func (m MinSize) IsSatisfied(obj interface{}) bool { + if str, ok := obj.(string); ok { + return utf8.RuneCountInString(str) >= m.Min + } + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Slice { + return v.Len() >= m.Min + } + return false +} + +func (m MinSize) DefaultMessage() string { + return fmt.Sprintln("Minimum size is", m.Min) +} + +// MaxSize requires an array or string to be at most a given length. +type MaxSize struct { + Max int +} + +func ValidMaxSize(max int) MaxSize { + return MaxSize{max} +} + +func (m MaxSize) IsSatisfied(obj interface{}) bool { + if str, ok := obj.(string); ok { + return utf8.RuneCountInString(str) <= m.Max + } + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Slice { + return v.Len() <= m.Max + } + return false +} + +func (m MaxSize) DefaultMessage() string { + return fmt.Sprintln("Maximum size is", m.Max) +} + +// Length requires an array or string to be exactly a given length. +type Length struct { + N int +} + +func ValidLength(n int) Length { + return Length{n} +} + +func (s Length) IsSatisfied(obj interface{}) bool { + if str, ok := obj.(string); ok { + return utf8.RuneCountInString(str) == s.N + } + v := reflect.ValueOf(obj) + if v.Kind() == reflect.Slice { + return v.Len() == s.N + } + return false +} + +func (s Length) DefaultMessage() string { + return fmt.Sprintln("Required length is", s.N) +} + +// Match requires a string to match a given regex. +type Match struct { + Regexp *regexp.Regexp +} + +func ValidMatch(regex *regexp.Regexp) Match { + return Match{regex} +} + +func (m Match) IsSatisfied(obj interface{}) bool { + str := obj.(string) + return m.Regexp.MatchString(str) +} + +func (m Match) DefaultMessage() string { + return fmt.Sprintln("Must match", m.Regexp) +} + +var emailPattern = regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$") + +type Email struct { + Match +} + +func ValidEmail() Email { + return Email{Match{emailPattern}} +} + +func (e Email) DefaultMessage() string { + return fmt.Sprintln("Must be a valid email address") +} + +const ( + None = 0 + IPAny = 1 + IPv4 = 32 // IPv4 (32 chars) + IPv6 = 39 // IPv6(39 chars) + IPv4MappedIPv6 = 45 // IP4-mapped IPv6 (45 chars) , Ex) ::FFFF:129.144.52.38 + IPv4CIDR = IPv4 + 3 + IPv6CIDR = IPv6 + 3 + IPv4MappedIPv6CIDR = IPv4MappedIPv6 + 3 +) + +// Requires a string(IP Address) to be within IP Pattern type inclusive. +type IPAddr struct { + Vaildtypes []int +} + +// Requires an IP Address string to be exactly a given validation type (IPv4, IPv6, IPv4MappedIPv6, IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR OR IPAny) +func ValidIPAddr(cktypes ...int) IPAddr { + + for _, cktype := range cktypes { + + if cktype != IPAny && cktype != IPv4 && cktype != IPv6 && cktype != IPv4MappedIPv6 && cktype != IPv4CIDR && cktype != IPv6CIDR && cktype != IPv4MappedIPv6CIDR { + return IPAddr{Vaildtypes: []int{None}} + } + } + + return IPAddr{Vaildtypes: cktypes} +} + +func isWithCIDR(str string, l int) bool { + + if str[l-3] == '/' || str[l-2] == '/' { + + cidr_bit := strings.Split(str, "/") + if 2 == len(cidr_bit) { + bit, err := strconv.Atoi(cidr_bit[1]) + //IPv4 : 0~32, IPv6 : 0 ~ 128 + if err == nil && bit >= 0 && bit <= 128 { + return true + } + } + } + + return false +} + +func getIPType(str string, l int) int { + + if l < 3 { //least 3 chars (::F) + return None + } + + has_dot := strings.Index(str[2:], ".") + has_colon := strings.Index(str[2:], ":") + + switch { + case has_dot > -1 && has_colon == -1 && l >= 7 && l <= IPv4CIDR: + if isWithCIDR(str, l) == true { + return IPv4CIDR + } else { + return IPv4 + } + case has_dot == -1 && has_colon > -1 && l >= 6 && l <= IPv6CIDR: + if isWithCIDR(str, l) == true { + return IPv6CIDR + } else { + return IPv6 + } + + case has_dot > -1 && has_colon > -1 && l >= 14 && l <= IPv4MappedIPv6: + if isWithCIDR(str, l) == true { + return IPv4MappedIPv6CIDR + } else { + return IPv4MappedIPv6 + } + } + + return None +} + +func (i IPAddr) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + + l := len(str) + ret := getIPType(str, l) + + for _, ck := range i.Vaildtypes { + + if ret != None && (ck == ret || ck == IPAny) { + + switch ret { + case IPv4, IPv6, IPv4MappedIPv6: + ip := net.ParseIP(str) + + if ip != nil { + return true + } + + case IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR: + _, _, err := net.ParseCIDR(str) + if err == nil { + return true + } + } + } + } + } + + return false +} + +func (i IPAddr) DefaultMessage() string { + return fmt.Sprintln("Must be a vaild IP address") +} + +// Requires a MAC Address string to be exactly +type MacAddr struct{} + +func ValidMacAddr() MacAddr { + + return MacAddr{} +} + +func (m MacAddr) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + if _, err := net.ParseMAC(str); err == nil { + return true + } + } + + return false +} + +func (m MacAddr) DefaultMessage() string { + return fmt.Sprintln("Must be a vaild MAC address") +} + +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}$`) + +// Requires a Domain string to be exactly +type Domain struct { + Regexp *regexp.Regexp +} + +func ValidDomain() Domain { + return Domain{domainPattern} +} + +func (d Domain) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + + l := len(str) + //can't exceed 253 chars. + if l > 253 { + return false + } + + //first and last char must be alphanumeric + if str[l-1] == 46 || str[0] == 46 { + return false + } + + return domainPattern.MatchString(str) + } + + return false +} + +func (d Domain) DefaultMessage() string { + return fmt.Sprintln("Must be a vaild domain address") +} + +var urlPattern = regexp.MustCompile(`^((((https?|ftps?|gopher|telnet|nntp)://)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-()_.!~*';/?:@#&=+$,A-Za-z0-9\p{L}])+)([).!';/?:,][[:blank:]])?$`) + +type URL struct { + Domain +} + +func ValidURL() URL { + return URL{Domain: ValidDomain()} +} + +func (u URL) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + + // TODO : Required lot of testing + return urlPattern.MatchString(str) + } + + return false +} + +func (u URL) DefaultMessage() string { + return fmt.Sprintln("Must be a vaild URL address") +} + +/* +NORMAL BenchmarkRegex-8 2000000000 0.24 ns/op +STRICT BenchmarkLoop-8 2000000000 0.01 ns/op +*/ +const ( + NORMAL = 0 + STRICT = 4 +) + +// Requires a string to be without invisible characters +type PureText struct { + Mode int +} + +func ValidPureText(m int) PureText { + if m != NORMAL && m != STRICT { // Q:required fatal error + m = STRICT + } + return PureText{m} +} + +func isPureTextStrict(str string) (bool, error) { + + l := len(str) + + for i := 0; i < l; i++ { + + c := str[i] + + // deny : control char (00-31 without 9(TAB) and Single 10(LF),13(CR) + if c >= 0 && c <= 31 && c != 9 && c != 10 && c != 13 { + return false, errors.New("detect control character") + } + + // deny : control char (DEL) + if c == 127 { + return false, errors.New("detect control character (DEL)") + } + + //deny : html tag (< ~ >) + if c == 60 { + + ds := 0 + for n := i; n < l; n++ { + + // 60 (<) , 47(/) | 33(!) | 63(?) + if str[n] == 60 && n+1 <= l && (str[n+1] == 47 || str[n+1] == 33 || str[n+1] == 63) { + ds = 1 + n += 3 //jump to next char + } + + // 62 (>) + if ds == 1 && str[n] == 62 { + return false, errors.New("detect tag (<[!|?]~>)") + } + } + } + + //deby : html encoded tag (&xxx;) + if c == 38 && i+1 <= l && str[i+1] != 35 { + + max := i + 64 + if max > l { + max = l + } + for n := i; n < max; n++ { + if str[n] == 59 { + return false, errors.New("detect html encoded ta (&XXX;)") + } + } + } + } + + return true, nil +} + +// Requires a string to match a given html tag elements regex pattern +// referrer : http://www.w3schools.com/Tags/ +var elementPattern = regexp.MustCompile(`(?im)<(?P(/*\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]+)))([^><]*)([><]*)`) + +// Requires a string to match a given urlencoded regex pattern +var urlencodedPattern = regexp.MustCompile(`(?im)(\%[0-9a-fA-F]{1,})`) + +// Requires a string to match a given control characters regex pattern (ASCII : 00-08, 11, 12, 14, 15-31) +var controlcharPattern = regexp.MustCompile(`(?im)([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)`) + +func isPureTextNormal(str string) (bool, error) { + + decoded_str := html.UnescapeString(str) + + matched_urlencoded := urlencodedPattern.MatchString(decoded_str) + if matched_urlencoded == true { + temp_buf, err := url.QueryUnescape(decoded_str) + if err == nil { + decoded_str = temp_buf + } + } + + matched_element := elementPattern.MatchString(decoded_str) + if matched_element == true { + return false, errors.New("detect html element") + } + + matched_cc := controlcharPattern.MatchString(decoded_str) + if matched_cc == true { + return false, errors.New("detect control character") + } + + return true, nil +} + +func (p PureText) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + + var ret bool + switch p.Mode { + case STRICT: + ret, _ = isPureTextStrict(str) + case NORMAL: + ret, _ = isPureTextStrict(str) + } + return ret + } + + return false +} + +func (p PureText) DefaultMessage() string { + return fmt.Sprintln("Must be a vaild Text") +} + +const ( + ONLY_FILENAME = 0 + ALLOW_RELATIVE_PATH = 1 +) + +const regexDenyFileNameCharList = `[\x00-\x1f|\x21-\x2c|\x3b-\x40|\x5b-\x5e|\x60|\x7b-\x7f]+` +const regexDenyFileName = `|\x2e\x2e\x2f+` + +var checkAllowRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + `)`) +var checkDenyRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + regexDenyFileName + `)`) + +// Requires an string to be sanitary file path +type FilePath struct { + Mode int +} + +func ValidFilePath(m int) FilePath { + + if m != ONLY_FILENAME && m != ALLOW_RELATIVE_PATH { + m = ONLY_FILENAME + } + return FilePath{m} +} + +func (f FilePath) IsSatisfied(obj interface{}) bool { + + if str, ok := obj.(string); ok { + + var ret bool + switch f.Mode { + + case ALLOW_RELATIVE_PATH: + ret = checkAllowRelativePath.MatchString(str) + if ret == false { + return true + } + default: //ONLY_FILENAME + ret = checkDenyRelativePath.MatchString(str) + if ret == false { + return true + } + } + } + + return false +} + +func (f FilePath) DefaultMessage() string { + return fmt.Sprintln("Must be a unsanitary string") +}