forked from goadesign/goa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidation.go
201 lines (178 loc) · 5.59 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package goa
import (
"bytes"
"encoding/json"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
"sync"
"time"
)
// Format defines a validation format.
type Format string
const (
// FormatDate describes RFC3339 date values.
FormatDate Format = "date"
// FormatDateTime describes RFC3339 date time values.
FormatDateTime Format = "date-time"
// FormatUUID describes RFC4122 UUID values.
FormatUUID = "uuid"
// FormatEmail describes RFC5322 email addresses.
FormatEmail = "email"
// FormatHostname describes RFC1035 Internet hostnames.
FormatHostname = "hostname"
// FormatIPv4 describes RFC2373 IPv4 address values.
FormatIPv4 = "ipv4"
// FormatIPv6 describes RFC2373 IPv6 address values.
FormatIPv6 = "ipv6"
// FormatIP describes RFC2373 IPv4 or IPv6 address values.
FormatIP = "ip"
// FormatURI describes RFC3986 URI values.
FormatURI = "uri"
// FormatMAC describes IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address values.
FormatMAC = "mac"
// FormatCIDR describes RFC4632 and RFC4291 CIDR notation IP address values.
FormatCIDR = "cidr"
// FormatRegexp describes regular expression syntax accepted by RE2.
FormatRegexp = "regexp"
// FormatJSON describes JSON text.
FormatJSON = "json"
// FormatRFC1123 describes RFC1123 date time values.
FormatRFC1123 = "rfc1123"
)
var (
hostnameRegex = regexp.MustCompile(`^[[:alnum:]][[:alnum:]\-]{0,61}[[:alnum:]]|[[:alpha:]]$`)
ipv4Regex = regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)
uuidURNPrefix = []byte("urn:uuid:")
uuidByteGroups = []int{8, 4, 4, 4, 12}
)
// ValidateFormat validates val against f. It returns nil if the string conforms
// to the format, an error otherwise. name is the name of the variable used in
// error messages. where in a data structure the error occurred if any. The
// format specification follows the json schema draft 4 validation extension.
// see http://json-schema.org/latest/json-schema-validation.html#anchor105
// Supported formats are:
//
// - "date": RFC3339 date value
// - "date-time": RFC3339 date time value
// - "email": RFC5322 email address
// - "hostname": RFC1035 Internet host name
// - "ipv4", "ipv6", "ip": RFC2673 and RFC2373 IP address values
// - "uri": RFC3986 URI value
// - "mac": IEEE 802 MAC-48, EUI-48 or EUI-64 MAC address value
// - "cidr": RFC4632 and RFC4291 CIDR notation IP address value
// - "regexp": Regular expression syntax accepted by RE2
// - "rfc1123": RFC1123 date time value
func ValidateFormat(name string, val string, f Format) error {
var err error
switch f {
case FormatDate:
_, err = time.Parse("2006-01-02", val)
case FormatDateTime:
_, err = time.Parse(time.RFC3339, val)
case FormatUUID:
err = validateUUID(val)
case FormatEmail:
_, err = mail.ParseAddress(val)
case FormatHostname:
if !hostnameRegex.MatchString(val) {
err = fmt.Errorf("hostname value '%s' does not match %s",
val, hostnameRegex.String())
}
case FormatIPv4, FormatIPv6, FormatIP:
ip := net.ParseIP(val)
if ip == nil {
err = fmt.Errorf("\"%s\" is an invalid %s value", val, f)
}
if f == FormatIPv4 {
if !ipv4Regex.MatchString(val) {
err = fmt.Errorf("\"%s\" is an invalid ipv4 value", val)
}
}
if f == FormatIPv6 {
if ipv4Regex.MatchString(val) {
err = fmt.Errorf("\"%s\" is an invalid ipv6 value", val)
}
}
case FormatURI:
_, err = url.ParseRequestURI(val)
case FormatMAC:
_, err = net.ParseMAC(val)
case FormatCIDR:
_, _, err = net.ParseCIDR(val)
case FormatRegexp:
_, err = regexp.Compile(val)
case FormatJSON:
if !json.Valid([]byte(val)) {
err = fmt.Errorf("invalid JSON")
}
case FormatRFC1123:
_, err = time.Parse(time.RFC1123, val)
default:
return fmt.Errorf("unknown format %#v", f)
}
if err != nil {
return InvalidFormatError(name, val, f, err)
}
return nil
}
// knownPatterns records the compiled patterns.
// TBD: refactor all this so that the generated code initializes the map on start to get rid of the
// need for a RW mutex.
var knownPatterns = make(map[string]*regexp.Regexp)
// knownPatternsLock is the mutex used to access knownPatterns
var knownPatternsLock = &sync.RWMutex{}
// ValidatePattern returns an error if val does not match the regular expression
// p. It makes an effort to minimize the number of times the regular expression
// needs to be compiled. name is the name of the variable used in error messages.
func ValidatePattern(name, val, p string) error {
knownPatternsLock.RLock()
r, ok := knownPatterns[p]
knownPatternsLock.RUnlock()
if !ok {
r = regexp.MustCompile(p) // DSL validation makes sure regexp is valid
knownPatternsLock.Lock()
knownPatterns[p] = r
knownPatternsLock.Unlock()
}
if !r.MatchString(val) {
return InvalidPatternError(name, val, p)
}
return nil
}
// The following formats are supported:
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
func validateUUID(uuid string) error {
if len(uuid) < 32 {
return fmt.Errorf("uuid: UUID string too short: %s", uuid)
}
t := []byte(uuid)
braced := false
if bytes.Equal(t[:9], uuidURNPrefix) {
t = t[9:]
} else if t[0] == '{' {
t = t[1:]
braced = true
}
for i, byteGroup := range uuidByteGroups {
if i > 0 {
if t[0] != '-' {
return fmt.Errorf("uuid: invalid string format")
}
t = t[1:]
}
if len(t) < byteGroup {
return fmt.Errorf("uuid: UUID string too short: %s", uuid)
}
if i == 4 && len(t) > byteGroup &&
((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) {
return fmt.Errorf("uuid: UUID string too long: %s", uuid)
}
t = t[byteGroup:]
}
return nil
}