self::MAX_VALUE_LENGTH || count($pairs) >= self::MAX_PAIRS) { throw new UserError('imapsync_custom_params_format'); } $pairs[] = ['o' => $option, 'v' => $value]; } return json_encode($pairs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } /** * The effective allowlist: $IMAPSYNC_OPTIONS (after vars.local.inc.php) without the old * 'whitelist' key and malformed entries; the defaults when nothing valid is left. */ public static function allowlist(array $configured, array $defaults): array { $allowlist = []; foreach ($configured as $option => $rule) { $option = strtolower(ltrim(trim((string)$option), '-')); if ($option !== 'whitelist' && preg_match('/^[a-z][a-z0-9]*$/', $option) && (is_bool($rule) || (is_string($rule) && $rule !== ''))) { $allowlist[$option] = $rule; } } if (!self::$warnedOldFormat && isset($configured['whitelist'])) { self::$warnedOldFormat = true; error_log("mailcow: \$IMAPSYNC_OPTIONS['whitelist'] (old format) is ignored, use 'option' => true|false|'regex' (see data/web/inc/vars.inc.php)"); } return empty($allowlist) ? $defaults : $allowlist; } /** The value to store for one option; throws when the allowlist rule is violated */ private static function checkValue(string $option, string $value, array $allowlist): string { $rule = $allowlist[$option] ?? null; if ($option === '' || !(is_bool($rule) || is_string($rule))) { throw new UserError('imapsync_custom_param_invalid', [htmlspecialchars($option)]); } $empty = (trim($value) === ''); $valid = match (true) { $rule === false => $empty, $rule === true => !$empty, default => !$empty && preg_match('~^(?:' . str_replace('~', '\~', $rule) . ')$~D', $value) === 1, }; if (!$valid) { throw new UserError('imapsync_custom_param_value', [$option]); } return ($rule === false) ? '' : $value; } }