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 21 22class CUserMacroParser extends CParser { 23 24 const STATE_NEW = 0; 25 const STATE_END = 1; 26 const STATE_UNQUOTED = 2; 27 const STATE_QUOTED = 3; 28 const STATE_END_OF_MACRO = 4; 29 public const REGEX_PREFIX = 'regex:'; 30 31 private $macro = ''; 32 private $context = null; 33 private $context_quoted = false; 34 private $regex = null; 35 36 public function __construct() { 37 $this->error_msgs['empty'] = _('macro is empty'); 38 $this->error_msgs['unexpected_end'] = _('unexpected end of macro'); 39 } 40 41 /** 42 * @inheritDoc 43 */ 44 public function parse($source, $pos = 0) { 45 $this->length = 0; 46 $this->match = ''; 47 $this->macro = ''; 48 $this->context = null; 49 $this->context_quoted = false; 50 $this->errorClear(); 51 $this->regex = null; 52 $has_regex = false; 53 54 $p = $pos; 55 56 if (!isset($source[$p]) || $source[$p] != '{') { 57 $this->errorPos(substr($source, $pos), $p - $pos); 58 59 return self::PARSE_FAIL; 60 } 61 $p++; 62 63 if (!isset($source[$p]) || $source[$p] != '$') { 64 $this->errorPos(substr($source, $pos), $p - $pos); 65 66 return self::PARSE_FAIL; 67 } 68 $p++; 69 70 for (; isset($source[$p]) && $this->isMacroChar($source[$p]); $p++) 71 ; 72 73 if ($p == $pos + 2 || !isset($source[$p])) { 74 $this->errorPos(substr($source, $pos), $p - $pos); 75 76 return self::PARSE_FAIL; 77 } 78 79 $this->macro = substr($source, $pos + 2, $p - $pos - 2); 80 81 if ($source[$p] == '}') { 82 $p++; 83 $this->length = $p - $pos; 84 $this->match = substr($source, $pos, $this->length); 85 86 if (isset($source[$p])) { 87 $this->errorPos(substr($source, $pos), $p - $pos); 88 89 return self::PARSE_SUCCESS_CONT; 90 } 91 92 return self::PARSE_SUCCESS; 93 } 94 95 if ($source[$p] != ':') { 96 $this->macro = ''; 97 $this->errorPos(substr($source, $pos), $p - $pos); 98 99 return self::PARSE_FAIL; 100 } 101 $p++; 102 103 if (preg_match("/^\s*".self::REGEX_PREFIX."/", substr($source, $p)) === 1) { 104 $has_regex = true; 105 $p += strpos(substr($source, $p), self::REGEX_PREFIX) + strlen(self::REGEX_PREFIX); 106 } 107 108 $this->context = ''; 109 $this->context_quoted = false; 110 $state = self::STATE_NEW; 111 112 for (; isset($source[$p]); $p++) { 113 switch ($state) { 114 case self::STATE_NEW: 115 switch ($source[$p]) { 116 case ' ': 117 break; 118 119 case '}': 120 $state = self::STATE_END_OF_MACRO; 121 break; 122 123 case '"': 124 $this->context .= $source[$p]; 125 $this->context_quoted = true; 126 $state = self::STATE_QUOTED; 127 break; 128 129 default: 130 $this->context .= $source[$p]; 131 $this->context_quoted = false; 132 $state = self::STATE_UNQUOTED; 133 break; 134 } 135 break; 136 137 case self::STATE_QUOTED: 138 $this->context .= $source[$p]; 139 if ($source[$p] == '"' && $source[$p - 1] != '\\') { 140 $state = self::STATE_END; 141 } 142 break; 143 144 case self::STATE_UNQUOTED: 145 switch ($source[$p]) { 146 case '}': 147 $state = self::STATE_END_OF_MACRO; 148 break; 149 150 default: 151 $this->context .= $source[$p]; 152 break; 153 } 154 break; 155 156 case self::STATE_END: 157 switch ($source[$p]) { 158 case ' ': 159 break; 160 161 case '}': 162 $state = self::STATE_END_OF_MACRO; 163 break; 164 165 default: 166 break 3; 167 } 168 break; 169 170 case self::STATE_END_OF_MACRO: 171 break 2; 172 } 173 } 174 175 if ($state != self::STATE_END_OF_MACRO) { 176 $this->macro = ''; 177 $this->context = null; 178 $this->context_quoted = false; 179 $this->errorPos(substr($source, $pos), $p - $pos); 180 181 return self::PARSE_FAIL; 182 } 183 184 if ($has_regex) { 185 $this->regex = $this->context; 186 $this->context = null; 187 } 188 189 $this->length = $p - $pos; 190 $this->match = substr($source, $pos, $this->length); 191 192 if (isset($source[$p])) { 193 $this->errorPos(substr($source, $pos), $p - $pos); 194 195 return self::PARSE_SUCCESS_CONT; 196 } 197 198 return self::PARSE_SUCCESS; 199 } 200 201 /** 202 * Returns true if the char is allowed in the macro, false otherwise. 203 * 204 * @param string $c 205 * 206 * @return bool 207 */ 208 private function isMacroChar(string $c): bool { 209 return (($c >= 'A' && $c <= 'Z') || $c == '.' || $c == '_' || ($c >= '0' && $c <= '9')); 210 } 211 212 /* 213 * Unquotes special symbols in context 214 * 215 * @param string $context 216 * 217 * @return string 218 */ 219 private function unquoteContext(string $context): string { 220 $unquoted = ''; 221 222 for ($p = 1; isset($context[$p]); $p++) { 223 if ('\\' == $context[$p] && '"' == $context[$p + 1]) { 224 continue; 225 } 226 227 $unquoted .= $context[$p]; 228 } 229 230 return substr($unquoted, 0, -1); 231 } 232 233 /** 234 * Returns parsed macro name. 235 * 236 * @return string 237 */ 238 public function getMacro(): string { 239 return $this->macro; 240 } 241 242 /** 243 * Returns parsed macro context. 244 * 245 * @return string|null 246 */ 247 public function getContext(): ?string { 248 return ($this->context !== null && $this->context_quoted) 249 ? $this->unquoteContext($this->context) 250 : $this->context; 251 } 252 253 /** 254 * Returns parsed regex string. 255 * 256 * @return string|null 257 */ 258 public function getRegex(): ?string { 259 return ($this->regex !== null && $this->context_quoted) ? $this->unquoteContext($this->regex) : $this->regex; 260 } 261} 262