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