mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-13 13:49:22 -04:00
The three profiles' settings were aliased by a switch with one arm per profile, each repeating the same 18 define() calls against a different ZM_WEB_<H|M|L>_ prefix. Sixty three lines in which only a single letter differed, and nothing held the arms in step: a setting added to one and not the others is undefined for two thirds of users, which is the same blank page the missing default case caused, just narrower. Name the settings once and build both sides from the prefix. The two settings that carried a defined() guard keep it, as a separate list with the fallback each one uses, so a genuinely absent setting is still distinguishable from a mistyped one - the rest go through constant() and fail loudly. Looking up an unknown profile now yields the low prefix instead of skipping every define, so the skin config no longer depends on skin.php having clamped the cookie first; that clamp remains the place a bad value is corrected. Verified by diffing every resulting ZM_WEB_ constant against the previous implementation for each of the three profiles: 49 constants, identical values. The test drops the checks that only made sense against the switch and gains ones for the new shape. It loads the skin config in a child process, once per profile probed, because constants cannot be redefined and loading it is itself what can fail. It now catches a mistyped setting name, a removed fallback, a profile the whitelist does not know, and a setting dropped from the alias list - the last by way of the per-profile config options, which are the authority on which settings exist and are independent of the lists under test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Y6FieTwEXuLhhR4e2yiax
133 lines
6.3 KiB
PHP
133 lines
6.3 KiB
PHP
<?php
|
|
// Tests the zmBandwidth whitelist and the ZM_WEB_* aliases it guards.
|
|
//
|
|
// web/skins/classic/includes/config.php aliases one bandwidth profile's
|
|
// ZM_WEB_<H|M|L>_* settings to the plain ZM_WEB_* names the rest of the skin
|
|
// reads. It used to do that with a switch that had no default case, so an
|
|
// unrecognised profile left all of them undefined and every page fatally
|
|
// stopped on the first one it used - on PHP 8 an undefined constant is an
|
|
// Error. Both the whitelist that keeps the profile recognisable and the alias
|
|
// loop that no longer depends on it are checked here.
|
|
//
|
|
// The skin config is loaded in a child process, once per profile under test:
|
|
// constants cannot be redefined or unset, and loading it is itself what can
|
|
// fail, so a fatal there should be a reported check rather than the end of the
|
|
// run.
|
|
//
|
|
// Run: sudo -u www-data php tests/php/test_bandwidth_clamp.php (from the repo root)
|
|
set_include_path(__DIR__.'/../../build/web/includes'.PATH_SEPARATOR.__DIR__.'/../../web/includes'.PATH_SEPARATOR.get_include_path());
|
|
require_once('config.php');
|
|
require_once('functions.php');
|
|
|
|
$skin_config = __DIR__.'/../../web/skins/classic/includes/config.php';
|
|
$skin_php = __DIR__.'/../../web/skins/classic/skin.php';
|
|
|
|
// ---------------------------------------------------------------- child mode
|
|
if (isset($argv[1]) && $argv[1] == '--probe') {
|
|
// lang.php is not loaded here, so translate() has to be stubbed.
|
|
if (!function_exists('translate')) { function translate($string) { return $string; } }
|
|
$_COOKIE['zmBandwidth'] = $argv[2];
|
|
require_once($skin_config);
|
|
|
|
$aliases = array_merge($bandwidth_settings, array_keys($bandwidth_optional_settings));
|
|
$undefined = array();
|
|
foreach ($aliases as $setting) {
|
|
if (!defined('ZM_WEB_'.$setting)) $undefined[] = 'ZM_WEB_'.$setting;
|
|
}
|
|
// A required setting with no value under some profile makes constant() throw
|
|
// as soon as that profile is selected - the same blank page by another route.
|
|
$missing = array();
|
|
foreach ($bandwidth_prefixes as $profile => $prefix) {
|
|
foreach ($bandwidth_settings as $setting) {
|
|
if (!defined('ZM_WEB_'.$prefix.'_'.$setting)) $missing[] = 'ZM_WEB_'.$prefix.'_'.$setting;
|
|
}
|
|
}
|
|
// Independent of the lists above: the per-profile config options are the
|
|
// authority on which settings exist, so every ZM_WEB_H_<X> has to end up
|
|
// aliased as ZM_WEB_<X>. Dropping a name from $bandwidth_settings would
|
|
// otherwise go unnoticed here while leaving that alias undefined in the skin.
|
|
$unaliased = array();
|
|
foreach (get_defined_constants() as $name => $value) {
|
|
if (preg_match('/^ZM_WEB_H_(.+)$/', $name, $m) && !defined('ZM_WEB_'.$m[1])) $unaliased[] = $name;
|
|
}
|
|
|
|
print(json_encode(array(
|
|
'profiles' => array_keys($bandwidth_prefixes),
|
|
'alias_count' => count($aliases),
|
|
'undefined_aliases' => $undefined,
|
|
'missing_sources' => $missing,
|
|
'unaliased_settings' => $unaliased,
|
|
)));
|
|
exit(0);
|
|
}
|
|
|
|
// --------------------------------------------------------------- parent mode
|
|
$failures = 0;
|
|
function check($label, $got, $want) {
|
|
global $failures;
|
|
$ok = ($got === $want);
|
|
if (!$ok) $failures++;
|
|
printf("[%s] %s\n", $ok ? 'PASS' : 'FAIL', $label);
|
|
if (!$ok) printf(" got: %s\n want: %s\n", var_export($got, true), var_export($want, true));
|
|
}
|
|
|
|
// Load the skin config under $profile and report what it defined. Anything the
|
|
// child printed that is not the expected JSON - a fatal, a warning - is handed
|
|
// back as an error string so the caller can fail a check with it.
|
|
function probe($profile) {
|
|
$out = trim(shell_exec(
|
|
escapeshellarg(PHP_BINARY).' '.escapeshellarg(__FILE__).' --probe '.escapeshellarg($profile).' 2>&1'));
|
|
$decoded = json_decode($out, true);
|
|
return is_array($decoded) ? $decoded : array('error' => $out);
|
|
}
|
|
|
|
$high = probe('high');
|
|
check('the skin config loads for a valid profile', $high['error'] ?? 'ok', 'ok');
|
|
if (isset($high['error'])) {
|
|
print("\ncannot continue without a loadable skin config\n");
|
|
exit(1);
|
|
}
|
|
|
|
// The three profiles are accepted, everything else is not. The rejected values
|
|
// are the ones that actually reach the cookie: unset, a cleared cookie, a
|
|
// casing the lookup would not match, and free-form junk.
|
|
foreach (array('high', 'medium', 'low') as $valid) {
|
|
check("'$valid' is a valid bandwidth", isValidBandwidth($valid), true);
|
|
}
|
|
foreach (array('', 'High', 'LOW', 'auto', 'garbage', '0', 'high ') as $invalid) {
|
|
check("'$invalid' is rejected", isValidBandwidth($invalid), false);
|
|
}
|
|
check('null is rejected', isValidBandwidth(null), false);
|
|
|
|
// Set equality in both directions between the whitelist and the profiles the
|
|
// skin actually has settings for. A profile in one and not the other is how
|
|
// the cookie ends up selecting something the skin cannot resolve.
|
|
$profiles = $high['profiles'];
|
|
sort($profiles);
|
|
check('the skin profiles are exactly the accepted profiles', $profiles, array('high', 'low', 'medium'));
|
|
foreach ($profiles as $profile) {
|
|
check("skin profile '$profile' is accepted by isValidBandwidth", isValidBandwidth($profile), true);
|
|
}
|
|
|
|
// skin.php's last-resort fallback must itself survive the whitelist, or the
|
|
// clamp would hand the skin a profile it does not have settings for.
|
|
$skin_src = file_get_contents($skin_php);
|
|
preg_match("/isValidBandwidth\(ZM_BANDWIDTH_DEFAULT\)\s*\?\s*ZM_BANDWIDTH_DEFAULT\s*:\s*'([a-z]+)'/", $skin_src, $fb);
|
|
check('found the clamp fallback in skin.php', !empty($fb), true);
|
|
check("skin.php fallback '".($fb[1] ?? '')."' is valid", isValidBandwidth($fb[1] ?? ''), true);
|
|
|
|
// Every alias resolves. This is what the page footer reads on every request.
|
|
check('loading the skin config defines every ZM_WEB_ alias', $high['undefined_aliases'], array());
|
|
check('there are aliases to define', $high['alias_count'] > 0, true);
|
|
check('every profile has a value for every required setting', $high['missing_sources'], array());
|
|
check('every per-profile setting is aliased', $high['unaliased_settings'], array());
|
|
|
|
// The regression itself: even with a profile the whitelist would have rejected,
|
|
// the aliases must all still be defined, so a bad cookie can never blank a page.
|
|
$junk = probe('not-a-profile');
|
|
check('an unrecognised profile loads the skin config', $junk['error'] ?? 'ok', 'ok');
|
|
check('an unrecognised profile still defines every alias', $junk['undefined_aliases'] ?? null, array());
|
|
|
|
print("\n".($failures ? "$failures FAILED" : 'all passed')."\n");
|
|
exit($failures ? 1 : 0);
|