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