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