summaryrefslogtreecommitdiffstats
path: root/library/Director/IcingaConfig/IcingaConfigHelper.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/IcingaConfig/IcingaConfigHelper.php')
-rw-r--r--library/Director/IcingaConfig/IcingaConfigHelper.php430
1 files changed, 430 insertions, 0 deletions
diff --git a/library/Director/IcingaConfig/IcingaConfigHelper.php b/library/Director/IcingaConfig/IcingaConfigHelper.php
new file mode 100644
index 0000000..03c017e
--- /dev/null
+++ b/library/Director/IcingaConfig/IcingaConfigHelper.php
@@ -0,0 +1,430 @@
+<?php
+
+namespace Icinga\Module\Director\IcingaConfig;
+
+use InvalidArgumentException;
+use function ctype_digit;
+use function explode;
+use function floor;
+use function implode;
+use function preg_match;
+use function preg_split;
+use function sprintf;
+use function strlen;
+use function strpos;
+use function substr;
+
+class IcingaConfigHelper
+{
+ /**
+ * Reserved words according to
+ * https://icinga.com/docs/icinga2/latest/doc/17-language-reference/#reserved-keywords
+ */
+ protected static $reservedWords = [
+ 'object',
+ 'template',
+ 'include',
+ 'include_recursive',
+ 'include_zones',
+ 'library',
+ 'null',
+ 'true',
+ 'false',
+ 'const',
+ 'var',
+ 'this',
+ 'globals',
+ 'locals',
+ 'use',
+ 'default',
+ 'ignore_on_error',
+ 'current_filename',
+ 'current_line',
+ 'apply',
+ 'to',
+ 'where',
+ 'import',
+ 'assign',
+ 'ignore',
+ 'function',
+ 'return',
+ 'break',
+ 'continue',
+ 'for',
+ 'if',
+ 'else',
+ 'while',
+ 'throw',
+ 'try',
+ 'except',
+ 'in',
+ 'using',
+ 'namespace',
+ ];
+
+ public static function renderKeyValue($key, $value, $prefix = ' ')
+ {
+ return self::renderKeyOperatorValue($key, '=', $value, $prefix);
+ }
+
+ public static function renderKeyOperatorValue($key, $operator, $value, $prefix = ' ')
+ {
+ $string = sprintf(
+ "%s %s %s",
+ $key,
+ $operator,
+ $value
+ );
+
+ if ($prefix && strpos($string, "\n") !== false) {
+ return $prefix . implode("\n" . $prefix, explode("\n", $string)) . "\n";
+ }
+
+ return $prefix . $string . "\n";
+ }
+
+ public static function renderBoolean($value)
+ {
+ if ($value === 'y' || $value === true) {
+ return 'true';
+ }
+ if ($value === 'n' || $value === false) {
+ return 'false';
+ }
+
+ throw new InvalidArgumentException(sprintf(
+ '%s is not a valid boolean',
+ $value
+ ));
+ }
+
+ protected static function renderInteger($value)
+ {
+ return (string) $value;
+ }
+
+ public static function renderFloat($value)
+ {
+ // Render .0000 floats as integers, mainly because of some JSON
+ // implementations:
+ if ((string) (int) $value === (string) $value) {
+ return static::renderInteger((int) $value);
+ }
+
+ return sprintf('%F', $value);
+ }
+
+ protected static function renderNull()
+ {
+ return 'null';
+ }
+
+ // TODO: Find out how to allow multiline {{{...}}} strings.
+ // Parameter? Dedicated method? Always if \n is found?
+ public static function renderString($string)
+ {
+ $special = [
+ '/\\\/',
+ '/"/',
+ '/\$/',
+ '/\t/',
+ '/\r/',
+ '/\n/',
+ // '/\b/', -> doesn't work
+ '/\f/',
+ ];
+
+ $replace = [
+ '\\\\\\',
+ '\\"',
+ '\\$',
+ '\\t',
+ '\\r',
+ '\\n',
+ // '\\b',
+ '\\f',
+ ];
+
+ $string = preg_replace($special, $replace, $string);
+
+ return '"' . $string . '"';
+ }
+
+ public static function renderPhpValue($value)
+ {
+ if (is_null($value)) {
+ return static::renderNull();
+ }
+ if (is_bool($value)) {
+ return static::renderBoolean($value);
+ }
+ if (is_int($value)) {
+ return static::renderInteger($value);
+ }
+ if (is_float($value)) {
+ return static::renderFloat($value);
+ }
+ // TODO:
+ // if (is_object($value) || static::isAssocArray($value)) {
+ // return static::renderHash($value, $prefix)
+ // TODO: also check array
+ if (is_array($value)) {
+ return static::renderArray($value);
+ }
+ if (is_string($value)) {
+ return static::renderString($value);
+ }
+
+ throw new InvalidArgumentException(sprintf(
+ 'Unexpected type %s',
+ var_export($value, 1)
+ ));
+ }
+
+ public static function renderDictionaryKey($key)
+ {
+ if (preg_match('/^[a-z_]+[a-z0-9_]*$/i', $key)) {
+ return static::escapeIfReserved($key);
+ }
+
+ return static::renderString($key);
+ }
+
+ // Requires an array
+ public static function renderArray($array)
+ {
+ $data = [];
+ foreach ($array as $entry) {
+ if ($entry instanceof IcingaConfigRenderer) {
+ $data[] = $entry;
+ } else {
+ $data[] = self::renderString($entry);
+ }
+ }
+
+ return static::renderEscapedArray($data);
+ }
+
+ public static function renderEscapedArray($array)
+ {
+ $str = '[ ' . implode(', ', $array) . ' ]';
+
+ if (strlen($str) < 60) {
+ return $str;
+ }
+
+ // Prefix for toConfigString?
+ return "[\n " . implode(",\n ", $array) . "\n]";
+ }
+
+ public static function renderDictionary($dictionary)
+ {
+ $values = [];
+ foreach ($dictionary as $key => $value) {
+ $values[$key] = rtrim(
+ self::renderKeyValue(
+ self::renderDictionaryKey($key),
+ $value
+ )
+ );
+ }
+
+ if (empty($values)) {
+ return '{}';
+ }
+ ksort($values, SORT_STRING);
+
+ // Prefix for toConfigString?
+ return "{\n" . implode("\n", $values) . "\n}";
+ }
+
+ public static function renderExpression($string)
+ {
+ return "{{\n " . $string . "\n}}";
+ }
+
+ public static function alreadyRendered($string)
+ {
+ return new IcingaConfigRendered($string);
+ }
+
+ public static function isReserved($string)
+ {
+ return in_array($string, self::$reservedWords, true);
+ }
+
+ public static function escapeIfReserved($string)
+ {
+ if (self::isReserved($string)) {
+ return '@' . $string;
+ }
+
+ return $string;
+ }
+
+ public static function isValidInterval($interval)
+ {
+ if (ctype_digit($interval)) {
+ return true;
+ }
+
+ $parts = preg_split('/\s+/', $interval, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($parts as $part) {
+ if (! preg_match('/^(\d+)([dhms]?)$/', $part)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static function parseInterval($interval)
+ {
+ if ($interval === null || $interval === '') {
+ return null;
+ }
+
+ if (is_int($interval) || ctype_digit($interval)) {
+ return (int) $interval;
+ }
+
+ $parts = preg_split('/\s+/', $interval, -1, PREG_SPLIT_NO_EMPTY);
+ $value = 0;
+ foreach ($parts as $part) {
+ if (! preg_match('/^(\d+)([dhms]?)$/', $part, $m)) {
+ throw new InvalidArgumentException(sprintf(
+ '"%s" is not a valid time (duration) definition',
+ $interval
+ ));
+ }
+
+ switch ($m[2]) {
+ case 'd':
+ $value += $m[1] * 86400;
+ break;
+ case 'h':
+ $value += $m[1] * 3600;
+ break;
+ case 'm':
+ $value += $m[1] * 60;
+ break;
+ default:
+ $value += (int) $m[1];
+ }
+ }
+
+ return $value;
+ }
+
+ public static function renderInterval($interval)
+ {
+ // TODO: compat only, do this at munge time. All db fields should be int
+ $seconds = self::parseInterval($interval);
+ if ($seconds === 0) {
+ return '0s';
+ }
+
+ $steps = [
+ 'd' => 86400,
+ 'h' => 3600,
+ 'm' => 60,
+ ];
+
+ foreach ($steps as $unit => $duration) {
+ if ($seconds % $duration === 0) {
+ return (int) floor($seconds / $duration) . $unit;
+ }
+ }
+
+ return $seconds . 's';
+ }
+
+ public static function stringHasMacro($string, $macroName = null)
+ {
+ $len = strlen($string);
+ $start = false;
+ // TODO: robust UTF8 support. It works, but it is not 100% correct
+ for ($i = 0; $i < $len; $i++) {
+ if ($string[$i] === '$') {
+ if ($start === false) {
+ $start = $i;
+ } else {
+ // Escaping, $$
+ if ($start + 1 === $i) {
+ $start = false;
+ } else {
+ if ($macroName === null) {
+ return true;
+ }
+ if ($macroName === substr($string, $start + 1, $i - $start - 1)) {
+ return true;
+ }
+
+ $start = false;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Hint: this isn't complete, but let's restrict ourselves right now
+ *
+ * @param $name
+ * @return bool
+ */
+ public static function isValidMacroName($name)
+ {
+ return preg_match('/^[A-z_][A-z_.\d]+$/', $name)
+ && ! preg_match('/\.$/', $name);
+ }
+
+ public static function renderStringWithVariables($string, array $whiteList = null)
+ {
+ $len = strlen($string);
+ $start = false;
+ $parts = [];
+ // TODO: UTF8...
+ $offset = 0;
+ for ($i = 0; $i < $len; $i++) {
+ if ($string[$i] === '$') {
+ if ($start === false) {
+ $start = $i;
+ } else {
+ // Ignore $$
+ if ($start + 1 === $i) {
+ $start = false;
+ } else {
+ // We got a macro
+ $macroName = substr($string, $start + 1, $i - $start - 1);
+ if (static::isValidMacroName($macroName)) {
+ if ($whiteList === null || in_array($macroName, $whiteList)) {
+ if ($start > $offset) {
+ $parts[] = static::renderString(
+ substr($string, $offset, $start - $offset)
+ );
+ }
+ $parts[] = $macroName;
+ $offset = $i + 1;
+ }
+ }
+
+ $start = false;
+ }
+ }
+ }
+ }
+
+ if ($offset < $i) {
+ $parts[] = static::renderString(substr($string, $offset, $i - $offset));
+ }
+
+ if (! empty($parts)) {
+ return implode(' + ', $parts);
+ }
+
+ return '""';
+ }
+}