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