xref: /freebsd/contrib/libucl/src/ucl_schema.c (revision e64bea71c21eb42e97aa615188ba91f6cce0d36d)
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
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 *
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(&reg, pattern, REG_EXTENDED | REG_NOSUB) == 0) {
91 		if (recursive) {
92 			while ((elt = ucl_object_iterate(obj, &iter, true)) != NULL) {
93 				if (regexec(&reg, ucl_object_key(elt), 0, NULL, 0) == 0) {
94 					res = elt;
95 					break;
96 				}
97 			}
98 		}
99 		else {
100 			if (regexec(&reg, ucl_object_key(obj), 0, NULL, 0) == 0)
101 				res = obj;
102 		}
103 		regfree(&reg);
104 	}
105 #endif
106 	return res;
107 }
108 
109 /*
110  * Check dependencies for an object
111  */
112 static bool
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
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
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
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 
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
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
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
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
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 *
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 *
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
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
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 
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 
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 
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