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_NAME: 146 return self::validateScriptName($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_TRIGGER_EXPRESSION: 176 return self::validateTriggerExpression($rule, $data, $path, $error); 177 178 case API_JSONRPC_PARAMS: 179 return self::validateJsonRpcParams($rule, $data, $path, $error); 180 181 case API_JSONRPC_ID: 182 return self::validateJsonRpcId($rule, $data, $path, $error); 183 } 184 185 // This message can be untranslated because warn about incorrect validation rules at a development stage. 186 $error = 'Incorrect validation rules.'; 187 188 return false; 189 } 190 191 /** 192 * Base data uniqueness validation function. 193 * 194 * @param array $rule 195 * @param mixed $data 196 * @param string $path 197 * @param string $error 198 * 199 * @return bool 200 */ 201 private static function validateDataUniqueness($rule, &$data, $path, &$error) { 202 switch ($rule['type']) { 203 case API_CALC_FORMULA: 204 case API_COLOR: 205 case API_MULTIPLE: 206 case API_STRING_UTF8: 207 case API_INT32: 208 case API_UINT64: 209 case API_UINTS64: 210 case API_FLOAT: 211 case API_FLOATS: 212 case API_ID: 213 case API_BOOLEAN: 214 case API_FLAG: 215 case API_OUTPUT: 216 case API_PSK: 217 case API_SORTORDER: 218 case API_HG_NAME: 219 case API_H_NAME: 220 case API_NUMERIC: 221 case API_SCRIPT_NAME: 222 case API_USER_MACRO: 223 case API_LLD_MACRO: 224 case API_RANGE_TIME: 225 case API_TIME_PERIOD: 226 case API_TIME_UNIT: 227 case API_REGEX: 228 case API_HTTP_POST: 229 case API_VARIABLE_NAME: 230 case API_URL: 231 case API_TRIGGER_EXPRESSION: 232 case API_JSONRPC_PARAMS: 233 case API_JSONRPC_ID: 234 return true; 235 236 case API_OBJECT: 237 foreach ($rule['fields'] as $field_name => $field_rule) { 238 if ($data !== null && array_key_exists($field_name, $data)) { 239 $subpath = ($path === '/' ? $path : $path.'/').$field_name; 240 if (!self::validateDataUniqueness($field_rule, $data[$field_name], $subpath, $error)) { 241 return false; 242 } 243 } 244 } 245 return true; 246 247 case API_IDS: 248 case API_STRINGS_UTF8: 249 case API_INTS32: 250 return self::validateStringsUniqueness($rule, $data, $path, $error); 251 252 case API_OBJECTS: 253 return self::validateObjectsUniqueness($rule, $data, $path, $error); 254 } 255 256 // This message can be untranslated because warn about incorrect validation rules at a development stage. 257 $error = 'Incorrect validation rules.'; 258 259 return false; 260 } 261 262 /** 263 * Generic string validator. 264 * 265 * @param int $flags API_NOT_EMPTY 266 * @param mixed $data 267 * @param string $path 268 * @param string $error 269 * 270 * @return bool 271 */ 272 private static function checkStringUtf8($flags, &$data, $path, &$error) { 273 if (!is_string($data)) { 274 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a character string is expected')); 275 return false; 276 } 277 278 if (mb_check_encoding($data, 'UTF-8') !== true) { 279 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid byte sequence in UTF-8')); 280 return false; 281 } 282 283 if (($flags & API_NOT_EMPTY) && $data === '') { 284 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 285 return false; 286 } 287 288 return true; 289 } 290 291 /** 292 * Calculated item formula validator. 293 * 294 * @param array $rule 295 * @param int $rule['flags'] (optional) API_ALLOW_LLD_MACRO 296 * @param mixed $data 297 * @param string $path 298 * @param string $error 299 * 300 * @return bool 301 */ 302 private static function validateCalcFormula($rule, &$data, $path, &$error) { 303 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 304 305 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 306 return false; 307 } 308 309 $expression_data = new CTriggerExpression(['calculated' => true, 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO)]); 310 if (!$expression_data->parse($data)) { 311 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_data->error); 312 return false; 313 } 314 315 return true; 316 } 317 318 /** 319 * Color validator. 320 * 321 * @param array $rule 322 * @param int $rule['flags'] (optional) API_NOT_EMPTY 323 * @param mixed $data 324 * @param string $path 325 * @param string $error 326 * 327 * @return bool 328 */ 329 private static function validateColor($rule, &$data, $path, &$error) { 330 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 331 332 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 333 return false; 334 } 335 336 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 337 return true; 338 } 339 340 if (preg_match('/^[0-9a-f]{6}$/i', $data) !== 1) { 341 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 342 _('a hexadecimal colour code (6 symbols) is expected') 343 ); 344 return false; 345 } 346 347 return true; 348 } 349 350 /** 351 * Multiple data types validator. 352 * 353 * @param array $rule 354 * @param array $rule['rules'] 355 * @param mixed $data 356 * @param string $path 357 * @param string $error 358 * @param array $parent_data 359 * 360 * @return bool 361 */ 362 private static function validateMultiple($rule, &$data, $path, &$error, array $parent_data) { 363 foreach ($rule['rules'] as $field_rule) { 364 if (self::Int32In($parent_data[$field_rule['if']['field']], $field_rule['if']['in'])) { 365 unset($field_rule['if']); 366 367 return self::validateData($field_rule, $data, $path, $error); 368 } 369 } 370 371 // This message can be untranslated because warn about incorrect validation rules at a development stage. 372 $error = 'Incorrect validation rules.'; 373 374 return false; 375 } 376 377 /** 378 * String validator. 379 * 380 * @param array $rule 381 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL 382 * @param string $rule['in'] (optional) a comma-delimited character string, for example: 'xml,json' 383 * @param int $rule['length'] (optional) 384 * @param mixed $data 385 * @param string $path 386 * @param string $error 387 * 388 * @return bool 389 */ 390 private static function validateStringUtf8($rule, &$data, $path, &$error) { 391 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 392 393 if (($flags & API_ALLOW_NULL) && $data === null) { 394 return true; 395 } 396 397 if (self::checkStringUtf8($flags, $data, $path, $error) === false) { 398 return false; 399 } 400 401 if (array_key_exists('in', $rule) && !in_array($data, explode(',', $rule['in']), true)) { 402 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 403 _s('value must be one of %1$s', str_replace(',', ', ', $rule['in'])) 404 ); 405 return false; 406 } 407 408 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 409 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 410 return false; 411 } 412 413 return true; 414 } 415 416 /** 417 * Array of strings validator. 418 * 419 * @param array $rule 420 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 421 * @param string $rule['in'] (optional) a comma-delimited character string, for example: 'xml,json' 422 * @param mixed $data 423 * @param string $path 424 * @param string $error 425 * 426 * @return bool 427 */ 428 private static function validateStringsUtf8($rule, &$data, $path, &$error) { 429 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 430 431 if (($flags & API_ALLOW_NULL) && $data === null) { 432 return true; 433 } 434 435 if (($flags & API_NORMALIZE) && self::validateStringUtf8([], $data, '', $e)) { 436 $data = [$data]; 437 } 438 unset($e); 439 440 if (!is_array($data)) { 441 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 442 return false; 443 } 444 445 if (($flags & API_NOT_EMPTY) && !$data) { 446 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 447 return false; 448 } 449 450 $data = array_values($data); 451 $rules = ['type' => API_STRING_UTF8]; 452 453 if (array_key_exists('in', $rule)) { 454 $rules['in'] = $rule['in']; 455 } 456 457 foreach ($data as $index => &$value) { 458 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 459 if (!self::validateData($rules, $value, $subpath, $error)) { 460 return false; 461 } 462 } 463 unset($value); 464 465 return true; 466 } 467 468 /** 469 * Integers validator. 470 * 471 * @param array $rule 472 * @param int $rule['flags'] (optional) API_ALLOW_NULL 473 * @param string $rule['in'] (optional) a comma-delimited character string, for example: '0,60:900' 474 * @param mixed $data 475 * @param string $path 476 * @param string $error 477 * 478 * @return bool 479 */ 480 private static function validateInt32($rule, &$data, $path, &$error) { 481 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 482 483 if (($flags & API_ALLOW_NULL) && $data === null) { 484 return true; 485 } 486 487 if ((!is_int($data) && !is_string($data)) || !preg_match('/^'.ZBX_PREG_INT.'$/', strval($data))) { 488 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an integer is expected')); 489 return false; 490 } 491 492 if ($data < ZBX_MIN_INT32 || $data > ZBX_MAX_INT32) { 493 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 494 return false; 495 } 496 497 if (!self::checkInt32In($rule, $data, $path, $error)) { 498 return false; 499 } 500 501 if (is_string($data)) { 502 $data = (int) $data; 503 } 504 505 return true; 506 } 507 508 /** 509 * Unsigned integers validator. 510 * 511 * @param array $rule 512 * @param int $rule['flags'] (optional) API_ALLOW_NULL 513 * @param mixed $data 514 * @param string $path 515 * @param string $error 516 * 517 * @return bool 518 */ 519 private static function validateUInt64($rule, &$data, $path, &$error) { 520 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 521 522 if (($flags & API_ALLOW_NULL) && $data === null) { 523 return true; 524 } 525 526 if (!is_scalar($data) || is_bool($data) || is_double($data) || !ctype_digit(strval($data))) { 527 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an unsigned integer is expected')); 528 return false; 529 } 530 531 if (bccomp($data, ZBX_MAX_UINT64) > 0) { 532 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 533 return false; 534 } 535 536 $data = (string) $data; 537 538 if ($data[0] === '0') { 539 $data = ltrim($data, '0'); 540 541 if ($data === '') { 542 $data = '0'; 543 } 544 } 545 546 return true; 547 } 548 549 /** 550 * Array of unsigned integers validator. 551 * 552 * @param array $rule 553 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 554 * @param mixed $data 555 * @param string $path 556 * @param string $error 557 * 558 * @return bool 559 */ 560 private static function validateUInts64($rule, &$data, $path, &$error) { 561 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 562 563 if (($flags & API_ALLOW_NULL) && $data === null) { 564 return true; 565 } 566 567 if (($flags & API_NORMALIZE) && self::validateUInt64([], $data, '', $e)) { 568 $data = [$data]; 569 } 570 unset($e); 571 572 if (!is_array($data)) { 573 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 574 return false; 575 } 576 577 if (($flags & API_NOT_EMPTY) && !$data) { 578 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 579 return false; 580 } 581 582 $data = array_values($data); 583 $rules = ['type' => API_UINT64]; 584 585 foreach ($data as $index => &$value) { 586 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 587 if (!self::validateData($rules, $value, $subpath, $error)) { 588 return false; 589 } 590 } 591 unset($value); 592 593 return true; 594 } 595 596 /** 597 * Floating point number validator. 598 * 599 * @param array $rule 600 * @param int $rule['flags'] (optional) API_ALLOW_NULL 601 * @param mixed $data 602 * @param string $path 603 * @param string $error 604 * 605 * @return bool 606 */ 607 private static function validateFloat($rule, &$data, $path, &$error) { 608 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 609 610 if (($flags & API_ALLOW_NULL) && $data === null) { 611 return true; 612 } 613 614 if (is_int($data) || is_float($data)) { 615 $value = (float) $data; 616 } 617 elseif (is_string($data)) { 618 $number_parser = new CNumberParser(); 619 620 if ($number_parser->parse($data) == CParser::PARSE_SUCCESS) { 621 $value = (float) $number_parser->getMatch(); 622 } 623 else { 624 $value = NAN; 625 } 626 } 627 else { 628 $value = NAN; 629 } 630 631 if (is_nan($value)) { 632 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a floating point value is expected')); 633 634 return false; 635 } 636 637 $data = $value; 638 639 return true; 640 } 641 642 /** 643 * Array of floating point numbers validator. 644 * 645 * @param array $rule 646 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 647 * @param mixed $data 648 * @param string $path 649 * @param string $error 650 * 651 * @return bool 652 */ 653 private static function validateFloats($rule, &$data, $path, &$error) { 654 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 655 656 if (($flags & API_ALLOW_NULL) && $data === null) { 657 return true; 658 } 659 660 if (($flags & API_NORMALIZE) && self::validateFloat([], $data, '', $e)) { 661 $data = [$data]; 662 } 663 unset($e); 664 665 if (!is_array($data)) { 666 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 667 return false; 668 } 669 670 if (($flags & API_NOT_EMPTY) && !$data) { 671 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 672 return false; 673 } 674 675 $data = array_values($data); 676 $rules = ['type' => API_FLOAT]; 677 678 foreach ($data as $index => &$value) { 679 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 680 if (!self::validateData($rules, $value, $subpath, $error)) { 681 return false; 682 } 683 } 684 unset($value); 685 686 return true; 687 } 688 689 private static function Int32In($data, $in) { 690 $valid = false; 691 692 foreach (explode(',', $in) as $in) { 693 if (strpos($in, ':') !== false) { 694 list($from, $to) = explode(':', $in); 695 } 696 else { 697 $from = $in; 698 $to = $in; 699 } 700 701 if ($from <= $data && $data <= $to) { 702 $valid = true; 703 break; 704 } 705 } 706 707 return $valid; 708 } 709 710 /** 711 * @param array $rule 712 * @param int $rule['in'] (optional) 713 * @param int $data 714 * @param string $path 715 * @param string $error 716 * 717 * @return bool 718 */ 719 private static function checkInt32In($rule, $data, $path, &$error) { 720 if (!array_key_exists('in', $rule)) { 721 return true; 722 } 723 724 $valid = self::Int32In($data, $rule['in']); 725 726 if (!$valid) { 727 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 728 _s('value must be one of %1$s', str_replace([',', ':'], [', ', '-'], $rule['in'])) 729 ); 730 } 731 732 return $valid; 733 } 734 735 /** 736 * Array of integers validator. 737 * 738 * @param array $rule 739 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 740 * @param string $rule['in'] (optional) a comma-delimited character string, for example: '0,60:900' 741 * @param mixed $data 742 * @param string $path 743 * @param string $error 744 * 745 * @return bool 746 */ 747 private static function validateInts32($rule, &$data, $path, &$error) { 748 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 749 750 if (($flags & API_ALLOW_NULL) && $data === null) { 751 return true; 752 } 753 754 if (($flags & API_NORMALIZE) && self::validateInt32([], $data, '', $e)) { 755 $data = [$data]; 756 } 757 unset($e); 758 759 if (!is_array($data)) { 760 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 761 return false; 762 } 763 764 if (($flags & API_NOT_EMPTY) && !$data) { 765 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 766 return false; 767 } 768 769 $data = array_values($data); 770 $rules = ['type' => API_INT32]; 771 772 if (array_key_exists('in', $rule)) { 773 $rules['in'] = $rule['in']; 774 } 775 776 foreach ($data as $index => &$value) { 777 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 778 if (!self::validateData($rules, $value, $subpath, $error)) { 779 return false; 780 } 781 } 782 unset($value); 783 784 return true; 785 } 786 787 /** 788 * Identifier validator. 789 * 790 * @param array $rule 791 * @param int $rule['flags'] (optional) API_ALLOW_NULL, API_NOT_EMPTY 792 * @param mixed $data 793 * @param string $path 794 * @param string $error 795 * 796 * @return bool 797 */ 798 private static function validateId($rule, &$data, $path, &$error) { 799 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 800 801 if (($flags & API_ALLOW_NULL) && $data === null) { 802 return true; 803 } 804 805 if (!is_scalar($data) || is_bool($data) || is_double($data) || !ctype_digit(strval($data))) { 806 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); 807 return false; 808 } 809 810 if (($flags & API_NOT_EMPTY) && $data == 0) { 811 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 812 return false; 813 } 814 815 if (bccomp($data, ZBX_DB_MAX_ID) > 0) { 816 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 817 return false; 818 } 819 820 $data = (string) $data; 821 822 if ($data[0] === '0') { 823 $data = ltrim($data, '0'); 824 825 if ($data === '') { 826 $data = '0'; 827 } 828 } 829 830 return true; 831 } 832 833 /** 834 * Boolean validator. 835 * 836 * @param array $rule 837 * @param int $rule['flags'] (optional) API_ALLOW_NULL 838 * @param mixed $data 839 * @param string $path 840 * @param string $error 841 * 842 * @return bool 843 */ 844 private static function validateBoolean($rule, &$data, $path, &$error) { 845 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 846 847 if (($flags & API_ALLOW_NULL) && $data === null) { 848 return true; 849 } 850 851 if (!is_bool($data)) { 852 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a boolean is expected')); 853 return false; 854 } 855 856 return true; 857 } 858 859 /** 860 * Flag validator. 861 * 862 * @param array $rule 863 * @param mixed $data 864 * @param string $path 865 * @param string $error 866 * 867 * @return bool 868 */ 869 private static function validateFlag($rule, &$data, $path, &$error) { 870 if (is_bool($data)) { 871 return true; 872 } 873 874 /** 875 * @deprecated As of version 3.4, use boolean flags only. 876 */ 877 trigger_error(_('Non-boolean flags are deprecated.'), E_USER_NOTICE); 878 879 $data = !is_null($data); 880 881 return true; 882 } 883 884 /** 885 * Object validator. 886 * 887 * @param array $rul 888 * @param int $rule['flags'] (optional) API_ALLOW_NULL 889 * @param array $rule['fields'] 890 * @param int $rule['fields'][<field_name>]['flags'] (optional) API_REQUIRED, API_DEPRECATED 891 * @param mixed $rule['fields'][<field_name>]['default'] (optional) 892 * @param string $rule['fields'][<field_name>]['default_source'] (optional) 893 * @param mixed $data 894 * @param string $path 895 * @param string $error 896 * 897 * @return bool 898 */ 899 private static function validateObject($rule, &$data, $path, &$error) { 900 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 901 902 if (($flags & API_ALLOW_NULL) && $data === null) { 903 return true; 904 } 905 906 if (!is_array($data)) { 907 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 908 return false; 909 } 910 911 // unexpected parameter validation 912 foreach ($data as $field_name => $value) { 913 if (!array_key_exists($field_name, $rule['fields'])) { 914 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('unexpected parameter "%1$s"', $field_name)); 915 return false; 916 } 917 } 918 919 // validation of the values type 920 foreach ($rule['fields'] as $field_name => $field_rule) { 921 $flags = array_key_exists('flags', $field_rule) ? $field_rule['flags'] : 0x00; 922 923 if (array_key_exists('default', $field_rule) && !array_key_exists($field_name, $data)) { 924 $data[$field_name] = $field_rule['default']; 925 } 926 927 if (array_key_exists('default_source', $field_rule) && !array_key_exists($field_name, $data)) { 928 $data[$field_name] = $data[$field_rule['default_source']]; 929 } 930 931 if (array_key_exists($field_name, $data)) { 932 $subpath = ($path === '/' ? $path : $path.'/').$field_name; 933 if (!self::validateData($field_rule, $data[$field_name], $subpath, $error, $data)) { 934 return false; 935 } 936 if ($flags & API_DEPRECATED) { 937 trigger_error(_s('Parameter "%1$s" is deprecated.', $subpath), E_USER_NOTICE); 938 } 939 } 940 elseif ($flags & API_REQUIRED) { 941 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 942 _s('the parameter "%1$s" is missing', $field_name) 943 ); 944 return false; 945 } 946 } 947 948 return true; 949 } 950 951 /** 952 * API output validator. 953 * 954 * @param array $rule 955 * @param int $rule['flags'] (optional) API_ALLOW_COUNT, API_ALLOW_NULL 956 * @param string $rule['in'] (optional) comma-delimited field names, for example: 'hostid,name' 957 * @param mixed $data 958 * @param string $path 959 * @param string $error 960 * 961 * @return bool 962 */ 963 private static function validateOutput($rule, &$data, $path, &$error) { 964 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 965 966 if (($flags & API_ALLOW_NULL) && $data === null) { 967 return true; 968 } 969 970 if (is_array($data)) { 971 $rules = ['type' => API_STRINGS_UTF8, 'uniq' => true]; 972 973 if (array_key_exists('in', $rule)) { 974 $rules['in'] = $rule['in']; 975 } 976 977 return self::validateData($rules, $data, $path, $error) 978 && self::validateDataUniqueness($rules, $data, $path, $error); 979 } 980 elseif (is_string($data)) { 981 $in = ($flags & API_ALLOW_COUNT) ? implode(',', [API_OUTPUT_EXTEND, API_OUTPUT_COUNT]) : API_OUTPUT_EXTEND; 982 983 return self::validateData(['type' => API_STRING_UTF8, 'in' => $in], $data, $path, $error); 984 } 985 986 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or a character string is expected')); 987 988 return false; 989 } 990 991 /** 992 * PSK key validator. 993 * 994 * @param array $rule 995 * @param int $rule['flags'] (optional) API_NOT_EMPTY 996 * @param int $rule['length'] (optional) 997 * @param mixed $data 998 * @param string $path 999 * @param string $error 1000 * 1001 * @return bool 1002 */ 1003 private static function validatePSK($rule, &$data, $path, &$error) { 1004 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1005 1006 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1007 return false; 1008 } 1009 1010 $mb_len = mb_strlen($data); 1011 1012 if ($mb_len != 0 && $mb_len < PSK_MIN_LEN) { 1013 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('minimum length is %1$s characters', PSK_MIN_LEN)); 1014 return false; 1015 } 1016 1017 if (preg_match('/^([0-9a-f]{2})*$/i', $data) !== 1) { 1018 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1019 _('an even number of hexadecimal characters is expected') 1020 ); 1021 return false; 1022 } 1023 1024 if (array_key_exists('length', $rule) && $mb_len > $rule['length']) { 1025 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1026 return false; 1027 } 1028 1029 return true; 1030 } 1031 1032 /** 1033 * API sort order validator. 1034 * 1035 * @param array $rule 1036 * @param mixed $data 1037 * @param string $path 1038 * @param string $error 1039 * 1040 * @return bool 1041 */ 1042 private static function validateSortOrder($rule, &$data, $path, &$error) { 1043 $in = ZBX_SORT_UP.','.ZBX_SORT_DOWN; 1044 1045 if (self::validateStringUtf8(['in' => $in], $data, $path, $e)) { 1046 return true; 1047 } 1048 1049 if (is_string($data)) { 1050 $error = $e; 1051 return false; 1052 } 1053 unset($e); 1054 1055 if (!is_array($data)) { 1056 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or a character string is expected')); 1057 return false; 1058 } 1059 1060 $data = array_values($data); 1061 $rules = [ 1062 'type' => API_STRING_UTF8, 1063 'in' => $in 1064 ]; 1065 1066 foreach ($data as $index => &$value) { 1067 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1068 if (!self::validateData($rules, $value, $subpath, $error)) { 1069 return false; 1070 } 1071 } 1072 unset($value); 1073 1074 return true; 1075 } 1076 1077 /** 1078 * Array of ids validator. 1079 * 1080 * @param array $rule 1081 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE 1082 * @param mixed $data 1083 * @param string $path 1084 * @param string $error 1085 * 1086 * @return bool 1087 */ 1088 private static function validateIds($rule, &$data, $path, &$error) { 1089 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1090 1091 if (($flags & API_ALLOW_NULL) && $data === null) { 1092 return true; 1093 } 1094 1095 if (($flags & API_NORMALIZE) && self::validateId([], $data, '', $e)) { 1096 $data = [$data]; 1097 } 1098 unset($e); 1099 1100 if (!is_array($data)) { 1101 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 1102 return false; 1103 } 1104 1105 if (($flags & API_NOT_EMPTY) && !$data) { 1106 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 1107 return false; 1108 } 1109 1110 $data = array_values($data); 1111 1112 foreach ($data as $index => &$value) { 1113 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1114 if (!self::validateId([], $value, $subpath, $error)) { 1115 return false; 1116 } 1117 } 1118 unset($value); 1119 1120 return true; 1121 } 1122 1123 /** 1124 * Array of objects validator. 1125 * 1126 * @param array $rule 1127 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_NULL, API_NORMALIZE, API_PRESERVE_KEYS 1128 * @param array $rule['fields'] 1129 * @param mixed $data 1130 * @param string $path 1131 * @param string $error 1132 * 1133 * @return bool 1134 */ 1135 private static function validateObjects($rule, &$data, $path, &$error) { 1136 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1137 1138 if (($flags & API_ALLOW_NULL) && $data === null) { 1139 return true; 1140 } 1141 1142 if (!is_array($data)) { 1143 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array is expected')); 1144 return false; 1145 } 1146 1147 if (($flags & API_NOT_EMPTY) && !$data) { 1148 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); 1149 return false; 1150 } 1151 1152 if (($flags & API_NORMALIZE) && $data) { 1153 reset($data); 1154 1155 if (!is_int(key($data))) { 1156 $data = [$data]; 1157 } 1158 } 1159 1160 if (!($flags & API_PRESERVE_KEYS)) { 1161 $data = array_values($data); 1162 } 1163 1164 foreach ($data as $index => &$value) { 1165 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1166 if (!self::validateObject(['fields' => $rule['fields']], $value, $subpath, $error)) { 1167 return false; 1168 } 1169 } 1170 unset($value); 1171 1172 return true; 1173 } 1174 1175 /** 1176 * Host group name validator. 1177 * 1178 * @param array $rule 1179 * @param int $rule['flags'] (optional) API_REQUIRED_LLD_MACRO 1180 * @param int $rule['length'] (optional) 1181 * @param mixed $data 1182 * @param string $path 1183 * @param string $error 1184 * 1185 * @return bool 1186 */ 1187 private static function validateHostGroupName($rule, &$data, $path, &$error) { 1188 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1189 return false; 1190 } 1191 1192 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1193 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1194 1195 return false; 1196 } 1197 1198 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1199 $host_group_name_parser = new CHostGroupNameParser(['lldmacros' => ($flags & API_REQUIRED_LLD_MACRO)]); 1200 1201 if ($host_group_name_parser->parse($data) != CParser::PARSE_SUCCESS) { 1202 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid host group name')); 1203 1204 return false; 1205 } 1206 1207 if (($flags & API_REQUIRED_LLD_MACRO) && !$host_group_name_parser->getMacros()) { 1208 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1209 _('must contain at least one low-level discovery macro') 1210 ); 1211 1212 return false; 1213 } 1214 1215 return true; 1216 } 1217 1218 /** 1219 * Host name validator. 1220 * 1221 * @param array $rule 1222 * @param int $rule['flags'] (optional) API_REQUIRED_LLD_MACRO 1223 * @param int $rule['length'] (optional) 1224 * @param mixed $data 1225 * @param string $path 1226 * @param string $error 1227 * 1228 * @return bool 1229 */ 1230 private static function validateHostName($rule, &$data, $path, &$error) { 1231 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1232 return false; 1233 } 1234 1235 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1236 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1237 1238 return false; 1239 } 1240 1241 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1242 $host_name_parser = new CHostNameParser(['lldmacros' => ($flags & API_REQUIRED_LLD_MACRO)]); 1243 1244 // For example, host prototype name MUST contain macros. 1245 if ($host_name_parser->parse($data) != CParser::PARSE_SUCCESS) { 1246 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid host name')); 1247 1248 return false; 1249 } 1250 1251 if (($flags & API_REQUIRED_LLD_MACRO) && !$host_name_parser->getMacros()) { 1252 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1253 _('must contain at least one low-level discovery macro') 1254 ); 1255 1256 return false; 1257 } 1258 1259 return true; 1260 } 1261 1262 /** 1263 * Validator for numeric data with optional suffix. 1264 * Supported time suffixes: s, m, h, d, w 1265 * Supported metric suffixes: K, M, G, T 1266 * 1267 * @param array $rule 1268 * @param int $rule['flags'] (optional) API_NOT_EMPTY 1269 * @param int $rule['length'] (optional) 1270 * @param mixed $data 1271 * @param string $path 1272 * @param string $error 1273 * 1274 * @return bool 1275 */ 1276 private static function validateNumeric($rule, &$data, $path, &$error) { 1277 global $DB; 1278 1279 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1280 1281 if (is_int($data)) { 1282 $data = (string) $data; 1283 } 1284 1285 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1286 return false; 1287 } 1288 1289 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1290 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1291 1292 return false; 1293 } 1294 1295 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1296 return true; 1297 } 1298 1299 $number_parser = new CNumberParser(['with_suffix' => true]); 1300 1301 if ($number_parser->parse($data) != CParser::PARSE_SUCCESS) { 1302 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); 1303 1304 return false; 1305 } 1306 1307 $value = $number_parser->calcValue(); 1308 1309 if ($DB['DOUBLE_IEEE754']) { 1310 if (abs($value) > ZBX_FLOAT_MAX) { 1311 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1312 1313 return false; 1314 } 1315 } 1316 else { 1317 if (abs($value) >= 1E+16) { 1318 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1319 1320 return false; 1321 } 1322 elseif ($value != round($value, 4)) { 1323 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number has too many fractional digits')); 1324 1325 return false; 1326 } 1327 } 1328 1329 // Remove leading zeros. 1330 $data = preg_replace('/^(-)?(0+)?(\d.*)$/', '${1}${3}', $data); 1331 1332 // Add leading zero. 1333 $data = preg_replace('/^(-)?(\..*)$/', '${1}0${2}', $data); 1334 1335 return true; 1336 } 1337 1338 /** 1339 * Global script name validator. 1340 * 1341 * @param array $rule 1342 * @param int $rule['length'] (optional) 1343 * @param mixed $data 1344 * @param string $path 1345 * @param string $error 1346 * 1347 * @return bool 1348 */ 1349 private static function validateScriptName($rule, &$data, $path, &$error) { 1350 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1351 return false; 1352 } 1353 1354 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1355 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1356 return false; 1357 } 1358 1359 $folders = splitPath($data); 1360 $folders = array_map('trim', $folders); 1361 1362 // folder1/{empty}/name or folder1/folder2/{empty} 1363 foreach ($folders as $folder) { 1364 if ($folder === '') { 1365 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('directory or script name cannot be empty')); 1366 return false; 1367 } 1368 } 1369 1370 return true; 1371 } 1372 1373 /** 1374 * User macro validator. 1375 * 1376 * @param array $rule 1377 * @param int $rule['length'] (optional) 1378 * @param mixed $data 1379 * @param string $path 1380 * @param string $error 1381 * 1382 * @return bool 1383 */ 1384 private static function validateUserMacro($rule, &$data, $path, &$error) { 1385 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1386 return false; 1387 } 1388 1389 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1390 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1391 return false; 1392 } 1393 1394 if ((new CUserMacroParser())->parse($data) != CParser::PARSE_SUCCESS) { 1395 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a user macro is expected')); 1396 return false; 1397 } 1398 1399 return true; 1400 } 1401 1402 /** 1403 * LLD macro validator. 1404 * 1405 * @param array $rule 1406 * @param int $rule['length'] (optional) 1407 * @param mixed $data 1408 * @param string $path 1409 * @param string $error 1410 * 1411 * @return bool 1412 */ 1413 private static function validateLLDMacro($rule, &$data, $path, &$error) { 1414 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1415 return false; 1416 } 1417 1418 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1419 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1420 return false; 1421 } 1422 1423 if ((new CLLDMacroParser())->parse($data) != CParser::PARSE_SUCCESS) { 1424 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a low-level discovery macro is expected')); 1425 return false; 1426 } 1427 1428 return true; 1429 } 1430 1431 /** 1432 * Time period validator like "1-7,00:00-24:00". 1433 * 1434 * @param array $rule 1435 * @param int $rule['length'] (optional) 1436 * @param mixed $data 1437 * @param string $path 1438 * @param string $error 1439 * 1440 * @return bool 1441 */ 1442 private static function validateRangeTime($rule, &$data, $path, &$error) { 1443 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1444 1445 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1446 return false; 1447 } 1448 1449 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1450 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1451 return false; 1452 } 1453 1454 $range_time_parser = new CRangeTimeParser(); 1455 1456 if ($range_time_parser->parse($data) != CParser::PARSE_SUCCESS) { 1457 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time range is expected')); 1458 return false; 1459 } 1460 1461 return true; 1462 } 1463 1464 /** 1465 * Time period validator like "1-7,00:00-24:00". 1466 * 1467 * @param array $rule 1468 * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO 1469 * @param int $rule['length'] (optional) 1470 * @param mixed $data 1471 * @param string $path 1472 * @param string $error 1473 * 1474 * @return bool 1475 */ 1476 private static function validateTimePeriod($rule, &$data, $path, &$error) { 1477 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1478 1479 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1480 return false; 1481 } 1482 1483 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1484 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1485 return false; 1486 } 1487 1488 $time_period_parser = new CTimePeriodsParser(['usermacros' => ($flags & API_ALLOW_USER_MACRO)]); 1489 1490 if ($time_period_parser->parse($data) != CParser::PARSE_SUCCESS) { 1491 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time period is expected')); 1492 return false; 1493 } 1494 1495 return true; 1496 } 1497 1498 /** 1499 * Regular expression validator. 1500 * 1501 * @param array $rule 1502 * @param int $rule['flags'] (optional) API_NOT_EMPTY 1503 * @param int $rule['length'] (optional) 1504 * @param mixed $data 1505 * @param string $path 1506 * @param string $error 1507 * 1508 * @return bool 1509 */ 1510 private static function validateRegex($rule, &$data, $path, &$error) { 1511 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1512 1513 if (self::checkStringUtf8($flags, $data, $path, $error) === false) { 1514 return false; 1515 } 1516 1517 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1518 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1519 return false; 1520 } 1521 1522 if ($data !== '' && $data[0] === '@') { 1523 return true; 1524 } 1525 1526 if (@preg_match('/'.str_replace('/', '\/', $data).'/', '') === false) { 1527 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid regular expression')); 1528 return false; 1529 } 1530 1531 return true; 1532 } 1533 1534 /** 1535 * Time unit validator like "10", "20s", "30m", "4h", "{$TIME}" etc. 1536 * 1537 * @param array $rule 1538 * @param int $rule['length'] (optional) 1539 * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO, 1540 * API_TIME_UNIT_WITH_YEAR 1541 * @param int $rule['in'] (optional) 1542 * @param mixed $data 1543 * @param string $path 1544 * @param string $error 1545 * 1546 * @return bool 1547 */ 1548 private static function validateTimeUnit($rule, &$data, $path, &$error) { 1549 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1550 1551 /* 1552 * It's possible to enter seconds as integers, but by default now we look for strings. For example: "30m". 1553 * Other rules like emptiness and invalid characters are validated by parsers. 1554 */ 1555 if (is_int($data)) { 1556 $data = (string) $data; 1557 } 1558 1559 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1560 return false; 1561 } 1562 1563 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1564 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1565 return false; 1566 } 1567 1568 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1569 return true; 1570 } 1571 1572 $simple_interval_parser = new CSimpleIntervalParser([ 1573 'usermacros' => ($flags & API_ALLOW_USER_MACRO), 1574 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 1575 'negative' => true, 1576 'with_year' => ($flags & API_TIME_UNIT_WITH_YEAR) 1577 ]); 1578 1579 if ($simple_interval_parser->parse($data) != CParser::PARSE_SUCCESS) { 1580 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a time unit is expected')); 1581 return false; 1582 } 1583 1584 if (($flags & (API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO)) && $data[0] === '{') { 1585 return true; 1586 } 1587 1588 $seconds = timeUnitToSeconds($data); 1589 1590 if ($seconds < ZBX_MIN_INT32 || $seconds > ZBX_MAX_INT32) { 1591 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is too large')); 1592 return false; 1593 } 1594 1595 return self::checkInt32In($rule, $seconds, $path, $error); 1596 } 1597 1598 /** 1599 * Array of ids, int32 or strings uniqueness validator. 1600 * 1601 * @param array $rule 1602 * @param bool $rule['uniq'] (optional) 1603 * @param array $data 1604 * @param string $path 1605 * @param string $error 1606 * 1607 * @return bool 1608 */ 1609 private static function validateStringsUniqueness($rule, array $data = null, $path, &$error) { 1610 // $data can be NULL when API_ALLOW_NULL is set 1611 if ($data === null) { 1612 return true; 1613 } 1614 1615 if (!array_key_exists('uniq', $rule) || $rule['uniq'] === false) { 1616 return true; 1617 } 1618 1619 $uniq = []; 1620 1621 foreach ($data as $index => $value) { 1622 if (array_key_exists($value, $uniq)) { 1623 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1624 $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, 1625 _s('value %1$s already exists', '('.$value.')') 1626 ); 1627 return false; 1628 } 1629 $uniq[$value] = true; 1630 } 1631 1632 return true; 1633 } 1634 1635 /** 1636 * Array of objects uniqueness validator. 1637 * 1638 * @param array $rule 1639 * @param array $rule['uniq'] (optional) subsets of unique fields ([['hostid', 'name'], [...]]) 1640 * @param array $rule['fields'] (optional) 1641 * @param array $data 1642 * @param string $path 1643 * @param string $error 1644 * 1645 * @return bool 1646 */ 1647 private static function validateObjectsUniqueness($rule, array $data = null, $path, &$error) { 1648 // $data can be NULL when API_ALLOW_NULL is set 1649 if ($data === null) { 1650 return true; 1651 } 1652 1653 if (array_key_exists('uniq', $rule)) { 1654 foreach ($rule['uniq'] as $field_names) { 1655 $uniq = []; 1656 1657 foreach ($data as $index => $object) { 1658 $_uniq = &$uniq; 1659 $values = []; 1660 $level = 1; 1661 1662 foreach ($field_names as $field_name) { 1663 if (!array_key_exists($field_name, $object)) { 1664 break; 1665 } 1666 1667 $values[] = $object[$field_name]; 1668 1669 if ($level < count($field_names)) { 1670 if (!array_key_exists($object[$field_name], $_uniq)) { 1671 $_uniq[$object[$field_name]] = []; 1672 } 1673 1674 $_uniq = &$_uniq[$object[$field_name]]; 1675 } 1676 else { 1677 if (array_key_exists($object[$field_name], $_uniq)) { 1678 $subpath = ($path === '/' ? $path : $path.'/').($index + 1); 1679 $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, _s('value %1$s already exists', 1680 '('.implode(', ', $field_names).')=('.implode(', ', $values).')' 1681 )); 1682 return false; 1683 } 1684 1685 $_uniq[$object[$field_name]] = true; 1686 } 1687 1688 $level++; 1689 } 1690 } 1691 } 1692 } 1693 1694 if (array_key_exists('fields', $rule)) { 1695 foreach ($data as $index => $object) { 1696 foreach ($rule['fields'] as $field_name => $field_rule) { 1697 if (array_key_exists($field_name, $object)) { 1698 $subpath = ($path === '/' ? $path : $path.'/').($index + 1).'/'.$field_name; 1699 if (!self::validateDataUniqueness($field_rule, $object[$field_name], $subpath, $error)) { 1700 return false; 1701 } 1702 } 1703 } 1704 } 1705 } 1706 1707 return true; 1708 } 1709 1710 /** 1711 * HTTP POST validator. Posts can be set to string (raw post) or to http pairs (form fields) 1712 * 1713 * @param array $rule 1714 * @param int $rule['length'] (optional) 1715 * @param int $rule['name-length'] (optional) 1716 * @param int $rule['value-length'] (optional) 1717 * @param mixed $data 1718 * @param string $path 1719 * @param string $error 1720 * 1721 * @return bool 1722 */ 1723 private static function validateHttpPosts($rule, &$data, $path, &$error) { 1724 if (is_array($data)) { 1725 $rules = ['type' => API_OBJECTS, 'fields' => [ 1726 'name' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY], 1727 'value' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED] 1728 ]]; 1729 1730 if (array_key_exists('name-length', $rule)) { 1731 $rules['fields']['name']['length'] = $rule['name-length']; 1732 } 1733 1734 if (array_key_exists('value-length', $rule)) { 1735 $rules['fields']['value']['length'] = $rule['value-length']; 1736 } 1737 } 1738 else { 1739 $rules = ['type' => API_STRING_UTF8]; 1740 1741 if (array_key_exists('length', $rule)) { 1742 $rules['length'] = $rule['length']; 1743 } 1744 } 1745 1746 return self::validateData($rules, $data, $path, $error); 1747 } 1748 1749 /** 1750 * HTTP variable validator. 1751 * 1752 * @param array $rule 1753 * @param int $rule['length'] (optional) 1754 * @param mixed $data 1755 * @param string $path 1756 * @param string $error 1757 * 1758 * @return bool 1759 */ 1760 private static function validateVariableName($rule, &$data, $path, &$error) { 1761 if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { 1762 return false; 1763 } 1764 1765 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1766 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1767 return false; 1768 } 1769 1770 if (preg_match('/^{[^{}]+}$/', $data) !== 1) { 1771 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('is not enclosed in {} or is malformed')); 1772 return false; 1773 } 1774 1775 return true; 1776 } 1777 1778 /** 1779 * URL validator. 1780 * 1781 * @param array $rule 1782 * @param int $rule['length'] (optional) 1783 * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO, API_ALLOW_EVENT_TAGS_MACRO, API_NOT_EMPTY 1784 * @param mixed $data 1785 * @param string $path 1786 * @param string $error 1787 * 1788 * @return bool 1789 */ 1790 private static function validateUrl($rule, &$data, $path, &$error) { 1791 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1792 1793 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1794 return false; 1795 } 1796 1797 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1798 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1799 return false; 1800 } 1801 1802 $options = [ 1803 'allow_user_macro' => (bool) ($flags & API_ALLOW_USER_MACRO), 1804 'allow_event_tags_macro' => (bool) ($flags & API_ALLOW_EVENT_TAGS_MACRO) 1805 ]; 1806 1807 if ($data !== '' && CHtmlUrlValidator::validate($data, $options) === false) { 1808 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('unacceptable URL')); 1809 return false; 1810 } 1811 1812 return true; 1813 } 1814 1815 /** 1816 * Trigger expression validator. 1817 * 1818 * @param array $rule 1819 * @param int $rule['length'] (optional) 1820 * @param int $rule['flags'] (optional) API_ALLOW_LLD_MACRO, API_NOT_EMPTY 1821 * @param mixed $data 1822 * @param string $path 1823 * @param string $error 1824 * 1825 * @return bool 1826 */ 1827 private static function validateTriggerExpression($rule, &$data, $path, &$error) { 1828 $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; 1829 1830 if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { 1831 return false; 1832 } 1833 1834 if (($flags & API_NOT_EMPTY) == 0 && $data === '') { 1835 return true; 1836 } 1837 1838 if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { 1839 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); 1840 return false; 1841 } 1842 1843 $expression_data = new CTriggerExpression([ 1844 'lldmacros' => ($flags & API_ALLOW_LLD_MACRO), 1845 'lowercase_errors' => true 1846 ]); 1847 1848 if (!$expression_data->parse($data)) { 1849 $error = _s('Invalid parameter "%1$s": %2$s.', $path, $expression_data->error); 1850 return false; 1851 } 1852 1853 if (!$expression_data->expressions) { 1854 $error = _s('Invalid parameter "%1$s": %2$s.', $path, 1855 _('trigger expression must contain at least one host:key reference') 1856 ); 1857 return false; 1858 } 1859 1860 return true; 1861 } 1862 1863 /** 1864 * JSON RPC parameters validator. Parameters MUST contain an array or object value. 1865 * 1866 * @param array $rule 1867 * @param mixed $data 1868 * @param string $path 1869 * @param string $error 1870 * 1871 * @return bool 1872 */ 1873 private static function validateJsonRpcParams($rule, &$data, $path, &$error) { 1874 if (is_array($data)) { 1875 return true; 1876 } 1877 1878 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('an array or object is expected')); 1879 1880 return false; 1881 } 1882 1883 /** 1884 * JSON RPC identifier validator. This identifier MUST contain a String, Number, or NULL value. 1885 * 1886 * @param array $rule 1887 * @param mixed $data 1888 * @param string $path 1889 * @param string $error 1890 * 1891 * @return bool 1892 */ 1893 private static function validateJsonRpcId($rule, &$data, $path, &$error) { 1894 if (is_string($data) || is_int($data) || is_float($data) || $data === null) { 1895 return true; 1896 } 1897 1898 $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a string, number or null value is expected')); 1899 1900 return false; 1901 } 1902} 1903