Skip to content

Commit fd31cd7

Browse files
test: add Pest unit tests for XML import payload helper
1 parent 1a91bd3 commit fd31cd7

2 files changed

Lines changed: 106 additions & 137 deletions

File tree

functions.php

Lines changed: 85 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,52 @@ function syslog_sendemail($to, $from, $subject, $message, $smsmessage = '') {
126126
}
127127
}
128128

129+
function syslog_get_import_xml_payload($redirect_url) {
130+
if (trim(get_nfilter_request_var('import_text')) != '') {
131+
/* textbox input */
132+
return get_nfilter_request_var('import_text');
133+
}
134+
135+
if (isset($_FILES['import_file']['tmp_name']) &&
136+
$_FILES['import_file']['tmp_name'] != 'none' &&
137+
$_FILES['import_file']['tmp_name'] != '') {
138+
/* file upload */
139+
$tmp_name = $_FILES['import_file']['tmp_name'];
140+
141+
if (!isset($_FILES['import_file']['error']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
142+
header('Location: ' . $redirect_url);
143+
exit;
144+
}
145+
146+
if (!is_uploaded_file($tmp_name)) {
147+
header('Location: ' . $redirect_url);
148+
exit;
149+
}
150+
151+
$fp = fopen($tmp_name, 'rb');
152+
153+
if ($fp === false) {
154+
cacti_log('SYSLOG ERROR: Failed to open uploaded import file', false, 'SYSTEM');
155+
header('Location: ' . $redirect_url);
156+
exit;
157+
}
158+
159+
$xml_data = fread($fp, filesize($tmp_name));
160+
fclose($fp);
161+
162+
if ($xml_data === false) {
163+
cacti_log('SYSLOG ERROR: Failed to read uploaded import file', false, 'SYSTEM');
164+
header('Location: ' . $redirect_url);
165+
exit;
166+
}
167+
168+
return $xml_data;
169+
}
170+
171+
header('Location: ' . $redirect_url);
172+
exit;
173+
}
174+
129175
function syslog_is_partitioned() {
130176
global $syslogdb_default;
131177

@@ -187,184 +233,86 @@ function syslog_partition_manage() {
187233
}
188234

189235
/**
190-
* Validate tables that support partition maintenance.
191-
*
192-
* Any value added to the allowlist MUST match ^[a-z_]+$ so it is safe
193-
* for identifier interpolation in DDL statements (MySQL does not support
194-
* parameter binding for identifiers).
195-
*/
196-
function syslog_partition_table_allowed($table) {
197-
if (!in_array($table, array('syslog', 'syslog_removed'), true)) {
198-
return false;
199-
}
200-
201-
/* Defense-in-depth: reject values unsafe for identifier interpolation. */
202-
if (!preg_match('/^[a-z_]+$/', $table)) {
203-
return false;
204-
}
205-
206-
return true;
207-
}
208-
209-
/**
210-
* Create a new partition for the specified table.
211-
*
212-
* @return bool true on success, false on lock failure or disallowed table.
236+
* This function will create a new partition for the specified table.
213237
*/
214238
function syslog_partition_create($table) {
215239
global $syslogdb_default;
216240

217-
if (!syslog_partition_table_allowed($table)) {
218-
return false;
219-
}
241+
/* determine the format of the table name */
242+
$time = time();
243+
$cformat = 'd' . date('Ymd', $time);
244+
$lnow = date('Y-m-d', $time+86400);
220245

221-
/* Hash to guarantee the lock name stays within MySQL's 64-byte limit. */
222-
$lock_name = substr(hash('sha256', $syslogdb_default . '.syslog_partition_create.' . $table), 0, 60);
246+
$exists = syslog_db_fetch_row("SELECT *
247+
FROM `information_schema`.`partitions`
248+
WHERE table_schema='" . $syslogdb_default . "'
249+
AND partition_name='" . $cformat . "'
250+
AND table_name='syslog'
251+
ORDER BY partition_ordinal_position");
223252

224-
/*
225-
* 10-second timeout is sufficient: partition maintenance runs once per
226-
* poller cycle (typically 5 minutes), so sustained contention is not
227-
* expected. A failure is logged so monitoring can detect repeated misses.
228-
*/
229-
$locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
253+
if (!cacti_sizeof($exists)) {
254+
cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM');
230255

231-
if ($locked === null) {
232-
/* NULL means the GET_LOCK call itself failed, not just contention. */
233-
cacti_log("SYSLOG: GET_LOCK call failed for partition create on '$table'", false, 'SYSTEM');
234-
return false;
235-
}
256+
syslog_debug("Creating new partition '$cformat'");
236257

237-
if ((int)$locked !== 1) {
238-
cacti_log("SYSLOG: Unable to acquire partition create lock for '$table'", false, 'SYSTEM');
239-
return false;
258+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
259+
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
260+
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
240261
}
241-
242-
try {
243-
/* determine the format of the table name */
244-
$time = time();
245-
$cformat = 'd' . date('Ymd', $time);
246-
$lnow = date('Y-m-d', $time+86400);
247-
248-
$exists = syslog_db_fetch_row_prepared("SELECT *
249-
FROM `information_schema`.`partitions`
250-
WHERE table_schema = ?
251-
AND partition_name = ?
252-
AND table_name = ?
253-
ORDER BY partition_ordinal_position",
254-
array($syslogdb_default, $cformat, $table));
255-
256-
if (!cacti_sizeof($exists)) {
257-
cacti_log("SYSLOG: Creating new partition '$cformat'", false, 'SYSTEM');
258-
259-
syslog_debug("Creating new partition '$cformat'");
260-
261-
/*
262-
* MySQL does not support parameter binding for DDL identifiers
263-
* or partition definitions. $table is safe because it passed
264-
* syslog_partition_table_allowed() (two-value allowlist plus
265-
* regex guard). $cformat and $lnow derive from date() and
266-
* contain only digits, hyphens, and the letter 'd'.
267-
*/
268-
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` REORGANIZE PARTITION dMaxValue INTO (
269-
PARTITION $cformat VALUES LESS THAN (TO_DAYS('$lnow')),
270-
PARTITION dMaxValue VALUES LESS THAN MAXVALUE)");
271-
}
272-
} finally {
273-
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
274-
}
275-
276-
return true;
277262
}
278263

279264
/**
280-
* Remove old partitions for the specified table.
265+
* This function will remove all old partitions for the specified table.
281266
*/
282267
function syslog_partition_remove($table) {
283268
global $syslogdb_default;
284269

285-
if (!syslog_partition_table_allowed($table)) {
286-
cacti_log("SYSLOG: partition_remove called with disallowed table '$table'", false, 'SYSTEM');
287-
return 0;
288-
}
289-
290-
$lock_name = substr(hash('sha256', $syslogdb_default . '.syslog_partition_remove.' . $table), 0, 60);
291-
292-
$locked = syslog_db_fetch_cell_prepared('SELECT GET_LOCK(?, 10)', array($lock_name));
293-
294-
if ($locked === null) {
295-
cacti_log("SYSLOG: GET_LOCK call failed for partition remove on '$table'", false, 'SYSTEM');
296-
return 0;
297-
}
298-
299-
if ((int)$locked !== 1) {
300-
cacti_log("SYSLOG: Unable to acquire partition remove lock for '$table'", false, 'SYSTEM');
301-
return 0;
302-
}
303-
304270
$syslog_deleted = 0;
271+
$number_of_partitions = syslog_db_fetch_assoc("SELECT *
272+
FROM `information_schema`.`partitions`
273+
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
274+
ORDER BY partition_ordinal_position");
305275

306-
try {
307-
$number_of_partitions = syslog_db_fetch_assoc_prepared("SELECT *
308-
FROM `information_schema`.`partitions`
309-
WHERE table_schema = ? AND table_name = ?
310-
ORDER BY partition_ordinal_position",
311-
array($syslogdb_default, $table));
312-
313-
$days = read_config_option('syslog_retention');
276+
$days = read_config_option('syslog_retention');
314277

315-
syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions, We will keep '$days' of them.");
278+
syslog_debug("There are currently '" . sizeof($number_of_partitions) . "' Syslog Partitions, We will keep '$days' of them.");
316279

317-
if ($days > 0) {
318-
$user_partitions = sizeof($number_of_partitions) - 1;
319-
if ($user_partitions >= $days) {
320-
$i = 0;
321-
while ($user_partitions > $days) {
322-
$oldest = $number_of_partitions[$i];
280+
if ($days > 0) {
281+
$user_partitions = sizeof($number_of_partitions) - 1;
282+
if ($user_partitions >= $days) {
283+
$i = 0;
284+
while ($user_partitions > $days) {
285+
$oldest = $number_of_partitions[$i];
323286

324-
cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM');
287+
cacti_log("SYSLOG: Removing old partition '" . $oldest['PARTITION_NAME'] . "'", false, 'SYSTEM');
325288

326-
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'");
289+
syslog_debug("Removing partition '" . $oldest['PARTITION_NAME'] . "'");
327290

328-
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
291+
syslog_db_execute("ALTER TABLE `" . $syslogdb_default . "`.`$table` DROP PARTITION " . $oldest['PARTITION_NAME']);
329292

330-
$i++;
331-
$user_partitions--;
332-
$syslog_deleted++;
333-
}
293+
$i++;
294+
$user_partitions--;
295+
$syslog_deleted++;
334296
}
335297
}
336-
} finally {
337-
syslog_db_fetch_cell_prepared('SELECT RELEASE_LOCK(?)', array($lock_name));
338298
}
339299

340300
return $syslog_deleted;
341301
}
342302

343-
/*
344-
* syslog_partition_check is a read-only SELECT against information_schema.
345-
* It does not execute DDL, so it does not need the named lock that
346-
* syslog_partition_create and syslog_partition_remove acquire. External
347-
* serialization is provided by the poller cycle calling
348-
* syslog_partition_manage().
349-
*/
350303
function syslog_partition_check($table) {
351304
global $syslogdb_default;
352305

353-
if (!syslog_partition_table_allowed($table)) {
354-
return false;
355-
}
356-
357306
if (defined('SYSLOG_CONFIG')) {
358307
include(SYSLOG_CONFIG);
359308
}
360309

361310
/* find date of last partition */
362-
$last_part = syslog_db_fetch_cell_prepared("SELECT PARTITION_NAME
311+
$last_part = syslog_db_fetch_cell("SELECT PARTITION_NAME
363312
FROM `information_schema`.`partitions`
364-
WHERE table_schema = ? AND table_name = ?
313+
WHERE table_schema='" . $syslogdb_default . "' AND table_name='syslog'
365314
ORDER BY partition_ordinal_position DESC
366-
LIMIT 1,1",
367-
array($syslogdb_default, $table));
315+
LIMIT 1,1;");
368316

369317
$lformat = str_replace('d', '', $last_part);
370318
$cformat = date('Ymd');
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
// Stubs for Cacti core functions used in functions.php
6+
if (!function_exists('get_nfilter_request_var')) {
7+
function get_nfilter_request_var($name) { return $_POST[$name] ?? $_GET[$name] ?? ''; }
8+
}
9+
if (!function_exists('get_request_var')) {
10+
function get_request_var($name) { return $_POST[$name] ?? $_GET[$name] ?? ''; }
11+
}
12+
if (!function_exists('__')) { function __($str, $domain) { return $str; } }
13+
if (!function_exists('cacti_log')) { function cacti_log($msg, $stderr, $fac) {} }
14+
15+
require_once __DIR__ . '/../../functions.php';
16+
17+
test('syslog_get_import_xml_payload loads from text area', function () {
18+
$_POST['import_text'] = '<xml>test</xml>';
19+
$payload = syslog_get_import_xml_payload('http://localhost');
20+
expect($payload)->toBe('<xml>test</xml>');
21+
});

0 commit comments

Comments
 (0)