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