1<?php declare(strict_types = 1); 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 21 22/** 23 * Class for validating trigger expressions and calculated item formulas. 24 */ 25class CExpressionValidator extends CValidator { 26 27 /** 28 * An options array. 29 * 30 * Supported options: 31 * 'usermacros' => false Enable user macros usage in function parameters. 32 * 'lldmacros' => false Enable low-level discovery macros usage in function parameters. 33 * 'calculated' => false Validate expression as part of calculated item formula. 34 * 'partial' => false Validate partial expression (relaxed requirements). 35 * 36 * @var array 37 */ 38 private $options = [ 39 'usermacros' => false, 40 'lldmacros' => false, 41 'calculated' => false, 42 'partial' => false 43 ]; 44 45 /** 46 * Provider of information on math functions. 47 * 48 * @var CMathFunctionData 49 */ 50 private $math_function_data; 51 52 /** 53 * Known math functions along with number or range of required parameters. 54 * 55 * @var array 56 */ 57 private $math_function_parameters = []; 58 59 /** 60 * Provider of information on history functions. 61 * 62 * @var CHistFunctionData 63 */ 64 private $hist_function_data; 65 66 /** 67 * Known history functions along with definition of parameters. 68 * 69 * @var array 70 */ 71 private $hist_function_parameters = []; 72 73 /** 74 * @param array $options 75 */ 76 public function __construct(array $options = []) { 77 $this->options = $options + $this->options; 78 79 $this->math_function_data = new CMathFunctionData(); 80 $this->math_function_parameters = $this->math_function_data->getParameters(); 81 82 $this->hist_function_data = new CHistFunctionData(['calculated' => $this->options['calculated']]); 83 $this->hist_function_parameters = $this->hist_function_data->getParameters(); 84 } 85 86 /** 87 * Validate expression. 88 * 89 * @param array $tokens A hierarchy of tokens of parsed expression. 90 * 91 * @return bool 92 */ 93 public function validate($tokens) { 94 if (!$this->validateRecursively($tokens, null)) { 95 return false; 96 } 97 98 if (!$this->options['calculated']) { 99 if (!$this->options['partial'] && !self::hasHistoryFunctions($tokens)) { 100 $this->setError(_('trigger expression must contain at least one /host/key reference')); 101 102 return false; 103 } 104 } 105 106 return true; 107 } 108 109 /** 110 * Validate expression (recursive helper). 111 * 112 * @param array $tokens A hierarchy of tokens. 113 * @param array|null $parent_token Parent token containing the hierarchy of tokens. 114 * 115 * @return bool 116 */ 117 private function validateRecursively(array $tokens, ?array $parent_token): bool { 118 foreach ($tokens as $token) { 119 switch ($token['type']) { 120 case CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION: 121 if (!$this->math_function_data->isKnownFunction($token['data']['function']) 122 && $this->hist_function_data->isKnownFunction($token['data']['function'])) { 123 $this->setError(_s('incorrect usage of function "%1$s"', $token['data']['function'])); 124 125 return false; 126 } 127 128 $math_function_validator = new CMathFunctionValidator([ 129 'parameters' => $this->math_function_parameters 130 ]); 131 132 if (!$math_function_validator->validate($token)) { 133 $this->setError($math_function_validator->getError()); 134 135 return false; 136 } 137 138 foreach ($token['data']['parameters'] as $parameter) { 139 if (!$this->validateRecursively($parameter['data']['tokens'], $token)) { 140 return false; 141 } 142 } 143 144 break; 145 146 case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION: 147 if (!$this->hist_function_data->isKnownFunction($token['data']['function']) 148 && $this->math_function_data->isKnownFunction($token['data']['function'])) { 149 $this->setError(_s('incorrect usage of function "%1$s"', $token['data']['function'])); 150 151 return false; 152 } 153 154 $options = [ 155 'parameters' => $this->hist_function_parameters, 156 'usermacros' => $this->options['usermacros'], 157 'lldmacros' => $this->options['lldmacros'], 158 'calculated' => $this->options['calculated'] 159 ]; 160 161 if ($this->options['calculated']) { 162 $options['aggregating'] = CHistFunctionData::isAggregating($token['data']['function']); 163 } 164 165 $hist_function_validator = new CHistFunctionValidator($options); 166 167 if (!$hist_function_validator->validate($token)) { 168 $this->setError($hist_function_validator->getError()); 169 170 return false; 171 } 172 173 if ($options['calculated'] && $options['aggregating']) { 174 if ($parent_token === null 175 || $parent_token['type'] != CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION 176 || !CMathFunctionData::isAggregating($parent_token['data']['function']) 177 || count($parent_token['data']['parameters']) != 1 178 || count($parent_token['data']['parameters'][0]['data']['tokens']) != 1) { 179 $this->setError(_s('incorrect usage of function "%1$s"', $token['data']['function'])); 180 181 return false; 182 } 183 } 184 185 break; 186 187 case CExpressionParserResult::TOKEN_TYPE_EXPRESSION: 188 if (!$this->validateRecursively($token['data']['tokens'], null)) { 189 return false; 190 } 191 192 break; 193 } 194 } 195 196 return true; 197 } 198 199 /** 200 * Check if there are history function tokens within the hierarchy of given tokens. 201 * 202 * @param array $tokens 203 * 204 * @static 205 * 206 * @return bool 207 */ 208 private static function hasHistoryFunctions(array $tokens): bool { 209 foreach ($tokens as $token) { 210 switch ($token['type']) { 211 case CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION: 212 foreach ($token['data']['parameters'] as $parameter) { 213 if (self::hasHistoryFunctions($parameter['data']['tokens'])) { 214 return true; 215 } 216 } 217 218 break; 219 220 case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION: 221 return true; 222 223 case CExpressionParserResult::TOKEN_TYPE_EXPRESSION: 224 return self::hasHistoryFunctions($token['data']['tokens']); 225 } 226 } 227 228 return false; 229 } 230} 231