diff options
Diffstat (limited to 'ui/include/classes/validators/CApiInputValidator.php')
-rw-r--r-- | ui/include/classes/validators/CApiInputValidator.php | 966 |
1 files changed, 930 insertions, 36 deletions
diff --git a/ui/include/classes/validators/CApiInputValidator.php b/ui/include/classes/validators/CApiInputValidator.php index 971e3f570a7..106677afd20 100644 --- a/ui/include/classes/validators/CApiInputValidator.php +++ b/ui/include/classes/validators/CApiInputValidator.php @@ -242,6 +242,30 @@ class CApiInputValidator { case API_TG_NAME: return self::validateTemplateGroupName($rule, $data, $path, $error); + + case API_ANY: + return true; + + case API_ITEM_KEY: + return self::validateItemKey($rule, $data, $path, $error); + + case API_ITEM_DELAY: + return self::validateItemDelay($rule, $data, $path, $error); + + case API_JSON: + return self::validateJson($rule, $data, $path, $error); + + case API_XML: + return self::validateXml($rule, $data, $path, $error); + + case API_PREPROC_PARAMS: + return self::validatePreprocParams($rule, $data, $path, $error); + + case API_PROMETHEUS_PATTERN: + return self::validatePrometheusPattern($rule, $data, $path, $error); + + case API_PROMETHEUS_LABEL: + return self::validatePrometheusLabel($rule, $data, $path, $error); } // This message can be untranslated because warn about incorrect validation rules at a development stage. @@ -314,6 +338,14 @@ class CApiInputValidator { case API_LAT_LNG_ZOOM: case API_TIMESTAMP: case API_TG_NAME: + case API_ANY: + case API_ITEM_KEY: + case API_ITEM_DELAY: + case API_JSON: + case API_XML: + case API_PREPROC_PARAMS: + case API_PROMETHEUS_PATTERN: + case API_PROMETHEUS_LABEL: return true; case API_OBJECT: @@ -924,7 +956,7 @@ class CApiInputValidator { * Floating point number validator. * * @param array $rule - * @param int $rule['flags'] (optional) API_ALLOW_NULL + * @param int $rule['flags'] (optional) API_ALLOW_NULL | API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO * @param string $rule['in'] (optional) a comma-delimited character string, for example: '0,60:900' * @param mixed $data * @param string $path @@ -932,7 +964,7 @@ class CApiInputValidator { * * @return bool */ - private static function validateFloat($rule, &$data, $path, &$error) { + private static function validateFloat(array $rule, &$data, string $path, string &$error): bool { $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; if (($flags & API_ALLOW_NULL) && $data === null) { @@ -949,6 +981,21 @@ class CApiInputValidator { $value = (float) $number_parser->getMatch(); } else { + if ($flags & API_ALLOW_USER_MACRO) { + $user_macro_parser = new CUserMacroParser(); + } + + if ($flags & API_ALLOW_LLD_MACRO) { + $lld_macro_parser = new CLLDMacroParser(); + $lld_macro_function_parser = new CLLDMacroFunctionParser(); + } + + if (($flags & API_ALLOW_USER_MACRO && $user_macro_parser->parse($data) == CParser::PARSE_SUCCESS) + || ($flags & API_ALLOW_LLD_MACRO && ($lld_macro_parser->parse($data) == CParser::PARSE_SUCCESS + || $lld_macro_function_parser->parse($data) == CParser::PARSE_SUCCESS))) { + return true; + } + $value = NAN; } } @@ -966,6 +1013,10 @@ class CApiInputValidator { return false; } + if (!self::checkCompare($rule, $value, $path, $error)) { + return false; + } + $data = $value; return true; @@ -989,6 +1040,8 @@ class CApiInputValidator { return true; } + $e = ''; + if (($flags & API_NORMALIZE) && self::validateFloat([], $data, '', $e)) { $data = [$data]; } @@ -1023,7 +1076,7 @@ class CApiInputValidator { foreach (explode(',', $in) as $in) { if (strpos($in, ':') !== false) { - list($from, $to) = explode(':', $in); + [$from, $to] = explode(':', $in); } else { $from = $in; @@ -1073,7 +1126,7 @@ class CApiInputValidator { * * @return bool */ - private static function checkFloatIn($rule, $data, $path, &$error) { + private static function checkFloatIn(array $rule, $data, string $path, string &$error) { if (!array_key_exists('in', $rule)) { return true; } @@ -1144,12 +1197,12 @@ class CApiInputValidator { /** * Validate integer ranges. * Example: - * 0-100,200,300-400 + * -100-0,0-100,200,300-{$MACRO},{$MACRO},{#LLD},400-500 * * @static * * @param array $rule - * @param int $rule['flags'] (optional) API_NOT_EMPTY + * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO * @param int $rule['length'] (optional) * @param string $rule['in'] (optional) A comma-delimited character string, for example: '0,60:900' * @param mixed $data @@ -1161,7 +1214,7 @@ class CApiInputValidator { private static function validateInt32Ranges(array $rule, &$data, string $path, string &$error): bool { $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; - if (self::checkStringUtf8($flags, $data, $path, $error) === false) { + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { return false; } @@ -1174,7 +1227,11 @@ class CApiInputValidator { return false; } - $parser = new CRangesParser(['with_minus' => true]); + $parser = new CRangesParser([ + 'usermacros' => (bool) ($flags & API_ALLOW_USER_MACRO), + 'lldmacros' => (bool) ($flags & API_ALLOW_LLD_MACRO), + 'with_minus' => true + ]); if ($parser->parse($data) != CParser::PARSE_SUCCESS) { $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid range expression')); @@ -1183,6 +1240,10 @@ class CApiInputValidator { foreach ($parser->getRanges() as $ranges) { foreach ($ranges as $range) { + if (($flags & (API_ALLOW_USER_MACRO | API_ALLOW_LLD_MACRO)) && $range[0] === '{') { + continue; + } + if (!self::checkInt32In($rule, $range, $path, $error)) { return false; } @@ -1196,13 +1257,14 @@ class CApiInputValidator { * Identifier validator. * * @param array $rule + * @param string $rule['in'] (optional) * @param mixed $data * @param string $path * @param string $error * * @return bool */ - private static function validateId($rule, &$data, $path, &$error) { + private static function validateId(array $rule, &$data, string $path, string &$error) :bool { if (!is_scalar($data) || is_bool($data) || is_double($data) || !ctype_digit(strval($data))) { $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('a number is expected')); return false; @@ -1223,6 +1285,39 @@ class CApiInputValidator { } } + if (!self::checkIdIn($rule, $data, $path, $error)) { + return false; + } + + return true; + } + + /** + * @param array $rule + * @param string $rule['in'] (optional) + * @param string $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function checkIdIn(array $rule, string $data, string $path, string &$error): bool { + if (!array_key_exists('in', $rule)) { + return true; + } + + if ($rule['in'] != 0) { + $error = 'Incorrect validation rules.'; + + return false; + } + + if ($data != 0) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('value must be %1$s', '0')); + + return false; + } + return true; } @@ -1392,6 +1487,10 @@ class CApiInputValidator { $field_rule['compare']['value'] = $data[$field_rule['compare']['field']]; } + if (array_key_exists('preproc_type', $field_rule)) { + $field_rule['preproc_type']['value'] = $data[$field_rule['preproc_type']['field']]; + } + if (array_key_exists($field_name, $data)) { $subpath = ($path === '/' ? $path : $path.'/').$field_name; if (!self::validateData($field_rule, $data[$field_name], $subpath, $error)) { @@ -1554,6 +1653,8 @@ class CApiInputValidator { return true; } + $e = ''; + if (($flags & API_NORMALIZE) && self::validateId([], $data, '', $e)) { $data = [$data]; } @@ -2023,7 +2124,7 @@ class CApiInputValidator { return true; } - if (@preg_match('/'.str_replace('/', '\/', $data).'/', '') === false) { + if (@preg_match('('.$data.')', '') === false) { $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid regular expression')); return false; } @@ -2098,7 +2199,7 @@ class CApiInputValidator { /** * Array of ids, int32 or strings uniqueness validator. * - * @param bool $rule + * @param array $rule * @param integer $rule['type'] * @param bool $rule['uniq'] (optional) * @param array|null $data @@ -2192,7 +2293,7 @@ class CApiInputValidator { foreach ($data as $index => $object) { $_uniq = &$uniq; - $values = []; + $object_values = []; $level = 1; foreach ($field_names as $field_name) { @@ -2200,29 +2301,104 @@ class CApiInputValidator { break; } - $values[] = $object[$field_name]; + $object_values[] = $object[$field_name]; - $value = ($rule['fields'][$field_name]['type'] == API_USER_MACRO) + $object_value = ($rule['fields'][$field_name]['type'] == API_USER_MACRO) ? self::trimMacro($object[$field_name]) : $object[$field_name]; if ($level < count($field_names)) { - if (!array_key_exists($value, $_uniq)) { - $_uniq[$value] = []; + if (!array_key_exists($object_value, $_uniq)) { + $_uniq[$object_value] = []; } - $_uniq = &$_uniq[$value]; + $_uniq = &$_uniq[$object_value]; } else { - if (array_key_exists($value, $_uniq)) { + if (array_key_exists($object_value, $_uniq)) { $subpath = ($path === '/' ? $path : $path.'/').($index + 1); $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, _s('value %1$s already exists', - '('.implode(', ', $field_names).')=('.implode(', ', $values).')' + '('.implode(', ', $field_names).')=('.implode(', ', $object_values).')' )); return false; } - $_uniq[$value] = true; + $_uniq[$object_value] = true; + } + + $level++; + } + } + } + } + + if (array_key_exists('uniq_by_values', $rule)) { + foreach ($rule['uniq_by_values'] as $field_values) { + $uniq = []; + $_uniqs = [&$uniq]; + + foreach ($data as $index => $object) { + $object_values = []; + $level = 1; + + foreach ($field_values as $field_name => $values) { + if (!array_key_exists($field_name, $object)) { + $_uniqs = [&$uniq]; + break; + } + + $object_values[] = $object[$field_name]; + + $object_value = ($rule['fields'][$field_name]['type'] == API_USER_MACRO) + ? self::trimMacro($object[$field_name]) + : $object[$field_name]; + + if (!in_array($object_value, $values)) { + $_uniqs = [&$uniq]; + break; + } + + if ($level < count($field_values)) { + $__uniqs = []; + + foreach ($_uniqs as &$_uniq) { + foreach ($values as $value) { + if (!array_key_exists($value, $_uniq)) { + $_uniq[$value] = []; + } + + $__uniqs[] = &$_uniq[$value]; + } + } + unset($_uniq); + + $_uniqs = $__uniqs; + } + else { + foreach ($_uniqs as &$_uniq) { + foreach ($values as $value) { + if (array_key_exists($value, $_uniq)) { + $subpath = ($path === '/' ? $path : $path.'/').($index + 1); + + $combinations = array_map(static function (array $values): string { + return '('.implode(', ', $values).')'; + }, $field_values); + + $error = _s('Invalid parameter "%1$s": %2$s.', $subpath, + _s('only one object can exist within the combinations of %1$s', + '('.implode(', ', array_keys($field_values)).')=('. + implode(', ', $combinations).')' + ) + ); + return false; + } + + $_uniq[$value] = true; + } + } + unset($_uniq); + + $_uniqs = [&$uniq]; } $level++; @@ -2445,21 +2621,22 @@ class CApiInputValidator { /** * Validate IP ranges. Multiple IPs separated by comma character. * Example: - * 127.0.0.1,192.168.1.1-254,192.168.2.1-100,192.168.3.0/24 + * 127.0.0.1,192.168.1.1-254,192.168.2.1-100,192.168.3.0/24,{$MACRO} * - * @param array $rule - * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_DNS, API_ALLOW_RANGE - * @param int $rule['length'] (optional) - * @param mixed $data - * @param string $path - * @param string $error + * @param array $rule + * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_DNS, API_ALLOW_RANGE, API_ALLOW_USER_MACRO + * @param array|bool $rule['macros'] (optional) An array of supported macros. True - all macros are supported. + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error * * @return bool */ private static function validateIpRanges($rule, &$data, $path, &$error) { $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; - if (self::checkStringUtf8($flags, $data, $path, $error) === false) { + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { return false; } @@ -2474,8 +2651,10 @@ class CApiInputValidator { $ip_range_parser = new CIPRangeParser([ 'v6' => ZBX_HAVE_IPV6, - 'dns' => ($flags & API_ALLOW_DNS), - 'ranges' => ($flags & API_ALLOW_RANGE) + 'dns' => (bool) ($flags & API_ALLOW_DNS), + 'ranges' => (bool) ($flags & API_ALLOW_RANGE), + 'usermacros' => (bool) ($flags & API_ALLOW_USER_MACRO), + 'macros' => array_key_exists('macros', $rule) ? $rule['macros'] : [] ]); if (!$ip_range_parser->parse($data)) { @@ -3186,10 +3365,10 @@ class CApiInputValidator { /** * @param array $rule - * @param array $rule[compare] (optional) - * @param string $rule[compare][operator] - * @param string $rule[compare][path] - * @param mixed $rule[compare][value] + * @param array $rule['compare'] (optional) + * @param string $rule['compare']['operator'] + * @param string $rule['compare']['path'] + * @param mixed $rule['compare']['value'] * @param int $data * @param string $path * @param string $error @@ -3197,7 +3376,7 @@ class CApiInputValidator { * @return bool */ private static function checkCompare(array $rule, $data, string $path, string &$error): bool { - if (!array_key_exists('compare', $rule)) { + if (!array_key_exists('compare', $rule) || $rule['compare']['value'] === null) { return true; } @@ -3226,7 +3405,7 @@ class CApiInputValidator { * * @param string $field_name * @param array $field_rule - * @param string $field_rule[error_type] (optional) API_ERR_INHERITED, API_ERR_DISCOVERED + * @param string $field_rule['error_type'] (optional) API_ERR_INHERITED, API_ERR_DISCOVERED * @param array $object * @param string $path * @param string $error @@ -3264,4 +3443,719 @@ class CApiInputValidator { return false; } + + /** + * @param array $rule + * @param int $rule['length'] (optional) + * @param int $rule['flags'] (optional) API_REQUIRED_LLD_MACRO + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validateItemKey(array $rule, &$data, string $path, string &$error): bool { + if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + return false; + } + + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + $item_key_parser = new CItemKey(); + + if ($item_key_parser->parse($data) != CParser::PARSE_SUCCESS) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, $item_key_parser->getError()); + return false; + } + + if (($flags & API_REQUIRED_LLD_MACRO)) { + $parameters = $item_key_parser->getParamsRaw(); + $lld_macro_parser = new CLLDMacroParser(); + $lld_macro_function_parser = new CLLDMacroFunctionParser(); + $has_lld_macros = false; + + if ($parameters) { + $parameters = $parameters[0]['raw']; + $p = 1; + + while (isset($parameters[$p])) { + if ($lld_macro_parser->parse($parameters, $p) != CParser::PARSE_FAIL + || $lld_macro_function_parser->parse($parameters, $p) != CParser::PARSE_FAIL) { + $has_lld_macros = true; + break; + } + + $p++; + } + } + + if (!$has_lld_macros) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _('must contain at least one low-level discovery macro') + ); + return false; + } + } + + return true; + } + + /** + * Try to parse delay/interval information and check that some polling can be performed during the schedule-week. + * + * Note: In case of non-convertible entries (containing macros), we can only check for edge cases, e.g. + * where the whole week is fully blocked by periods with an update interval of 0. + * + * @param array $rule + * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validateItemDelay(array $rule, &$data, string $path, string &$error): bool { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (is_int($data)) { + $data = (string) $data; + } + + if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + + return false; + } + + $update_interval_parser = new CUpdateIntervalParser([ + 'usermacros' => (bool) ($flags & API_ALLOW_USER_MACRO), + 'lldmacros' => (bool) ($flags & API_ALLOW_LLD_MACRO) + ]); + + if ($update_interval_parser->parse($data) != CParser::PARSE_SUCCESS) { + $error = strpos($data, ';') === false + ? _s('Invalid parameter "%1$s": %2$s.', $path, _('a time unit is expected')) + : _s('Invalid parameter "%1$s": %2$s.', $path, $update_interval_parser->getError()); + + return false; + } + + $delay = $update_interval_parser->getDelay(); + $intervals = $update_interval_parser->getIntervals(); + + if ($delay[0] !== '{') { + $delay_sec = timeUnitToSeconds($delay); + + if ($delay_sec == 0 && !$intervals) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _('cannot be equal to zero without custom intervals') + ); + + return false; + } + + if ($delay_sec > SEC_PER_DAY) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _s('value must be one of %1$s', implode('-', [0, SEC_PER_DAY])) + ); + + return false; + } + } + + if (!$intervals || array_key_exists(ITEM_DELAY_SCHEDULING, array_column($intervals, null, 'type'))) { + return true; + } + + $active_macro_interval = false; + + foreach ($intervals as $i => $interval) { + if (strpos($interval['interval'], '{') !== false) { + unset($intervals[$i]); + + if (strpos($interval['update_interval'], '{') === false) { + if ($interval['update_interval'] != 0) { + $active_macro_interval = true; + } + } + else { + $active_macro_interval = true; + } + } + } + + $inactive_intervals = []; + $active_intervals = []; + + foreach ($intervals as $interval) { + $update_interval = timeUnitToSeconds($interval['update_interval']); + + [$day_period, $time_period] = explode(',', $interval['time_period']); + + [$day_from, $day_to] = (strpos($day_period, '-') === false) + ? [$day_period, $day_period] + : explode('-', $day_period); + + [$time_from, $time_to] = explode('-', $time_period); + + [$time_from_hours, $time_from_minutes] = explode(':', $time_from); + [$time_to_hours, $time_to_minutes] = explode(':', $time_to); + + $time_from = $time_from_hours * SEC_PER_HOUR + $time_from_minutes * SEC_PER_MIN; + $time_to = $time_to_hours * SEC_PER_HOUR + $time_to_minutes * SEC_PER_MIN; + + if ($update_interval > 0) { + if ($time_from == 0 && $time_to == SEC_PER_DAY && $day_to - $day_from > 0) { + $_interval = $day_to * SEC_PER_DAY + $time_to - $day_from * SEC_PER_DAY + $time_from; + } + else { + $_interval = $time_to - $time_from; + } + + if ($update_interval > $_interval) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _s('update interval "%1$s" is longer than period "%2$s"', $interval['update_interval'], + $interval['time_period'] + ) + ); + + return false; + } + } + + for ($day = $day_from; $day <= $day_to; $day++) { + if ($update_interval == 0) { + $inactive_intervals[] = [ + 'time_from' => ($day - 1) * SEC_PER_DAY + $time_from, + 'time_to' => ($day - 1) * SEC_PER_DAY + $time_to + ]; + } + else { + $active_intervals[] = [ + 'update_interval' => $update_interval, + 'time_from' => ($day - 1) * SEC_PER_DAY + $time_from, + 'time_to' => ($day - 1) * SEC_PER_DAY + $time_to + ]; + } + } + } + + if ($delay[0] !== '{' && $delay_sec == 0 && !$active_intervals && !$active_macro_interval) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('must have at least one interval greater than 0')); + + return false; + } + + CArrayHelper::sort($inactive_intervals, ['time_from']); + + $_inactive_intervals = $inactive_intervals ? [array_shift($inactive_intervals)] : []; + $last = 0; + + foreach ($inactive_intervals as $interval) { + if ($interval['time_from'] > $_inactive_intervals[$last]['time_to']) { + $_inactive_intervals[++$last] = $interval; + continue; + } + + if ($interval['time_to'] <= $_inactive_intervals[$last]['time_to']) { + continue; + } + + $_inactive_intervals[$last]['time_to'] = $interval['time_to']; + } + + $inactive_intervals = $_inactive_intervals; + + if ($inactive_intervals && $inactive_intervals[0]['time_from'] == 0 + && $inactive_intervals[0]['time_to'] == 7 * SEC_PER_DAY) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _('non-active intervals cannot fill the entire time') + ); + + return false; + } + + if ($delay[0] === '{' || $active_macro_interval) { + return true; + } + + CArrayHelper::sort($active_intervals, ['time_from']); + + $_active_intervals = $active_intervals ? [array_shift($active_intervals)] : []; + $last = 0; + + foreach ($active_intervals as $i => $interval) { + if ($interval['time_from'] > $_active_intervals[$last]['time_to']) { + $_active_intervals[++$last] = $interval; + continue; + } + + if ($interval['update_interval'] >= $_active_intervals[$last]['update_interval']) { + if ($interval['time_to'] <= $_active_intervals[$last]['time_to']) { + continue; + } + + if ($interval['update_interval'] == $_active_intervals[$last]['update_interval']) { + $_active_intervals[$last]['time_to'] = $interval['time_to']; + } + else { + ++$last; + $_active_intervals[$last] = ['time_from' => $_active_intervals[$last - 1]['time_to']] + $interval; + } + } + else { + $_active_interval = $_active_intervals[$last]; + + if ($_active_intervals[$last]['time_from'] == $interval['time_from']) { + $_active_intervals[$last] = $interval; + } + else { + $_active_intervals[$last]['time_to'] = $interval['time_from']; + $_active_intervals[++$last] = $interval; + } + + if ($_active_interval['time_to'] > $interval['time_to']) { + $_active_intervals[++$last] = ['time_from' => $interval['time_to']] + $_active_interval; + } + } + } + + $active_intervals = $_active_intervals; + + foreach ($active_intervals as $active_interval) { + if ($active_interval['time_to'] - $active_interval['time_from'] < $active_interval['update_interval']) { + continue; + } + + if (!$inactive_intervals) { + return true; + } + + $_inactive_intervals = []; + + foreach ($inactive_intervals as $inactive_interval) { + if ($inactive_interval['time_from'] < $active_interval['time_to'] + && $inactive_interval['time_to'] > $active_interval['time_from']) { + $_inactive_intervals[] = $inactive_interval; + } + } + + if (!$_inactive_intervals) { + return true; + } + + foreach ($_inactive_intervals as $i => $inactive_interval) { + if ($i == 0 && $inactive_interval['time_from'] > $active_interval['time_from']) { + $active_time_from = $active_interval['time_from']; + $active_time_to = $inactive_interval['time_from']; + + if ($active_time_to - $active_time_from >= $active_interval['update_interval']) { + return true; + } + } + + $active_time_from = $inactive_interval['time_to']; + + $active_time_to = array_key_exists($i + 1, $_inactive_intervals) + ? $_inactive_intervals[$i + 1]['time_from'] + : $active_interval['time_to']; + + if ($active_time_to - $active_time_from >= $active_interval['update_interval']) { + return true; + } + } + } + + if ($delay_sec > 0) { + $intervals = array_merge($inactive_intervals, $active_intervals); + CArrayHelper::sort($intervals, ['time_from']); + + $_intervals = $intervals ? [array_shift($intervals)] : []; + $last = 0; + + foreach ($intervals as $interval) { + if ($interval['time_from'] > $_intervals[$last]['time_to']) { + $_intervals[++$last] = $interval; + continue; + } + + if ($interval['time_to'] <= $_intervals[$last]['time_to']) { + continue; + } + + $_intervals[$last]['time_to'] = $interval['time_to']; + } + + foreach ($_intervals as $i => $interval) { + if ($i == 0) { + if ($interval['time_from'] > 0 && $interval['time_from'] >= $delay_sec) { + return true; + } + + continue; + } + + if ($interval['time_from'] - $_intervals[$i - 1]['time_to'] >= $delay_sec) { + return true; + } + } + + if (7 * SEC_PER_DAY - $interval['time_to'] >= $delay_sec) { + return true; + } + } + + $error = _s('Invalid parameter "%1$s": %2$s.', $path, + _('must have a polling interval not blocked by non-active interval periods') + ); + + return false; + } + + /** + * JSON validator. + * + * @param array $rule + * @param int $rule['flags'] (optional) API_NOT_EMPTY, API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO + * @param array $rule['macros_n'] (optional) An array of supported macros. Example: ['{HOST.IP}', '{ITEM.KEY}']. + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validateJson($rule, &$data, $path, &$error) { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if ($data === '') { + return true; + } + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + return false; + } + + $json = $data; + + $types = []; + + if ($flags & API_ALLOW_USER_MACRO) { + $types['usermacros'] = true; + } + + if ($flags & API_ALLOW_LLD_MACRO) { + $types['lldmacros'] = true; + } + + if (array_key_exists('macros_n', $rule)) { + $types['macros_n'] = $rule['macros_n']; + } + + if ($types) { + $matches = (new CMacrosResolverGeneral())->getMacroPositions($json, $types); + $shift = 0; + + foreach ($matches as $pos => $substr) { + $json = substr_replace($json, '1', $pos + $shift, strlen($substr)); + $shift = $shift + 1 - strlen($substr); + } + } + + json_decode($json); + + if (json_last_error() != JSON_ERROR_NONE) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('JSON is expected')); + return false; + } + + return true; + } + + /** + * XML validator. + * + * @param array $rule + * @param int $rule['flags'] (optional) API_NOT_EMPTY + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validateXml(array $rule, &$data, string $path, string &$error): bool { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if ($data === '') { + return true; + } + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + return false; + } + + libxml_use_internal_errors(true); + + if (simplexml_load_string($data, null, LIBXML_IMPORT_FLAGS) === false) { + $errors = libxml_get_errors(); + libxml_clear_errors(); + + if ($errors) { + $error = reset($errors); + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _s('%1$s [Line: %2$s | Column: %3$s]', + '('.$error->code.') '.trim($error->message), $error->line, $error->column + )); + return false; + } + + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('XML is expected')); + return false; + } + + return true; + } + + /** + * @param array $rule + * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO + * @param array $rule['preproc_type'] + * @param int $rule['preproc_type']['value'] + * @param int $rule['length'] (optional) + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validatePreprocParams(array $rule, &$data, string $path, string &$error): bool { + $preproc_type = $rule['preproc_type']['value']; + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8(0x00, $data, $path, $error) === false) { + return false; + } + + $data = str_replace("\r\n", "\n", $data); + + if (array_key_exists('length', $rule) && mb_strlen($data) > $rule['length']) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('value is too long')); + + return false; + } + + $params = []; + + if ($preproc_type == ZBX_PREPROC_SCRIPT) { + $params[1] = $data; + } + else { + foreach (explode("\n", $data) as $i => $param) { + $params[$i + 1] = $param; + } + } + + switch ($preproc_type) { + case ZBX_PREPROC_MULTIPLIER: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_FLOAT, 'flags' => API_REQUIRED | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO)] + ]]; + break; + + case ZBX_PREPROC_RTRIM: + case ZBX_PREPROC_LTRIM: + case ZBX_PREPROC_TRIM: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_REGSUB: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_REGEX, 'flags' => API_REQUIRED | API_NOT_EMPTY], + '2' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_XPATH: + case ZBX_PREPROC_JSONPATH: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_VALIDATE_RANGE: + if (count($params) == 2 && ($params[1] === '' || $params[2] === '')) { + if ($params[1] === '' && $params[2] === '') { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('cannot be empty')); + + return false; + } + + $params[1] = $params[1] === '' ? null : $params[1]; + $params[2] = $params[2] === '' ? null : $params[2]; + } + + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_FLOAT, 'flags' => API_REQUIRED | API_ALLOW_NULL | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO)], + '2' => ['type' => API_FLOAT, 'flags' => API_REQUIRED | API_ALLOW_NULL | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO), 'compare' => ['operator' => '>', 'field' => '1']] + ]]; + break; + + case ZBX_PREPROC_VALIDATE_REGEX: + case ZBX_PREPROC_VALIDATE_NOT_REGEX: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_REGEX, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_ERROR_FIELD_JSON: + case ZBX_PREPROC_ERROR_FIELD_XML: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_ERROR_FIELD_REGEX: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_REGEX, 'flags' => API_REQUIRED | API_NOT_EMPTY], + '2' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_THROTTLE_TIMED_VALUE: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_TIME_UNIT, 'flags' => API_REQUIRED | API_NOT_EMPTY | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO), 'in' => implode(':', [1, 25 * SEC_PER_YEAR])] + ]]; + break; + + case ZBX_PREPROC_SCRIPT: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY] + ]]; + break; + + case ZBX_PREPROC_PROMETHEUS_PATTERN: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_PROMETHEUS_PATTERN, 'flags' => API_REQUIRED | API_NOT_EMPTY | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO)], + '2' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PREPROC_PROMETHEUS_VALUE, ZBX_PREPROC_PROMETHEUS_LABEL, ZBX_PREPROC_PROMETHEUS_FUNCTION])], + '3' => ['type' => API_MULTIPLE, 'rules' =>[ + ['if' => ['field' => '2', 'in' => implode(',', [ZBX_PREPROC_PROMETHEUS_VALUE])], 'type' => API_STRING_UTF8, 'in' => '', 'default' => ''], + ['if' => ['field' => '2', 'in' => implode(',', [ZBX_PREPROC_PROMETHEUS_LABEL])], 'type' => API_PROMETHEUS_LABEL, 'flags' => API_REQUIRED | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO)], + ['if' => ['field' => '2', 'in' => implode(',', [ZBX_PREPROC_PROMETHEUS_FUNCTION])], 'type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PREPROC_PROMETHEUS_SUM, ZBX_PREPROC_PROMETHEUS_MIN, ZBX_PREPROC_PROMETHEUS_MAX, ZBX_PREPROC_PROMETHEUS_AVG, ZBX_PREPROC_PROMETHEUS_COUNT])] + ]] + ]]; + break; + + case ZBX_PREPROC_PROMETHEUS_TO_JSON: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_PROMETHEUS_PATTERN, 'flags' => API_REQUIRED | ($flags & API_ALLOW_USER_MACRO) | ($flags & API_ALLOW_LLD_MACRO)] + ]]; + break; + + case ZBX_PREPROC_CSV_TO_JSON: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => 1], + '2' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'length' => 1], + '3' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [ZBX_PREPROC_CSV_NO_HEADER, ZBX_PREPROC_CSV_HEADER])] + ]]; + break; + + case ZBX_PREPROC_STR_REPLACE: + $api_input_rules = ['type' => API_OBJECT, 'fields' => [ + '1' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY], + '2' => ['type' => API_STRING_UTF8, 'default' => ''] + ]]; + break; + } + + if (self::validate($api_input_rules, $params, $path, $error)) { + $data = implode("\n", $params); + + return true; + } + + return false; + } + + /** + * @param array $rule + * @param int $rule['flags'] (optional) API_NOT_EMPTY API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validatePrometheusPattern(array $rule, &$data, string $path, string &$error): bool { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8($flags & API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + if (($flags & API_NOT_EMPTY) == 0 && $data === '') { + return true; + } + + $prometheus_pattern_parser = new CPrometheusPatternParser([ + 'usermacros' => (bool) ($flags & API_ALLOW_USER_MACRO), + 'lldmacros' => (bool) ($flags & API_ALLOW_LLD_MACRO) + ]); + + if ($prometheus_pattern_parser->parse($data) != CParser::PARSE_SUCCESS) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid Prometheus pattern')); + return false; + } + + return true; + } + + /** + * @param array $rule + * @param int $rule['flags'] (optional) API_ALLOW_USER_MACRO, API_ALLOW_LLD_MACRO + * @param mixed $data + * @param string $path + * @param string $error + * + * @return bool + */ + private static function validatePrometheusLabel(array $rule, &$data, string $path, string &$error): bool { + $flags = array_key_exists('flags', $rule) ? $rule['flags'] : 0x00; + + if (self::checkStringUtf8(API_NOT_EMPTY, $data, $path, $error) === false) { + return false; + } + + $prometheus_output_parser = new CPrometheusOutputParser([ + 'usermacros' => (bool) ($flags & API_ALLOW_USER_MACRO), + 'lldmacros' => (bool) ($flags & API_ALLOW_LLD_MACRO) + ]); + + if ($prometheus_output_parser->parse($data) != CParser::PARSE_SUCCESS) { + $error = _s('Invalid parameter "%1$s": %2$s.', $path, _('invalid Prometheus label')); + return false; + } + + return true; + } } |