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 */
52
53#ifdef __GNUC__
54static inline void
55ucl_schema_create_error (struct ucl_schema_error *err,
56		enum ucl_schema_error_code code, const ucl_object_t *obj,
57		const char *fmt, ...)
58__attribute__ (( format( printf, 4, 5) ));
59#endif
60
61static inline void
62ucl_schema_create_error (struct ucl_schema_error *err,
63		enum ucl_schema_error_code code, const ucl_object_t *obj,
64		const char *fmt, ...)
65{
66	va_list va;
67
68	if (err != NULL) {
69		err->code = code;
70		err->obj = obj;
71		va_start (va, fmt);
72		vsnprintf (err->msg, sizeof (err->msg), fmt, va);
73		va_end (va);
74	}
75}
76
77/*
78 * Check whether we have a pattern specified
79 */
80static const ucl_object_t *
81ucl_schema_test_pattern (const ucl_object_t *obj, const char *pattern, bool recursive)
82{
83	const ucl_object_t *res = NULL;
84#ifdef HAVE_REGEX_H
85	regex_t reg;
86	const ucl_object_t *elt;
87	ucl_object_iter_t iter = NULL;
88
89	if (regcomp (&reg, pattern, REG_EXTENDED | REG_NOSUB) == 0) {
90		if (recursive) {
91			while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) {
92				if (regexec (&reg, ucl_object_key (elt), 0, NULL, 0) == 0) {
93					res = elt;
94					break;
95				}
96			}
97		} else {
98			if (regexec (&reg, ucl_object_key (obj), 0, NULL, 0) == 0)
99				res = obj;
100		}
101		regfree (&reg);
102	}
103#endif
104	return res;
105}
106
107/*
108 * Check dependencies for an object
109 */
110static bool
111ucl_schema_validate_dependencies (const ucl_object_t *deps,
112		const ucl_object_t *obj, struct ucl_schema_error *err,
113		const ucl_object_t *root,
114		ucl_object_t *ext_ref)
115{
116	const ucl_object_t *elt, *cur, *cur_dep;
117	ucl_object_iter_t iter = NULL, piter;
118	bool ret = true;
119
120	while (ret && (cur = ucl_object_iterate (deps, &iter, true)) != NULL) {
121		elt = ucl_object_lookup (obj, ucl_object_key (cur));
122		if (elt != NULL) {
123			/* Need to check dependencies */
124			if (cur->type == UCL_ARRAY) {
125				piter = NULL;
126				while (ret && (cur_dep = ucl_object_iterate (cur, &piter, true)) != NULL) {
127					if (ucl_object_lookup (obj, ucl_object_tostring (cur_dep)) == NULL) {
128						ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt,
129								"dependency %s is missing for key %s",
130								ucl_object_tostring (cur_dep), ucl_object_key (cur));
131						ret = false;
132						break;
133					}
134				}
135			}
136			else if (cur->type == UCL_OBJECT) {
137				ret = ucl_schema_validate (cur, obj, true, err, root, ext_ref);
138			}
139		}
140	}
141
142	return ret;
143}
144
145/*
146 * Validate object
147 */
148static bool
149ucl_schema_validate_object (const ucl_object_t *schema,
150		const ucl_object_t *obj, struct ucl_schema_error *err,
151		const ucl_object_t *root,
152		ucl_object_t *ext_ref)
153{
154	const ucl_object_t *elt, *prop, *found, *additional_schema = NULL,
155			*required = NULL, *pat, *pelt;
156	ucl_object_iter_t iter = NULL, piter = NULL;
157	bool ret = true, allow_additional = true;
158	int64_t minmax;
159
160	while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
161		if (elt->type == UCL_OBJECT &&
162				strcmp (ucl_object_key (elt), "properties") == 0) {
163			piter = NULL;
164			while (ret && (prop = ucl_object_iterate (elt, &piter, true)) != NULL) {
165				found = ucl_object_lookup (obj, ucl_object_key (prop));
166				if (found) {
167					ret = ucl_schema_validate (prop, found, true, err, root,
168							ext_ref);
169				}
170			}
171		}
172		else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) {
173			if (elt->type == UCL_BOOLEAN) {
174				if (!ucl_object_toboolean (elt)) {
175					/* Deny additional fields completely */
176					allow_additional = false;
177				}
178			}
179			else if (elt->type == UCL_OBJECT) {
180				/* Define validator for additional fields */
181				additional_schema = elt;
182			}
183			else {
184				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
185						"additionalProperties attribute is invalid in schema");
186				ret = false;
187				break;
188			}
189		}
190		else if (strcmp (ucl_object_key (elt), "required") == 0) {
191			if (elt->type == UCL_ARRAY) {
192				required = elt;
193			}
194			else {
195				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
196						"required attribute is invalid in schema");
197				ret = false;
198				break;
199			}
200		}
201		else if (strcmp (ucl_object_key (elt), "minProperties") == 0
202				&& ucl_object_toint_safe (elt, &minmax)) {
203			if (obj->len < minmax) {
204				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
205						"object has not enough properties: %u, minimum is: %u",
206						obj->len, (unsigned)minmax);
207				ret = false;
208				break;
209			}
210		}
211		else if (strcmp (ucl_object_key (elt), "maxProperties") == 0
212				&& 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
303static bool
304ucl_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
369static bool
370ucl_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
428struct ucl_compare_node {
429	const ucl_object_t *obj;
430	TREE_ENTRY(ucl_compare_node) link;
431	struct ucl_compare_node *next;
432};
433
434typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t;
435
436TREE_DEFINE(ucl_compare_node, link)
437
438static int
439ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2)
440{
441	const ucl_object_t *o1 = n1->obj, *o2 = n2->obj;
442
443	return ucl_object_compare (o1, o2);
444}
445
446static bool
447ucl_schema_array_is_unique (const ucl_object_t *obj, struct ucl_schema_error *err)
448{
449	ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare);
450	ucl_object_iter_t iter = NULL;
451	const ucl_object_t *elt;
452	struct ucl_compare_node *node, test, *nodes = NULL, *tmp;
453	bool ret = true;
454
455	while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) {
456		test.obj = elt;
457		node = TREE_FIND (&tree, ucl_compare_node, link, &test);
458		if (node != NULL) {
459			ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt,
460					"duplicate values detected while uniqueItems is true");
461			ret = false;
462			break;
463		}
464		node = calloc (1, sizeof (*node));
465		if (node == NULL) {
466			ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt,
467					"cannot allocate tree node");
468			ret = false;
469			break;
470		}
471		node->obj = elt;
472		TREE_INSERT (&tree, ucl_compare_node, link, node);
473		LL_PREPEND (nodes, node);
474	}
475
476	LL_FOREACH_SAFE (nodes, node, tmp) {
477		free (node);
478	}
479
480	return ret;
481}
482
483static bool
484ucl_schema_validate_array (const ucl_object_t *schema,
485		const ucl_object_t *obj, struct ucl_schema_error *err,
486		const ucl_object_t *root,
487		ucl_object_t *ext_ref)
488{
489	const ucl_object_t *elt, *it, *found, *additional_schema = NULL,
490			*first_unvalidated = NULL;
491	ucl_object_iter_t iter = NULL, piter = NULL;
492	bool ret = true, allow_additional = true, need_unique = false;
493	int64_t minmax;
494	unsigned int idx = 0;
495
496	while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) {
497		if (strcmp (ucl_object_key (elt), "items") == 0) {
498			if (elt->type == UCL_ARRAY) {
499				found = ucl_array_head (obj);
500				while (ret && (it = ucl_object_iterate (elt, &piter, true)) != NULL) {
501					if (found) {
502						ret = ucl_schema_validate (it, found, false, err,
503								root, ext_ref);
504						found = ucl_array_find_index (obj, ++idx);
505					}
506				}
507				if (found != NULL) {
508					/* The first element that is not validated */
509					first_unvalidated = found;
510				}
511			}
512			else if (elt->type == UCL_OBJECT) {
513				/* Validate all items using the specified schema */
514				while (ret && (it = ucl_object_iterate (obj, &piter, true)) != NULL) {
515					ret = ucl_schema_validate (elt, it, false, err, root,
516							ext_ref);
517				}
518			}
519			else {
520				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
521						"items attribute is invalid in schema");
522				ret = false;
523				break;
524			}
525		}
526		else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) {
527			if (elt->type == UCL_BOOLEAN) {
528				if (!ucl_object_toboolean (elt)) {
529					/* Deny additional fields completely */
530					allow_additional = false;
531				}
532			}
533			else if (elt->type == UCL_OBJECT) {
534				/* Define validator for additional fields */
535				additional_schema = elt;
536			}
537			else {
538				ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt,
539						"additionalItems attribute is invalid in schema");
540				ret = false;
541				break;
542			}
543		}
544		else if (elt->type == UCL_BOOLEAN &&
545				strcmp (ucl_object_key (elt), "uniqueItems") == 0) {
546			need_unique = ucl_object_toboolean (elt);
547		}
548		else if (strcmp (ucl_object_key (elt), "minItems") == 0
549				&& ucl_object_toint_safe (elt, &minmax)) {
550			if (obj->len < minmax) {
551				ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj,
552						"array has not enough items: %u, minimum is: %u",
553						obj->len, (unsigned)minmax);
554				ret = false;
555				break;
556			}
557		}
558		else if (strcmp (ucl_object_key (elt), "maxItems") == 0
559				&& 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 */
604static bool
605ucl_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 */
660static bool
661ucl_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 */
687static const ucl_object_t *
688ucl_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 */
740static const ucl_object_t *
741ucl_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
882static bool
883ucl_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
928static bool
929ucl_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			if (!ucl_schema_validate (schema, cur, false, err, root, external_refs)) {
955				return false;
956			}
957		}
958		return true;
959	}
960
961	elt = ucl_object_lookup (schema, "enum");
962	if (elt != NULL && elt->type == UCL_ARRAY) {
963		if (!ucl_schema_validate_enum (elt, obj, err)) {
964			return false;
965		}
966	}
967
968	elt = ucl_object_lookup (schema, "allOf");
969	if (elt != NULL && elt->type == UCL_ARRAY) {
970		iter = NULL;
971		while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
972			ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
973			if (!ret) {
974				return false;
975			}
976		}
977	}
978
979	elt = ucl_object_lookup (schema, "anyOf");
980	if (elt != NULL && elt->type == UCL_ARRAY) {
981		iter = NULL;
982		while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
983			ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
984			if (ret) {
985				break;
986			}
987		}
988		if (!ret) {
989			return false;
990		}
991		else {
992			/* Reset error */
993			err->code = UCL_SCHEMA_OK;
994		}
995	}
996
997	elt = ucl_object_lookup (schema, "oneOf");
998	if (elt != NULL && elt->type == UCL_ARRAY) {
999		iter = NULL;
1000		ret = false;
1001		while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) {
1002			if (!ret) {
1003				ret = ucl_schema_validate (cur, obj, true, err, root, external_refs);
1004			}
1005			else if (ucl_schema_validate (cur, obj, true, err, root, external_refs)) {
1006				ret = false;
1007				break;
1008			}
1009		}
1010		if (!ret) {
1011			return false;
1012		}
1013	}
1014
1015	elt = ucl_object_lookup (schema, "not");
1016	if (elt != NULL && elt->type == UCL_OBJECT) {
1017		if (ucl_schema_validate (elt, obj, true, err, root, external_refs)) {
1018			return false;
1019		}
1020		else {
1021			/* Reset error */
1022			err->code = UCL_SCHEMA_OK;
1023		}
1024	}
1025
1026	elt = ucl_object_lookup (schema, "$ref");
1027	if (elt != NULL) {
1028		ref_root = root;
1029		cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt),
1030				err, external_refs, &ref_root);
1031
1032		if (cur == NULL) {
1033			return false;
1034		}
1035		if (!ucl_schema_validate (cur, obj, try_array, err, ref_root,
1036				external_refs)) {
1037			return false;
1038		}
1039	}
1040
1041	elt = ucl_object_lookup (schema, "type");
1042	if (!ucl_schema_type_is_allowed (elt, obj, err)) {
1043		return false;
1044	}
1045
1046	switch (obj->type) {
1047	case UCL_OBJECT:
1048		return ucl_schema_validate_object (schema, obj, err, root, external_refs);
1049		break;
1050	case UCL_ARRAY:
1051		return ucl_schema_validate_array (schema, obj, err, root, external_refs);
1052		break;
1053	case UCL_INT:
1054	case UCL_FLOAT:
1055		return ucl_schema_validate_number (schema, obj, err);
1056		break;
1057	case UCL_STRING:
1058		return ucl_schema_validate_string (schema, obj, err);
1059		break;
1060	default:
1061		break;
1062	}
1063
1064	return true;
1065}
1066
1067bool
1068ucl_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
1074bool
1075ucl_object_validate_root (const ucl_object_t *schema,
1076		const ucl_object_t *obj,
1077		const ucl_object_t *root,
1078		struct ucl_schema_error *err)
1079{
1080	return ucl_object_validate_root_ext (schema, obj, root, NULL, err);
1081}
1082
1083bool
1084ucl_object_validate_root_ext (const ucl_object_t *schema,
1085		const ucl_object_t *obj,
1086		const ucl_object_t *root,
1087		ucl_object_t *ext_refs,
1088		struct ucl_schema_error *err)
1089{
1090	bool ret, need_unref = false;
1091
1092	if (ext_refs == NULL) {
1093		ext_refs = ucl_object_typed_new (UCL_OBJECT);
1094		need_unref = true;
1095	}
1096
1097	ret = ucl_schema_validate (schema, obj, true, err, root, ext_refs);
1098
1099	if (need_unref) {
1100		ucl_object_unref (ext_refs);
1101	}
1102
1103	return ret;
1104}
1105