diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:39:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:39:39 +0000 |
commit | 8ca6cc32b2c789a3149861159ad258f2cb9491e3 (patch) | |
tree | 2492de6f1528dd44eaa169a5c1555026d9cb75ec /library/Icinga/Data/Filter/FilterQueryString.php | |
parent | Initial commit. (diff) | |
download | icingaweb2-a598b28402ead15d3701df32e198513e7b1f299c.tar.xz icingaweb2-a598b28402ead15d3701df32e198513e7b1f299c.zip |
Adding upstream version 2.11.4.upstream/2.11.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | library/Icinga/Data/Filter/FilterQueryString.php | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/library/Icinga/Data/Filter/FilterQueryString.php b/library/Icinga/Data/Filter/FilterQueryString.php new file mode 100644 index 0000000..8535df5 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterQueryString.php @@ -0,0 +1,320 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Data\Filter; + +class FilterQueryString +{ + protected $string; + + protected $pos; + + protected $debug = array(); + + protected $reportDebug = false; + + protected $length; + + protected function __construct() + { + } + + protected function debug($msg, $level = 0, $op = null) + { + if ($op === null) { + $op = 'NULL'; + } + $this->debug[] = sprintf( + '%s[%d=%s] (%s): %s', + str_repeat('* ', $level), + $this->pos, + $this->string[$this->pos - 1], + $op, + $msg + ); + } + + public static function parse($string) + { + $parser = new static(); + return $parser->parseQueryString($string); + } + + protected function readNextKey() + { + $str = $this->readUnlessSpecialChar(); + + if ($str === false) { + return $str; + } + return rawurldecode($str); + } + + protected function readNextValue() + { + if ($this->nextChar() === '(') { + $this->readChar(); + $var = preg_split('~\|~', $this->readUnless(')')); + if ($this->readChar() !== ')') { + $this->parseError(null, 'Expected ")"'); + } + } else { + $var = rawurldecode($this->readUnless(array(')', '&', '|', '>', '<'))); + } + return $var; + } + + protected function readNextExpression() + { + if ('' === ($key = $this->readNextKey())) { + return false; + } + + foreach (array('<', '>') as $sign) { + if (false !== ($pos = strpos($key, $sign))) { + if ($this->nextChar() === '=') { + break; + } + $var = substr($key, $pos + 1); + $key = substr($key, 0, $pos); + + if (ctype_digit($var)) { + $var = (float) $var; + } + + return Filter::expression($key, $sign, $var); + } + } + if (in_array($this->nextChar(), array('=', '>', '<', '!'))) { + $sign = $this->readChar(); + } else { + $sign = false; + } + if ($sign === false) { + return Filter::expression($key, '=', true); + } + + $toFloat = false; + if ($sign === '=') { + $last = substr($key, -1); + if ($last === '>' || $last === '<') { + $sign = $last . $sign; + $key = substr($key, 0, -1); + $toFloat = true; + } + // TODO: Same as above for unescaped <> - do we really need this? + } elseif ($sign === '>' || $sign === '<' || $sign === '!') { + $toFloat = $sign === '>' || $sign === '<'; + if ($this->nextChar() === '=') { + $sign .= $this->readChar(); + } + } + + $var = $this->readNextValue(); + if ($toFloat && ctype_digit($var)) { + $var = (float) $var; + } + + return Filter::expression($key, $sign, $var); + } + + protected function parseError($char = null, $extraMsg = null) + { + if ($extraMsg === null) { + $extra = ''; + } else { + $extra = ': ' . $extraMsg; + } + if ($char === null) { + $char = $this->string[$this->pos]; + } + if ($this->reportDebug) { + $extra .= "\n" . implode("\n", $this->debug); + } + + throw new FilterParseException( + 'Invalid filter "%s", unexpected %s at pos %d%s', + $this->string, + $char, + $this->pos, + $extra + ); + } + + protected function readFilters($nestingLevel = 0, $op = null) + { + $filters = array(); + while ($this->pos < $this->length) { + if ($op === '!' && count($filters) === 1) { + break; + } + $filter = $this->readNextExpression(); + $next = $this->readChar(); + + + if ($filter === false) { + $this->debug('Got no next expression, next is ' . $next, $nestingLevel, $op); + if ($next === '!') { + $not = $this->readFilters($nestingLevel + 1, '!'); + $filters[] = $not; + if (in_array($this->nextChar(), array('|', '&', ')'))) { + $next = $this->readChar(); + $this->debug('Got NOT, next is now: ' . $next, $nestingLevel, $op); + } else { + $this->debug('Breaking after NOT: ' . $not, $nestingLevel, $op); + break; + } + } + + if ($op === null && count($filters) > 0 && ($next === '&' || $next === '|')) { + $op = $next; + continue; + } + + if ($next === false) { + // Nothing more to read + break; + } + + if ($next === ')') { + if ($nestingLevel > 0) { + $this->debug('Closing without filter: ' . $next, $nestingLevel, $op); + break; + } + $this->parseError($next); + } + if ($next === '(') { + $filters[] = $this->readFilters($nestingLevel + 1, null); + continue; + } + if ($next === $op) { + continue; + } + $this->parseError($next, "$op level $nestingLevel"); + } else { + $this->debug('Got new expression: ' . $filter, $nestingLevel, $op); + + $filters[] = $filter; + + if ($next === false) { + $this->debug('Next is false, nothing to read but got filter', $nestingLevel, $op); + // Got filter, nothing more to read + break; + } + + if ($op === '!') { + $this->pos--; + break; + } + if ($next === $op) { + $this->debug('Next matches operator', $nestingLevel, $op); + continue; // Break?? + } + + if ($next === ')') { + if ($nestingLevel > 0) { + $this->debug('Closing with filter: ' . $next, $nestingLevel, $op); + break; + } + $this->parseError($next); + } + if ($op === null && in_array($next, array('&', '|'))) { + $this->debug('Setting op to ' . $next, $nestingLevel, $op); + $op = $next; + continue; + } + $this->parseError($next); + } + } + + if ($nestingLevel === 0 && $this->pos < $this->length) { + $this->parseError($op, 'Did not read full filter'); + } + + if ($nestingLevel === 0 && count($filters) === 1 && $op !== '!') { + // There is only one filter expression, no chain + $this->debug('Returning first filter only: ' . $filters[0], $nestingLevel, $op); + return $filters[0]; + } + + if ($op === null && count($filters) === 1) { + $this->debug('No op, single filter, setting AND', $nestingLevel, $op); + $op = '&'; + } + $this->debug(sprintf('Got %d filters, returning', count($filters)), $nestingLevel, $op); + + switch ($op) { + case '&': + return Filter::matchAll($filters); + case '|': + return Filter::matchAny($filters); + case '!': + return Filter::not($filters); + case null: + return Filter::matchAll(); + default: + $this->parseError($op); + } + } + + protected function parseQueryString($string) + { + $this->pos = 0; + + $this->string = $string; + + $this->length = $string ? strlen($string) : 0; + + if ($this->length === 0) { + return Filter::matchAll(); + } + return $this->readFilters(); + } + + protected function readUnless($char) + { + $buffer = ''; + while (false !== ($c = $this->readChar())) { + if (is_array($char)) { + if (in_array($c, $char)) { + $this->pos--; + break; + } + } else { + if ($c === $char) { + $this->pos--; + break; + } + } + $buffer .= $c; + } + + return $buffer; + } + + protected function readUnlessSpecialChar() + { + return $this->readUnless(array('=', '(', ')', '&', '|', '>', '<', '!')); + } + + protected function readExpressionOperator() + { + return $this->readUnless(array('=', '>', '<', '!')); + } + + protected function readChar() + { + if ($this->length > $this->pos) { + return $this->string[$this->pos++]; + } + return false; + } + + protected function nextChar() + { + if ($this->length > $this->pos) { + return $this->string[$this->pos]; + } + return false; + } +} |