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 }
99 else {
100 if (regexec(®, ucl_object_key(obj), 0, NULL, 0) == 0)
101 res = obj;
102 }
103 regfree(®);
104 }
105 #endif
106 return res;
107 }
108
109 /*
110 * Check dependencies for an object
111 */
112 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)113 ucl_schema_validate_dependencies(const ucl_object_t *deps,
114 const ucl_object_t *obj, struct ucl_schema_error *err,
115 const ucl_object_t *root,
116 ucl_object_t *ext_ref)
117 {
118 const ucl_object_t *elt, *cur, *cur_dep;
119 ucl_object_iter_t iter = NULL, piter;
120 bool ret = true;
121
122 while (ret && (cur = ucl_object_iterate(deps, &iter, true)) != NULL) {
123 elt = ucl_object_lookup(obj, ucl_object_key(cur));
124 if (elt != NULL) {
125 /* Need to check dependencies */
126 if (cur->type == UCL_ARRAY) {
127 piter = NULL;
128 while (ret && (cur_dep = ucl_object_iterate(cur, &piter, true)) != NULL) {
129 if (ucl_object_lookup(obj, ucl_object_tostring(cur_dep)) == NULL) {
130 ucl_schema_create_error(err, UCL_SCHEMA_MISSING_DEPENDENCY, elt,
131 "dependency %s is missing for key %s",
132 ucl_object_tostring(cur_dep), ucl_object_key(cur));
133 ret = false;
134 break;
135 }
136 }
137 }
138 else if (cur->type == UCL_OBJECT) {
139 ret = ucl_schema_validate(cur, obj, true, err, root, ext_ref);
140 }
141 }
142 }
143
144 return ret;
145 }
146
147 /*
148 * Validate object
149 */
150 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)151 ucl_schema_validate_object(const ucl_object_t *schema,
152 const ucl_object_t *obj, struct ucl_schema_error *err,
153 const ucl_object_t *root,
154 ucl_object_t *ext_ref)
155 {
156 const ucl_object_t *elt, *prop, *found, *additional_schema = NULL,
157 *required = NULL, *pat, *pelt;
158 ucl_object_iter_t iter = NULL, piter = NULL;
159 bool ret = true, allow_additional = true;
160 int64_t minmax;
161
162 while (ret && (elt = ucl_object_iterate(schema, &iter, true)) != NULL) {
163 if (elt->type == UCL_OBJECT &&
164 strcmp(ucl_object_key(elt), "properties") == 0) {
165 piter = NULL;
166 while (ret && (prop = ucl_object_iterate(elt, &piter, true)) != NULL) {
167 found = ucl_object_lookup(obj, ucl_object_key(prop));
168 if (found) {
169 ret = ucl_schema_validate(prop, found, true, err, root,
170 ext_ref);
171 }
172 }
173 }
174 else if (strcmp(ucl_object_key(elt), "additionalProperties") == 0) {
175 if (elt->type == UCL_BOOLEAN) {
176 if (!ucl_object_toboolean(elt)) {
177 /* Deny additional fields completely */
178 allow_additional = false;
179 }
180 }
181 else if (elt->type == UCL_OBJECT) {
182 /* Define validator for additional fields */
183 additional_schema = elt;
184 }
185 else {
186 ucl_schema_create_error(err, UCL_SCHEMA_INVALID_SCHEMA, elt,
187 "additionalProperties attribute is invalid in schema");
188 ret = false;
189 break;
190 }
191 }
192 else if (strcmp(ucl_object_key(elt), "required") == 0) {
193 if (elt->type == UCL_ARRAY) {
194 required = elt;
195 }
196 else {
197 ucl_schema_create_error(err, UCL_SCHEMA_INVALID_SCHEMA, elt,
198 "required attribute is invalid in schema");
199 ret = false;
200 break;
201 }
202 }
203 else if (strcmp(ucl_object_key(elt), "minProperties") == 0 && 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 && 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)
431 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 {
479 free(node);
480 }
481
482 return ret;
483 }
484
485 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)486 ucl_schema_validate_array(const ucl_object_t *schema,
487 const ucl_object_t *obj, struct ucl_schema_error *err,
488 const ucl_object_t *root,
489 ucl_object_t *ext_ref)
490 {
491 const ucl_object_t *elt, *it, *found, *additional_schema = NULL,
492 *first_unvalidated = NULL;
493 ucl_object_iter_t iter = NULL, piter = NULL;
494 bool ret = true, allow_additional = true, need_unique = false;
495 int64_t minmax;
496 unsigned int idx = 0;
497
498 while (ret && (elt = ucl_object_iterate(schema, &iter, true)) != NULL) {
499 if (strcmp(ucl_object_key(elt), "items") == 0) {
500 if (elt->type == UCL_ARRAY) {
501 found = ucl_array_head(obj);
502 while (ret && (it = ucl_object_iterate(elt, &piter, true)) != NULL) {
503 if (found) {
504 ret = ucl_schema_validate(it, found, false, err,
505 root, ext_ref);
506 found = ucl_array_find_index(obj, ++idx);
507 }
508 }
509 if (found != NULL) {
510 /* The first element that is not validated */
511 first_unvalidated = found;
512 }
513 }
514 else if (elt->type == UCL_OBJECT) {
515 /* Validate all items using the specified schema */
516 while (ret && (it = ucl_object_iterate(obj, &piter, true)) != NULL) {
517 ret = ucl_schema_validate(elt, it, false, err, root,
518 ext_ref);
519 }
520 }
521 else {
522 ucl_schema_create_error(err, UCL_SCHEMA_INVALID_SCHEMA, elt,
523 "items attribute is invalid in schema");
524 ret = false;
525 break;
526 }
527 }
528 else if (strcmp(ucl_object_key(elt), "additionalItems") == 0) {
529 if (elt->type == UCL_BOOLEAN) {
530 if (!ucl_object_toboolean(elt)) {
531 /* Deny additional fields completely */
532 allow_additional = false;
533 }
534 }
535 else if (elt->type == UCL_OBJECT) {
536 /* Define validator for additional fields */
537 additional_schema = elt;
538 }
539 else {
540 ucl_schema_create_error(err, UCL_SCHEMA_INVALID_SCHEMA, elt,
541 "additionalItems attribute is invalid in schema");
542 ret = false;
543 break;
544 }
545 }
546 else if (elt->type == UCL_BOOLEAN &&
547 strcmp(ucl_object_key(elt), "uniqueItems") == 0) {
548 need_unique = ucl_object_toboolean(elt);
549 }
550 else if (strcmp(ucl_object_key(elt), "minItems") == 0 && 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 && 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 {
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
ucl_object_validate(const ucl_object_t * schema,const ucl_object_t * obj,struct ucl_schema_error * err)1068 bool 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
ucl_object_validate_root(const ucl_object_t * schema,const ucl_object_t * obj,const ucl_object_t * root,struct ucl_schema_error * err)1074 bool ucl_object_validate_root(const ucl_object_t *schema,
1075 const ucl_object_t *obj,
1076 const ucl_object_t *root,
1077 struct ucl_schema_error *err)
1078 {
1079 return ucl_object_validate_root_ext(schema, obj, root, NULL, err);
1080 }
1081
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)1082 bool ucl_object_validate_root_ext(const ucl_object_t *schema,
1083 const ucl_object_t *obj,
1084 const ucl_object_t *root,
1085 ucl_object_t *ext_refs,
1086 struct ucl_schema_error *err)
1087 {
1088 bool ret, need_unref = false;
1089
1090 if (ext_refs == NULL) {
1091 ext_refs = ucl_object_typed_new(UCL_OBJECT);
1092 need_unref = true;
1093 }
1094
1095 ret = ucl_schema_validate(schema, obj, true, err, root, ext_refs);
1096
1097 if (need_unref) {
1098 ucl_object_unref(ext_refs);
1099 }
1100
1101 return ret;
1102 }
1103