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 * A parser for scheduling intervals.
24 */
25class CSchedulingIntervalParser extends CParser {
26
27	private $options = [
28		'usermacros' => false,
29		'lldmacros' => false
30	];
31
32	private $user_macro_parser;
33	private $lld_macro_parser;
34	private $lld_macro_function_parser;
35
36	public function __construct($options = []) {
37		if (array_key_exists('usermacros', $options)) {
38			$this->options['usermacros'] = $options['usermacros'];
39		}
40		if (array_key_exists('lldmacros', $options)) {
41			$this->options['lldmacros'] = $options['lldmacros'];
42		}
43
44		if ($this->options['usermacros']) {
45			$this->user_macro_parser = new CUserMacroParser();
46		}
47		if ($this->options['lldmacros']) {
48			$this->lld_macro_parser = new CLLDMacroParser();
49			$this->lld_macro_function_parser = new CLLDMacroFunctionParser();
50		}
51	}
52
53	/**
54	 * Parse the given scheduled interval.
55	 *
56	 * @param string $source	Source string that needs to be parsed.
57	 * @param int    $pos		Position offset.
58	 */
59	public function parse($source, $pos = 0) {
60		$this->length = 0;
61		$this->match = '';
62
63		$p = $pos;
64
65		if ($this->options['usermacros'] && $this->user_macro_parser->parse($source, $p) != self::PARSE_FAIL) {
66			$p += $this->user_macro_parser->getLength();
67		}
68		elseif ($this->options['lldmacros'] && $this->lld_macro_parser->parse($source, $p) != self::PARSE_FAIL) {
69			$p += $this->lld_macro_parser->getLength();
70		}
71		elseif ($this->options['lldmacros']
72				&& $this->lld_macro_function_parser->parse($source, $p) != self::PARSE_FAIL) {
73			$p += $this->lld_macro_function_parser->getLength();
74		}
75		elseif (!self::parseIntervals($source, $p)) {
76			return self::PARSE_FAIL;
77		}
78
79		$this->length = $p - $pos;
80		$this->match = substr($source, $pos, $this->length);
81
82		return isset($source[$p]) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS;
83	}
84
85	/**
86	 * Parse multiple intervals.
87	 *
88	 * @param string	$source
89	 * @param int		$pos
90	 *
91	 * @return bool
92	 */
93	private static function parseIntervals($source, &$pos) {
94		$p = $pos;
95
96		$precedence = 0;
97		$prefixes = [
98			0 => ['prefix' => 'md', 'min' => 1, 'max' => 31],
99			1 => ['prefix' => 'wd', 'min' => 1, 'max' => 7],
100			2 => ['prefix' => 'h', 'min' => 0, 'max' => 23],
101			3 => ['prefix' => 'm', 'min' => 0, 'max' => 59],
102			4 => ['prefix' => 's', 'min' => 0, 'max' => 59]
103		];
104
105		while (isset($source[$p])) {
106			for ($i = $precedence; $i < count($prefixes); $i++) {
107				$prefix = $prefixes[$i];
108
109				if (self::parseInterval($source, $p, $prefix['prefix'], $prefix['min'], $prefix['max'])) {
110					$precedence = $i + 1;
111					continue 2;
112				}
113			}
114			break;
115		}
116
117		$ret = ($p != $pos);
118		$pos = $p;
119
120		return $ret;
121	}
122
123	/**
124	 * Parse single interval.
125	 *
126	 * @param string	$source
127	 * @param int		$pos
128	 * @param string	$prefix
129	 * @param int		$min
130	 * @param int		$max
131	 *
132	 * @return bool
133	 */
134	private static function parseInterval($source, &$pos, $prefix, $min, $max) {
135		$p = $pos;
136		$len = strlen($prefix);
137
138		if (substr($source, $p, $len) !== $prefix) {
139			return false;
140		}
141		$p += $len;
142
143		if (!self::parseFilter($source, $p, $min, $max)) {
144			return false;
145		}
146
147		$pos = $p;
148
149		return true;
150	}
151
152	/**
153	 * Detect and move position at the end of filter.
154	 */
155	private static function parseFilter($source, &$pos, $min, $max) {
156		$p = $pos;
157
158		$max_digits = strlen((string) $max);
159		$pattern_range = '(?<from>[0-9]{1,'.$max_digits.'})(-(?P<to>[0-9]{1,'.$max_digits.'}))?';
160		$pattern_step = '\/(?P<step>[0-9]{1,'.$max_digits.'})';
161		$delimiter = '';
162
163		while (isset($source[$p])) {
164			$len = 0;
165
166			if (preg_match('/^'.$delimiter.$pattern_range.'/', substr($source, $p), $matches)) {
167				$from = $matches['from'];
168				$to = (array_key_exists('to', $matches) && $matches['to'] !== '') ? $matches['to'] : $from;
169
170				if ($from < $min || $to > $max || $from > $to) {
171					break;
172				}
173
174				$len += strlen($matches[0]);
175				$delimiter = '';
176			}
177			else {
178				$from = $min;
179				$to = $max;
180			}
181
182			if (preg_match('/^'.$delimiter.$pattern_step.'/', substr($source, $p + $len), $matches)) {
183				if ($matches['step'] >= 1 && $matches['step'] <= $to - $from) {
184					$len += strlen($matches[0]);
185				}
186			}
187
188			if ($len == 0) {
189				break;
190			}
191
192			$p += $len;
193			$delimiter = ',';
194		}
195
196		$ret = ($p != $pos);
197		$pos = $p;
198
199		return $ret;
200	}
201}
202