diff --git a/README.md b/README.md index a76bd45..44c9092 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,17 @@ bash scripts/package.sh ### Lokaler Test +Die PHP-Statushelper lassen sich unabhängig vom laufenden MUA-Dienst testen +(PHP 8 erforderlich): + +```bash +php scripts/test-notifications.php +php scripts/test-docker-update-status.php +``` + +Der Benachrichtigungsparser liest auch mehrzeilige URBM-Meldungen und erhält +Text wörtlich, ohne INI-Variablen wie `${HOME}` auszuwerten. + ```bash # Test-Setup (ohne Unraid) MUA_CONFIG_DIR=/tmp/mua-test MUA_PORT=3999 MUA_CONFIG_PORT=3998 ./dist/mua-mac diff --git a/scripts/test-notifications.php b/scripts/test-notifications.php new file mode 100644 index 0000000..362a9e4 --- /dev/null +++ b/scripts/test-notifications.php @@ -0,0 +1,33 @@ +'', 'subject'=>'OFF', 'timestamp'=>'001'], notification_fields("event=\"\"\nsubject=OFF\ntimestamp=001\n")); +expect_same('comments and whitespace', ['event'=>'test'], notification_fields("; comment\n# comment\n\nevent=\"test\" \n")); +expect_same('truncated record is rejected', [], notification_fields("event=\"URBM\"\ndescription=\"unfinished\n")); +expect_same('malformed record is rejected', [], notification_fields("not a record\n")); +expect_same('empty record', [], notification_fields('')); +echo "All notification parser tests passed.\n"; diff --git a/scripts/unraid-mcp-status-helper.php b/scripts/unraid-mcp-status-helper.php index 1c5dbca..a693c79 100644 --- a/scripts/unraid-mcp-status-helper.php +++ b/scripts/unraid-mcp-status-helper.php @@ -170,14 +170,48 @@ function disk_health(?string $requested): array { return ['schema_version' => '1.0', 'disk_count' => count($result), 'disks' => $result]; } +/** Read Unraid's flat notification records without INI value interpolation. + * INI_SCANNER_RAW rejects multiline quoted descriptions; INI_SCANNER_NORMAL + * expands ${...} and coerces bare values. Notification text must stay literal. + */ +function notification_fields(string $contents): array { + $fields = []; + $pending = null; + $value = ''; + foreach (preg_split('/\r\n|\n|\r/', $contents) as $line) { + if ($pending !== null) { + $value .= "\n" . $line; + } else { + if (trim($line) === '' || preg_match('/^\s*[;#]/', $line)) continue; + if (!preg_match('/^([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/', $line, $match)) return []; + $pending = $match[1]; + $value = ltrim($match[2]); + if (!str_starts_with($value, '"')) { + $fields[$pending] = rtrim($value); + $pending = null; + continue; + } + } + $end = rtrim($value, " \t"); + if (strlen($end) >= 2 && str_ends_with($end, '"')) { + $fields[$pending] = substr($end, 1, -1); + $pending = null; + } + } + // Never return a partial record when a quoted field is truncated. + return $pending === null ? $fields : []; +} + function notification_list(int $limit, ?string $importance): array { $limit = max(1, min(50, $limit)); $files = array_merge(glob('/tmp/notifications/*.notify') ?: [], glob('/tmp/notifications/archive/*.notify') ?: []); usort($files, fn($a, $b) => filemtime($b) <=> filemtime($a)); $items = []; foreach ($files as $file) { - $n = parse_ini_file($file, false, INI_SCANNER_RAW); - if (!is_array($n)) continue; + $contents = @file_get_contents($file); + if ($contents === false) continue; // Notifications may be archived/deleted concurrently. + $n = notification_fields($contents); + if (!$n) continue; $level = trim((string)($n['importance'] ?? 'normal'), '"'); if ($importance && $importance !== 'all' && $level !== $importance) continue; $items[] = [