1 package jsonschema
2
3 import (
4 "context"
5 "fmt"
6 "net"
7 "net/mail"
8 "net/url"
9 "regexp"
10 "strconv"
11 "strings"
12 "time"
13
14 jptr "github.com/qri-io/jsonpointer"
15 )
16
17 const (
18 hostname string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
19 unescapedTilda = `\~[^01]`
20 endingTilda = `\~$`
21 schemePrefix = `^[^\:]+\:`
22 uriTemplate = `\{[^\{\}\\]*\}`
23 )
24
25 var (
26
27 hostnamePattern = regexp.MustCompile(hostname)
28 unescaptedTildaPattern = regexp.MustCompile(unescapedTilda)
29 endingTildaPattern = regexp.MustCompile(endingTilda)
30 schemePrefixPattern = regexp.MustCompile(schemePrefix)
31 uriTemplatePattern = regexp.MustCompile(uriTemplate)
32
33 disallowedIdnChars = map[string]bool{"\u0020": true, "\u002D": true, "\u00A2": true, "\u00A3": true, "\u00A4": true, "\u00A5": true, "\u034F": true, "\u0640": true, "\u07FA": true, "\u180B": true, "\u180C": true, "\u180D": true, "\u200B": true, "\u2060": true, "\u2104": true, "\u2108": true, "\u2114": true, "\u2117": true, "\u2118": true, "\u211E": true, "\u211F": true, "\u2123": true, "\u2125": true, "\u2282": true, "\u2283": true, "\u2284": true, "\u2285": true, "\u2286": true, "\u2287": true, "\u2288": true, "\u2616": true, "\u2617": true, "\u2619": true, "\u262F": true, "\u2638": true, "\u266C": true, "\u266D": true, "\u266F": true, "\u2752": true, "\u2756": true, "\u2758": true, "\u275E": true, "\u2761": true, "\u2775": true, "\u2794": true, "\u2798": true, "\u27AF": true, "\u27B1": true, "\u27BE": true, "\u3004": true, "\u3012": true, "\u3013": true, "\u3020": true, "\u302E": true, "\u302F": true, "\u3031": true, "\u3032": true, "\u3035": true, "\u303B": true, "\u3164": true, "\uFFA0": true}
34 )
35
36
37 type Format string
38
39
40 func NewFormat() Keyword {
41 return new(Format)
42 }
43
44
45 func (f *Format) Register(uri string, registry *SchemaRegistry) {}
46
47
48 func (f *Format) Resolve(pointer jptr.Pointer, uri string) *Schema {
49 return nil
50 }
51
52
53 func (f Format) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) {
54 schemaDebug("[Format] Validating")
55 var err error
56 if str, ok := data.(string); ok {
57 switch f {
58 case "date-time":
59 err = isValidDateTime(str)
60 case "date":
61 err = isValidDate(str)
62 case "email":
63 err = isValidEmail(str)
64 case "hostname":
65 err = isValidHostname(str)
66 case "idn-email":
67 err = isValidIDNEmail(str)
68 case "idn-hostname":
69 err = isValidIDNHostname(str)
70 case "ipv4":
71 err = isValidIPv4(str)
72 case "ipv6":
73 err = isValidIPv6(str)
74 case "iri-reference":
75 err = isValidIriRef(str)
76 case "iri":
77 err = isValidIri(str)
78 case "json-pointer":
79 err = isValidJSONPointer(str)
80 case "regex":
81 err = isValidRegex(str)
82 case "relative-json-pointer":
83 err = isValidRelJSONPointer(str)
84 case "time":
85 err = isValidTime(str)
86 case "uri-reference":
87 err = isValidURIRef(str)
88 case "uri-template":
89 err = isValidURITemplate(str)
90 case "uri":
91 err = isValidURI(str)
92 default:
93 err = nil
94 }
95 if err != nil {
96 currentState.AddError(data, fmt.Sprintf("invalid %s: %s", f, err.Error()))
97 }
98 }
99 }
100
101
102
103
104
105 func isValidDateTime(dateTime string) error {
106 if _, err := time.Parse(time.RFC3339, strings.ToUpper(dateTime)); err != nil {
107 return fmt.Errorf("date-time incorrectly Formatted: %s", err.Error())
108 }
109 return nil
110 }
111
112
113
114
115
116 func isValidDate(date string) error {
117 arbitraryTime := "T08:30:06.283185Z"
118 dateTime := fmt.Sprintf("%s%s", date, arbitraryTime)
119 return isValidDateTime(dateTime)
120 }
121
122
123
124
125 func isValidEmail(email string) error {
126
127
128
129 if _, err := mail.ParseAddress(email); err != nil {
130 return fmt.Errorf("email address incorrectly Formatted: %s", err.Error())
131 }
132 return nil
133 }
134
135
136
137
138
139
140
141 func isValidHostname(hostname string) error {
142 if !hostnamePattern.MatchString(hostname) || len(hostname) > 255 {
143 return fmt.Errorf("invalid hostname string")
144 }
145 return nil
146 }
147
148
149
150
151 func isValidIDNEmail(idnEmail string) error {
152 if _, err := mail.ParseAddress(idnEmail); err != nil {
153 return fmt.Errorf("email address incorrectly Formatted: %s", err.Error())
154 }
155 return nil
156 }
157
158
159
160
161
162
163
164
165 func isValidIDNHostname(idnHostname string) error {
166 if len(idnHostname) > 255 {
167 return fmt.Errorf("invalid idn hostname string")
168 }
169 for _, r := range idnHostname {
170 s := string(r)
171 if disallowedIdnChars[s] {
172 return fmt.Errorf("invalid hostname: contains illegal character %#U", r)
173 }
174 }
175 return nil
176 }
177
178
179
180
181
182 func isValidIPv4(ipv4 string) error {
183 parsedIP := net.ParseIP(ipv4)
184 hasDots := strings.Contains(ipv4, ".")
185 if !hasDots || parsedIP == nil {
186 return fmt.Errorf("invalid IPv4 address")
187 }
188 return nil
189 }
190
191
192
193
194
195 func isValidIPv6(ipv6 string) error {
196 parsedIP := net.ParseIP(ipv6)
197 hasColons := strings.Contains(ipv6, ":")
198 if !hasColons || parsedIP == nil {
199 return fmt.Errorf("invalid IPv4 address")
200 }
201 return nil
202 }
203
204
205
206
207
208 func isValidIriRef(iriRef string) error {
209 return isValidURIRef(iriRef)
210 }
211
212
213
214
215 func isValidIri(iri string) error {
216 return isValidURI(iri)
217 }
218
219
220
221
222
223 func isValidJSONPointer(jsonPointer string) error {
224 if len(jsonPointer) == 0 {
225 return nil
226 }
227 if jsonPointer[0] != '/' {
228 return fmt.Errorf("non-empty references must begin with a '/' character")
229 }
230 str := jsonPointer[1:]
231 if unescaptedTildaPattern.MatchString(str) {
232 return fmt.Errorf("unescaped tilda error")
233 }
234 if endingTildaPattern.MatchString(str) {
235 return fmt.Errorf("unescaped tilda error")
236 }
237 return nil
238 }
239
240
241
242
243
244
245
246
247
248
249 func isValidRegex(regex string) error {
250 if _, err := regexp.Compile(regex); err != nil {
251 return fmt.Errorf("invalid regex expression")
252 }
253 return nil
254 }
255
256
257
258
259 func isValidRelJSONPointer(relJSONPointer string) error {
260 parts := strings.Split(relJSONPointer, "/")
261 if len(parts) == 1 {
262 parts = strings.Split(relJSONPointer, "#")
263 }
264 if i, err := strconv.Atoi(parts[0]); err != nil || i < 0 {
265 return fmt.Errorf("RJP must begin with positive integer")
266 }
267
268 str := relJSONPointer[len(parts[0]):]
269 if len(str) > 0 && str[0] == '#' {
270 return nil
271 }
272 return isValidJSONPointer(str)
273 }
274
275
276
277
278
279 func isValidTime(time string) error {
280 arbitraryDate := "1963-06-19"
281 dateTime := fmt.Sprintf("%sT%s", arbitraryDate, time)
282 return isValidDateTime(dateTime)
283 return nil
284 }
285
286
287
288
289
290 func isValidURIRef(uriRef string) error {
291 if _, err := url.Parse(uriRef); err != nil {
292 return fmt.Errorf("uri incorrectly Formatted: %s", err.Error())
293 }
294 if strings.Contains(uriRef, "\\") {
295 return fmt.Errorf("invalid uri")
296 }
297 return nil
298 }
299
300
301
302
303
304
305 func isValidURITemplate(uriTemplate string) error {
306 arbitraryValue := "aaa"
307 uriRef := uriTemplatePattern.ReplaceAllString(uriTemplate, arbitraryValue)
308 if strings.Contains(uriRef, "{") || strings.Contains(uriRef, "}") {
309 return fmt.Errorf("invalid uri template")
310 }
311 return isValidURIRef(uriRef)
312 }
313
314
315
316
317 func isValidURI(uri string) error {
318 if _, err := url.Parse(uri); err != nil {
319 return fmt.Errorf("uri incorrectly Formatted: %s", err.Error())
320 }
321 if !schemePrefixPattern.MatchString(uri) {
322 return fmt.Errorf("uri missing scheme prefix")
323 }
324 return nil
325 }
326
View as plain text