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 CConditionFormula {
22
23	// possible parsing states
24	const STATE_AFTER_OPEN_BRACE = 0;
25	const STATE_AFTER_OPERATOR = 1;
26	const STATE_AFTER_CLOSE_BRACE = 2;
27	const STATE_AFTER_CONSTANT = 3;
28
29	/**
30	 * Set to true of the formula is valid.
31	 *
32	 * @var bool
33	 */
34	public $isValid;
35
36	/**
37	 * Error message if the formula is invalid.
38	 *
39	 * @var string
40	 */
41	public $error;
42
43	/**
44	 * The parsed formula.
45	 *
46	 * @var string
47	 */
48	public $formula;
49
50	/**
51	 * Array of unique constants used in the formula.
52	 *
53	 * @var array
54	 */
55	public $constants = [];
56
57	/**
58	 * Array of supported operators.
59	 *
60	 * @var array
61	 */
62	protected $allowedOperators = ['and', 'or'];
63
64	/**
65	 * Current position on a parsed element.
66	 *
67	 * @var integer
68	 */
69	private $pos;
70
71	/**
72	 * Parses the given condition formula.
73	 *
74	 * @param string $formula
75	 *
76	 * @return bool		true if the formula is valid
77	 */
78	public function parse($formula) {
79		$this->isValid = true;
80		$this->error = '';
81		$this->constants = [];
82
83		$this->pos = 0;
84		$this->formula = $formula;
85
86		$state = self::STATE_AFTER_OPEN_BRACE;
87		$afterSpace = false;
88		$level = 0;
89
90		while (isset($this->formula[$this->pos])) {
91			if ($this->formula[$this->pos] === ' ') {
92				$afterSpace = true;
93				$this->pos++;
94
95				continue;
96			}
97
98			switch ($state) {
99				case self::STATE_AFTER_OPEN_BRACE:
100					switch ($this->formula[$this->pos]) {
101						case '(':
102							$state = self::STATE_AFTER_OPEN_BRACE;
103							$level++;
104							break;
105						default:
106							if ($this->parseConstant()) {
107								$state = self::STATE_AFTER_CONSTANT;
108							}
109							else {
110								break 3;
111							}
112					}
113					break;
114
115				case self::STATE_AFTER_OPERATOR:
116					switch ($this->formula[$this->pos]) {
117						case '(':
118							$state = self::STATE_AFTER_OPEN_BRACE;
119							$level++;
120							break;
121						default:
122							if (!$afterSpace) {
123								break 3;
124							}
125
126							if ($this->parseConstant()) {
127								$state = self::STATE_AFTER_CONSTANT;
128							}
129							else {
130								break 3;
131							}
132					}
133					break;
134
135				case self::STATE_AFTER_CLOSE_BRACE:
136					switch ($this->formula[$this->pos]) {
137						case ')':
138							$state = self::STATE_AFTER_CLOSE_BRACE;
139							if ($level == 0) {
140								break 3;
141							}
142							$level--;
143							break;
144						default:
145							if ($this->parseOperator()) {
146								$state = self::STATE_AFTER_OPERATOR;
147							}
148							else {
149								break 3;
150							}
151					}
152					break;
153
154				case self::STATE_AFTER_CONSTANT:
155					switch ($this->formula[$this->pos]) {
156						case ')':
157							$state = self::STATE_AFTER_CLOSE_BRACE;
158							if ($level == 0) {
159								break 3;
160							}
161							$level--;
162							break;
163						default:
164							if (!$afterSpace) {
165								break 3;
166							}
167
168							if ($this->parseOperator()) {
169								$state = self::STATE_AFTER_OPERATOR;
170							}
171							else {
172								break 3;
173							}
174					}
175					break;
176			}
177
178			$afterSpace = false;
179			$this->pos++;
180		}
181
182		if ($this->pos == 0) {
183			$this->error = _('expression is empty');
184			$this->isValid = false;
185		}
186
187		if ($level != 0 || isset($this->formula[$this->pos]) || $state == self::STATE_AFTER_OPERATOR) {
188			$this->error = _s('check expression starting from "%1$s"',
189				substr($this->formula, $this->pos == 0 ? 0 : $this->pos - 1)
190			);
191			$this->isValid = false;
192		}
193
194		return $this->isValid;
195	}
196
197	/**
198	 * Parses a constant and advances the position to its last character.
199	 *
200	 * @return bool
201	 */
202	protected function parseConstant() {
203		$start = $this->pos;
204
205		while (isset($this->formula[$this->pos]) && $this->isConstantChar($this->formula[$this->pos])) {
206			$this->pos++;
207		}
208
209		// empty constant
210		if ($start == $this->pos) {
211			return false;
212		}
213
214		$constant = substr($this->formula, $start, $this->pos - $start);
215		$this->constants[] = [
216			'value' => $constant,
217			'pos' => $start
218		];
219
220		$this->pos--;
221
222		return true;
223	}
224
225	/**
226	 * Parses an operator and advances the position to its last character.
227	 *
228	 * @return bool
229	 */
230	protected function parseOperator() {
231		$start = $this->pos;
232
233		while (isset($this->formula[$this->pos]) && $this->isOperatorChar($this->formula[$this->pos])) {
234			$this->pos++;
235		}
236
237		// empty operator
238		if ($start == $this->pos) {
239			return false;
240		}
241
242		$operator = substr($this->formula, $start, $this->pos - $start);
243
244		$this->pos--;
245
246		// check if this is a valid operator
247		if (!in_array($operator, $this->allowedOperators)) {
248			return false;
249		}
250
251		return true;
252	}
253
254	/**
255	 * Returns true if the given character is a valid constant character.
256	 *
257	 * @param string $c
258	 *
259	 * @return bool
260	 */
261	protected function isConstantChar($c) {
262		return ($c >= 'A' && $c <= 'Z');
263	}
264
265	/**
266	 * Returns true if the given character is a valid operator character.
267	 *
268	 * @param string $c
269	 *
270	 * @return bool
271	 */
272	protected function isOperatorChar($c) {
273		return ($c >= 'a' && $c <= 'z');
274	}
275}
276