You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
8.8 KiB

1 year ago
<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
/**
* Class is used to validate and parse item keys.
*/
class CItemKey extends CParser {
const STATE_NEW = 0;
const STATE_END = 1;
const STATE_UNQUOTED = 2;
const STATE_QUOTED = 3;
const STATE_END_OF_PARAMS = 4;
const PARAM_ARRAY = 0;
const PARAM_UNQUOTED = 1;
const PARAM_QUOTED = 2;
private $key = ''; // main part of the key (for 'key[1, 2, 3]' key id would be 'key')
private $parameters = [];
/**
* An options array
*
* Supported options:
* '18_simple_checks' => true with support for old-style simple checks like "ftp,{$PORT}"
*
* @var array
*/
private $options = ['18_simple_checks' => false];
/**
* @param array $options
*/
public function __construct($options = []) {
$this->error_msgs['empty'] = _('key is empty');
$this->error_msgs['unexpected_end'] = _('unexpected end of key');
if (array_key_exists('18_simple_checks', $options)) {
$this->options['18_simple_checks'] = $options['18_simple_checks'];
}
}
/**
* Check if given character is a valid key id char
* this function is a copy of zbx_is_key_char() from src/libs/zbxexpr/expr.c
* don't forget to take look in there before changing anything.
*
* @param string $char
* @return bool
*/
function isKeyChar($char) {
return (
($char >= 'a' && $char <= 'z')
|| $char == '.' || $char == '_' || $char == '-'
|| ($char >= 'A' && $char <= 'Z')
|| ($char >= '0' && $char <= '9')
);
}
/**
* Parse key and parameters and put them into $this->parameters array.
*
* @param string $data
* @param int $offset
*/
public function parse($data, $offset = 0) {
$this->length = 0;
$this->match = '';
$this->key = '';
$this->parameters = [];
$this->errorClear();
for ($p = $offset; isset($data[$p]) && $this->isKeyChar($data[$p]); $p++) {
// Code is not missing here.
}
// is key empty?
if ($p == $offset) {
$this->errorPos(substr($data, $offset), 0);
return self::PARSE_FAIL;
}
$_18_simple_check = false;
// old-style simple checks
if ($this->options['18_simple_checks'] && isset($data[$p]) && $data[$p] === ',') {
$p++;
$user_macro_parser = new CUserMacroParser();
if ($user_macro_parser->parse($data, $p) != CParser::PARSE_FAIL) {
$p += $user_macro_parser->getLength();
}
// numeric parameter or empty parameter
else {
for (; isset($data[$p]) && $data[$p] > '0' && $data[$p] < '9'; $p++) {
// Code is not missing here.
}
}
$_18_simple_check = true;
}
$this->key = substr($data, $offset, $p - $offset);
$p2 = $p;
if (!$_18_simple_check && isset($data[$p2]) && $data[$p2] == '[') {
$_parameters = [
'type' => self::PARAM_ARRAY,
'raw' => '',
'pos' => $p2 - $offset,
'parameters' => []
];
if ($this->parseKeyParameters($data, $p2, $_parameters['parameters'])) {
$_parameters['raw'] = substr($data, $p, $p2 - $p);
$this->parameters[] = $_parameters;
$p = $p2;
}
}
$this->length = $p - $offset;
$this->match = substr($data, $offset, $this->length);
if (!isset($data[$p])) {
return self::PARSE_SUCCESS;
}
$this->errorPos(substr($data, $offset), $p2 - $offset);
return self::PARSE_SUCCESS_CONT;
}
private function parseKeyParameters($data, &$pos, array &$parameters) {
$state = self::STATE_NEW;
$num = 0;
for ($p = $pos + 1; isset($data[$p]); $p++) {
switch ($state) {
// a new parameter started
case self::STATE_NEW:
switch ($data[$p]) {
case ' ':
break;
case ',':
$parameters[$num++] = [
'type' => self::PARAM_UNQUOTED,
'raw' => '',
'pos' => $p - $pos
];
break;
case '[':
$_p = $p;
$_parameters = [
'type' => self::PARAM_ARRAY,
'raw' => '',
'pos' => $p - $pos,
'parameters' => []
];
if (!$this->parseKeyParameters($data, $_p, $_parameters['parameters'])) {
break 3;
}
foreach ($_parameters['parameters'] as $param) {
if ($param['type'] == self::PARAM_ARRAY) {
break 4;
}
}
$_parameters['raw'] = substr($data, $p, $_p - $p);
$parameters[$num] = $_parameters;
$p = $_p - 1;
$state = self::STATE_END;
break;
case ']':
$parameters[$num] = [
'type' => self::PARAM_UNQUOTED,
'raw' => '',
'pos' => $p - $pos
];
$state = self::STATE_END_OF_PARAMS;
break;
case '"':
$parameters[$num] = [
'type' => self::PARAM_QUOTED,
'raw' => $data[$p],
'pos' => $p - $pos
];
$state = self::STATE_QUOTED;
break;
default:
$parameters[$num] = [
'type' => self::PARAM_UNQUOTED,
'raw' => $data[$p],
'pos' => $p - $pos
];
$state = self::STATE_UNQUOTED;
}
break;
// end of parameter
case self::STATE_END:
switch ($data[$p]) {
case ' ':
break;
case ',':
$state = self::STATE_NEW;
$num++;
break;
case ']':
$state = self::STATE_END_OF_PARAMS;
break;
default:
break 3;
}
break;
// an unquoted parameter
case self::STATE_UNQUOTED:
switch ($data[$p]) {
case ']':
$state = self::STATE_END_OF_PARAMS;
break;
case ',':
$state = self::STATE_NEW;
$num++;
break;
default:
$parameters[$num]['raw'] .= $data[$p];
}
break;
// a quoted parameter
case self::STATE_QUOTED:
$parameters[$num]['raw'] .= $data[$p];
if ($data[$p] == '"' && $data[$p - 1] != '\\') {
$state = self::STATE_END;
}
break;
// end of parameters
case self::STATE_END_OF_PARAMS:
break 2;
}
}
$pos = $p;
return ($state == self::STATE_END_OF_PARAMS);
}
/**
* Returns the left part of key without parameters.
*
* @return string
*/
public function getKey() {
return $this->key;
}
/**
* Returns the list of key parameters.
*
* @return array
*/
public function getParamsRaw() {
return $this->parameters;
}
/**
* Returns the number of key parameters.
*
* @return int
*/
public function getParamsNum() {
$num = 0;
foreach ($this->parameters as $parameter) {
$num += count($parameter['parameters']);
}
return $num;
}
/*
* Unquotes special symbols in item key parameter
*
* @param string $param
*
* @return string
*/
public static function unquoteParam($param) {
$unquoted = '';
for ($p = 1; isset($param[$p]); $p++) {
if ($param[$p] == '\\' && $param[$p + 1] == '"') {
continue;
}
$unquoted .= $param[$p];
}
return substr($unquoted, 0, -1);
}
/*
* Quotes special symbols in item key parameter.
*
* @param string $param Item key parameter.
* @param bool $forced true - enclose parameter in " even if it does not contain any special characters.
* false - do nothing if the parameter does not contain any special characters.
*
* @return string|bool false - if parameter ends with backslash (cannot be quoted), string - otherwise.
*/
public static function quoteParam($param, $forced = false) {
if (!$forced)
{
if ($param === '') {
return $param;
}
if (strpos('" ', $param[0]) === false && strpos($param, ',') === false && strpos($param, ']') === false) {
return $param;
}
}
if ('\\' == substr($param, -1)) {
return false;
}
return '"'.str_replace ('"', '\\"', $param).'"';
}
/**
* Returns an unquoted parameter.
*
* @param int $n the number of the requested parameter
*
* @return string|null
*/
public function getParam($n) {
$num = 0;
foreach ($this->parameters as $parameter) {
foreach ($parameter['parameters'] as $param) {
if ($num++ == $n) {
switch ($param['type']) {
case self::PARAM_ARRAY:
// return parameter without square brackets
return substr($param['raw'], 1, strlen($param['raw']) - 2);
case self::PARAM_UNQUOTED:
// return parameter without any changes
return $param['raw'];
case self::PARAM_QUOTED:
return $this->unquoteParam($param['raw']);
}
}
}
}
return null;
}
}