diff --git a/CHANGELOG.md b/CHANGELOG.md index 614b7fb..efd102a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * security: Require POST for the save, remove, mute and card-order actions so csrf-magic covers them, and only let a dashboard's owner save over it * security: Bind the plugin version lookup and the device_remove cleanup deletes as prepared statements instead of building them from the device list +* bug: Escape the device description, location, site name and Monitor Text in the List view * bug: Restore alert audio for down and triggered devices; getMonitorSound() looked for sounds in includes/sounds/ after the controller moved, so the audio element was never rendered * feature: Replace the single Status column/filter with a multi-select Events filter (additive) and an Events column that shows a short pill per active event (Down, Recovering, Triggered, Down/Triggered M/A, Warn/Alert Ping, Failed Svc), 'None' when clean; the Status scope dropdown now only selects All Monitored / All Devices / Not Monitored / Without Thresholds * bug: Centre the tile hostname under the glyph and keep it clickable in the Tiles & Time view diff --git a/includes/render.php b/includes/render.php index 6870ec4..1e13c57 100644 --- a/includes/render.php +++ b/includes/render.php @@ -1463,9 +1463,9 @@ function renderHostList(array $host): string { } form_selectable_cell($actions, $host['id'], '1%', 'left'); - form_selectable_cell($host['description'], $host['id'], '', 'left'); - form_selectable_cell($host['location'], $host['id'], '', 'left'); - form_selectable_cell($host['site_name'], $host['id'], '', 'left'); + form_selectable_ecell($host['description'], $host['id'], '', 'left'); + form_selectable_ecell($host['location'], $host['id'], '', 'left'); + form_selectable_ecell($host['site_name'], $host['id'], '', 'left'); form_selectable_cell($host['id'], $host['id'], '', 'left'); form_selectable_cell($host_crit, $host['id'], '', 'left'); form_selectable_cell($events_cell, $host['id'], '', 'center'); @@ -1479,7 +1479,7 @@ function renderHostList(array $host): string { } form_selectable_cell($host_warn, $host['id'], '', 'left'); - form_selectable_cell($host_admin, $host['id'], '', 'white-space:pre-wrap;text-align:left'); + form_selectable_ecell($host_admin, $host['id'], '', 'white-space:pre-wrap;text-align:left'); form_selectable_cell(round($host['availability'], 2) . ' %', $host['id'], '', 'right'); form_selectable_cell($host_avg, $host['id'], '', 'right'); form_selectable_cell($dt, $host['id'], '', 'right'); diff --git a/tests/Unit/MonitorListViewEscapingTest.php b/tests/Unit/MonitorListViewEscapingTest.php new file mode 100644 index 0000000..7e4267f --- /dev/null +++ b/tests/Unit/MonitorListViewEscapingTest.php @@ -0,0 +1,108 @@ +" . $contents . "\n"; + + return true; + } + } + + if (!function_exists('form_end_row')) { + function form_end_row() { + print "\n"; + } + } + + if (!function_exists('get_timeinstate')) { + function get_timeinstate($host) { + return '1m'; + } + } + + $source = file_get_contents($GLOBALS['config']['base_path'] . '/lib/html_utility.php'); + if (!preg_match('/^function form_selectable_ecell\([^\n]*\n.*?^}/ms', $source, $definition)) { + throw new RuntimeException('Cannot load the core escaping cell renderer'); + } + $path = tempnam(sys_get_temp_dir(), 'monitor-core-cell-'); + try { + file_put_contents($path, ' 7, + 'status' => 1, + 'status_rec_date' => '2026-01-01 00:00:00', + 'status_fail_date' => '2026-01-01 00:00:00', + 'description' => 'core & "edge"', + 'location' => 'rack & \'row\'', + 'site_name' => 'HQ & "annex"', + 'monitor_text' => 'note & "text"', + 'monitor_criticality' => 0, + 'availability_method' => 0, + 'availability' => 100, + ]; + + $GLOBALS['monitor_test_ecell_values'] = []; + $output = renderHostList($host); + + expect($GLOBALS['monitor_test_ecell_values'])->toBe([$host['description'], $host['location'], $host['site_name'], $host['monitor_text']]); + + expect($output)->toContain('<b>core</b> & "edge"'); + expect($output)->toContain('<i>rack</i> & 'row''); + expect($output)->toContain('<u>HQ</u> & "annex"'); + expect($output)->toContain('<s>note</s> & "text"'); + + foreach (['', '', '', ''] as $markup) { + expect($output)->not->toContain($markup); + } + + expect($output)->not->toContain('&amp;'); +});