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