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
21class CValidationRule {
22	const STATE_BEGIN = 0;
23	const STATE_END = 1;
24
25	/**
26	 * An error message if validation rule is not valid.
27	 *
28	 * @var string
29	 */
30	private $error = '';
31
32	/**
33	 * Parse validation rule. Returns array of rules or fail if $buffer is not valid.
34	 *
35	 * @param string $buffer
36	 *
37	 * @return array|bool
38	 */
39	public function parse($buffer) {
40		$this->error = '';
41
42		$pos = 0;
43		$state = self::STATE_BEGIN;
44		$rules = [];
45		$is_empty = true;
46
47		while (isset($buffer[$pos])) {
48			switch ($state) {
49				case self::STATE_BEGIN:
50					switch ($buffer[$pos]) {
51						case ' ':
52							$pos++;
53							break;
54
55						default:
56							$is_empty = false;
57							$rule = [];
58
59							if (!$this->parseString($buffer, $pos, $rule)		// string
60									&& !$this->parseRangeTime($buffer, $pos, $rule)		// range time
61									&& !$this->parseTimePeriods($buffer, $pos, $rule)	// time periods
62									&& !$this->parseRgb($buffer, $pos, $rule)			// rgb
63									&& !$this->parseRequired($buffer, $pos, $rule)		// required
64									&& !$this->parseNotEmpty($buffer, $pos, $rule)		// not_empty
65									&& !$this->parseLE($buffer, $pos, $rule)			// le
66									&& !$this->parseJson($buffer, $pos, $rule)			// json
67									&& !$this->parseInt32($buffer, $pos, $rule)			// int32
68									&& !$this->parseIn($buffer, $pos, $rule)			// in
69									&& !$this->parseId($buffer, $pos, $rule)			// id
70									&& !$this->parseGE($buffer, $pos, $rule)			// ge
71									&& !$this->parseFatal($buffer, $pos, $rule)			// fatal
72									&& !$this->parseDB($buffer, $pos, $rule)			// db
73									&& !$this->parseArrayId($buffer, $pos, $rule)		// array_id
74									&& !$this->parseArrayDB($buffer, $pos, $rule)		// array_db
75									&& !$this->parseArray($buffer, $pos, $rule)			// array
76									&& !$this->parseFlags($buffer, $pos, $rule)) {		// flags
77								// incorrect validation rule
78								break 3;
79							}
80
81							if (array_key_exists(key($rule), $rules)) {
82								// the message can be not translated because it is an internal error
83								$this->error = 'Validation rule "'.key($rule).'" already exists.';
84								return false;
85							}
86
87							$rules = array_merge($rules, $rule);
88							$state = self::STATE_END;
89							break;
90					}
91					break;
92
93				case self::STATE_END:
94					switch ($buffer[$pos]) {
95						case ' ':
96							$pos++;
97							break;
98						case '|':
99							$state = self::STATE_BEGIN;
100							$pos++;
101							break;
102						default:
103							// incorrect validation rule
104							break 3;
105					}
106					break;
107			}
108		}
109
110		if (isset($buffer[$pos])) {
111			// the message can be not translated because it is an internal error
112			$this->error = 'Cannot parse validation rules "'.$buffer.'" at position '.$pos.'.';
113			return false;
114		}
115
116		return $rules;
117	}
118
119	/**
120	 * Returns the error message if validation rule is invalid.
121	 *
122	 * @return string
123	 */
124	public function getError() {
125		return $this->error;
126	}
127
128	/**
129	 * fatal
130	 *
131	 * 'fatal' => true
132	 */
133	private function parseFatal($buffer, &$pos, &$rule) {
134		if (strncmp(substr($buffer, $pos), 'fatal', 5) != 0) {
135			return false;
136		}
137
138		$pos += 5;
139		$rule['fatal'] = true;
140
141		return true;
142	}
143
144	/**
145	 * string
146	 *
147	 * 'string' => true
148	 */
149	private function parseString($buffer, &$pos, &$rules) {
150		if (strncmp(substr($buffer, $pos), 'string', 6) != 0) {
151			return false;
152		}
153
154		$pos += 6;
155		$rules['string'] = true;
156
157		return true;
158	}
159
160	/**
161	 * range_time
162	 *
163	 * 'range_time' => true
164	 */
165	private function parseRangeTime($buffer, &$pos, &$rules) {
166		if (strncmp(substr($buffer, $pos), 'range_time', 10) != 0) {
167			return false;
168		}
169
170		$pos += 10;
171		$rules['range_time'] = true;
172
173		return true;
174	}
175
176	/**
177	 * range_time
178	 *
179	 * 'time_periods' => true
180	 */
181	private function parseTimePeriods($buffer, &$pos, &$rules) {
182		if (strncmp(substr($buffer, $pos), 'time_periods', 12) != 0) {
183			return false;
184		}
185
186		$pos += 12;
187		$rules['time_periods'] = true;
188
189		return true;
190	}
191
192	/**
193	 * rgb
194	 *
195	 * 'rgb' => true
196	 */
197	private function parseRgb($buffer, &$pos, &$rules) {
198		if (strncmp(substr($buffer, $pos), 'rgb', 3) != 0) {
199			return false;
200		}
201
202		$pos += 3;
203		$rules['rgb'] = true;
204
205		return true;
206	}
207
208	/**
209	 * required
210	 *
211	 * 'required' => true
212	 */
213	private function parseRequired($buffer, &$pos, &$rules) {
214		if (strncmp(substr($buffer, $pos), 'required', 8) != 0) {
215			return false;
216		}
217
218		$pos += 8;
219		$rules['required'] = true;
220
221		return true;
222	}
223
224	/**
225	 * not_empty
226	 *
227	 * 'not_empty' => true
228	 */
229	private function parseNotEmpty($buffer, &$pos, &$rules) {
230		if (strncmp(substr($buffer, $pos), 'not_empty', 9) != 0) {
231			return false;
232		}
233
234		$pos += 9;
235		$rules['not_empty'] = true;
236
237		return true;
238	}
239
240	/**
241	 * le <value>
242	 *
243	 * 'le' => '<value>'
244	 */
245	private function parseLE($buffer, &$pos, &$rules) {
246		$i = $pos;
247
248		if (0 != strncmp(substr($buffer, $i), 'le ', 3)) {
249			return false;
250		}
251
252		$i += 3;
253		$value = '';
254
255		while (isset($buffer[$i]) && $buffer[$i] != '|') {
256			$value .= $buffer[$i++];
257		}
258
259		if (!CNewValidator::is_int32($value)) {
260			return false;
261		}
262
263		$pos = $i;
264		$rules['le'] = $value;
265
266		return true;
267	}
268
269	/**
270	 * json
271	 *
272	 * 'json' => true
273	 */
274	private function parseJson($buffer, &$pos, &$rules) {
275		if (strncmp(substr($buffer, $pos), 'json', 4) != 0) {
276			return false;
277		}
278
279		$pos += 4;
280		$rules['json'] = true;
281
282		return true;
283	}
284
285	/**
286	 * int32
287	 *
288	 * 'int32' => true
289	 */
290	private function parseInt32($buffer, &$pos, &$rules) {
291		if (strncmp(substr($buffer, $pos), 'int32', 5) != 0) {
292			return false;
293		}
294
295		$pos += 5;
296		$rules['int32'] = true;
297
298		return true;
299	}
300
301	/**
302	 * in <value1>[,...,<valueN>]
303	 *
304	 * 'in' => array('<value1>', ..., '<valueN>')
305	 */
306	private function parseIn($buffer, &$pos, &$rules) {
307		$i = $pos;
308
309		if (strncmp(substr($buffer, $i), 'in ', 3) != 0) {
310			return false;
311		}
312
313		$i += 3;
314
315		while (isset($buffer[$i]) && $buffer[$i] == ' ') {
316			$i++;
317		}
318
319		$values = [];
320
321		if (!$this->parseValues($buffer, $i, $values)) {
322			return false;
323		}
324
325		$pos = $i;
326		$rules['in'] = $values;
327
328		return true;
329	}
330
331	/**
332	 * id
333	 *
334	 * 'id' => true
335	 */
336	private function parseId($buffer, &$pos, &$rules) {
337		if (strncmp(substr($buffer, $pos), 'id', 2) != 0) {
338			return false;
339		}
340
341		$pos += 2;
342		$rules['id'] = true;
343
344		return true;
345	}
346
347	/**
348	 * ge <value>
349	 *
350	 * 'ge' => '<value>'
351	 */
352	private function parseGE($buffer, &$pos, &$rules) {
353		$i = $pos;
354
355		if (0 != strncmp(substr($buffer, $i), 'ge ', 3)) {
356			return false;
357		}
358
359		$i += 3;
360		$value = '';
361
362		while (isset($buffer[$i]) && $buffer[$i] != '|') {
363			$value .= $buffer[$i++];
364		}
365
366		if (!CNewValidator::is_int32($value)) {
367			return false;
368		}
369
370		$pos = $i;
371		$rules['ge'] = $value;
372
373		return true;
374	}
375
376	/**
377	 * db <table>.<field>
378	 *
379	 * 'db' => array(
380	 *     'table' => '<table>',
381	 *     'field' => '<field>'
382	 * )
383	 */
384	private function parseDB($buffer, &$pos, &$rules) {
385		$i = $pos;
386
387		if (strncmp(substr($buffer, $i), 'db ', 3) != 0) {
388			return false;
389		}
390
391		$i += 3;
392
393		while (isset($buffer[$i]) && $buffer[$i] == ' ') {
394			$i++;
395		}
396
397		$table = '';
398
399		if (!$this->parseField($buffer, $i, $table) || !isset($buffer[$i]) || $buffer[$i++] != '.') {
400			return false;
401		}
402
403		$field = '';
404
405		if (!$this->parseField($buffer, $i, $field)) {
406			return false;
407		}
408
409		$pos = $i;
410		$rules['db'] = [
411			'table' => $table,
412			'field' => $field
413		];
414
415		return true;
416	}
417
418	/**
419	 * array
420	 *
421	 * 'array' => true
422	 */
423	private function parseArray($buffer, &$pos, &$rules) {
424		if (strncmp(substr($buffer, $pos), 'array', 5) != 0) {
425			return false;
426		}
427
428		$pos += 5;
429		$rules['array'] = true;
430
431		return true;
432	}
433
434	/**
435	 * array_id
436	 *
437	 * 'array_id' => true
438	 */
439	private function parseArrayId($buffer, &$pos, &$rules) {
440		if (strncmp(substr($buffer, $pos), 'array_id', 8) != 0) {
441			return false;
442		}
443
444		$pos += 8;
445		$rules['array_id'] = true;
446
447		return true;
448	}
449
450	/**
451	 * array_db <table>.<field>
452	 *
453	 * 'array_db' => array(
454	 *     'table' => '<table>',
455	 *     'field' => '<field>'
456	 * )
457	 */
458	private function parseArrayDB($buffer, &$pos, &$rules) {
459		$i = $pos;
460
461		if (strncmp(substr($buffer, $i), 'array_db ', 9) != 0) {
462			return false;
463		}
464
465		$i += 9;
466
467		while (isset($buffer[$i]) && $buffer[$i] == ' ') {
468			$i++;
469		}
470
471		$table = '';
472
473		if (!$this->parseField($buffer, $i, $table) || !isset($buffer[$i]) || $buffer[$i++] != '.') {
474			return false;
475		}
476
477		$field = '';
478
479		if (!$this->parseField($buffer, $i, $field)) {
480			return false;
481		}
482
483		$pos = $i;
484		$rules['array_db'] = [
485			'table' => $table,
486			'field' => $field
487		];
488
489		return true;
490	}
491
492	/**
493	 * flags <value1> | <value2> | ... | <valueN>
494	 *
495	 * 'flags' => <value1> | <value2> | ... | <valueN>
496	 */
497	private function parseFlags($buffer, &$pos, &$rules) {
498		$i = $pos;
499
500		if (0 != strncmp(substr($buffer, $i), 'flags ', 6)) {
501			return false;
502		}
503
504		$i += 6;
505
506		$value = 0x00;
507
508		if (!$this->parseValue($buffer, $i, $value)) {
509			return false;
510		}
511
512		$pos = $i;
513		$rules['flags'] = $value;
514
515		return true;
516	}
517
518	/**
519	 * <field>
520	 */
521	private function parseField($buffer, &$pos, &$field) {
522		$matches = [];
523		if (1 != preg_match('/^[A-Za-z0-9_]+/', substr($buffer, $pos), $matches))
524			return false;
525
526		$pos += strlen($matches[0]);
527		$field = $matches[0];
528
529		return true;
530	}
531
532	/**
533	 * <value1>[,...,<valueN>]
534	 */
535	private function parseValues($buffer, &$pos, array &$values) {
536		$i = $pos;
537
538		while (true) {
539			$value = '';
540
541			if (!isset($buffer[$i]) || !$this->parseValue($buffer, $i, $value)) {
542				return false;
543			}
544
545			$values[] = $value;
546
547			if (!isset($buffer[$i]) || $buffer[$i] == ' ' || $buffer[$i] == '|') {
548				break;
549			}
550
551			$i++;
552		}
553
554		$pos = $i;
555
556		return true;
557	}
558
559	/**
560	 * <value>
561	 */
562	private function parseValue($buffer, &$pos, &$value) {
563		$i = $pos;
564
565		while (isset($buffer[$i]) && $buffer[$i] != ',' && $buffer[$i] != ' ' && $buffer[$i] != '|') {
566			$i++;
567		}
568
569		if ($pos == $i) {
570			return false;
571		}
572
573		$value = substr($buffer, $pos, $i - $pos);
574		$pos = $i;
575
576		return true;
577	}
578}
579