Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'ui/include/classes/validators/CApiInputValidator.php')
-rw-r--r--ui/include/classes/validators/CApiInputValidator.php966
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;
+ }
}