1<?php 2/* 3** Zabbix 4** Copyright (C) 2001-2021 Zabbix SIA 5** 6** This program is free software; you can redistribute it and/or modify 7** it under the terms of the GNU General Public License as published by 8** the Free Software Foundation; either version 2 of the License, or 9** (at your option) any later version. 10** 11** This program is distributed in the hope that it will be useful, 12** but WITHOUT ANY WARRANTY; without even the implied warranty of 13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14** GNU General Public License for more details. 15** 16** You should have received a copy of the GNU General Public License 17** along with this program; if not, write to the Free Software 18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19**/ 20 21 22/** 23 * API validator 24 */ 25class CApiInputValidator { 26 27 /** 28 * Base validation function. 29 * 30 * @param array $rule validation rule 31 * @param mixed $data import data 32 * @param string $path data path (for error reporting) 33 * @param string $error 34 * 35 * @return bool 36 */ 37 public static function validate(array $rule, &$data, $path, &$error) { 38 $error = ''; 39 40 return self::validateData($rule, $data, $path, $error) 41 && self::validateDataUniqueness($rule, $data, $path, $error); 42 } 43 44 /** 45 * Base uniqueness validation function. 46 * 47 * @param array $rule validation rule 48 * @param mixed $data import data 49 * @param string $path data path (for error reporting) 50 * @param string $error 51 * 52 * @return bool 53 */ 54 public static function validateUniqueness(array $rule, $data, $path, &$error) { 55 $error = ''; 56 57 return self::validateDataUniqueness($rule, $data, $path, $error); 58 } 59 60 /** 61 * Base data validation function. 62 * 63 * @param array $rule 64 * @param mixed $data 65 * @param string $path 66 * @param string $error 67 * @param array $parent_data 68 * 69 * @return bool 70 */ 71 private static function validateData($rule, &$data, $path, &$error, array $parent_data = null) { 72 switch ($rule['type']) { 73 case API_CALC_FORMULA: 74 return self::validateCalcFormula($rule, $data, $path, $error); 75 76 case API_COLOR: 77 return self::validateColor($rule, $data, $path, $error); 78 79 case API_MULTIPLE: 80 if ($parent_data !== null) { 81 return self::validateMultiple($rule, $data, $path, $error, $parent_data); 82 } 83 break; 84 85 case API_STRING_UTF8: 86 return self::validateStringUtf8($rule, $data, $path, $error); 87 88 case API_STRINGS_UTF8: 89 return self::validateStringsUtf8($rule, $data, $path, $error); 90 91 case API_INT32: 92 return self::validateInt32($rule, $data, $path, $error); 93 94 case API_INTS32: 95 return self::validateInts32($rule, $data, $path, $error); 96 97 case API_UINT64: 98 return self::validateUInt64($rule, $data, $path, $error); 99 100 case API_UINTS64: 101 return self::validateUInts64($rule, $data, $path, $error); 102 103 case API_FLOAT: 104 return self::validateFloat($rule, $data, $path, $error); 105 106 case API_FLOATS: 107 return self::validateFloats($rule, $data, $path, $error); 108 109 case API_ID: 110 return self::validateId($rule, $data, $path, $error); 111 112 case API_BOOLEAN: 113 return self::validateBoolean($rule, $data, $path, $error); 114 115 case API_FLAG: 116 return self::validateFlag($rule, $data, $path, $error); 117 118 case API_OBJECT: 119 return self::validateObject($rule, $data, $path, $error); 120 121 case API_OUTPUT: 122 return self::validateOutput($rule, $data, $path, $error); 123 124 case API_PSK: 125 return self::validatePSK($rule, $data, $path, $error); 126 127 case API_SORTORDER: 128 return self::validateSortOrder($rule, $data, $path, $error); 129 130 case API_IDS: 131 return self::validateIds($rule, $data, $path, $error); 132 133 case API_OBJECTS: 134 return self::validateObjects($rule, $data, $path, $error); 135 136 case API_HG_NAME: 137 return self::validateHostGroupName($rule, $data, $path, $error); 138 139 case API_H_NAME: 140 return self::validateHostName($rule, $data, $path, $error); 141 142 case API_NUMERIC: 143 return self::validateNumeric($rule, $data, $path, $error); 144 145 case API_SCRIPT_MENU_PATH: 146 return self::validateScriptMenuPath($rule, $data, $path, $error); 147 148 case API_USER_MACRO: 149 return self::validateUserMacro($rule, $data, $path, $error); 150 151 case API_LLD_MACRO: 152 return self::validateLLDMacro($rule, $data, $path, $error); 153 154 case API_RANGE_TIME: 155 return self::validateRangeTime($rule, $data, $path, $error); 156 157 case API_TIME_PERIOD: 158 return self::validateTimePeriod($rule, $data, $path, $error); 159 160 case API_REGEX: 161 return self::validateRegex($rule, $data, $path, $error); 162 163 case API_HTTP_POST: 164 return self::validateHttpPosts($rule, $data, $path, $error); 165 166 case API_VARIABLE_NAME: 167 return self::validateVariableName($rule, $data, $path, $error); 168 169 case API_TIME_UNIT: 170 return self::validateTimeUnit($rule, $data, $path, $error); 171 172 case API_URL: 173 return self::validateUrl($rule, $data, $path, $error); 174 175 case API_IP: 176 return self::validateIp($rule, $data, $path, $error); 177 178 case API_DNS: 179 return self::validateDns($rule, $data, $path, $error); 180 181 case API_PORT: 182 return self::validatePort($rule, $data, $path, $error); 183 184 case API_TRIGGER_EXPRESSION: 185 return self::validateTriggerExpression($rule, $data, $path, $error); 186 187 case API_EVENT_NAME: 188 return self::validateEventName($rule, $data, $path, $error); 189 190 case API_JSONRPC_PARAMS: 191 return self::validateJsonRpcParams($rule, $data, $path, $error); 192 193 case API_JSONRPC_ID: 194 return self::validateJsonRpcId($rule, $data, $path, $error); 195 196 case API_DATE: 197 return self::validateDate($rule, $data, $path, $error); 198 199 case API_NUMERIC_RANGES: 200 return self::validateNumericRanges($rule, $data, $path, $error); 201 202 case API_UUID: 203 return self::validateUuid($rule, $data, $path, $error); 204 205 case API_VAULT_SECRET: 206 return self::validateVaultSecret($rule, $data, $path, $error); 207 } 208 209 // This message can be untranslated because warn about incorrect validation rules at a development stage. 210 $error = 'Incorrect validation rules.'; 211 212 return false; 213 } 214 215 /** 216 * Base data uniqueness validation function. 217 * 218 * @param array $rule 219 * @param mixed $data 220 * @param string $path 221 * @param string $error 222 * 223 * @return bool 224 */ 225 private static function validateDataUniqueness($rule, &$data, $path, &$error) { 226 switch ($rule['type']) { 227 case API_CALC_FORMULA: 228 case API_COLOR: 229 case API_MULTIPLE: 230 case API_STRING_UTF8: 231 case API_INT32: 232 case API_UINT64: 233 case API_UINTS64: 234 case API_FLOAT: 235 case API_FLOATS: 236 case API_ID: 237 case API_BOOLEAN: 238 case API_FLAG: 239 case API_OUTPUT: 240 case API_PSK: 241 case API_SORTORDER: 242 case API_HG_NAME: 243 case API_H_NAME: 244 case API_NUMERIC: 245 case API_SCRIPT_MENU_PATH: 246 case API_USER_MACRO: 247 case API_LLD_MACRO: 248 case API_RANGE_TIME: 249 case API_TIME_PERIOD: 250 case API_TIME_UNIT: 251 case API_REGEX: 252 case API_HTTP_POST: 253 case API_VARIABLE_NAME: 254 case API_URL: 255 case API_IP: 256 case API_DNS: 257 case API_PORT: 258 case API_TRIGGER_EXPRESSION: 259 case API_EVENT_NAME: 260 case API_JSONRPC_PARAMS: 261 case API_JSONRPC_ID: 262 case API_DATE: 263 case API_NUMERIC_RANGES: 264 case API_UUID: 265 case API_VAULT_SECRET: 266 return true; 267 268 case API_OBJECT: 269 foreach ($rule['fields'] as $field_name => $field_rule) { 270 if ($data !== null && array_key_exists($field_name, $data)) { 271 $subpath = ($path === '/' ? $path : $path.'/').$field_name; 272 if (!self::validateDataUniqueness($field_rule, $data[$field_name], $subpath, $error)) { 273 return false; 274 } 275 } 276 } 277 return true; 278 279 case API_IDS: 280 case API_STRINGS_UTF8: 281 case API_INTS32: 282 return self::validateStringsUniqueness($rule, $data, $path, $error); 283 284 case API_OBJECTS: 285 return self::validateObjectsUniqueness($rule, $data, $path, $error); 286 } 287 288 // This message can be untranslated because warn about incorrect validation rules at a development stage. 289 $error = 'Incorrect validation rules.'; 290 291 return false; 292 } 293 294 /** 295 * Generic string validator. 296 * 297 * @param int $flags API_NOT_EMPTY 298 * @param mixed $data 299 * @param string $path 300 * @param string $error 301 * 302 * @return bool 303 */ 304 private static function checkStringUtf8($flags, &$data, $path, &$error) { 305 if (!is_string($data)) { 306 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a character string is expected')); 307 return false; 308 } 309 310 if (mb_check_encoding($data, 'UTF-8') !== true) { 311 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid byte sequence in UTF-8')); 312 return false; 313 } 314 315 if (($flags & API_NOT_EMPTY) && $data === '') { 316 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 317 return false; 318 } 319 320 return true; 321 } 322 323 /** 324 * Calculated item formula validator. 325 * 326 * @param array $rule 327 * @param int $rule['flags'] (optional) API_ALLOW_LLD_MACRO 328 * @param int $rule['length'] (optional) 329 * @param mixed $data 330 * @param string $path 331 * @param string $error 332 * 333 * @return bool 334 */ 335 private static function validateCalcFormula($rule, &$data, $path, &$error) { 336 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 337 338 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 339 return false; 340 } 341 342 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 343 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 344 return false; 345 } 346 347 $expression_parser = new CExpressionParser([ 348 'usermacros' => true, 349 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 350 'calculated' => true, 351 'host_macro' => true, 352 'empty_host' => true 353 ]); 354 355 if ($expression_parser->parse($data) != CParser::PARSE_SUCCESS) { 356 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_parser->getError()); 357 return false; 358 } 359 360 $expression_validator = new CExpressionValidator([ 361 'usermacros' => true, 362 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 363 'calculated' => true 364 ]); 365 366 if (!$expression_validator->validate($expression_parser->getResult()->getTokens())) { 367 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_validator->getError()); 368 return false; 369 } 370 371 return true; 372 } 373 374 /** 375 * Color validator. 376 * 377 * @param array $rule 378 * @param int $rule['flags'] (optional) API_NOT_EMPTY 379 * @param mixed $data 380 * @param string $path 381 * @param string $error 382 * 383 * @return bool 384 */ 385 private static function validateColor($rule, &$data, $path, &$error) { 386 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 387 388 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 389 return false; 390 } 391 392 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 393 return true; 394 } 395 396 if (preg_match('/^[0-9a-f]{6}$/i', $data) !== 1) { 397 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 398 _('a hexadecimal colour code (6 symbols) is expected') 399 ); 400 return false; 401 } 402 403 return true; 404 } 405 406 /** 407 * Multiple data types validator. 408 * 409 * @param array $rule 410 * @param array $rule['rules'] 411 * @param mixed $data 412 * @param string $path 413 * @param string $error 414 * @param array $parent_data 415 * 416 * @return bool 417 */ 418 private static function validateMultiple($rule, &$data, $path, &$error, array $parent_data) { 419 foreach ($rule['rules'] as $field_rule) { 420 if (self::Int32In($parent_data[$field_rule['if']['field']], $field_rule['if']['in'])) { 421 unset($field_rule['if']); 422 423 return self::validateData($field_rule, $data, $path, $error); 424 } 425 } 426 427 // This message can be untranslated because warn about incorrect validation rules at a development stage. 428 $error = 'Incorrect validation rules.'; 429 430 return false; 431 } 432 433 /** 434 * String validator. 435 * 436 * @param array $rule 437 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL 438 * @param string $rule['in'] (optional) a comma-delimited character string, for example: 'xml,json' 439 * @param int $rule['length'] (optional) 440 * @param mixed $data 441 * @param string $path 442 * @param string $error 443 * 444 * @return bool 445 */ 446 private static function validateStringUtf8($rule, &$data, $path, &$error) { 447 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 448 449 if (($flags & API_ALLOW_NULL) && $data === null) { 450 return true; 451 } 452 453 if (self::checkStringUtf8($flags, $data, $path, $error) === false) { 454 return false; 455 } 456 457 if (array_key_exists('in', $rule) && !in_array($data, explode(',', $rule['in']), true)) { 458 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 459 _s('value must be one of %1$s', str_replace(',', ', ', $rule['in'])) 460 ); 461 return false; 462 } 463 464 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 465 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 466 return false; 467 } 468 469 return true; 470 } 471 472 /** 473 * Array of strings validator. 474 * 475 * @param array $rule 476 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 477 * @param string $rule['in'] (optional) a comma-delimited character string, for example: 'xml,json' 478 * @param mixed $data 479 * @param string $path 480 * @param string $error 481 * 482 * @return bool 483 */ 484 private static function validateStringsUtf8($rule, &$data, $path, &$error) { 485 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 486 487 if (($flags & API_ALLOW_NULL) && $data === null) { 488 return true; 489 } 490 491 if (($flags & API_NORMALIZE) && self::validateStringUtf8([], $data, '', $e)) { 492 $data = [$data]; 493 } 494 unset($e); 495 496 if (!is_array($data)) { 497 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 498 return false; 499 } 500 501 if (($flags & API_NOT_EMPTY) && !$data) { 502 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 503 return false; 504 } 505 506 $data = array_values($data); 507 $rules = ['type' => API_STRING_UTF8]; 508 509 if (array_key_exists('in', $rule)) { 510 $rules['in'] = $rule['in']; 511 } 512 513 foreach ($data as $index => &$value) { 514 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 515 if (!self::validateData($rules, $value, $subpath, $error)) { 516 return false; 517 } 518 } 519 unset($value); 520 521 return true; 522 } 523 524 /** 525 * Integers validator. 526 * 527 * @param array $rule 528 * @param int $rule['flags'] (optional) API_ALLOW_NULL 529 * @param string $rule['in'] (optional) a comma-delimited character string, for example: '0,60:900' 530 * @param mixed $data 531 * @param string $path 532 * @param string $error 533 * 534 * @return bool 535 */ 536 private static function validateInt32($rule, &$data, $path, &$error) { 537 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 538 539 if (($flags & API_ALLOW_NULL) && $data === null) { 540 return true; 541 } 542 543 if ((!is_int($data) && !is_string($data)) || !preg_match('/^'.ZBX_PREG_INT.'$/', strval($data))) { 544 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an integer is expected')); 545 return false; 546 } 547 548 if ($data < ZBX_MIN_INT32 || $data > ZBX_MAX_INT32) { 549 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 550 return false; 551 } 552 553 if (!self::checkInt32In($rule, $data, $path, $error)) { 554 return false; 555 } 556 557 if (is_string($data)) { 558 $data = (int) $data; 559 } 560 561 return true; 562 } 563 564 /** 565 * Unsigned integers validator. 566 * 567 * @param array $rule 568 * @param int $rule['flags'] (optional) API_ALLOW_NULL 569 * @param mixed $data 570 * @param string $path 571 * @param string $error 572 * 573 * @return bool 574 */ 575 private static function validateUInt64($rule, &$data, $path, &$error) { 576 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 577 578 if (($flags & API_ALLOW_NULL) && $data === null) { 579 return true; 580 } 581 582 if (!is_scalar($data) || is_bool($data) || is_double($data) || !ctype_digit(strval($data))) { 583 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an unsigned integer is expected')); 584 return false; 585 } 586 587 if (bccomp($data, ZBX_MAX_UINT64) > 0) { 588 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 589 return false; 590 } 591 592 $data = (string) $data; 593 594 if ($data[0] === '0') { 595 $data = ltrim($data, '0'); 596 597 if ($data === '') { 598 $data = '0'; 599 } 600 } 601 602 return true; 603 } 604 605 /** 606 * Array of unsigned integers validator. 607 * 608 * @param array $rule 609 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 610 * @param mixed $data 611 * @param string $path 612 * @param string $error 613 * 614 * @return bool 615 */ 616 private static function validateUInts64($rule, &$data, $path, &$error) { 617 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 618 619 if (($flags & API_ALLOW_NULL) && $data === null) { 620 return true; 621 } 622 623 if (($flags & API_NORMALIZE) && self::validateUInt64([], $data, '', $e)) { 624 $data = [$data]; 625 } 626 unset($e); 627 628 if (!is_array($data)) { 629 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 630 return false; 631 } 632 633 if (($flags & API_NOT_EMPTY) && !$data) { 634 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 635 return false; 636 } 637 638 $data = array_values($data); 639 $rules = ['type' => API_UINT64]; 640 641 foreach ($data as $index => &$value) { 642 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 643 if (!self::validateData($rules, $value, $subpath, $error)) { 644 return false; 645 } 646 } 647 unset($value); 648 649 return true; 650 } 651 652 /** 653 * Floating point number validator. 654 * 655 * @param array $rule 656 * @param int $rule['flags'] (optional) API_ALLOW_NULL 657 * @param mixed $data 658 * @param string $path 659 * @param string $error 660 * 661 * @return bool 662 */ 663 private static function validateFloat($rule, &$data, $path, &$error) { 664 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 665 666 if (($flags & API_ALLOW_NULL) && $data === null) { 667 return true; 668 } 669 670 if (is_int($data) || is_float($data)) { 671 $value = (float) $data; 672 } 673 elseif (is_string($data)) { 674 $number_parser = new CNumberParser(); 675 676 if ($number_parser->parse($data) == CParser::PARSE_SUCCESS) { 677 $value = (float) $number_parser->getMatch(); 678 } 679 else { 680 $value = NAN; 681 } 682 } 683 else { 684 $value = NAN; 685 } 686 687 if (is_nan($value)) { 688 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a floating point value is expected')); 689 690 return false; 691 } 692 693 $data = $value; 694 695 return true; 696 } 697 698 /** 699 * Array of floating point numbers validator. 700 * 701 * @param array $rule 702 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 703 * @param mixed $data 704 * @param string $path 705 * @param string $error 706 * 707 * @return bool 708 */ 709 private static function validateFloats($rule, &$data, $path, &$error) { 710 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 711 712 if (($flags & API_ALLOW_NULL) && $data === null) { 713 return true; 714 } 715 716 if (($flags & API_NORMALIZE) && self::validateFloat([], $data, '', $e)) { 717 $data = [$data]; 718 } 719 unset($e); 720 721 if (!is_array($data)) { 722 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 723 return false; 724 } 725 726 if (($flags & API_NOT_EMPTY) && !$data) { 727 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 728 return false; 729 } 730 731 $data = array_values($data); 732 $rules = ['type' => API_FLOAT]; 733 734 foreach ($data as $index => &$value) { 735 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 736 if (!self::validateData($rules, $value, $subpath, $error)) { 737 return false; 738 } 739 } 740 unset($value); 741 742 return true; 743 } 744 745 private static function Int32In($data, $in) { 746 $valid = false; 747 748 foreach (explode(',', $in) as $in) { 749 if (strpos($in, ':') !== false) { 750 list($from, $to) = explode(':', $in); 751 } 752 else { 753 $from = $in; 754 $to = $in; 755 } 756 757 if ($from <= $data && $data <= $to) { 758 $valid = true; 759 break; 760 } 761 } 762 763 return $valid; 764 } 765 766 /** 767 * @param array $rule 768 * @param int $rule['in'] (optional) 769 * @param int $data 770 * @param string $path 771 * @param string $error 772 * 773 * @return bool 774 */ 775 private static function checkInt32In($rule, $data, $path, &$error) { 776 if (!array_key_exists('in', $rule)) { 777 return true; 778 } 779 780 $valid = self::Int32In($data, $rule['in']); 781 782 if (!$valid) { 783 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 784 _s('value must be one of %1$s', str_replace([',', ':'], [', ', '-'], $rule['in'])) 785 ); 786 } 787 788 return $valid; 789 } 790 791 /** 792 * Array of integers validator. 793 * 794 * @param array $rule 795 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 796 * @param string $rule['in'] (optional) a comma-delimited character string, for example: '0,60:900' 797 * @param mixed $data 798 * @param string $path 799 * @param string $error 800 * 801 * @return bool 802 */ 803 private static function validateInts32($rule, &$data, $path, &$error) { 804 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 805 806 if (($flags & API_ALLOW_NULL) && $data === null) { 807 return true; 808 } 809 810 if (($flags & API_NORMALIZE) && self::validateInt32([], $data, '', $e)) { 811 $data = [$data]; 812 } 813 unset($e); 814 815 if (!is_array($data)) { 816 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 817 return false; 818 } 819 820 if (($flags & API_NOT_EMPTY) && !$data) { 821 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 822 return false; 823 } 824 825 $data = array_values($data); 826 $rules = ['type' => API_INT32]; 827 828 if (array_key_exists('in', $rule)) { 829 $rules['in'] = $rule['in']; 830 } 831 832 foreach ($data as $index => &$value) { 833 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 834 if (!self::validateData($rules, $value, $subpath, $error)) { 835 return false; 836 } 837 } 838 unset($value); 839 840 return true; 841 } 842 843 /** 844 * Identifier validator. 845 * 846 * @param array $rule 847 * @param int $rule['flags'] (optional) API_ALLOW_NULL, API_NOT_EMPTY 848 * @param mixed $data 849 * @param string $path 850 * @param string $error 851 * 852 * @return bool 853 */ 854 private static function validateId($rule, &$data, $path, &$error) { 855 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 856 857 if (($flags & API_ALLOW_NULL) && $data === null) { 858 return true; 859 } 860 861 if (!is_scalar($data) || is_bool($data) || is_double($data) || !ctype_digit(strval($data))) { 862 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); 863 return false; 864 } 865 866 if (($flags & API_NOT_EMPTY) && $data == 0) { 867 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 868 return false; 869 } 870 871 if (bccomp($data, ZBX_DB_MAX_ID) > 0) { 872 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 873 return false; 874 } 875 876 $data = (string) $data; 877 878 if ($data[0] === '0') { 879 $data = ltrim($data, '0'); 880 881 if ($data === '') { 882 $data = '0'; 883 } 884 } 885 886 return true; 887 } 888 889 /** 890 * Boolean validator. 891 * 892 * @param array $rule 893 * @param int $rule['flags'] (optional) API_ALLOW_NULL 894 * @param mixed $data 895 * @param string $path 896 * @param string $error 897 * 898 * @return bool 899 */ 900 private static function validateBoolean($rule, &$data, $path, &$error) { 901 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 902 903 if (($flags & API_ALLOW_NULL) && $data === null) { 904 return true; 905 } 906 907 if (!is_bool($data)) { 908 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a boolean is expected')); 909 return false; 910 } 911 912 return true; 913 } 914 915 /** 916 * Flag validator. 917 * 918 * @param array $rule 919 * @param mixed $data 920 * @param string $path 921 * @param string $error 922 * 923 * @return bool 924 */ 925 private static function validateFlag($rule, &$data, $path, &$error) { 926 if (is_bool($data)) { 927 return true; 928 } 929 930 /** 931 * @deprecated As of version 3.4, use boolean flags only. 932 */ 933 trigger_error(_('Non-boolean flags are deprecated.'), E_USER_NOTICE); 934 935 $data = !is_null($data); 936 937 return true; 938 } 939 940 /** 941 * Object validator. 942 * 943 * @param array $rul 944 * @param int $rule['flags'] (optional) API_ALLOW_NULL 945 * @param array $rule['fields'] 946 * @param int $rule['fields'][<field_name>]['flags'] (optional) API_REQUIRED, API_DEPRECATED 947 * @param mixed $rule['fields'][<field_name>]['default'] (optional) 948 * @param string $rule['fields'][<field_name>]['default_source'] (optional) 949 * @param mixed $data 950 * @param string $path 951 * @param string $error 952 * 953 * @return bool 954 */ 955 private static function validateObject($rule, &$data, $path, &$error) { 956 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 957 958 if (($flags & API_ALLOW_NULL) && $data === null) { 959 return true; 960 } 961 962 if (!is_array($data)) { 963 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 964 return false; 965 } 966 967 // unexpected parameter validation 968 foreach ($data as $field_name => $value) { 969 if (!array_key_exists($field_name, $rule['fields'])) { 970 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('unexpected parameter "%1$s"', $field_name)); 971 return false; 972 } 973 } 974 975 // validation of the values type 976 foreach ($rule['fields'] as $field_name => $field_rule) { 977 $flags = array_key_exists('flags', $field_rule) ? $field_rule['flags'] : 0x00; 978 979 if (array_key_exists('default', $field_rule) && !array_key_exists($field_name, $data)) { 980 $data[$field_name] = $field_rule['default']; 981 } 982 983 if (array_key_exists('default_source', $field_rule) && !array_key_exists($field_name, $data)) { 984 $data[$field_name] = $data[$field_rule['default_source']]; 985 } 986 987 if (array_key_exists($field_name, $data)) { 988 $subpath = ($path === '/' ? $path : $path.'/').$field_name; 989 if (!self::validateData($field_rule, $data[$field_name], $subpath, $error, $data)) { 990 return false; 991 } 992 if ($flags & API_DEPRECATED) { 993 trigger_error(_s('Parameter "%1$s" is deprecated.', $subpath), E_USER_NOTICE); 994 } 995 } 996 elseif ($flags & API_REQUIRED) { 997 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 998 _s('the parameter "%1$s" is missing', $field_name) 999 ); 1000 return false; 1001 } 1002 } 1003 1004 return true; 1005 } 1006 1007 /** 1008 * API output validator. 1009 * 1010 * @param array $rule 1011 * @param int $rule['flags'] (optional) API_ALLOW_COUNT, API_ALLOW_NULL 1012 * @param string $rule['in'] (optional) comma-delimited field names, for example: 'hostid,name' 1013 * @param mixed $data 1014 * @param string $path 1015 * @param string $error 1016 * 1017 * @return bool 1018 */ 1019 private static function validateOutput($rule, &$data, $path, &$error) { 1020 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1021 1022 if (($flags & API_ALLOW_NULL) && $data === null) { 1023 return true; 1024 } 1025 1026 if (is_array($data)) { 1027 $rules = ['type' => API_STRINGS_UTF8, 'uniq' => true]; 1028 1029 if (array_key_exists('in', $rule)) { 1030 $rules['in'] = $rule['in']; 1031 } 1032 1033 return self::validateData($rules, $data, $path, $error) 1034 && self::validateDataUniqueness($rules, $data, $path, $error); 1035 } 1036 elseif (is_string($data)) { 1037 $in = ($flags & API_ALLOW_COUNT) ? implode(',', [API_OUTPUT_EXTEND, API_OUTPUT_COUNT]) : API_OUTPUT_EXTEND; 1038 1039 return self::validateData(['type' => API_STRING_UTF8, 'in' => $in], $data, $path, $error); 1040 } 1041 1042 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or a character string is expected')); 1043 1044 return false; 1045 } 1046 1047 /** 1048 * PSK key validator. 1049 * 1050 * @param array $rule 1051 * @param int $rule['flags'] (optional) API_NOT_EMPTY 1052 * @param int $rule['length'] (optional) 1053 * @param mixed $data 1054 * @param string $path 1055 * @param string $error 1056 * 1057 * @return bool 1058 */ 1059 private static function validatePSK($rule, &$data, $path, &$error) { 1060 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1061 1062 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1063 return false; 1064 } 1065 1066 $mb_len = mb_strlen($data); 1067 1068 if ($mb_len != 0 && $mb_len < PSK_MIN_LEN) { 1069 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('minimum length is %1$s characters', PSK_MIN_LEN)); 1070 return false; 1071 } 1072 1073 if (preg_match('/^([0-9a-f]{2})*$/i', $data) !== 1) { 1074 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1075 _('an even number of hexadecimal characters is expected') 1076 ); 1077 return false; 1078 } 1079 1080 if (array_key_exists('length', $rule) && $mb_len > $rule['length']) { 1081 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1082 return false; 1083 } 1084 1085 return true; 1086 } 1087 1088 /** 1089 * API sort order validator. 1090 * 1091 * @param array $rule 1092 * @param mixed $data 1093 * @param string $path 1094 * @param string $error 1095 * 1096 * @return bool 1097 */ 1098 private static function validateSortOrder($rule, &$data, $path, &$error) { 1099 $in = ZBX_SORT_UP.','.ZBX_SORT_DOWN; 1100 1101 if (self::validateStringUtf8(['in' => $in], $data, $path, $e)) { 1102 return true; 1103 } 1104 1105 if (is_string($data)) { 1106 $error = $e; 1107 return false; 1108 } 1109 unset($e); 1110 1111 if (!is_array($data)) { 1112 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or a character string is expected')); 1113 return false; 1114 } 1115 1116 $data = array_values($data); 1117 $rules = [ 1118 'type' => API_STRING_UTF8, 1119 'in' => $in 1120 ]; 1121 1122 foreach ($data as $index => &$value) { 1123 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1124 if (!self::validateData($rules, $value, $subpath, $error)) { 1125 return false; 1126 } 1127 } 1128 unset($value); 1129 1130 return true; 1131 } 1132 1133 /** 1134 * Array of ids validator. 1135 * 1136 * @param array $rule 1137 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 1138 * @param mixed $data 1139 * @param string $path 1140 * @param string $error 1141 * 1142 * @return bool 1143 */ 1144 private static function validateIds($rule, &$data, $path, &$error) { 1145 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1146 1147 if (($flags & API_ALLOW_NULL) && $data === null) { 1148 return true; 1149 } 1150 1151 if (($flags & API_NORMALIZE) && self::validateId([], $data, '', $e)) { 1152 $data = [$data]; 1153 } 1154 unset($e); 1155 1156 if (!is_array($data)) { 1157 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 1158 return false; 1159 } 1160 1161 if (($flags & API_NOT_EMPTY) && !$data) { 1162 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 1163 return false; 1164 } 1165 1166 $data = array_values($data); 1167 1168 foreach ($data as $index => &$value) { 1169 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1170 if (!self::validateId([], $value, $subpath, $error)) { 1171 return false; 1172 } 1173 } 1174 unset($value); 1175 1176 return true; 1177 } 1178 1179 /** 1180 * Array of objects validator. 1181 * 1182 * @param array $rule 1183 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE, API_PRESERVE_KEYS 1184 * @param array $rule['fields'] 1185 * @param int $rule['length'] (optional) 1186 * @param mixed $data 1187 * @param string $path 1188 * @param string $error 1189 * 1190 * @return bool 1191 */ 1192 private static function validateObjects($rule, &$data, $path, &$error) { 1193 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1194 1195 if (($flags & API_ALLOW_NULL) && $data === null) { 1196 return true; 1197 } 1198 1199 if (!is_array($data)) { 1200 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 1201 return false; 1202 } 1203 1204 if (($flags & API_NOT_EMPTY) && !$data) { 1205 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 1206 return false; 1207 } 1208 1209 if (array_key_exists('length', $rule) && count($data) > $rule['length']) { 1210 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1211 return false; 1212 } 1213 1214 if (($flags & API_NORMALIZE) && $data) { 1215 reset($data); 1216 1217 if (!is_int(key($data))) { 1218 $data = [$data]; 1219 } 1220 } 1221 1222 if (!($flags & API_PRESERVE_KEYS)) { 1223 $data = array_values($data); 1224 } 1225 1226 foreach ($data as $index => &$value) { 1227 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1228 if (!self::validateObject(['fields' => $rule['fields']], $value, $subpath, $error)) { 1229 return false; 1230 } 1231 } 1232 unset($value); 1233 1234 return true; 1235 } 1236 1237 /** 1238 * Host group name validator. 1239 * 1240 * @param array $rule 1241 * @param int $rule['flags'] (optional) API_REQUIRED_LLD_MACRO 1242 * @param int $rule['length'] (optional) 1243 * @param mixed $data 1244 * @param string $path 1245 * @param string $error 1246 * 1247 * @return bool 1248 */ 1249 private static function validateHostGroupName($rule, &$data, $path, &$error) { 1250 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1251 return false; 1252 } 1253 1254 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1255 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1256 1257 return false; 1258 } 1259 1260 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1261 $host_group_name_parser = new CHostGroupNameParser(['lldmacros' => ($flags & API_REQUIRED_LLD_MACRO)]); 1262 1263 if ($host_group_name_parser->parse($data) != CParser::PARSE_SUCCESS) { 1264 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid host group name')); 1265 1266 return false; 1267 } 1268 1269 if (($flags & API_REQUIRED_LLD_MACRO) && !$host_group_name_parser->getMacros()) { 1270 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1271 _('must contain at least one low-level discovery macro') 1272 ); 1273 1274 return false; 1275 } 1276 1277 return true; 1278 } 1279 1280 /** 1281 * Host name validator. 1282 * 1283 * @param array $rule 1284 * @param int $rule['flags'] (optional) API_REQUIRED_LLD_MACRO 1285 * @param int $rule['length'] (optional) 1286 * @param mixed $data 1287 * @param string $path 1288 * @param string $error 1289 * 1290 * @return bool 1291 */ 1292 private static function validateHostName($rule, &$data, $path, &$error) { 1293 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1294 return false; 1295 } 1296 1297 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1298 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1299 1300 return false; 1301 } 1302 1303 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1304 $host_name_parser = new CHostNameParser(['lldmacros' => ($flags & API_REQUIRED_LLD_MACRO)]); 1305 1306 // For example, host prototype name MUST contain macros. 1307 if ($host_name_parser->parse($data) != CParser::PARSE_SUCCESS) { 1308 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid host name')); 1309 1310 return false; 1311 } 1312 1313 if (($flags & API_REQUIRED_LLD_MACRO) && !$host_name_parser->getMacros()) { 1314 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1315 _('must contain at least one low-level discovery macro') 1316 ); 1317 1318 return false; 1319 } 1320 1321 return true; 1322 } 1323 1324 /** 1325 * Validator for numeric data with optional suffix. 1326 * Supported time suffixes: s, m, h, d, w 1327 * Supported metric suffixes: K, M, G, T 1328 * 1329 * @param array $rule 1330 * @param int $rule['flags'] (optional) API_NOT_EMPTY 1331 * @param int $rule['length'] (optional) 1332 * @param mixed $data 1333 * @param string $path 1334 * @param string $error 1335 * 1336 * @return bool 1337 */ 1338 private static function validateNumeric($rule, &$data, $path, &$error) { 1339 global $DB; 1340 1341 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1342 1343 if (is_int($data)) { 1344 $data = (string) $data; 1345 } 1346 1347 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1348 return false; 1349 } 1350 1351 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1352 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1353 1354 return false; 1355 } 1356 1357 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1358 return true; 1359 } 1360 1361 $number_parser = new CNumberParser(['with_suffix' => true]); 1362 1363 if ($number_parser->parse($data) != CParser::PARSE_SUCCESS) { 1364 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); 1365 1366 return false; 1367 } 1368 1369 $value = $number_parser->calcValue(); 1370 1371 if ($DB['DOUBLE_IEEE754']) { 1372 if (abs($value) > ZBX_FLOAT_MAX) { 1373 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1374 1375 return false; 1376 } 1377 } 1378 else { 1379 if (abs($value) >= 1E+16) { 1380 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1381 1382 return false; 1383 } 1384 elseif ($value != round($value, 4)) { 1385 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number has too many fractional digits')); 1386 1387 return false; 1388 } 1389 } 1390 1391 // Remove leading zeros. 1392 $data = preg_replace('/^(-)?(0+)?(\d.*)$/', '${1}${3}', $data); 1393 1394 // Add leading zero. 1395 $data = preg_replace('/^(-)?(\..*)$/', '${1}0${2}', $data); 1396 1397 return true; 1398 } 1399 1400 /** 1401 * Global script menu_path validator. 1402 * 1403 * @param array $rule 1404 * @param int $rule['flags'] (optional) 1405 * @param int $rule['length'] (optional) 1406 * @param mixed $data 1407 * @param string $path 1408 * @param string $error 1409 * 1410 * @return bool 1411 */ 1412 private static function validateScriptMenuPath($rule, &$data, $path, &$error) { 1413 // Having only a root folder is the same as being empty. Temporary modify data to check if it is actually empty. 1414 $tmp_data = $data; 1415 1416 if ($tmp_data === '/') { 1417 $tmp_data = ''; 1418 } 1419 1420 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1421 1422 if (self::checkStringUtf8($flags, $tmp_data, $path, $error) === false) { 1423 return false; 1424 } 1425 1426 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1427 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1428 return false; 1429 } 1430 1431 // If empty is allowed there is only root folder, return early. 1432 if ($data === '/') { 1433 return true; 1434 } 1435 1436 $folders = splitPath($data); 1437 $folders = array_map('trim', $folders); 1438 $count = count($folders); 1439 1440 // folder1/{empty}/name or folder1/folder2/{empty} 1441 foreach ($folders as $num => $folder) { 1442 // Allow the trailing slash. 1443 if ($folder === '' && $num != ($count - 1) && $num != 0) { 1444 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('directory cannot be empty')); 1445 1446 return false; 1447 } 1448 } 1449 1450 return true; 1451 } 1452 1453 /** 1454 * User macro validator. 1455 * 1456 * @param array $rule 1457 * @param int $rule['length'] (optional) 1458 * @param mixed $data 1459 * @param string $path 1460 * @param string $error 1461 * 1462 * @return bool 1463 */ 1464 private static function validateUserMacro($rule, &$data, $path, &$error) { 1465 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1466 return false; 1467 } 1468 1469 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1470 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1471 return false; 1472 } 1473 1474 $user_macro_parser = new CUserMacroParser(); 1475 1476 if ($user_macro_parser->parse($data) != CParser::PARSE_SUCCESS) { 1477 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $user_macro_parser->getError()); 1478 return false; 1479 } 1480 1481 return true; 1482 } 1483 1484 /** 1485 * LLD macro validator. 1486 * 1487 * @param array $rule 1488 * @param int $rule['length'] (optional) 1489 * @param mixed $data 1490 * @param string $path 1491 * @param string $error 1492 * 1493 * @return bool 1494 */ 1495 private static function validateLLDMacro($rule, &$data, $path, &$error) { 1496 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1497 return false; 1498 } 1499 1500 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1501 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1502 return false; 1503 } 1504 1505 if ((new CLLDMacroParser())->parse($data) != CParser::PARSE_SUCCESS) { 1506 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a low-level discovery macro is expected')); 1507 return false; 1508 } 1509 1510 return true; 1511 } 1512 1513 /** 1514 * Time period validator like "1-7,00:00-24:00". 1515 * 1516 * @param array $rule 1517 * @param int $rule['length'] (optional) 1518 * @param mixed $data 1519 * @param string $path 1520 * @param string $error 1521 * 1522 * @return bool 1523 */ 1524 private static function validateRangeTime($rule, &$data, $path, &$error) { 1525 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1526 1527 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1528 return false; 1529 } 1530 1531 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1532 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1533 return false; 1534 } 1535 1536 $range_time_parser = new CRangeTimeParser(); 1537 1538 if ($range_time_parser->parse($data) != CParser::PARSE_SUCCESS) { 1539 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time range is expected')); 1540 return false; 1541 } 1542 1543 return true; 1544 } 1545 1546 /** 1547 * Time period validator like "1-7,00:00-24:00". 1548 * 1549 * @param array $rule 1550 * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO 1551 * @param int $rule['length'] (optional) 1552 * @param mixed $data 1553 * @param string $path 1554 * @param string $error 1555 * 1556 * @return bool 1557 */ 1558 private static function validateTimePeriod($rule, &$data, $path, &$error) { 1559 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1560 1561 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1562 return false; 1563 } 1564 1565 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1566 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1567 return false; 1568 } 1569 1570 $time_period_parser = new CTimePeriodsParser(['usermacros' => ($flags & API_ALLOW_USER_MACRO)]); 1571 1572 if ($time_period_parser->parse($data) != CParser::PARSE_SUCCESS) { 1573 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time period is expected')); 1574 return false; 1575 } 1576 1577 return true; 1578 } 1579 1580 /** 1581 * Regular expression validator. 1582 * 1583 * @param array $rule 1584 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_GLOBAL_REGEX 1585 * @param int $rule['length'] (optional) 1586 * @param mixed $data 1587 * @param string $path 1588 * @param string $error 1589 * 1590 * @return bool 1591 */ 1592 private static function validateRegex($rule, &$data, $path, &$error) { 1593 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1594 1595 if (self::checkStringUtf8($flags, $data, $path, $error) === false) { 1596 return false; 1597 } 1598 1599 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1600 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1601 return false; 1602 } 1603 1604 if (($flags & API_ALLOW_GLOBAL_REGEX) && $data !== '' && $data[0] === '@') { 1605 return true; 1606 } 1607 1608 if (@preg_match('/'.str_replace('/', '\/', $data).'/', '') === false) { 1609 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid regular expression')); 1610 return false; 1611 } 1612 1613 return true; 1614 } 1615 1616 /** 1617 * Time unit validator like "10", "20s", "30m", "4h", "{$TIME}" etc. 1618 * 1619 * @param array $rule 1620 * @param int $rule['length'] (optional) 1621 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO, 1622 * API_TIME_UNIT_WITH_YEAR 1623 * @param int $rule['in'] (optional) 1624 * @param mixed $data 1625 * @param string $path 1626 * @param string $error 1627 * 1628 * @return bool 1629 */ 1630 private static function validateTimeUnit($rule, &$data, $path, &$error) { 1631 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1632 1633 /* 1634 * It's possible to enter seconds as integers, but by default now we look for strings. For example: "30m". 1635 * Other rules like emptiness and invalid characters are validated by parsers. 1636 */ 1637 if (is_int($data)) { 1638 $data = (string) $data; 1639 } 1640 1641 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1642 return false; 1643 } 1644 1645 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1646 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1647 return false; 1648 } 1649 1650 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1651 return true; 1652 } 1653 1654 $simple_interval_parser = new CSimpleIntervalParser([ 1655 'usermacros' => ($flags & API_ALLOW_USER_MACRO), 1656 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 1657 'negative' => true, 1658 'with_year' => ($flags & API_TIME_UNIT_WITH_YEAR) 1659 ]); 1660 1661 if ($simple_interval_parser->parse($data) != CParser::PARSE_SUCCESS) { 1662 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time unit is expected')); 1663 return false; 1664 } 1665 1666 if (($flags & (API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO)) && $data[0] === '{') { 1667 return true; 1668 } 1669 1670 $seconds = timeUnitToSeconds($data, ($flags & API_TIME_UNIT_WITH_YEAR)); 1671 1672 if ($seconds < ZBX_MIN_INT32 || $seconds > ZBX_MAX_INT32) { 1673 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1674 return false; 1675 } 1676 1677 return self::checkInt32In($rule, $seconds, $path, $error); 1678 } 1679 1680 /** 1681 * Array of ids, int32 or strings uniqueness validator. 1682 * 1683 * @param array $rule 1684 * @param bool $rule['uniq'] (optional) 1685 * @param array $data 1686 * @param string $path 1687 * @param string $error 1688 * 1689 * @return bool 1690 */ 1691 private static function validateStringsUniqueness($rule, array $data = null, $path, &$error) { 1692 // $data can be NULL when API_ALLOW_NULL is set 1693 if ($data === null) { 1694 return true; 1695 } 1696 1697 if (!array_key_exists('uniq', $rule) || $rule['uniq'] === false) { 1698 return true; 1699 } 1700 1701 $uniq = []; 1702 1703 foreach ($data as $index => $value) { 1704 if (array_key_exists($value, $uniq)) { 1705 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1706 $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, 1707 _s('value %1$s already exists', '('.$value.')') 1708 ); 1709 return false; 1710 } 1711 $uniq[$value] = true; 1712 } 1713 1714 return true; 1715 } 1716 1717 /** 1718 * Returns macro without spaces and curly braces. 1719 * 1720 * "{$MACRO}" => "MACRO" 1721 * "{$MACRO:}" => "MACRO:context:" 1722 * "{$MACRO: /var}" => "MACRO:context:/var" 1723 * "{$MACRO: /"var"}" => "MACRO:context:/var" 1724 * "{$MACRO:regex: ^[a-z]+}" => "MACRO:regex:^[a-z]+" 1725 * 1726 * @param string $macro 1727 * 1728 * @return string 1729 */ 1730 public static function trimMacro(string $macro): string { 1731 $user_macro_parser = new CUserMacroParser(); 1732 1733 $user_macro_parser->parse($macro); 1734 1735 $macro = $user_macro_parser->getMacro(); 1736 $context = $user_macro_parser->getContext(); 1737 $regex = $user_macro_parser->getRegex(); 1738 1739 if ($context !== null) { 1740 $macro .= ':context:'.$context; 1741 } 1742 elseif ($regex !== null) { 1743 $macro .= ':regex:'.$regex; 1744 } 1745 1746 return $macro; 1747 } 1748 1749 /** 1750 * Array of objects uniqueness validator. 1751 * 1752 * @param array $rule 1753 * @param array $rule['uniq'] (optional) subsets of unique fields ([['hostid', 'name'], [...]]) 1754 * @param array $rule['fields'] 1755 * @param array $data 1756 * @param string $path 1757 * @param string $error 1758 * 1759 * @return bool 1760 */ 1761 private static function validateObjectsUniqueness($rule, array $data = null, $path, &$error) { 1762 // $data can be NULL when API_ALLOW_NULL is set 1763 if ($data === null) { 1764 return true; 1765 } 1766 1767 if (array_key_exists('uniq', $rule)) { 1768 foreach ($rule['uniq'] as $field_names) { 1769 $uniq = []; 1770 1771 foreach ($data as $index => $object) { 1772 $_uniq = &$uniq; 1773 $values = []; 1774 $level = 1; 1775 1776 foreach ($field_names as $field_name) { 1777 if (!array_key_exists($field_name, $object)) { 1778 break; 1779 } 1780 1781 $values[] = $object[$field_name]; 1782 1783 $value = ($rule['fields'][$field_name]['type'] == API_USER_MACRO) 1784 ? self::trimMacro($object[$field_name]) 1785 : $object[$field_name]; 1786 1787 if ($level < count($field_names)) { 1788 if (!array_key_exists($value, $_uniq)) { 1789 $_uniq[$value] = []; 1790 } 1791 1792 $_uniq = &$_uniq[$value]; 1793 } 1794 else { 1795 if (array_key_exists($value, $_uniq)) { 1796 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1797 $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, _s('value %1$s already exists', 1798 '('.implode(', ', $field_names).')=('.implode(', ', $values).')' 1799 )); 1800 return false; 1801 } 1802 1803 $_uniq[$value] = true; 1804 } 1805 1806 $level++; 1807 } 1808 } 1809 } 1810 } 1811 1812 foreach ($data as $index => $object) { 1813 foreach ($rule['fields'] as $field_name => $field_rule) { 1814 if (array_key_exists($field_name, $object)) { 1815 $subpath = ($path === '/' ? $path : $path.'/').($index + 1).'/'.$field_name; 1816 if (!self::validateDataUniqueness($field_rule, $object[$field_name], $subpath, $error)) { 1817 return false; 1818 } 1819 } 1820 } 1821 } 1822 1823 return true; 1824 } 1825 1826 /** 1827 * HTTP POST validator. Posts can be set to string (raw post) or to http pairs (form fields) 1828 * 1829 * @param array $rule 1830 * @param int $rule['length'] (optional) 1831 * @param int $rule['name-length'] (optional) 1832 * @param int $rule['value-length'] (optional) 1833 * @param mixed $data 1834 * @param string $path 1835 * @param string $error 1836 * 1837 * @return bool 1838 */ 1839 private static function validateHttpPosts($rule, &$data, $path, &$error) { 1840 if (is_array($data)) { 1841 $rules = ['type' => API_OBJECTS, 'fields' => [ 1842 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY], 1843 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED] 1844 ]]; 1845 1846 if (array_key_exists('name-length', $rule)) { 1847 $rules['fields']['name']['length'] = $rule['name-length']; 1848 } 1849 1850 if (array_key_exists('value-length', $rule)) { 1851 $rules['fields']['value']['length'] = $rule['value-length']; 1852 } 1853 } 1854 else { 1855 $rules = ['type' => API_STRING_UTF8]; 1856 1857 if (array_key_exists('length', $rule)) { 1858 $rules['length'] = $rule['length']; 1859 } 1860 } 1861 1862 return self::validateData($rules, $data, $path, $error); 1863 } 1864 1865 /** 1866 * HTTP variable validator. 1867 * 1868 * @param array $rule 1869 * @param int $rule['length'] (optional) 1870 * @param mixed $data 1871 * @param string $path 1872 * @param string $error 1873 * 1874 * @return bool 1875 */ 1876 private static function validateVariableName($rule, &$data, $path, &$error) { 1877 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1878 return false; 1879 } 1880 1881 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1882 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1883 return false; 1884 } 1885 1886 if (preg_match('/^{[^{}]+}$/', $data) !== 1) { 1887 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('is not enclosed in {} or is malformed')); 1888 return false; 1889 } 1890 1891 return true; 1892 } 1893 1894 /** 1895 * URL validator. 1896 * 1897 * @param array $rule 1898 * @param int $rule['length'] (optional) 1899 * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO, API_ALLOW_EVENT_TAGS_MACRO, API_NOT_EMPTY 1900 * @param mixed $data 1901 * @param string $path 1902 * @param string $error 1903 * 1904 * @return bool 1905 */ 1906 private static function validateUrl($rule, &$data, $path, &$error) { 1907 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1908 1909 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1910 return false; 1911 } 1912 1913 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1914 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1915 return false; 1916 } 1917 1918 $options = [ 1919 'allow_user_macro' => (bool) ($flags & API_ALLOW_USER_MACRO), 1920 'allow_event_tags_macro' => (bool) ($flags & API_ALLOW_EVENT_TAGS_MACRO) 1921 ]; 1922 1923 if ($data !== '' && CHtmlUrlValidator::validate($data, $options) === false) { 1924 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('unacceptable URL')); 1925 return false; 1926 } 1927 1928 return true; 1929 } 1930 1931 /** 1932 * IP address validator. 1933 * 1934 * @param array $rule 1935 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO, 1936 * API_ALLOW_MACRO 1937 * @param int $rule['length'] (optional) 1938 * @param mixed $data 1939 * @param string $path 1940 * @param string $error 1941 * 1942 * @return bool 1943 */ 1944 private static function validateIp($rule, &$data, $path, &$error) { 1945 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1946 1947 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1948 return false; 1949 } 1950 1951 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1952 return true; 1953 } 1954 1955 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1956 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1957 return false; 1958 } 1959 1960 $ip_parser = new CIPParser([ 1961 'v6' => ZBX_HAVE_IPV6, 1962 'usermacros' => ($flags & API_ALLOW_USER_MACRO), 1963 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 1964 'macros' => ($flags & API_ALLOW_MACRO) 1965 ]); 1966 1967 if ($ip_parser->parse($data) != CParser::PARSE_SUCCESS) { 1968 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an IP address is expected')); 1969 return false; 1970 } 1971 1972 return true; 1973 } 1974 1975 /** 1976 * DNS name validator. 1977 * 1978 * @param array $rule 1979 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO, 1980 * API_ALLOW_MACRO 1981 * @param int $rule['length'] (optional) 1982 * @param mixed $data 1983 * @param string $path 1984 * @param string $error 1985 * 1986 * @return bool 1987 */ 1988 private static function validateDns($rule, &$data, $path, &$error) { 1989 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1990 1991 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1992 return false; 1993 } 1994 1995 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1996 return true; 1997 } 1998 1999 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2000 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2001 return false; 2002 } 2003 2004 $dns_parser = new CDnsParser([ 2005 'usermacros' => ($flags & API_ALLOW_USER_MACRO), 2006 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 2007 'macros' => ($flags & API_ALLOW_MACRO) 2008 ]); 2009 2010 if ($dns_parser->parse($data) != CParser::PARSE_SUCCESS) { 2011 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a DNS name is expected')); 2012 return false; 2013 } 2014 2015 return true; 2016 } 2017 2018 /** 2019 * Port number validator. 2020 * 2021 * @param array $rule 2022 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO 2023 * @param int $rule['length'] (optional) 2024 * @param mixed $data 2025 * @param string $path 2026 * @param string $error 2027 * 2028 * @return bool 2029 */ 2030 private static function validatePort($rule, &$data, $path, &$error) { 2031 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 2032 2033 if (!is_int($data) && !is_string($data)) { 2034 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); 2035 return false; 2036 } 2037 2038 $data = (string) $data; 2039 2040 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 2041 return false; 2042 } 2043 2044 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 2045 return true; 2046 } 2047 2048 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2049 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2050 return false; 2051 } 2052 2053 if ($flags & API_ALLOW_USER_MACRO) { 2054 $user_macro_parser = new CUserMacroParser(); 2055 2056 if ($user_macro_parser->parse($data) == CParser::PARSE_SUCCESS) { 2057 return true; 2058 } 2059 } 2060 2061 if ($flags & API_ALLOW_LLD_MACRO) { 2062 $lld_macro_parser = new CLLDMacroParser(); 2063 2064 if ($lld_macro_parser->parse($data) == CParser::PARSE_SUCCESS) { 2065 return true; 2066 } 2067 } 2068 2069 if (!self::validateInt32(['in' => ZBX_MIN_PORT_NUMBER.':'.ZBX_MAX_PORT_NUMBER], $data, $path, $error)) { 2070 return false; 2071 } 2072 2073 $data = (string) $data; 2074 2075 return true; 2076 } 2077 2078 /** 2079 * Trigger expression validator. 2080 * 2081 * @param array $rule 2082 * @param int $rule['length'] (optional) 2083 * @param int $rule['flags'] (optional) API_ALLOW_LLD_MACRO, API_NOT_EMPTY 2084 * @param mixed $data 2085 * @param string $path 2086 * @param string $error 2087 * 2088 * @return bool 2089 */ 2090 private static function validateTriggerExpression($rule, &$data, $path, &$error) { 2091 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 2092 2093 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 2094 return false; 2095 } 2096 2097 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 2098 return true; 2099 } 2100 2101 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2102 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2103 return false; 2104 } 2105 2106 $expression_parser = new CExpressionParser([ 2107 'usermacros' => true, 2108 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO) 2109 ]); 2110 2111 if ($expression_parser->parse($data) != CParser::PARSE_SUCCESS) { 2112 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_parser->getError()); 2113 return false; 2114 } 2115 2116 $expression_validator = new CExpressionValidator([ 2117 'usermacros' => true, 2118 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO) 2119 ]); 2120 2121 if (!$expression_validator->validate($expression_parser->getResult()->getTokens())) { 2122 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_validator->getError()); 2123 return false; 2124 } 2125 2126 return true; 2127 } 2128 2129 /** 2130 * Event name validator. 2131 * 2132 * @param array $rule 2133 * @param int $rule['length'] (optional) 2134 * @param mixed $data 2135 * @param string $path 2136 * @param string $error 2137 * 2138 * @return bool 2139 */ 2140 private static function validateEventName($rule, &$data, $path, &$error) { 2141 if (self::checkStringUtf8(0, $data, $path, $error) === false) { 2142 return false; 2143 } 2144 2145 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2146 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2147 return false; 2148 } 2149 2150 $eventname_validator = new CEventNameValidator(); 2151 2152 if (!$eventname_validator->validate($data)) { 2153 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $eventname_validator->getError()); 2154 return false; 2155 } 2156 2157 return true; 2158 } 2159 2160 /** 2161 * JSON RPC parameters validator. Parameters MUST contain an array or object value. 2162 * 2163 * @param array $rule 2164 * @param mixed $data 2165 * @param string $path 2166 * @param string $error 2167 * 2168 * @return bool 2169 */ 2170 private static function validateJsonRpcParams($rule, &$data, $path, &$error) { 2171 if (is_array($data)) { 2172 return true; 2173 } 2174 2175 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or object is expected')); 2176 2177 return false; 2178 } 2179 2180 /** 2181 * JSON RPC identifier validator. This identifier MUST contain a String, Number, or NULL value. 2182 * 2183 * @param array $rule 2184 * @param mixed $data 2185 * @param string $path 2186 * @param string $error 2187 * 2188 * @return bool 2189 */ 2190 private static function validateJsonRpcId($rule, &$data, $path, &$error) { 2191 if (is_string($data) || is_int($data) || is_float($data) || $data === null) { 2192 return true; 2193 } 2194 2195 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a string, number or null value is expected')); 2196 2197 return false; 2198 } 2199 2200 /** 2201 * Date validator in YYYY-MM-DD format. 2202 * 2203 * @param array $rule 2204 * @param int $rule['flags'] (optional) API_NOT_EMPTY 2205 * @param mixed $data 2206 * @param string $path 2207 * @param string $error 2208 * 2209 * @return bool 2210 */ 2211 private static function validateDate(array $rule, &$data, string $path, string &$error): bool { 2212 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 2213 2214 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 2215 return false; 2216 } 2217 2218 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 2219 return true; 2220 } 2221 2222 [$year, $month, $day] = sscanf($data, '%d-%d-%d'); 2223 2224 if (!checkdate($month, $day, $year)) { 2225 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a date in YYYY-MM-DD format is expected')); 2226 return false; 2227 } 2228 2229 if (!validateDateInterval($year, $month, $day)) { 2230 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 2231 _s('value must be between "%1$s" and "%2$s"', '1970-01-01', '2038-01-18') 2232 ); 2233 return false; 2234 } 2235 2236 return true; 2237 } 2238 2239 /** 2240 * Validate numeric ranges. Multiple ranges separated by comma character. 2241 * Example: 2242 * 10-20,-20--10,-5-0,0.5-0.7,-20--10,-20.20--20.10 2243 * 30,-10,0.7,-0.5 2244 * 2245 * @param array $rule 2246 * @param int $rule['flags'] (optional) API_NOT_EMPTY 2247 * @param int $rule['length'] (optional) 2248 * @param mixed $data 2249 * @param string $path 2250 * @param string $error 2251 * 2252 * @return bool 2253 */ 2254 private static function validateNumericRanges($rule, &$data, $path, &$error) { 2255 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 2256 2257 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 2258 return false; 2259 } 2260 2261 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 2262 return true; 2263 } 2264 2265 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2266 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2267 return false; 2268 } 2269 2270 $parser = new CRangesParser(['with_minus' => true, 'with_float' => true, 'with_suffix' => true]); 2271 2272 if ($parser->parse($data) != CParser::PARSE_SUCCESS) { 2273 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid range expression')); 2274 2275 return false; 2276 } 2277 2278 return true; 2279 } 2280 2281 /** 2282 * UUIDv4 validator. 2283 * 2284 * @param array $rule 2285 * @param int $rule['flags'] (optional) API_NOT_EMPTY 2286 * @param mixed $data 2287 * @param string $path 2288 * @param string $error 2289 * 2290 * @return bool 2291 */ 2292 private static function validateUuid(array $rule, &$data, string $path, string &$error): bool { 2293 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 2294 return false; 2295 } 2296 2297 if (mb_strlen($data) != 32) { 2298 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('must be %1$s characters long', 32)); 2299 return false; 2300 } 2301 2302 if (!ctype_xdigit($data)) { 2303 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('UUIDv4 is expected')); 2304 return false; 2305 } 2306 2307 $binary = hex2bin($data); 2308 if ((ord($binary[6]) & 0xf0) != 0x40 || (ord($binary[8]) & 0xc0) != 0x80) { 2309 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('UUIDv4 is expected')); 2310 return false; 2311 } 2312 2313 $data = strtolower($data); 2314 2315 return true; 2316 } 2317 2318 /** 2319 * User vault secret. 2320 * 2321 * @param array $rule 2322 * @param int $rule['length'] (optional) 2323 * @param mixed $data 2324 * @param string $path 2325 * @param string $error 2326 * 2327 * @return bool 2328 */ 2329 private static function validateVaultSecret($rule, &$data, $path, &$error) { 2330 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 2331 return false; 2332 } 2333 2334 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 2335 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 2336 return false; 2337 } 2338 2339 $vault_secret_parser = new CVaultSecretParser(); 2340 2341 if ($vault_secret_parser->parse($data) != CParser::PARSE_SUCCESS) { 2342 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $vault_secret_parser->getError()); 2343 return false; 2344 } 2345 2346 return true; 2347 } 2348} 2349