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