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