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