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
22/**
23 * Class is used to validate and parse a trigger function.
24 */
25class C10FunctionParser extends CParser {
26
27	const STATE_NEW = 0;
28	const STATE_END = 1;
29	const STATE_UNQUOTED = 2;
30	const STATE_QUOTED = 3;
31	const STATE_END_OF_PARAMS = 4;
32
33	const PARAM_ARRAY = 0;
34	const PARAM_UNQUOTED = 1;
35	const PARAM_QUOTED = 2;
36
37	private $function = '';
38	private $parameters = '';
39	private $params_raw = [];
40
41	/**
42	 * Returns true if the char is allowed in the function name, false otherwise.
43	 *
44	 * @param string $c
45	 *
46	 * @return bool
47	 */
48	protected function isFunctionChar($c) {
49		return ($c >= 'a' && $c <= 'z');
50	}
51
52	/**
53	 * Parse a trigger function and parameters and put them into $this->params_raw array.
54	 *
55	 * @param string	$source
56	 * @param int		$pos
57	 */
58	public function parse($source, $pos = 0) {
59		$this->length = 0;
60		$this->match = '';
61		$this->function = '';
62		$this->parameters = '';
63		$this->params_raw = [];
64
65		for ($p = $pos; isset($source[$p]) && $this->isFunctionChar($source[$p]); $p++) {
66		}
67
68		if ($p == $pos) {
69			return self::PARSE_FAIL;
70		}
71
72		$p2 = $p;
73
74		$params_raw = [
75			'type' => self::PARAM_ARRAY,
76			'raw' => '',
77			'pos' => $p - $pos,
78			'parameters' => []
79		];
80		if (!$this->parseFunctionParameters($source, $p, $params_raw['parameters'])) {
81			return self::PARSE_FAIL;
82		}
83
84		$params_raw['raw'] = substr($source, $p2, $p - $p2);
85
86		$this->length = $p - $pos;
87		$this->match = substr($source, $pos, $this->length);
88		$this->function = substr($source, $pos, $p2 - $pos);
89		$this->parameters = substr($source, $p2 + 1, $p - $p2 - 2);
90		$this->params_raw = $params_raw;
91
92		return isset($source[$p]) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS;
93	}
94
95	private function parseFunctionParameters($source, &$pos, array &$parameters) {
96		if (!isset($source[$pos]) || $source[$pos] != '(') {
97			return false;
98		}
99
100		$_parameters = [];
101		$state = self::STATE_NEW;
102		$num = 0;
103
104		for ($p = $pos + 1; isset($source[$p]); $p++) {
105			switch ($state) {
106				// a new parameter started
107				case self::STATE_NEW:
108					switch ($source[$p]) {
109						case ' ':
110							break;
111
112						case ',':
113							$_parameters[$num++] = [
114								'type' => self::PARAM_UNQUOTED,
115								'raw' => '',
116								'pos' => $p - $pos
117							];
118							break;
119
120						case ')':
121							$_parameters[$num] = [
122								'type' => self::PARAM_UNQUOTED,
123								'raw' => '',
124								'pos' => $p - $pos
125							];
126							$state = self::STATE_END_OF_PARAMS;
127							break;
128
129						case '"':
130							$_parameters[$num] = [
131								'type' => self::PARAM_QUOTED,
132								'raw' => $source[$p],
133								'pos' => $p - $pos
134							];
135							$state = self::STATE_QUOTED;
136							break;
137
138						default:
139							$_parameters[$num] = [
140								'type' => self::PARAM_UNQUOTED,
141								'raw' => $source[$p],
142								'pos' => $p - $pos
143							];
144							$state = self::STATE_UNQUOTED;
145					}
146					break;
147
148				// end of parameter
149				case self::STATE_END:
150					switch ($source[$p]) {
151						case ' ':
152							break;
153
154						case ',':
155							$state = self::STATE_NEW;
156							$num++;
157							break;
158
159						case ')':
160							$state = self::STATE_END_OF_PARAMS;
161							break;
162
163						default:
164							break 3;
165					}
166					break;
167
168				// an unquoted parameter
169				case self::STATE_UNQUOTED:
170					switch ($source[$p]) {
171						case ')':
172							$state = self::STATE_END_OF_PARAMS;
173							break;
174
175						case ',':
176							$state = self::STATE_NEW;
177							$num++;
178							break;
179
180						default:
181							$_parameters[$num]['raw'] .= $source[$p];
182					}
183					break;
184
185				// a quoted parameter
186				case self::STATE_QUOTED:
187					$_parameters[$num]['raw'] .= $source[$p];
188
189					if ($source[$p] == '"' && $source[$p - 1] != '\\') {
190						$state = self::STATE_END;
191					}
192					break;
193
194				// end of parameters
195				case self::STATE_END_OF_PARAMS:
196					break 2;
197			}
198		}
199
200		if ($state == self::STATE_END_OF_PARAMS) {
201			$parameters = $_parameters;
202			$pos = $p;
203
204			return true;
205		}
206
207		return false;
208	}
209
210	/**
211	 * Returns the left part of the trigger function without parameters.
212	 *
213	 * @return string
214	 */
215	public function getFunction() {
216		return $this->function;
217	}
218
219	/**
220	 * Returns the parameters of the function.
221	 *
222	 * @return string
223	 */
224	public function getParameters() {
225		return $this->parameters;
226	}
227
228	/**
229	 * Returns the list of the parameters.
230	 *
231	 * @return array
232	 */
233	public function getParamsRaw() {
234		return $this->params_raw;
235	}
236
237	/**
238	 * Returns the number of the parameters.
239	 *
240	 * @return int
241	 */
242	public function getParamsNum() {
243		return array_key_exists('parameters', $this->params_raw) ? count($this->params_raw['parameters']) : 0;
244	}
245
246	/*
247	 * Unquotes special symbols in item the parameter
248	 *
249	 * @param string $param
250	 *
251	 * @return string
252	 */
253	public static function unquoteParam($param) {
254		$unquoted = '';
255
256		for ($p = 1; isset($param[$p]); $p++) {
257			if ($param[$p] == '\\' && $param[$p + 1] == '"') {
258				continue;
259			}
260
261			$unquoted .= $param[$p];
262		}
263
264		return substr($unquoted, 0, -1);
265	}
266
267	/**
268	 * Returns an unquoted parameter.
269	 *
270	 * @param int $n	the number of the requested parameter
271	 *
272	 * @return string|null
273	 */
274	public function getParam($n) {
275		$num = 0;
276
277		foreach ($this->params_raw['parameters'] as $param) {
278			if ($num++ == $n) {
279				switch ($param['type']) {
280					case self::PARAM_UNQUOTED:
281						// return parameter without any changes
282						return $param['raw'];
283					case self::PARAM_QUOTED:
284						return $this->unquoteParam($param['raw']);
285				}
286			}
287		}
288
289		return null;
290	}
291}
292