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