ucl_schema.c revision 298166
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
43static 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 */
52static void
53ucl_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 */
71static const ucl_object_t *
72ucl_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 */
96static bool
97ucl_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 */
134static bool
135ucl_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
280static bool
281ucl_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
346static bool
347ucl_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
405struct ucl_compare_node {
406	const ucl_object_t *obj;
407	TREE_ENTRY(ucl_compare_node) link;
408	struct ucl_compare_node *next;
409};
410
411typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t;
412
413TREE_DEFINE(ucl_compare_node, link)
414
415static int
416ucl_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
423static bool
424ucl_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
460static bool
461ucl_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 */
581static bool
582ucl_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 */
637static bool
638ucl_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 */
664static const ucl_object_t *
665ucl_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 */
717static const ucl_object_t *
718ucl_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
859static bool
860ucl_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
905static bool
906ucl_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
1044bool
1045ucl_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
1051bool
1052ucl_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
1060bool
1061ucl_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