diff options
Diffstat (limited to 'contrib/libucl/ucl_schema.c')
-rw-r--r-- | contrib/libucl/ucl_schema.c | 1104 |
1 files changed, 1104 insertions, 0 deletions
diff --git a/contrib/libucl/ucl_schema.c b/contrib/libucl/ucl_schema.c new file mode 100644 index 0000000..68f0118 --- /dev/null +++ b/contrib/libucl/ucl_schema.c @@ -0,0 +1,1104 @@ +/* + * Copyright (c) 2014, Vsevolod Stakhov + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucl.h" +#include "ucl_internal.h" +#include "tree.h" +#include "utlist.h" +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif +#ifdef HAVE_STDIO_H +#include <stdio.h> +#endif +#ifdef HAVE_REGEX_H +#include <regex.h> +#endif +#ifdef HAVE_MATH_H +#include <math.h> +#endif + +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 *ext_ref); + +/* + * Create validation error + */ + +#ifdef __GNUC__ +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, ...) +__attribute__ (( format( printf, 4, 5) )); +#endif + +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, ...) +{ + va_list va; + + if (err != NULL) { + err->code = code; + err->obj = obj; + va_start (va, fmt); + vsnprintf (err->msg, sizeof (err->msg), fmt, va); + va_end (va); + } +} + +/* + * Check whether we have a pattern specified + */ +static const ucl_object_t * +ucl_schema_test_pattern (const ucl_object_t *obj, const char *pattern, bool recursive) +{ + const ucl_object_t *res = NULL; +#ifdef HAVE_REGEX_H + regex_t reg; + const ucl_object_t *elt; + ucl_object_iter_t iter = NULL; + + if (regcomp (®, pattern, REG_EXTENDED | REG_NOSUB) == 0) { + if (recursive) { + while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) { + if (regexec (®, ucl_object_key (elt), 0, NULL, 0) == 0) { + res = elt; + break; + } + } + } else { + if (regexec (®, ucl_object_key (obj), 0, NULL, 0) == 0) + res = obj; + } + regfree (®); + } +#endif + return res; +} + +/* + * Check dependencies for an object + */ +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) +{ + const ucl_object_t *elt, *cur, *cur_dep; + ucl_object_iter_t iter = NULL, piter; + bool ret = true; + + while (ret && (cur = ucl_object_iterate (deps, &iter, true)) != NULL) { + elt = ucl_object_lookup (obj, ucl_object_key (cur)); + if (elt != NULL) { + /* Need to check dependencies */ + if (cur->type == UCL_ARRAY) { + piter = NULL; + while (ret && (cur_dep = ucl_object_iterate (cur, &piter, true)) != NULL) { + if (ucl_object_lookup (obj, ucl_object_tostring (cur_dep)) == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt, + "dependency %s is missing for key %s", + ucl_object_tostring (cur_dep), ucl_object_key (cur)); + ret = false; + break; + } + } + } + else if (cur->type == UCL_OBJECT) { + ret = ucl_schema_validate (cur, obj, true, err, root, ext_ref); + } + } + } + + return ret; +} + +/* + * Validate object + */ +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) +{ + const ucl_object_t *elt, *prop, *found, *additional_schema = NULL, + *required = NULL, *pat, *pelt; + ucl_object_iter_t iter = NULL, piter = NULL; + bool ret = true, allow_additional = true; + int64_t minmax; + + while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) { + if (elt->type == UCL_OBJECT && + strcmp (ucl_object_key (elt), "properties") == 0) { + piter = NULL; + while (ret && (prop = ucl_object_iterate (elt, &piter, true)) != NULL) { + found = ucl_object_lookup (obj, ucl_object_key (prop)); + if (found) { + ret = ucl_schema_validate (prop, found, true, err, root, + ext_ref); + } + } + } + else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) { + if (elt->type == UCL_BOOLEAN) { + if (!ucl_object_toboolean (elt)) { + /* Deny additional fields completely */ + allow_additional = false; + } + } + else if (elt->type == UCL_OBJECT) { + /* Define validator for additional fields */ + additional_schema = elt; + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "additionalProperties attribute is invalid in schema"); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "required") == 0) { + if (elt->type == UCL_ARRAY) { + required = elt; + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "required attribute is invalid in schema"); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "minProperties") == 0 + && ucl_object_toint_safe (elt, &minmax)) { + if (obj->len < minmax) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object has not enough properties: %u, minimum is: %u", + obj->len, (unsigned)minmax); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "maxProperties") == 0 + && ucl_object_toint_safe (elt, &minmax)) { + if (obj->len > minmax) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object has too many properties: %u, maximum is: %u", + obj->len, (unsigned)minmax); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "patternProperties") == 0) { + const ucl_object_t *vobj; + ucl_object_iter_t viter; + piter = NULL; + while (ret && (prop = ucl_object_iterate (elt, &piter, true)) != NULL) { + viter = NULL; + while (ret && (vobj = ucl_object_iterate (obj, &viter, true)) != NULL) { + found = ucl_schema_test_pattern (vobj, ucl_object_key (prop), false); + if (found) { + ret = ucl_schema_validate (prop, found, true, err, root, + ext_ref); + } + } + } + } + else if (elt->type == UCL_OBJECT && + strcmp (ucl_object_key (elt), "dependencies") == 0) { + ret = ucl_schema_validate_dependencies (elt, obj, err, root, + ext_ref); + } + } + + if (ret) { + /* Additional properties */ + if (!allow_additional || additional_schema != NULL) { + /* Check if we have exactly the same properties in schema and object */ + iter = ucl_object_iterate_new (obj); + prop = ucl_object_lookup (schema, "properties"); + while ((elt = ucl_object_iterate_safe (iter, true)) != NULL) { + found = ucl_object_lookup (prop, ucl_object_key (elt)); + if (found == NULL) { + /* Try patternProperties */ + pat = ucl_object_lookup (schema, "patternProperties"); + piter = ucl_object_iterate_new (pat); + while ((pelt = ucl_object_iterate_safe (piter, true)) != NULL) { + found = ucl_schema_test_pattern (obj, ucl_object_key (pelt), true); + if (found != NULL) { + break; + } + } + ucl_object_iterate_free (piter); + piter = NULL; + } + if (found == NULL) { + if (!allow_additional) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object has non-allowed property %s", + ucl_object_key (elt)); + ret = false; + break; + } + else if (additional_schema != NULL) { + if (!ucl_schema_validate (additional_schema, elt, + true, err, root, ext_ref)) { + ret = false; + break; + } + } + } + } + ucl_object_iterate_free (iter); + iter = NULL; + } + /* Required properties */ + if (required != NULL) { + iter = NULL; + while ((elt = ucl_object_iterate (required, &iter, true)) != NULL) { + if (ucl_object_lookup (obj, ucl_object_tostring (elt)) == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_MISSING_PROPERTY, obj, + "object has missing property %s", + ucl_object_tostring (elt)); + ret = false; + break; + } + } + } + } + + + return ret; +} + +static bool +ucl_schema_validate_number (const ucl_object_t *schema, + const ucl_object_t *obj, struct ucl_schema_error *err) +{ + const ucl_object_t *elt, *test; + ucl_object_iter_t iter = NULL; + bool ret = true, exclusive = false; + double constraint, val; + const double alpha = 1e-16; + + while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) { + if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && + strcmp (ucl_object_key (elt), "multipleOf") == 0) { + constraint = ucl_object_todouble (elt); + if (constraint <= 0) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "multipleOf must be greater than zero"); + ret = false; + break; + } + val = ucl_object_todouble (obj); + if (fabs (remainder (val, constraint)) > alpha) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "number %.4f is not multiple of %.4f, remainder is %.7f", + val, constraint, remainder (val, constraint)); + ret = false; + break; + } + } + else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && + strcmp (ucl_object_key (elt), "maximum") == 0) { + constraint = ucl_object_todouble (elt); + test = ucl_object_lookup (schema, "exclusiveMaximum"); + if (test && test->type == UCL_BOOLEAN) { + exclusive = ucl_object_toboolean (test); + } + val = ucl_object_todouble (obj); + if (val > constraint || (exclusive && val >= constraint)) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "number is too big: %.3f, maximum is: %.3f", + val, constraint); + ret = false; + break; + } + } + else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && + strcmp (ucl_object_key (elt), "minimum") == 0) { + constraint = ucl_object_todouble (elt); + test = ucl_object_lookup (schema, "exclusiveMinimum"); + if (test && test->type == UCL_BOOLEAN) { + exclusive = ucl_object_toboolean (test); + } + val = ucl_object_todouble (obj); + if (val < constraint || (exclusive && val <= constraint)) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "number is too small: %.3f, minimum is: %.3f", + val, constraint); + ret = false; + break; + } + } + } + + return ret; +} + +static bool +ucl_schema_validate_string (const ucl_object_t *schema, + const ucl_object_t *obj, struct ucl_schema_error *err) +{ + const ucl_object_t *elt; + ucl_object_iter_t iter = NULL; + bool ret = true; + int64_t constraint; +#ifdef HAVE_REGEX_H + regex_t re; +#endif + + while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) { + if (elt->type == UCL_INT && + strcmp (ucl_object_key (elt), "maxLength") == 0) { + constraint = ucl_object_toint (elt); + if (obj->len > constraint) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "string is too big: %u, maximum is: %" PRId64, + obj->len, constraint); + ret = false; + break; + } + } + else if (elt->type == UCL_INT && + strcmp (ucl_object_key (elt), "minLength") == 0) { + constraint = ucl_object_toint (elt); + if (obj->len < constraint) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "string is too short: %u, minimum is: %" PRId64, + obj->len, constraint); + ret = false; + break; + } + } +#ifdef HAVE_REGEX_H + else if (elt->type == UCL_STRING && + strcmp (ucl_object_key (elt), "pattern") == 0) { + if (regcomp (&re, ucl_object_tostring (elt), + REG_EXTENDED | REG_NOSUB) != 0) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "cannot compile pattern %s", ucl_object_tostring (elt)); + ret = false; + break; + } + if (regexec (&re, ucl_object_tostring (obj), 0, NULL, 0) != 0) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "string doesn't match regexp %s", + ucl_object_tostring (elt)); + ret = false; + } + regfree (&re); + } +#endif + } + + return ret; +} + +struct ucl_compare_node { + const ucl_object_t *obj; + TREE_ENTRY(ucl_compare_node) link; + struct ucl_compare_node *next; +}; + +typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t; + +TREE_DEFINE(ucl_compare_node, link) + +static int +ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2) +{ + const ucl_object_t *o1 = n1->obj, *o2 = n2->obj; + + return ucl_object_compare (o1, o2); +} + +static bool +ucl_schema_array_is_unique (const ucl_object_t *obj, struct ucl_schema_error *err) +{ + ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare); + ucl_object_iter_t iter = NULL; + const ucl_object_t *elt; + struct ucl_compare_node *node, test, *nodes = NULL, *tmp; + bool ret = true; + + while ((elt = ucl_object_iterate (obj, &iter, true)) != NULL) { + test.obj = elt; + node = TREE_FIND (&tree, ucl_compare_node, link, &test); + if (node != NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt, + "duplicate values detected while uniqueItems is true"); + ret = false; + break; + } + node = calloc (1, sizeof (*node)); + if (node == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt, + "cannot allocate tree node"); + ret = false; + break; + } + node->obj = elt; + TREE_INSERT (&tree, ucl_compare_node, link, node); + LL_PREPEND (nodes, node); + } + + LL_FOREACH_SAFE (nodes, node, tmp) { + free (node); + } + + return ret; +} + +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) +{ + const ucl_object_t *elt, *it, *found, *additional_schema = NULL, + *first_unvalidated = NULL; + ucl_object_iter_t iter = NULL, piter = NULL; + bool ret = true, allow_additional = true, need_unique = false; + int64_t minmax; + unsigned int idx = 0; + + while (ret && (elt = ucl_object_iterate (schema, &iter, true)) != NULL) { + if (strcmp (ucl_object_key (elt), "items") == 0) { + if (elt->type == UCL_ARRAY) { + found = ucl_array_head (obj); + while (ret && (it = ucl_object_iterate (elt, &piter, true)) != NULL) { + if (found) { + ret = ucl_schema_validate (it, found, false, err, + root, ext_ref); + found = ucl_array_find_index (obj, ++idx); + } + } + if (found != NULL) { + /* The first element that is not validated */ + first_unvalidated = found; + } + } + else if (elt->type == UCL_OBJECT) { + /* Validate all items using the specified schema */ + while (ret && (it = ucl_object_iterate (obj, &piter, true)) != NULL) { + ret = ucl_schema_validate (elt, it, false, err, root, + ext_ref); + } + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "items attribute is invalid in schema"); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) { + if (elt->type == UCL_BOOLEAN) { + if (!ucl_object_toboolean (elt)) { + /* Deny additional fields completely */ + allow_additional = false; + } + } + else if (elt->type == UCL_OBJECT) { + /* Define validator for additional fields */ + additional_schema = elt; + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, + "additionalItems attribute is invalid in schema"); + ret = false; + break; + } + } + else if (elt->type == UCL_BOOLEAN && + strcmp (ucl_object_key (elt), "uniqueItems") == 0) { + need_unique = ucl_object_toboolean (elt); + } + else if (strcmp (ucl_object_key (elt), "minItems") == 0 + && ucl_object_toint_safe (elt, &minmax)) { + if (obj->len < minmax) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "array has not enough items: %u, minimum is: %u", + obj->len, (unsigned)minmax); + ret = false; + break; + } + } + else if (strcmp (ucl_object_key (elt), "maxItems") == 0 + && ucl_object_toint_safe (elt, &minmax)) { + if (obj->len > minmax) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "array has too many items: %u, maximum is: %u", + obj->len, (unsigned)minmax); + ret = false; + break; + } + } + } + + if (ret) { + /* Additional properties */ + if (!allow_additional || additional_schema != NULL) { + if (first_unvalidated != NULL) { + if (!allow_additional) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "array has undefined item"); + ret = false; + } + else if (additional_schema != NULL) { + elt = ucl_array_find_index (obj, idx); + while (elt) { + if (!ucl_schema_validate (additional_schema, elt, false, + err, root, ext_ref)) { + ret = false; + break; + } + elt = ucl_array_find_index (obj, idx ++); + } + } + } + } + /* Required properties */ + if (ret && need_unique) { + ret = ucl_schema_array_is_unique (obj, err); + } + } + + return ret; +} + +/* + * Returns whether this object is allowed for this type + */ +static bool +ucl_schema_type_is_allowed (const ucl_object_t *type, const ucl_object_t *obj, + struct ucl_schema_error *err) +{ + ucl_object_iter_t iter = NULL; + const ucl_object_t *elt; + const char *type_str; + ucl_type_t t; + + if (type == NULL) { + /* Any type is allowed */ + return true; + } + + if (type->type == UCL_ARRAY) { + /* One of allowed types */ + while ((elt = ucl_object_iterate (type, &iter, true)) != NULL) { + if (ucl_schema_type_is_allowed (elt, obj, err)) { + return true; + } + } + } + else if (type->type == UCL_STRING) { + type_str = ucl_object_tostring (type); + if (!ucl_object_string_to_type (type_str, &t)) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, type, + "Type attribute is invalid in schema"); + return false; + } + if (obj->type != t) { + /* Some types are actually compatible */ + if (obj->type == UCL_TIME && t == UCL_FLOAT) { + return true; + } + else if (obj->type == UCL_INT && t == UCL_FLOAT) { + return true; + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_TYPE_MISMATCH, obj, + "Invalid type of %s, expected %s", + ucl_object_type_to_string (obj->type), + ucl_object_type_to_string (t)); + } + } + else { + /* Types are equal */ + return true; + } + } + + return false; +} + +/* + * Check if object is equal to one of elements of enum + */ +static bool +ucl_schema_validate_enum (const ucl_object_t *en, const ucl_object_t *obj, + struct ucl_schema_error *err) +{ + ucl_object_iter_t iter = NULL; + const ucl_object_t *elt; + bool ret = false; + + while ((elt = ucl_object_iterate (en, &iter, true)) != NULL) { + if (ucl_object_compare (elt, obj) == 0) { + ret = true; + break; + } + } + + if (!ret) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object is not one of enumerated patterns"); + } + + return ret; +} + + +/* + * Check a single ref component + */ +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) +{ + const ucl_object_t *res = NULL; + char *err_str; + int num, i; + + if (cur->type == UCL_OBJECT) { + /* Find a key inside an object */ + res = ucl_object_lookup_len (cur, refc, len); + if (res == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, + "reference %s is invalid, missing path component", refc); + return NULL; + } + } + else if (cur->type == UCL_ARRAY) { + /* We must figure out a number inside array */ + num = strtoul (refc, &err_str, 10); + if (err_str != NULL && *err_str != '/' && *err_str != '\0') { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, + "reference %s is invalid, invalid item number", refc); + return NULL; + } + res = ucl_array_head (cur); + i = 0; + while (res != NULL) { + if (i == num) { + break; + } + res = res->next; + } + if (res == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, + "reference %s is invalid, item number %d does not exist", + refc, num); + return NULL; + } + } + else { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, + "reference %s is invalid, contains primitive object in the path", + refc); + return NULL; + } + + return res; +} +/* + * Find reference schema + */ +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) +{ + UT_string *url_err = NULL; + struct ucl_parser *parser; + const ucl_object_t *res = NULL, *ext_obj = NULL; + ucl_object_t *url_obj; + const char *p, *c, *hash_ptr = NULL; + char *url_copy = NULL; + unsigned char *url_buf; + size_t url_buflen; + + if (ref[0] != '#') { + hash_ptr = strrchr (ref, '#'); + + if (hash_ptr) { + url_copy = malloc (hash_ptr - ref + 1); + + if (url_copy == NULL) { + ucl_schema_create_error (err, UCL_SCHEMA_INTERNAL_ERROR, root, + "cannot allocate memory"); + return NULL; + } + + ucl_strlcpy (url_copy, ref, hash_ptr - ref + 1); + p = url_copy; + } + else { + /* Full URL */ + p = ref; + } + + ext_obj = ucl_object_lookup (ext_ref, p); + + if (ext_obj == NULL) { + if (ucl_strnstr (p, "://", strlen (p)) != NULL) { + if (!ucl_fetch_url (p, &url_buf, &url_buflen, &url_err, true)) { + + ucl_schema_create_error (err, + UCL_SCHEMA_INVALID_SCHEMA, + root, + "cannot fetch reference %s: %s", + p, + url_err != NULL ? utstring_body (url_err) + : "unknown"); + free (url_copy); + + return NULL; + } + } + else { + if (!ucl_fetch_file (p, &url_buf, &url_buflen, &url_err, + true)) { + ucl_schema_create_error (err, + UCL_SCHEMA_INVALID_SCHEMA, + root, + "cannot fetch reference %s: %s", + p, + url_err != NULL ? utstring_body (url_err) + : "unknown"); + free (url_copy); + + return NULL; + } + } + + parser = ucl_parser_new (0); + + if (!ucl_parser_add_chunk (parser, url_buf, url_buflen)) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root, + "cannot fetch reference %s: %s", p, + ucl_parser_get_error (parser)); + ucl_parser_free (parser); + free (url_copy); + + return NULL; + } + + url_obj = ucl_parser_get_object (parser); + ext_obj = url_obj; + ucl_object_insert_key (ext_ref, url_obj, p, 0, true); + free (url_buf); + } + + free (url_copy); + + if (hash_ptr) { + p = hash_ptr + 1; + } + else { + p = ""; + } + } + else { + p = ref + 1; + } + + res = ext_obj != NULL ? ext_obj : root; + *nroot = res; + + if (*p == '/') { + p++; + } + else if (*p == '\0') { + return res; + } + + c = p; + + while (*p != '\0') { + if (*p == '/') { + if (p - c == 0) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, + "reference %s is invalid, empty path component", ref); + return NULL; + } + /* Now we have some url part, so we need to figure out where we are */ + res = ucl_schema_resolve_ref_component (res, c, p - c, err); + if (res == NULL) { + return NULL; + } + c = p + 1; + } + p ++; + } + + if (p - c != 0) { + res = ucl_schema_resolve_ref_component (res, c, p - c, err); + } + + if (res == NULL || res->type != UCL_OBJECT) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, + "reference %s is invalid, cannot find specified object", + ref); + return NULL; + } + + return res; +} + +static bool +ucl_schema_validate_values (const ucl_object_t *schema, const ucl_object_t *obj, + struct ucl_schema_error *err) +{ + const ucl_object_t *elt, *cur; + int64_t constraint, i; + + elt = ucl_object_lookup (schema, "maxValues"); + if (elt != NULL && elt->type == UCL_INT) { + constraint = ucl_object_toint (elt); + cur = obj; + i = 0; + while (cur) { + if (i > constraint) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object has more values than defined: %ld", + (long int)constraint); + return false; + } + i ++; + cur = cur->next; + } + } + elt = ucl_object_lookup (schema, "minValues"); + if (elt != NULL && elt->type == UCL_INT) { + constraint = ucl_object_toint (elt); + cur = obj; + i = 0; + while (cur) { + if (i >= constraint) { + break; + } + i ++; + cur = cur->next; + } + if (i < constraint) { + ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, + "object has less values than defined: %ld", + (long int)constraint); + return false; + } + } + + return true; +} + +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) +{ + const ucl_object_t *elt, *cur, *ref_root; + ucl_object_iter_t iter = NULL; + bool ret; + + if (schema->type != UCL_OBJECT) { + ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, schema, + "schema is %s instead of object", + ucl_object_type_to_string (schema->type)); + return false; + } + + if (try_array) { + /* + * Special case for multiple values + */ + if (!ucl_schema_validate_values (schema, obj, err)) { + return false; + } + LL_FOREACH (obj, cur) { + if (!ucl_schema_validate (schema, cur, false, err, root, external_refs)) { + return false; + } + } + return true; + } + + elt = ucl_object_lookup (schema, "enum"); + if (elt != NULL && elt->type == UCL_ARRAY) { + if (!ucl_schema_validate_enum (elt, obj, err)) { + return false; + } + } + + elt = ucl_object_lookup (schema, "allOf"); + if (elt != NULL && elt->type == UCL_ARRAY) { + iter = NULL; + while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) { + ret = ucl_schema_validate (cur, obj, true, err, root, external_refs); + if (!ret) { + return false; + } + } + } + + elt = ucl_object_lookup (schema, "anyOf"); + if (elt != NULL && elt->type == UCL_ARRAY) { + iter = NULL; + while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) { + ret = ucl_schema_validate (cur, obj, true, err, root, external_refs); + if (ret) { + break; + } + } + if (!ret) { + return false; + } + else { + /* Reset error */ + err->code = UCL_SCHEMA_OK; + } + } + + elt = ucl_object_lookup (schema, "oneOf"); + if (elt != NULL && elt->type == UCL_ARRAY) { + iter = NULL; + ret = false; + while ((cur = ucl_object_iterate (elt, &iter, true)) != NULL) { + if (!ret) { + ret = ucl_schema_validate (cur, obj, true, err, root, external_refs); + } + else if (ucl_schema_validate (cur, obj, true, err, root, external_refs)) { + ret = false; + break; + } + } + if (!ret) { + return false; + } + } + + elt = ucl_object_lookup (schema, "not"); + if (elt != NULL && elt->type == UCL_OBJECT) { + if (ucl_schema_validate (elt, obj, true, err, root, external_refs)) { + return false; + } + else { + /* Reset error */ + err->code = UCL_SCHEMA_OK; + } + } + + elt = ucl_object_lookup (schema, "$ref"); + if (elt != NULL) { + ref_root = root; + cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt), + err, external_refs, &ref_root); + + if (cur == NULL) { + return false; + } + if (!ucl_schema_validate (cur, obj, try_array, err, ref_root, + external_refs)) { + return false; + } + } + + elt = ucl_object_lookup (schema, "type"); + if (!ucl_schema_type_is_allowed (elt, obj, err)) { + return false; + } + + switch (obj->type) { + case UCL_OBJECT: + return ucl_schema_validate_object (schema, obj, err, root, external_refs); + break; + case UCL_ARRAY: + return ucl_schema_validate_array (schema, obj, err, root, external_refs); + break; + case UCL_INT: + case UCL_FLOAT: + return ucl_schema_validate_number (schema, obj, err); + break; + case UCL_STRING: + return ucl_schema_validate_string (schema, obj, err); + break; + default: + break; + } + + return true; +} + +bool +ucl_object_validate (const ucl_object_t *schema, + const ucl_object_t *obj, struct ucl_schema_error *err) +{ + return ucl_object_validate_root_ext (schema, obj, schema, NULL, err); +} + +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) +{ + return ucl_object_validate_root_ext (schema, obj, root, NULL, err); +} + +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) +{ + bool ret, need_unref = false; + + if (ext_refs == NULL) { + ext_refs = ucl_object_typed_new (UCL_OBJECT); + need_unref = true; + } + + ret = ucl_schema_validate (schema, obj, true, err, root, ext_refs); + + if (need_unref) { + ucl_object_unref (ext_refs); + } + + return ret; +} |