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