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