1 /*
2 * Copyright (c) 2014, Vsevolod Stakhov
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "ucl.h"
27 #include "ucl_internal.h"
28 #include "tree.h"
29 #include "utlist.h"
30 #ifdef HAVE_STDARG_H
31 #include <stdarg.h>
32 #endif
33 #ifdef HAVE_STDIO_H
34 #include <stdio.h>
35 #endif
36 #ifdef HAVE_REGEX_H
37 #include <regex.h>
38 #endif
39 #ifdef HAVE_MATH_H
40 #include <math.h>
41 #endif
42 #include <inttypes.h>
43
44 static bool ucl_schema_validate (const ucl_object_t *schema,
45 const ucl_object_t *obj, bool try_array,
46 struct ucl_schema_error *err,
47 const ucl_object_t *root,
48 ucl_object_t *ext_ref);
49
50 /*
51 * Create validation error
52 */
53
54 #ifdef __GNUC__
55 static inline void
56 ucl_schema_create_error (struct ucl_schema_error *err,
57 enum ucl_schema_error_code code, const ucl_object_t *obj,
58 const char *fmt, ...)
59 __attribute__ (( format( printf, 4, 5) ));
60 #endif
61
62 static inline void
ucl_schema_create_error(struct ucl_schema_error * err,enum ucl_schema_error_code code,const ucl_object_t * obj,const char * fmt,...)63 ucl_schema_create_error (struct ucl_schema_error *err,
64 enum ucl_schema_error_code code, const ucl_object_t *obj,
65 const char *fmt, ...)
66 {
67 va_list va;
68
69 if (err != NULL) {
70 err->code = code;
71 err->obj = obj;
72 va_start (va, fmt);
73 vsnprintf (err->msg, sizeof (err->msg), fmt, va);
74 va_end (va);
75 }
76 }
77
78 /*
79 * Check whether we have a pattern specified
80 */
81 static const ucl_object_t *
ucl_schema_test_pattern(const ucl_object_t * obj,const char * pattern,bool recursive)82 ucl_schema_test_pattern (const ucl_object_t *obj, const char *pattern, bool recursive)
83 {
84 const ucl_object_t *res = NULL;
85 #ifdef HAVE_REGEX_H
86 regex_t reg;
87 const ucl_object_t *elt;
88 ucl_object_iter_t iter = NULL;
89
90 if (regcomp (®, pattern, REG_EXTENDED | REG_NOSUB) == 0) {
91 if (recursive) {
92 while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) {
93 if (regexec (®, ucl_object_key (elt), 0, NULL, 0) == 0) {
94 res = elt;
95 break;
96 }
97 }
98 } else {
99 if (regexec (®, ucl_object_key (obj), 0, NULL, 0) == 0)
100 res = obj;
101 }
102 regfree (®);
103 }
104 #endif
105 return res;
106 }
107
108 /*
109 * Check dependencies for an object
110 */
111 static bool
ucl_schema_validate_dependencies(const ucl_object_t * deps,const ucl_object_t * obj,struct ucl_schema_error * err,const ucl_object_t * root,ucl_object_t * ext_ref)112 ucl_schema_validate_dependencies (const ucl_object_t *deps,
113 const ucl_object_t *obj, struct ucl_schema_error *err,
114 const ucl_object_t *root,
115 ucl_object_t *ext_ref)
116 {
117 const ucl_object_t *elt, *cur, *cur_dep;
118 ucl_object_iter_t iter = NULL, piter;
119 bool ret = true;
120
121 while (ret && (cur = ucl_object_iterate (deps, &iter, true)) != NULL) {
122 elt = ucl_object_lookup (obj, ucl_object_key (cur));
123 if (elt != NULL) {
124 /* Need to check dependencies */
125 if (cur->type == UCL_ARRAY) {
126 piter = NULL;
127 while (ret && (cur_dep = ucl_object_iterate (cur, &piter, true)) != NULL) {
128 if (ucl_object_lookup (obj, ucl_object_tostring (cur_dep)) == NULL) {
129 ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt,
130 "dependency %s is missing for key %s",
131 ucl_object_tostring (cur_dep), ucl_object_key (cur));
132 ret = false;
133 break;
134 }
135 }
136 }
137 else if (cur->type == UCL_OBJECT) {
138 ret = ucl_schema_validate (cur, obj, true, err, root, ext_ref);
139 }
140 }
141 }
142
143 return ret;
144 }
145
146 /*
147 * Validate object
148 */
149 static bool
ucl_schema_validate_object(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err,const ucl_object_t * root,ucl_object_t * ext_ref)150 ucl_schema_validate_object (const ucl_object_t *schema,
151 const ucl_object_t *obj, struct ucl_schema_error *err,
152 const ucl_object_t *root,
153 ucl_object_t *ext_ref)
154 {
155 const ucl_object_t *elt, *prop, *found, *additional_schema = NULL,
156 *required = NULL, *pat, *pelt;
157 ucl_object_iter_t iter = NULL, piter = NULL;
158 bool ret = true, allow_additional = true;
159 int64_t minmax;
160
161 while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
162 if (elt->type == UCL_OBJECT &&
163 strcmp (ucl_object_key (elt), "properties") == 0) {
164 piter = NULL;
165 while (ret && (prop = ucl_object_iterate (elt, &piter, true)) != NULL) {
166 found = ucl_object_lookup (obj, ucl_object_key (prop));
167 if (found) {
168 ret = ucl_schema_validate (prop, found, true, err, root,
169 ext_ref);
170 }
171 }
172 }
173 else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) {
174 if (elt->type == UCL_BOOLEAN) {
175 if (!ucl_object_toboolean (elt)) {
176 /* Deny additional fields completely */
177 allow_additional = false;
178 }
179 }
180 else if (elt->type == UCL_OBJECT) {
181 /* Define validator for additional fields */
182 additional_schema = elt;
183 }
184 else {
185 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
186 "additionalProperties attribute is invalid in schema");
187 ret = false;
188 break;
189 }
190 }
191 else if (strcmp (ucl_object_key (elt), "required") == 0) {
192 if (elt->type == UCL_ARRAY) {
193 required = elt;
194 }
195 else {
196 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
197 "required attribute is invalid in schema");
198 ret = false;
199 break;
200 }
201 }
202 else if (strcmp (ucl_object_key (elt), "minProperties") == 0
203 && ucl_object_toint_safe (elt, &minmax)) {
204 if (obj->len < minmax) {
205 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
206 "object has not enough properties: %u, minimum is: %u",
207 obj->len, (unsigned)minmax);
208 ret = false;
209 break;
210 }
211 }
212 else if (strcmp (ucl_object_key (elt), "maxProperties") == 0
213 && ucl_object_toint_safe (elt, &minmax)) {
214 if (obj->len > minmax) {
215 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
216 "object has too many properties: %u, maximum is: %u",
217 obj->len, (unsigned)minmax);
218 ret = false;
219 break;
220 }
221 }
222 else if (strcmp (ucl_object_key (elt), "patternProperties") == 0) {
223 const ucl_object_t *vobj;
224 ucl_object_iter_t viter;
225 piter = NULL;
226 while (ret && (prop = ucl_object_iterate (elt, &piter, true)) != NULL) {
227 viter = NULL;
228 while (ret && (vobj = ucl_object_iterate (obj, &viter, true)) != NULL) {
229 found = ucl_schema_test_pattern (vobj, ucl_object_key (prop), false);
230 if (found) {
231 ret = ucl_schema_validate (prop, found, true, err, root,
232 ext_ref);
233 }
234 }
235 }
236 }
237 else if (elt->type == UCL_OBJECT &&
238 strcmp (ucl_object_key (elt), "dependencies") == 0) {
239 ret = ucl_schema_validate_dependencies (elt, obj, err, root,
240 ext_ref);
241 }
242 }
243
244 if (ret) {
245 /* Additional properties */
246 if (!allow_additional || additional_schema != NULL) {
247 /* Check if we have exactly the same properties in schema and object */
248 iter = ucl_object_iterate_new (obj);
249 prop = ucl_object_lookup (schema, "properties");
250 while ((elt = ucl_object_iterate_safe (iter, true)) != NULL) {
251 found = ucl_object_lookup (prop, ucl_object_key (elt));
252 if (found == NULL) {
253 /* Try patternProperties */
254 pat = ucl_object_lookup (schema, "patternProperties");
255 piter = ucl_object_iterate_new (pat);
256 while ((pelt = ucl_object_iterate_safe (piter, true)) != NULL) {
257 found = ucl_schema_test_pattern (obj, ucl_object_key (pelt), true);
258 if (found != NULL) {
259 break;
260 }
261 }
262 ucl_object_iterate_free (piter);
263 piter = NULL;
264 }
265 if (found == NULL) {
266 if (!allow_additional) {
267 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
268 "object has non-allowed property %s",
269 ucl_object_key (elt));
270 ret = false;
271 break;
272 }
273 else if (additional_schema != NULL) {
274 if (!ucl_schema_validate (additional_schema, elt,
275 true, err, root, ext_ref)) {
276 ret = false;
277 break;
278 }
279 }
280 }
281 }
282 ucl_object_iterate_free (iter);
283 iter = NULL;
284 }
285 /* Required properties */
286 if (required != NULL) {
287 iter = NULL;
288 while ((elt = ucl_object_iterate (required, &iter, true)) != NULL) {
289 if (ucl_object_lookup (obj, ucl_object_tostring (elt)) == NULL) {
290 ucl_schema_create_error (err, UCL_SCHEMA_MISSING_PROPERTY, obj,
291 "object has missing property %s",
292 ucl_object_tostring (elt));
293 ret = false;
294 break;
295 }
296 }
297 }
298 }
299
300
301 return ret;
302 }
303
304 static bool
ucl_schema_validate_number(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err)305 ucl_schema_validate_number (const ucl_object_t *schema,
306 const ucl_object_t *obj, struct ucl_schema_error *err)
307 {
308 const ucl_object_t *elt, *test;
309 ucl_object_iter_t iter = NULL;
310 bool ret = true, exclusive = false;
311 double constraint, val;
312 const double alpha = 1e-16;
313
314 while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
315 if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
316 strcmp (ucl_object_key (elt), "multipleOf") == 0) {
317 constraint = ucl_object_todouble (elt);
318 if (constraint <= 0) {
319 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
320 "multipleOf must be greater than zero");
321 ret = false;
322 break;
323 }
324 val = ucl_object_todouble (obj);
325 if (fabs (remainder (val, constraint)) > alpha) {
326 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
327 "number %.4f is not multiple of %.4f, remainder is %.7f",
328 val, constraint, remainder (val, constraint));
329 ret = false;
330 break;
331 }
332 }
333 else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
334 strcmp (ucl_object_key (elt), "maximum") == 0) {
335 constraint = ucl_object_todouble (elt);
336 test = ucl_object_lookup (schema, "exclusiveMaximum");
337 if (test && test->type == UCL_BOOLEAN) {
338 exclusive = ucl_object_toboolean (test);
339 }
340 val = ucl_object_todouble (obj);
341 if (val > constraint || (exclusive && val >= constraint)) {
342 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
343 "number is too big: %.3f, maximum is: %.3f",
344 val, constraint);
345 ret = false;
346 break;
347 }
348 }
349 else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) &&
350 strcmp (ucl_object_key (elt), "minimum") == 0) {
351 constraint = ucl_object_todouble (elt);
352 test = ucl_object_lookup (schema, "exclusiveMinimum");
353 if (test && test->type == UCL_BOOLEAN) {
354 exclusive = ucl_object_toboolean (test);
355 }
356 val = ucl_object_todouble (obj);
357 if (val < constraint || (exclusive && val <= constraint)) {
358 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
359 "number is too small: %.3f, minimum is: %.3f",
360 val, constraint);
361 ret = false;
362 break;
363 }
364 }
365 }
366
367 return ret;
368 }
369
370 static bool
ucl_schema_validate_string(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err)371 ucl_schema_validate_string (const ucl_object_t *schema,
372 const ucl_object_t *obj, struct ucl_schema_error *err)
373 {
374 const ucl_object_t *elt;
375 ucl_object_iter_t iter = NULL;
376 bool ret = true;
377 int64_t constraint;
378 #ifdef HAVE_REGEX_H
379 regex_t re;
380 #endif
381
382 while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
383 if (elt->type == UCL_INT &&
384 strcmp (ucl_object_key (elt), "maxLength") == 0) {
385 constraint = ucl_object_toint (elt);
386 if (obj->len > constraint) {
387 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
388 "string is too big: %u, maximum is: %" PRId64,
389 obj->len, constraint);
390 ret = false;
391 break;
392 }
393 }
394 else if (elt->type == UCL_INT &&
395 strcmp (ucl_object_key (elt), "minLength") == 0) {
396 constraint = ucl_object_toint (elt);
397 if (obj->len < constraint) {
398 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
399 "string is too short: %u, minimum is: %" PRId64,
400 obj->len, constraint);
401 ret = false;
402 break;
403 }
404 }
405 #ifdef HAVE_REGEX_H
406 else if (elt->type == UCL_STRING &&
407 strcmp (ucl_object_key (elt), "pattern") == 0) {
408 if (regcomp (&re, ucl_object_tostring (elt),
409 REG_EXTENDED | REG_NOSUB) != 0) {
410 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
411 "cannot compile pattern %s", ucl_object_tostring (elt));
412 ret = false;
413 break;
414 }
415 if (regexec (&re, ucl_object_tostring (obj), 0, NULL, 0) != 0) {
416 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
417 "string doesn't match regexp %s",
418 ucl_object_tostring (elt));
419 ret = false;
420 }
421 regfree (&re);
422 }
423 #endif
424 }
425
426 return ret;
427 }
428
429 struct ucl_compare_node {
430 const ucl_object_t *obj;
431 TREE_ENTRY(ucl_compare_node) link;
432 struct ucl_compare_node *next;
433 };
434
435 typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t;
436
TREE_DEFINE(ucl_compare_node,link)437 TREE_DEFINE(ucl_compare_node, link)
438
439 static int
440 ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2)
441 {
442 const ucl_object_t *o1 = n1->obj, *o2 = n2->obj;
443
444 return ucl_object_compare (o1, o2);
445 }
446
447 static bool
ucl_schema_array_is_unique(const ucl_object_t * obj,struct ucl_schema_error * err)448 ucl_schema_array_is_unique (const ucl_object_t *obj, struct ucl_schema_error *err)
449 {
450 ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare);
451 ucl_object_iter_t iter = NULL;
452 const ucl_object_t *elt;
453 struct ucl_compare_node *node, test, *nodes = NULL, *tmp;
454 bool ret = true;
455
456 while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) {
457 test.obj = elt;
458 node = TREE_FIND (&tree, ucl_compare_node, link, &test);
459 if (node != NULL) {
460 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt,
461 "duplicate values detected while uniqueItems is true");
462 ret = false;
463 break;
464 }
465 node = calloc (1, sizeof (*node));
466 if (node == NULL) {
467 ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt,
468 "cannot allocate tree node");
469 ret = false;
470 break;
471 }
472 node->obj = elt;
473 TREE_INSERT (&tree, ucl_compare_node, link, node);
474 LL_PREPEND (nodes, node);
475 }
476
477 LL_FOREACH_SAFE (nodes, node, tmp) {
478 free (node);
479 }
480
481 return ret;
482 }
483
484 static bool
ucl_schema_validate_array(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err,const ucl_object_t * root,ucl_object_t * ext_ref)485 ucl_schema_validate_array (const ucl_object_t *schema,
486 const ucl_object_t *obj, struct ucl_schema_error *err,
487 const ucl_object_t *root,
488 ucl_object_t *ext_ref)
489 {
490 const ucl_object_t *elt, *it, *found, *additional_schema = NULL,
491 *first_unvalidated = NULL;
492 ucl_object_iter_t iter = NULL, piter = NULL;
493 bool ret = true, allow_additional = true, need_unique = false;
494 int64_t minmax;
495 unsigned int idx = 0;
496
497 while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
498 if (strcmp (ucl_object_key (elt), "items") == 0) {
499 if (elt->type == UCL_ARRAY) {
500 found = ucl_array_head (obj);
501 while (ret && (it = ucl_object_iterate (elt, &piter, true)) != NULL) {
502 if (found) {
503 ret = ucl_schema_validate (it, found, false, err,
504 root, ext_ref);
505 found = ucl_array_find_index (obj, ++idx);
506 }
507 }
508 if (found != NULL) {
509 /* The first element that is not validated */
510 first_unvalidated = found;
511 }
512 }
513 else if (elt->type == UCL_OBJECT) {
514 /* Validate all items using the specified schema */
515 while (ret && (it = ucl_object_iterate (obj, &piter, true)) != NULL) {
516 ret = ucl_schema_validate (elt, it, false, err, root,
517 ext_ref);
518 }
519 }
520 else {
521 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
522 "items attribute is invalid in schema");
523 ret = false;
524 break;
525 }
526 }
527 else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) {
528 if (elt->type == UCL_BOOLEAN) {
529 if (!ucl_object_toboolean (elt)) {
530 /* Deny additional fields completely */
531 allow_additional = false;
532 }
533 }
534 else if (elt->type == UCL_OBJECT) {
535 /* Define validator for additional fields */
536 additional_schema = elt;
537 }
538 else {
539 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
540 "additionalItems attribute is invalid in schema");
541 ret = false;
542 break;
543 }
544 }
545 else if (elt->type == UCL_BOOLEAN &&
546 strcmp (ucl_object_key (elt), "uniqueItems") == 0) {
547 need_unique = ucl_object_toboolean (elt);
548 }
549 else if (strcmp (ucl_object_key (elt), "minItems") == 0
550 && ucl_object_toint_safe (elt, &minmax)) {
551 if (obj->len < minmax) {
552 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
553 "array has not enough items: %u, minimum is: %u",
554 obj->len, (unsigned)minmax);
555 ret = false;
556 break;
557 }
558 }
559 else if (strcmp (ucl_object_key (elt), "maxItems") == 0
560 && ucl_object_toint_safe (elt, &minmax)) {
561 if (obj->len > minmax) {
562 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
563 "array has too many items: %u, maximum is: %u",
564 obj->len, (unsigned)minmax);
565 ret = false;
566 break;
567 }
568 }
569 }
570
571 if (ret) {
572 /* Additional properties */
573 if (!allow_additional || additional_schema != NULL) {
574 if (first_unvalidated != NULL) {
575 if (!allow_additional) {
576 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
577 "array has undefined item");
578 ret = false;
579 }
580 else if (additional_schema != NULL) {
581 elt = ucl_array_find_index (obj, idx);
582 while (elt) {
583 if (!ucl_schema_validate (additional_schema, elt, false,
584 err, root, ext_ref)) {
585 ret = false;
586 break;
587 }
588 elt = ucl_array_find_index (obj, idx ++);
589 }
590 }
591 }
592 }
593 /* Required properties */
594 if (ret && need_unique) {
595 ret = ucl_schema_array_is_unique (obj, err);
596 }
597 }
598
599 return ret;
600 }
601
602 /*
603 * Returns whether this object is allowed for this type
604 */
605 static bool
ucl_schema_type_is_allowed(const ucl_object_t * type,const ucl_object_t * obj,struct ucl_schema_error * err)606 ucl_schema_type_is_allowed (const ucl_object_t *type, const ucl_object_t *obj,
607 struct ucl_schema_error *err)
608 {
609 ucl_object_iter_t iter = NULL;
610 const ucl_object_t *elt;
611 const char *type_str;
612 ucl_type_t t;
613
614 if (type == NULL) {
615 /* Any type is allowed */
616 return true;
617 }
618
619 if (type->type == UCL_ARRAY) {
620 /* One of allowed types */
621 while ((elt = ucl_object_iterate (type, &iter, true)) != NULL) {
622 if (ucl_schema_type_is_allowed (elt, obj, err)) {
623 return true;
624 }
625 }
626 }
627 else if (type->type == UCL_STRING) {
628 type_str = ucl_object_tostring (type);
629 if (!ucl_object_string_to_type (type_str, &t)) {
630 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, type,
631 "Type attribute is invalid in schema");
632 return false;
633 }
634 if (obj->type != t) {
635 /* Some types are actually compatible */
636 if (obj->type == UCL_TIME && t == UCL_FLOAT) {
637 return true;
638 }
639 else if (obj->type == UCL_INT && t == UCL_FLOAT) {
640 return true;
641 }
642 else {
643 ucl_schema_create_error (err, UCL_SCHEMA_TYPE_MISMATCH, obj,
644 "Invalid type of %s, expected %s",
645 ucl_object_type_to_string (obj->type),
646 ucl_object_type_to_string (t));
647 }
648 }
649 else {
650 /* Types are equal */
651 return true;
652 }
653 }
654
655 return false;
656 }
657
658 /*
659 * Check if object is equal to one of elements of enum
660 */
661 static bool
ucl_schema_validate_enum(const ucl_object_t * en,const ucl_object_t * obj,struct ucl_schema_error * err)662 ucl_schema_validate_enum (const ucl_object_t *en, const ucl_object_t *obj,
663 struct ucl_schema_error *err)
664 {
665 ucl_object_iter_t iter = NULL;
666 const ucl_object_t *elt;
667 bool ret = false;
668
669 while ((elt = ucl_object_iterate (en, &iter, true)) != NULL) {
670 if (ucl_object_compare (elt, obj) == 0) {
671 ret = true;
672 break;
673 }
674 }
675
676 if (!ret) {
677 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
678 "object is not one of enumerated patterns");
679 }
680
681 return ret;
682 }
683
684
685 /*
686 * Check a single ref component
687 */
688 static const ucl_object_t *
ucl_schema_resolve_ref_component(const ucl_object_t * cur,const char * refc,int len,struct ucl_schema_error * err)689 ucl_schema_resolve_ref_component (const ucl_object_t *cur,
690 const char *refc, int len,
691 struct ucl_schema_error *err)
692 {
693 const ucl_object_t *res = NULL;
694 char *err_str;
695 int num, i;
696
697 if (cur->type == UCL_OBJECT) {
698 /* Find a key inside an object */
699 res = ucl_object_lookup_len (cur, refc, len);
700 if (res == NULL) {
701 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
702 "reference %s is invalid, missing path component", refc);
703 return NULL;
704 }
705 }
706 else if (cur->type == UCL_ARRAY) {
707 /* We must figure out a number inside array */
708 num = strtoul (refc, &err_str, 10);
709 if (err_str != NULL && *err_str != '/' && *err_str != '\0') {
710 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
711 "reference %s is invalid, invalid item number", refc);
712 return NULL;
713 }
714 res = ucl_array_head (cur);
715 i = 0;
716 while (res != NULL) {
717 if (i == num) {
718 break;
719 }
720 res = res->next;
721 }
722 if (res == NULL) {
723 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur,
724 "reference %s is invalid, item number %d does not exist",
725 refc, num);
726 return NULL;
727 }
728 }
729 else {
730 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
731 "reference %s is invalid, contains primitive object in the path",
732 refc);
733 return NULL;
734 }
735
736 return res;
737 }
738 /*
739 * Find reference schema
740 */
741 static const ucl_object_t *
ucl_schema_resolve_ref(const ucl_object_t * root,const char * ref,struct ucl_schema_error * err,ucl_object_t * ext_ref,ucl_object_t const ** nroot)742 ucl_schema_resolve_ref (const ucl_object_t *root, const char *ref,
743 struct ucl_schema_error *err, ucl_object_t *ext_ref,
744 ucl_object_t const ** nroot)
745 {
746 UT_string *url_err = NULL;
747 struct ucl_parser *parser;
748 const ucl_object_t *res = NULL, *ext_obj = NULL;
749 ucl_object_t *url_obj;
750 const char *p, *c, *hash_ptr = NULL;
751 char *url_copy = NULL;
752 unsigned char *url_buf;
753 size_t url_buflen;
754
755 if (ref[0] != '#') {
756 hash_ptr = strrchr (ref, '#');
757
758 if (hash_ptr) {
759 url_copy = malloc (hash_ptr - ref + 1);
760
761 if (url_copy == NULL) {
762 ucl_schema_create_error (err, UCL_SCHEMA_INTERNAL_ERROR, root,
763 "cannot allocate memory");
764 return NULL;
765 }
766
767 ucl_strlcpy (url_copy, ref, hash_ptr - ref + 1);
768 p = url_copy;
769 }
770 else {
771 /* Full URL */
772 p = ref;
773 }
774
775 ext_obj = ucl_object_lookup (ext_ref, p);
776
777 if (ext_obj == NULL) {
778 if (ucl_strnstr (p, "://", strlen (p)) != NULL) {
779 if (!ucl_fetch_url (p, &url_buf, &url_buflen, &url_err, true)) {
780
781 ucl_schema_create_error (err,
782 UCL_SCHEMA_INVALID_SCHEMA,
783 root,
784 "cannot fetch reference %s: %s",
785 p,
786 url_err != NULL ? utstring_body (url_err)
787 : "unknown");
788 free (url_copy);
789
790 return NULL;
791 }
792 }
793 else {
794 if (!ucl_fetch_file (p, &url_buf, &url_buflen, &url_err,
795 true)) {
796 ucl_schema_create_error (err,
797 UCL_SCHEMA_INVALID_SCHEMA,
798 root,
799 "cannot fetch reference %s: %s",
800 p,
801 url_err != NULL ? utstring_body (url_err)
802 : "unknown");
803 free (url_copy);
804
805 return NULL;
806 }
807 }
808
809 parser = ucl_parser_new (0);
810
811 if (!ucl_parser_add_chunk (parser, url_buf, url_buflen)) {
812 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root,
813 "cannot fetch reference %s: %s", p,
814 ucl_parser_get_error (parser));
815 ucl_parser_free (parser);
816 free (url_copy);
817
818 return NULL;
819 }
820
821 url_obj = ucl_parser_get_object (parser);
822 ext_obj = url_obj;
823 ucl_object_insert_key (ext_ref, url_obj, p, 0, true);
824 free (url_buf);
825 }
826
827 free (url_copy);
828
829 if (hash_ptr) {
830 p = hash_ptr + 1;
831 }
832 else {
833 p = "";
834 }
835 }
836 else {
837 p = ref + 1;
838 }
839
840 res = ext_obj != NULL ? ext_obj : root;
841 *nroot = res;
842
843 if (*p == '/') {
844 p++;
845 }
846 else if (*p == '\0') {
847 return res;
848 }
849
850 c = p;
851
852 while (*p != '\0') {
853 if (*p == '/') {
854 if (p - c == 0) {
855 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
856 "reference %s is invalid, empty path component", ref);
857 return NULL;
858 }
859 /* Now we have some url part, so we need to figure out where we are */
860 res = ucl_schema_resolve_ref_component (res, c, p - c, err);
861 if (res == NULL) {
862 return NULL;
863 }
864 c = p + 1;
865 }
866 p ++;
867 }
868
869 if (p - c != 0) {
870 res = ucl_schema_resolve_ref_component (res, c, p - c, err);
871 }
872
873 if (res == NULL || res->type != UCL_OBJECT) {
874 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res,
875 "reference %s is invalid, cannot find specified object",
876 ref);
877 return NULL;
878 }
879
880 return res;
881 }
882
883 static bool
ucl_schema_validate_values(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err)884 ucl_schema_validate_values (const ucl_object_t *schema, const ucl_object_t *obj,
885 struct ucl_schema_error *err)
886 {
887 const ucl_object_t *elt, *cur;
888 int64_t constraint, i;
889
890 elt = ucl_object_lookup (schema, "maxValues");
891 if (elt != NULL && elt->type == UCL_INT) {
892 constraint = ucl_object_toint (elt);
893 cur = obj;
894 i = 0;
895 while (cur) {
896 if (i > constraint) {
897 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
898 "object has more values than defined: %ld",
899 (long int)constraint);
900 return false;
901 }
902 i ++;
903 cur = cur->next;
904 }
905 }
906 elt = ucl_object_lookup (schema, "minValues");
907 if (elt != NULL && elt->type == UCL_INT) {
908 constraint = ucl_object_toint (elt);
909 cur = obj;
910 i = 0;
911 while (cur) {
912 if (i >= constraint) {
913 break;
914 }
915 i ++;
916 cur = cur->next;
917 }
918 if (i < constraint) {
919 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
920 "object has less values than defined: %ld",
921 (long int)constraint);
922 return false;
923 }
924 }
925
926 return true;
927 }
928
929 static bool
ucl_schema_validate(const ucl_object_t * schema,const ucl_object_t * obj,bool try_array,struct ucl_schema_error * err,const ucl_object_t * root,ucl_object_t * external_refs)930 ucl_schema_validate (const ucl_object_t *schema,
931 const ucl_object_t *obj, bool try_array,
932 struct ucl_schema_error *err,
933 const ucl_object_t *root,
934 ucl_object_t *external_refs)
935 {
936 const ucl_object_t *elt, *cur, *ref_root;
937 ucl_object_iter_t iter = NULL;
938 bool ret;
939
940 if (schema->type != UCL_OBJECT) {
941 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, schema,
942 "schema is %s instead of object",
943 ucl_object_type_to_string (schema->type));
944 return false;
945 }
946
947 if (try_array) {
948 /*
949 * Special case for multiple values
950 */
951 if (!ucl_schema_validate_values (schema, obj, err)) {
952 return false;
953 }
954 LL_FOREACH (obj, cur) {
955 if (!ucl_schema_validate (schema, cur, false, err, root, external_refs)) {
956 return false;
957 }
958 }
959 return true;
960 }
961
962 elt = ucl_object_lookup (schema, "enum");
963 if (elt != NULL && elt->type == UCL_ARRAY) {
964 if (!ucl_schema_validate_enum (elt, obj, err)) {
965 return false;
966 }
967 }
968
969 elt = ucl_object_lookup (schema, "allOf");
970 if (elt != NULL && elt->type == UCL_ARRAY) {
971 iter = NULL;
972 while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
973 ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
974 if (!ret) {
975 return false;
976 }
977 }
978 }
979
980 elt = ucl_object_lookup (schema, "anyOf");
981 if (elt != NULL && elt->type == UCL_ARRAY) {
982 iter = NULL;
983 while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
984 ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
985 if (ret) {
986 break;
987 }
988 }
989 if (!ret) {
990 return false;
991 }
992 else {
993 /* Reset error */
994 err->code = UCL_SCHEMA_OK;
995 }
996 }
997
998 elt = ucl_object_lookup (schema, "oneOf");
999 if (elt != NULL && elt->type == UCL_ARRAY) {
1000 iter = NULL;
1001 ret = false;
1002 while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
1003 if (!ret) {
1004 ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
1005 }
1006 else if (ucl_schema_validate (cur, obj, true, err, root, external_refs)) {
1007 ret = false;
1008 break;
1009 }
1010 }
1011 if (!ret) {
1012 return false;
1013 }
1014 }
1015
1016 elt = ucl_object_lookup (schema, "not");
1017 if (elt != NULL && elt->type == UCL_OBJECT) {
1018 if (ucl_schema_validate (elt, obj, true, err, root, external_refs)) {
1019 return false;
1020 }
1021 else {
1022 /* Reset error */
1023 err->code = UCL_SCHEMA_OK;
1024 }
1025 }
1026
1027 elt = ucl_object_lookup (schema, "$ref");
1028 if (elt != NULL) {
1029 ref_root = root;
1030 cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt),
1031 err, external_refs, &ref_root);
1032
1033 if (cur == NULL) {
1034 return false;
1035 }
1036 if (!ucl_schema_validate (cur, obj, try_array, err, ref_root,
1037 external_refs)) {
1038 return false;
1039 }
1040 }
1041
1042 elt = ucl_object_lookup (schema, "type");
1043 if (!ucl_schema_type_is_allowed (elt, obj, err)) {
1044 return false;
1045 }
1046
1047 switch (obj->type) {
1048 case UCL_OBJECT:
1049 return ucl_schema_validate_object (schema, obj, err, root, external_refs);
1050 break;
1051 case UCL_ARRAY:
1052 return ucl_schema_validate_array (schema, obj, err, root, external_refs);
1053 break;
1054 case UCL_INT:
1055 case UCL_FLOAT:
1056 return ucl_schema_validate_number (schema, obj, err);
1057 break;
1058 case UCL_STRING:
1059 return ucl_schema_validate_string (schema, obj, err);
1060 break;
1061 default:
1062 break;
1063 }
1064
1065 return true;
1066 }
1067
1068 bool
ucl_object_validate(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err)1069 ucl_object_validate (const ucl_object_t *schema,
1070 const ucl_object_t *obj, struct ucl_schema_error *err)
1071 {
1072 return ucl_object_validate_root_ext (schema, obj, schema, NULL, err);
1073 }
1074
1075 bool
ucl_object_validate_root(const ucl_object_t * schema,const ucl_object_t * obj,const ucl_object_t * root,struct ucl_schema_error * err)1076 ucl_object_validate_root (const ucl_object_t *schema,
1077 const ucl_object_t *obj,
1078 const ucl_object_t *root,
1079 struct ucl_schema_error *err)
1080 {
1081 return ucl_object_validate_root_ext (schema, obj, root, NULL, err);
1082 }
1083
1084 bool
ucl_object_validate_root_ext(const ucl_object_t * schema,const ucl_object_t * obj,const ucl_object_t * root,ucl_object_t * ext_refs,struct ucl_schema_error * err)1085 ucl_object_validate_root_ext (const ucl_object_t *schema,
1086 const ucl_object_t *obj,
1087 const ucl_object_t *root,
1088 ucl_object_t *ext_refs,
1089 struct ucl_schema_error *err)
1090 {
1091 bool ret, need_unref = false;
1092
1093 if (ext_refs == NULL) {
1094 ext_refs = ucl_object_typed_new (UCL_OBJECT);
1095 need_unref = true;
1096 }
1097
1098 ret = ucl_schema_validate (schema, obj, true, err, root, ext_refs);
1099
1100 if (need_unref) {
1101 ucl_object_unref (ext_refs);
1102 }
1103
1104 return ret;
1105 }
1106