You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
594 lines
15 KiB
594 lines
15 KiB
<?php
|
|
/*
|
|
** Zabbix
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
|
|
class CNewValidator {
|
|
|
|
private $rules;
|
|
private $input = [];
|
|
private $output = [];
|
|
private $errors = [];
|
|
private $errorsFatal = [];
|
|
|
|
/**
|
|
* Parser for validation rules.
|
|
*
|
|
* @var CValidationRule
|
|
*/
|
|
private $validationRuleParser;
|
|
|
|
/**
|
|
* Parser for range date/time.
|
|
*
|
|
* @var CRangeTimeParser
|
|
*/
|
|
private $range_time_parser;
|
|
|
|
/**
|
|
* A parser for a list of time periods separated by a semicolon.
|
|
*
|
|
* @var CTimePeriodsParser
|
|
*/
|
|
private $time_periods_parser;
|
|
|
|
public function __construct(array $input, array $rules) {
|
|
$this->input = $input;
|
|
$this->rules = $rules;
|
|
$this->validationRuleParser = new CValidationRule();
|
|
|
|
$this->validate();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the given $value is valid, or set's an error and returns false otherwise.
|
|
*/
|
|
private function validate() {
|
|
foreach ($this->rules as $field => $rule) {
|
|
$this->validateField($field, $rule);
|
|
|
|
if (array_key_exists($field, $this->input)) {
|
|
$this->output[$field] = $this->input[$field];
|
|
}
|
|
}
|
|
}
|
|
|
|
private function validateField($field, $rules): bool {
|
|
$rules = $this->validationRuleParser->parse($rules);
|
|
|
|
if ($rules === false) {
|
|
$this->addError(true, $this->validationRuleParser->getError());
|
|
|
|
return false;
|
|
}
|
|
|
|
$fatal = array_key_exists('fatal', $rules);
|
|
$flags = array_key_exists('flags', $rules) ? $rules['flags'] : 0x00;
|
|
|
|
if (!array_key_exists($field, $this->input)) {
|
|
if (array_key_exists('required', $rules)) {
|
|
$this->addError($fatal, _s('Field "%1$s" is mandatory.', $field));
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unset($rules['fatal'], $rules['flags'], $rules['required']);
|
|
|
|
foreach ($rules as $rule => $params) {
|
|
$value = $this->input[$field];
|
|
|
|
switch ($rule) {
|
|
case 'not_empty':
|
|
if ($value === '') {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('cannot be empty'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'json':
|
|
if (!is_string($value) || json_decode($value) === null) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('JSON string is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'in':
|
|
if ((!is_string($value) && !is_numeric($value)) || !in_array($value, $params)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'int32':
|
|
if ((!is_string($value) && !is_numeric($value)) || !self::is_int32($value)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'uint64':
|
|
if ((!is_string($value) && !is_numeric($value)) || !self::is_uint64($value)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'id':
|
|
if ((!is_string($value) && !is_numeric($value)) || !self::is_id($value)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'array_id':
|
|
if (!is_array($value) || !$this->is_array_id($value)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'array':
|
|
if (!is_array($value)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'array_db':
|
|
if (!is_array($value) || !$this->is_array_db($value, $params['table'], $params['field'], $flags)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'ge':
|
|
if ((!is_string($value) && !is_numeric($value)) || !self::is_int32($value) || $value < $params) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field,
|
|
_s('value must be no less than "%1$s"', $params)
|
|
)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'le':
|
|
if ((!is_string($value) && !is_numeric($value)) || !self::is_int32($value) || $value > $params) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field,
|
|
_s('value must be no greater than "%1$s"', $params)
|
|
)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'db':
|
|
$table_fields = DB::getSchema($params['table'])['fields'];
|
|
|
|
if ((!is_string($value) && !is_numeric($value))
|
|
|| !$this->check_db_value($table_fields[$params['field']], $value, $flags)) {
|
|
$this->addError($fatal,
|
|
is_scalar($value)
|
|
? _s('Incorrect value "%1$s" for "%2$s" field.', $value, $field)
|
|
: _s('Incorrect value for "%1$s" field.', $field)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'range_time':
|
|
if ($this->range_time_parser === null) {
|
|
$this->range_time_parser = new CRangeTimeParser();
|
|
}
|
|
|
|
if (!is_string($value) || $this->range_time_parser->parse($value) != CParser::PARSE_SUCCESS) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a time is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'abs_date':
|
|
$absolute_time_parser = new CAbsoluteTimeParser();
|
|
|
|
$has_errors = !is_string($value)
|
|
|| $absolute_time_parser->parse($value) != CParser::PARSE_SUCCESS
|
|
|| $absolute_time_parser->getDateTime(true)->format('H:i:s') !== '00:00:00';
|
|
|
|
if ($has_errors) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a date is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'abs_time':
|
|
$absolute_time_parser = new CAbsoluteTimeParser();
|
|
|
|
$has_errors = !is_string($value) || $absolute_time_parser->parse($value) != CParser::PARSE_SUCCESS;
|
|
|
|
if ($has_errors) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a time is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'time_periods':
|
|
if ($this->time_periods_parser === null) {
|
|
$this->time_periods_parser = new CTimePeriodsParser(['usermacros' => true]);
|
|
}
|
|
|
|
if (!is_string($value) || $this->time_periods_parser->parse($value) != CParser::PARSE_SUCCESS) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a time period is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'time_unit':
|
|
if (is_string($value) || is_numeric($value)) {
|
|
$result = $this->isTimeUnit($value, $params);
|
|
$error_message = $result['is_valid'] ? null : $result['error'];
|
|
}
|
|
else {
|
|
$error_message = _('a time unit is expected');
|
|
}
|
|
|
|
if ($error_message !== null) {
|
|
$this->addError($fatal, _s('Incorrect value for field "%1$s": %2$s.', $field, $error_message));
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'rgb':
|
|
if (!is_string($value) || preg_match('/^[A-F0-9]{6}$/', $value) == 0) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field,
|
|
_('a hexadecimal color code (6 symbols) is expected')
|
|
)
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'string':
|
|
if (!is_string($value) && !is_numeric($value)) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a character string is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'bool':
|
|
if (!is_bool($value)) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('a boolean value is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'cuid':
|
|
if (!self::isCuid($value)) {
|
|
$this->addError($fatal,
|
|
_s('Incorrect value for field "%1$s": %2$s.', $field, _('CUID is expected'))
|
|
);
|
|
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Do not translate.
|
|
$this->addError($fatal, 'Invalid validation rule "'.$rule.'".');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function is_id($value) {
|
|
if (!preg_match('/^'.ZBX_PREG_INT.'$/', $value)) {
|
|
return false;
|
|
}
|
|
|
|
return (bccomp($value, '0') >= 0 && bccomp($value, ZBX_DB_MAX_ID) <= 0);
|
|
}
|
|
|
|
public static function is_int32($value) {
|
|
if (!preg_match('/^'.ZBX_PREG_INT.'$/', $value)) {
|
|
return false;
|
|
}
|
|
|
|
return ($value >= ZBX_MIN_INT32 && $value <= ZBX_MAX_INT32);
|
|
}
|
|
|
|
public static function is_uint64($value) {
|
|
if (!preg_match('/^'.ZBX_PREG_INT.'$/', $value)) {
|
|
return false;
|
|
}
|
|
|
|
return ($value >= 0 && bccomp($value, ZBX_MAX_UINT64) <= 0);
|
|
}
|
|
|
|
public static function isCuid($value): bool {
|
|
if (!is_string($value)) {
|
|
return false;
|
|
}
|
|
|
|
if (!CCuid::checkLength($value)) {
|
|
return false;
|
|
}
|
|
|
|
if (!CCuid::isCuid($value)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate value against DB schema.
|
|
*
|
|
* @param array $field_schema Array of DB schema.
|
|
* @param string $field_schema['type'] Type of DB field.
|
|
* @param string $field_schema['length'] Length of DB field.
|
|
* @param string $value [IN/OUT] IN - input value, OUT - changed value according to flags.
|
|
* @param int $flags Validation flags.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function check_db_value($field_schema, &$value, $flags) {
|
|
switch ($field_schema['type']) {
|
|
case DB::FIELD_TYPE_ID:
|
|
return self::is_id($value);
|
|
|
|
case DB::FIELD_TYPE_INT:
|
|
return self::is_int32($value);
|
|
|
|
case DB::FIELD_TYPE_CHAR:
|
|
if ($flags & P_CRLF) {
|
|
$value = CRLFtoLF($value);
|
|
}
|
|
|
|
return (mb_strlen($value) <= $field_schema['length']);
|
|
|
|
case DB::FIELD_TYPE_NCLOB:
|
|
case DB::FIELD_TYPE_TEXT:
|
|
if ($flags & P_CRLF) {
|
|
$value = CRLFtoLF($value);
|
|
}
|
|
|
|
// TODO: check length
|
|
return true;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function is_array_id(array $values) {
|
|
foreach ($values as $value) {
|
|
if (!is_string($value) || !self::is_id($value)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate array of string values against DB schema.
|
|
*
|
|
* @param array $values [IN/OUT] IN - input values, OUT - changed values according to flags.
|
|
* @param string $table DB table name.
|
|
* @param string $field DB field name.
|
|
* @param int $flags Validation flags.
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function is_array_db(array &$values, $table, $field, $flags) {
|
|
$table_schema = DB::getSchema($table);
|
|
|
|
foreach ($values as &$value) {
|
|
if (!is_string($value) || !$this->check_db_value($table_schema['fields'][$field], $value, $flags)) {
|
|
return false;
|
|
}
|
|
}
|
|
unset($value);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate a configuration value. Use simple interval parser to parse the string, convert to seconds and check
|
|
* if the value is in between given min and max values. In some cases it's possible to enter 0, or even 0s or 0d.
|
|
* If the value is incorrect, set an error.
|
|
*
|
|
* @param string $value Value to parse and validate.
|
|
* @param bool $options['with_year'] Set to "true" to allow month and year unit support.
|
|
* @param bool $options['allow_zero'] Set to "true" to allow value to be zero.
|
|
* @param string $options['min'] Lower bound.
|
|
* @param string $options['max'] Upper bound.
|
|
*
|
|
* @return array An array with parameter 'is_valid' containing validation result. If validation fails, additionally
|
|
* returned parameter 'error' containing error message.
|
|
*/
|
|
private function isTimeUnit($value, $params) {
|
|
$simple_interval_parser = new CSimpleIntervalParser(
|
|
array_key_exists('with_year', $params) ? ['with_year' => true] : []
|
|
);
|
|
$value = (string) $value;
|
|
|
|
if ($simple_interval_parser->parse($value) == CParser::PARSE_SUCCESS) {
|
|
if (!$params) {
|
|
return ['is_valid' => true];
|
|
}
|
|
|
|
if ($value[0] !== '{') {
|
|
$value = timeUnitToSeconds($value,
|
|
array_key_exists('with_year', $params) ? $params['with_year'] : false
|
|
);
|
|
|
|
if (array_key_exists('ranges', $params)) {
|
|
$in_range = false;
|
|
$message_ranges = [];
|
|
|
|
foreach ($params['ranges'] as $range) {
|
|
if ($range['from'] <= $value && $value <= $range['to']) {
|
|
$in_range = true;
|
|
break;
|
|
}
|
|
|
|
$message_ranges[] = ($range['from'] == $range['to'])
|
|
? $range['from']
|
|
: $range['from'].'-'.$range['to'];
|
|
}
|
|
|
|
if (!$in_range) {
|
|
return [
|
|
'is_valid' => false,
|
|
'error' => _s('value must be one of %1$s', implode(', ', $message_ranges))
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return ['is_valid' => false, 'error' => _('a time unit is expected')];
|
|
}
|
|
|
|
return ['is_valid' => true];
|
|
}
|
|
|
|
/**
|
|
* Add validation error.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function addError($fatal, $error) {
|
|
if ($fatal) {
|
|
$this->errorsFatal[] = $error;
|
|
}
|
|
else {
|
|
$this->errors[] = $error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get valid fields.
|
|
*
|
|
* @return array of fields passed validation
|
|
*/
|
|
public function getValidInput() {
|
|
return $this->output;
|
|
}
|
|
|
|
/**
|
|
* Returns array of error messages.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getAllErrors() {
|
|
return array_merge($this->errorsFatal, $this->errors);
|
|
}
|
|
|
|
/**
|
|
* Returns true if validation failed with errors.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isError() {
|
|
return (bool) $this->errors;
|
|
}
|
|
|
|
/**
|
|
* Returns true if validation failed with fatal errors.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isErrorFatal() {
|
|
return (bool) $this->errorsFatal;
|
|
}
|
|
}
|