feat: initial commit
This commit is contained in:
Executable
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/** 回归:fp 模式(独立配置,勿与其他 fixture 同进程 include) */
|
||||
define('SHOW_SITE_MODE_SWITCH', 'fp');
|
||||
define('SHOW_SITE_IP', '');
|
||||
define('BLACKLIST_GROUP_ID', 0);
|
||||
define('WHITELIST_GROUP_ID', 0);
|
||||
define('SHOW_SITE_COUNTRY', 'US');
|
||||
define('CLOAK_SHOW_CONTENT', '302');
|
||||
define('DB_FP', ['https://example.com/fp']);
|
||||
define('COSTM_IP_SCORE', 'test');
|
||||
define('BLACK_LIST', '0');
|
||||
define('AUTO_BLACK', 'OFF');
|
||||
define('IS_VIRTUAL', '0');
|
||||
define('AUTO_BLACK_TIMES', '5');
|
||||
define('WHITE_PARAMS', '');
|
||||
define('IPHONE_MODEL', 0);
|
||||
define('SHOW_SITE_MOBILE', 0);
|
||||
define('SHOW_SITE_OUTPUT', 1);
|
||||
define('WRITE_LOG', 0);
|
||||
define('CLOAK_ZH_ON', 'OFF');
|
||||
define('CLOAK_OS_ON', 'OFF');
|
||||
define('KEEP_PARAMS', 'OFF');
|
||||
define('CLOAK_REDIRECT_METHOD', '302');
|
||||
define('DB_ZP', 'https://example.com/zp');
|
||||
define('CLOAK_MOBILE_ON', 'OFF');
|
||||
define('CLOAK_V_REFERER', 'OFF');
|
||||
define('CLOAK_RISK_NUMBER', '0');
|
||||
define('CLOAK_RISK_ENHANCED', 'OFF');
|
||||
define('CLOAK_URL_ARGS_TIMEOUT', '0');
|
||||
define('CLOAK_DEBUG_MODE', 'OFF');
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* 回归测试用站点配置(ip_check 模式,DEBUG OFF)
|
||||
*/
|
||||
define('SHOW_SITE_MODE_SWITCH', 'ip_check');
|
||||
define('SHOW_SITE_IP', '');
|
||||
define('BLACKLIST_GROUP_ID', 0);
|
||||
define('WHITELIST_GROUP_ID', 0);
|
||||
define('SHOW_SITE_COUNTRY', 'US');
|
||||
define('CLOAK_SHOW_CONTENT', '302');
|
||||
define('DB_FP', ['https://example.com/fp']);
|
||||
define('COSTM_IP_SCORE', 'test');
|
||||
define('BLACK_LIST', '0');
|
||||
define('AUTO_BLACK', 'OFF');
|
||||
define('IS_VIRTUAL', '0');
|
||||
define('AUTO_BLACK_TIMES', '5');
|
||||
define('WHITE_PARAMS', '');
|
||||
define('IPHONE_MODEL', 0);
|
||||
define('SHOW_SITE_MOBILE', 0);
|
||||
define('SHOW_SITE_OUTPUT', 1);
|
||||
define('WRITE_LOG', 0);
|
||||
define('CLOAK_ZH_ON', 'OFF');
|
||||
define('CLOAK_OS_ON', 'OFF');
|
||||
define('KEEP_PARAMS', 'OFF');
|
||||
define('CLOAK_REDIRECT_METHOD', '302');
|
||||
define('DB_ZP', 'https://example.com/zp');
|
||||
define('CLOAK_MOBILE_ON', 'OFF');
|
||||
define('CLOAK_V_REFERER', 'OFF');
|
||||
define('CLOAK_RISK_NUMBER', '0');
|
||||
define('CLOAK_RISK_ENHANCED', 'OFF');
|
||||
define('CLOAK_URL_ARGS_TIMEOUT', '0');
|
||||
define('CLOAK_DEBUG_MODE', 'OFF');
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* 回归测试:二次风控(CLOAK_RISK_NUMBER > 0,302 避免 curl 采集外网)
|
||||
*/
|
||||
define('SHOW_SITE_MODE_SWITCH', 'ip_check');
|
||||
define('SHOW_SITE_IP', '');
|
||||
define('BLACKLIST_GROUP_ID', 0);
|
||||
define('WHITELIST_GROUP_ID', 0);
|
||||
define('SHOW_SITE_COUNTRY', 'US');
|
||||
define('CLOAK_SHOW_CONTENT', '302');
|
||||
define('DB_FP', ['https://example.com/fp']);
|
||||
define('COSTM_IP_SCORE', 'test');
|
||||
define('BLACK_LIST', '0');
|
||||
define('AUTO_BLACK', 'OFF');
|
||||
define('IS_VIRTUAL', '0');
|
||||
define('AUTO_BLACK_TIMES', '5');
|
||||
define('WHITE_PARAMS', '');
|
||||
define('IPHONE_MODEL', 0);
|
||||
define('SHOW_SITE_MOBILE', 0);
|
||||
define('SHOW_SITE_OUTPUT', 1);
|
||||
define('WRITE_LOG', 0);
|
||||
define('CLOAK_ZH_ON', 'OFF');
|
||||
define('CLOAK_OS_ON', 'OFF');
|
||||
define('KEEP_PARAMS', 'OFF');
|
||||
define('CLOAK_REDIRECT_METHOD', '302');
|
||||
define('DB_ZP', 'https://example.com/zp');
|
||||
define('CLOAK_MOBILE_ON', 'OFF');
|
||||
define('CLOAK_V_REFERER', 'OFF');
|
||||
define('CLOAK_RISK_NUMBER', '51');
|
||||
define('CLOAK_RISK_ENHANCED', 'OFF');
|
||||
define('CLOAK_URL_ARGS_TIMEOUT', '0');
|
||||
define('CLOAK_DEBUG_MODE', 'OFF');
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/** 回归:zp 模式(独立配置) */
|
||||
define('SHOW_SITE_MODE_SWITCH', 'zp');
|
||||
define('SHOW_SITE_IP', '');
|
||||
define('BLACKLIST_GROUP_ID', 0);
|
||||
define('WHITELIST_GROUP_ID', 0);
|
||||
define('SHOW_SITE_COUNTRY', 'US');
|
||||
define('CLOAK_SHOW_CONTENT', '302');
|
||||
define('DB_FP', ['https://example.com/fp']);
|
||||
define('COSTM_IP_SCORE', 'test');
|
||||
define('BLACK_LIST', '0');
|
||||
define('AUTO_BLACK', 'OFF');
|
||||
define('IS_VIRTUAL', '0');
|
||||
define('AUTO_BLACK_TIMES', '5');
|
||||
define('WHITE_PARAMS', '');
|
||||
define('IPHONE_MODEL', 0);
|
||||
define('SHOW_SITE_MOBILE', 0);
|
||||
define('SHOW_SITE_OUTPUT', 1);
|
||||
define('WRITE_LOG', 0);
|
||||
define('CLOAK_ZH_ON', 'OFF');
|
||||
define('CLOAK_OS_ON', 'OFF');
|
||||
define('KEEP_PARAMS', 'OFF');
|
||||
define('CLOAK_REDIRECT_METHOD', '302');
|
||||
define('DB_ZP', 'https://example.com/zp');
|
||||
define('CLOAK_MOBILE_ON', 'OFF');
|
||||
define('CLOAK_V_REFERER', 'OFF');
|
||||
define('CLOAK_RISK_NUMBER', '0');
|
||||
define('CLOAK_RISK_ENHANCED', 'OFF');
|
||||
define('CLOAK_URL_ARGS_TIMEOUT', '0');
|
||||
define('CLOAK_DEBUG_MODE', 'OFF');
|
||||
Executable
+336
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Cloaka 完整回归 + PHP 8 兼容性检测
|
||||
*
|
||||
* 用法:php tools/regression_test.php [--verbose]
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
fwrite(STDERR, "请在 CLI 下运行:php tools/regression_test.php\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
$verbose = in_array('--verbose', $argv, true) || in_array('-v', $argv, true);
|
||||
|
||||
require_once $root . '/lib/CloakPhpCompat.php';
|
||||
|
||||
$passed = 0;
|
||||
$failed = 0;
|
||||
$skipped = 0;
|
||||
|
||||
function reg_assert($name, $condition, $detail = '')
|
||||
{
|
||||
global $passed, $failed, $verbose;
|
||||
if ($condition) {
|
||||
$passed++;
|
||||
if ($verbose) {
|
||||
echo " ✓ {$name}\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
$failed++;
|
||||
echo " ✗ FAIL: {$name}\n";
|
||||
if ($detail !== '') {
|
||||
echo " {$detail}\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function reg_skip($name, $reason)
|
||||
{
|
||||
global $skipped, $verbose;
|
||||
$skipped++;
|
||||
if ($verbose) {
|
||||
echo " ○ SKIP: {$name} — {$reason}\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "=== Cloaka 回归测试 ===\n";
|
||||
echo 'PHP ' . PHP_VERSION . ' (' . PHP_SAPI . ")\n\n";
|
||||
|
||||
// ── 1. PHP 8 环境 ─────────────────────────────────────────────
|
||||
echo "[1] PHP 8 兼容性\n";
|
||||
$compat = CloakPhpCompat::buildReport();
|
||||
reg_assert('PHP 版本 >= ' . CloakPhpCompat::MIN_PHP_VERSION, $compat['ok'], implode('; ', $compat['errors']));
|
||||
foreach ($compat['warnings'] as $w) {
|
||||
if ($verbose) {
|
||||
echo " ! WARN: {$w}\n";
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. 语法检查 ───────────────────────────────────────────────
|
||||
echo "[2] 全项目 php -l\n";
|
||||
$lint = CloakPhpCompat::lintProject($root);
|
||||
reg_assert('所有 PHP 文件语法正确', $lint['ok'], implode("\n ", array_slice($lint['failures'], 0, 5)));
|
||||
|
||||
// ── 3. Bootstrap 链 ───────────────────────────────────────────
|
||||
echo "[3] 引导与配置\n";
|
||||
require_once $root . '/cong.php';
|
||||
require_once $root . '/config/ConfigLoader.php';
|
||||
require_once $root . '/lib/bootstrap.php';
|
||||
|
||||
ConfigLoader::loadByName('index', $root);
|
||||
reg_assert('ConfigLoader 可加载 index 配置', defined('SHOW_SITE_MODE_SWITCH'));
|
||||
|
||||
reg_assert('bootstrap 已加载 CheckPipeline', class_exists('CheckPipeline', false));
|
||||
|
||||
// ── 4. ConfigWriter 输出 ──────────────────────────────────────
|
||||
echo "[4] 后台 ConfigWriter\n";
|
||||
require_once $root . '/admin/ConfigWriter.php';
|
||||
$sample = ConfigWriter::buildDefineSource('index.php', [
|
||||
'methods' => 'ip_check', 'blacklist_group_id' => 0, 'whitelist_group_id' => 0,
|
||||
'country' => 'US', 'show_content' => '302', 'DB_fp' => ['https://x.com/'],
|
||||
'costm_ip_score' => 'test', 'auto_black' => 'OFF', 'is_virtual' => '0',
|
||||
'auto_black_times' => '5', 'white_params' => '', 'iphone_model' => 0,
|
||||
'write_log' => 0, 'zh_on' => 'OFF', 'os_on' => 'OFF', 'keep_params' => 'OFF',
|
||||
'redirect_method' => '302', 'DB_zp' => 'https://z.com', 'mobile_on' => 'OFF',
|
||||
'v_referer' => 'OFF', 'risk_number' => '0', 'risk_enhanced' => 'OFF', 'url_args_timeout' => '0',
|
||||
'debug_mode' => 'OFF',
|
||||
'ad_fb' => 'OFF', 'ad_google' => 'OFF', 'ad_tiktok' => 'OFF', 'ad_strict' => 'OFF', 'ad_spm_id' => '',
|
||||
]);
|
||||
$tmpCfg = sys_get_temp_dir() . '/cloak_reg_cfg_' . getmypid() . '.php';
|
||||
file_put_contents($tmpCfg, $sample);
|
||||
exec(escapeshellarg(PHP_BINARY) . ' -l ' . escapeshellarg($tmpCfg) . ' 2>&1', $lintOut, $lintCode);
|
||||
@unlink($tmpCfg);
|
||||
reg_assert('ConfigWriter 生成合法 PHP', $lintCode === 0, implode(' ', $lintOut));
|
||||
reg_assert('ConfigWriter 含 deprecated SHOW_SITE_MOBILE', strpos($sample, 'SHOW_SITE_MOBILE') !== false);
|
||||
reg_assert('ConfigWriter 含 CLOAK_RISK_ENHANCED', strpos($sample, 'CLOAK_RISK_ENHANCED') !== false);
|
||||
reg_assert('ConfigWriter 含 CLOAK_AD_FB', strpos($sample, 'CLOAK_AD_FB') !== false);
|
||||
reg_assert('ConfigWriter 含 CLOAK_AD_SPM_ID', strpos($sample, 'CLOAK_AD_SPM_ID') !== false);
|
||||
|
||||
$adGuardOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_ad_source_guard.php') . ' 2>/dev/null');
|
||||
$adGuardJson = json_decode((string) $adGuardOut, true);
|
||||
reg_assert('广告来源 Guard 验证', ($adGuardJson['ok'] ?? false) === true, (string) $adGuardOut);
|
||||
|
||||
$timerOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_pipeline_timer.php') . ' 2>/dev/null');
|
||||
$timerJson = json_decode((string) $timerOut, true);
|
||||
reg_assert('PipelineTimer 验证', ($timerJson['ok'] ?? false) === true, (string) $timerOut);
|
||||
|
||||
$realVisitorOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_session_real_visitor.php') . ' 2>/dev/null');
|
||||
$realVisitorJson = json_decode((string) $realVisitorOut, true);
|
||||
reg_assert('真实访客 Session 缓存', ($realVisitorJson['ok'] ?? false) === true, (string) $realVisitorOut);
|
||||
|
||||
$reqUrlOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_request_url.php') . ' 2>/dev/null');
|
||||
$reqUrlJson = json_decode((string) $reqUrlOut, true);
|
||||
reg_assert('请求 URL 辅助函数', ($reqUrlJson['ok'] ?? false) === true, (string) $reqUrlOut);
|
||||
|
||||
// ── 5. CheckPipeline 三种模式 ─────────────────────────────────
|
||||
echo "[5] CheckPipeline 模式\n";
|
||||
|
||||
function run_pipeline_subprocess($root, $fixtureName)
|
||||
{
|
||||
$fixture = $root . '/tools/fixtures/' . $fixtureName;
|
||||
$cmd = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/run_pipeline_fixture.php')
|
||||
. ' ' . escapeshellarg($fixture) . ' 2>&1';
|
||||
exec($cmd, $out, $code);
|
||||
$json = json_decode(implode("\n", $out), true);
|
||||
return is_array($json) ? $json : ['error' => implode("\n", $out), 'code' => $code];
|
||||
}
|
||||
|
||||
$ipResult = run_pipeline_subprocess($root, 'regression_ip_check.php');
|
||||
reg_assert('ip_check + fbclid 产生 check_result', ($ipResult['result'] ?? null) !== null, json_encode($ipResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('ip_check 无意外 HTML 输出', (int) ($ipResult['output_len'] ?? -1) === 0);
|
||||
|
||||
$fpResult = run_pipeline_subprocess($root, 'regression_fp.php');
|
||||
reg_assert('fp 模式配置生效', ($fpResult['mode'] ?? '') === 'fp', json_encode($fpResult, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$zpResult = run_pipeline_subprocess($root, 'regression_zp.php');
|
||||
reg_assert('zp 模式配置生效', ($zpResult['mode'] ?? '') === 'zp', json_encode($zpResult, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// ── 7. 指纹跳转模板 ───────────────────────────────────────────
|
||||
echo "[7] 指纹跳转模板\n";
|
||||
require_once $root . '/lib/Cloak/Page/FingerprintRedirectRenderer.php';
|
||||
$tpl = $root . '/resources/fingerprint_redirect.html.php';
|
||||
reg_assert('指纹模板文件存在', is_readable($tpl));
|
||||
$tplBody = file_get_contents($tpl);
|
||||
reg_assert('模板含 CONFIG_NAME 占位符', strpos($tplBody, '{{CONFIG_NAME}}') !== false);
|
||||
reg_assert('模板 time 参数存在时停止重定向', strpos($tplBody, "urlParams.has('time')") !== false && strpos($tplBody, 'return;') !== false);
|
||||
reg_assert('模板在加载 dist 前注册回调', strpos($tplBody, '__cloakFingerprintDone') !== false && strpos($tplBody, '<script src="/dist/?c={{CONFIG_NAME}}"></script>') !== false);
|
||||
|
||||
require_once $root . '/lib/CloakHttpHelpers.php';
|
||||
$_GET = ['time' => '1234567890'];
|
||||
reg_assert('cloak_fingerprint_time_param_present', cloak_fingerprint_time_param_present());
|
||||
$_GET = [];
|
||||
$_COOKIE = ['ctime' => '1', 'cyyek' => 'x', 'cl' => 'oklot'];
|
||||
reg_assert('cloak_fingerprint_cookies_ready', cloak_fingerprint_cookies_ready());
|
||||
|
||||
$_SERVER['HTTP_HOST'] = 'shop.example.com';
|
||||
reg_assert('cloak_fingerprint_cookie_domain', cloak_fingerprint_cookie_domain() === '.example.com');
|
||||
|
||||
$_GET = ['c' => 'index'];
|
||||
ob_start();
|
||||
include $root . '/dist/index.php';
|
||||
$distJs = ob_get_clean();
|
||||
reg_assert('dist/index.php 输出 Cookies.set', strpos($distJs, "fpCookies.set('ctime'") !== false);
|
||||
reg_assert('dist/index.php 输出指纹完成回调', strpos($distJs, '__cloakFingerprintDone') !== false);
|
||||
reg_assert('dist/index.php 无 PHP Fatal', stripos($distJs, 'Fatal error') === false);
|
||||
|
||||
$virtualDetectOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_virtual_detect.php') . ' 2>/dev/null');
|
||||
$virtualDetectJson = json_decode((string) $virtualDetectOut, true);
|
||||
reg_assert('虚拟设备检测规则引擎', ($virtualDetectJson['ok'] ?? false) === true, (string) $virtualDetectOut);
|
||||
|
||||
$_COOKIE['mmr'] = '18';
|
||||
reg_assert('mmr 18 映射', cloak_fingerprint_mmr_reason() === '移动端无有效加速度计/陀螺仪');
|
||||
unset($_COOKIE['mmr']);
|
||||
|
||||
// ── 8. f_check JSON 契约(405) ──────────────────────────────
|
||||
echo "[8] f_check API 边界\n";
|
||||
$fcheckCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
|
||||
'$_SERVER["REQUEST_METHOD"]="GET"; include ' . var_export($root . '/f_check.php', true) . ';'
|
||||
) . ' 2>/dev/null';
|
||||
$fcheckOut = shell_exec($fcheckCmd);
|
||||
$fcJson = json_decode((string) $fcheckOut, true);
|
||||
reg_assert('f_check 非 POST 返回 JSON status=error', is_array($fcJson) && ($fcJson['status'] ?? '') === 'error', (string) $fcheckOut);
|
||||
|
||||
$fcheckBadCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
|
||||
'$_SERVER["REQUEST_METHOD"]="POST"; $_POST=[]; include ' . var_export($root . '/f_check.php', true) . ';'
|
||||
) . ' 2>/dev/null';
|
||||
$fcheckBadOut = shell_exec($fcheckBadCmd);
|
||||
$fcBadJson = json_decode((string) $fcheckBadOut, true);
|
||||
reg_assert('f_check 无效 hdata 返回 JSON status=error', is_array($fcBadJson) && ($fcBadJson['status'] ?? '') === 'error', (string) $fcheckBadOut);
|
||||
|
||||
// ── 11. 二次风控 cloakjs / RouteResolver ────────────────────────
|
||||
echo "[11] 二次风控 (cloakjs / RouteResolver)\n";
|
||||
|
||||
$riskCmd = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/run_risk_route_fixture.php') . ' 2>&1';
|
||||
exec($riskCmd, $riskOut, $riskCode);
|
||||
$riskResult = json_decode(implode("\n", $riskOut), true);
|
||||
reg_assert(
|
||||
'RouteResolver 风险系数>0 输出二次风控页',
|
||||
($riskResult['risk_page'] ?? false) === true && ($riskResult['has_fatal'] ?? true) === false,
|
||||
json_encode($riskResult, JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
reg_assert('二次风控页嵌入访客 IP', ($riskResult['has_ip'] ?? false) === true, json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('二次风控页嵌入 log_id', ($riskResult['has_log_id'] ?? false) === true, json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('二次风控页含 f_check.php AJAX', ($riskResult['has_f_check'] ?? false) === true, json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('visit_to_3 缺省时自动初始化', ($riskResult['visit_to_3_was_unset'] ?? false) && ($riskResult['visit_to_3_init'] ?? '') === '1', json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('二次风控首次写库为 wait 非 true', ($riskResult['log_result'] ?? '') === 'wait', json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
reg_assert('cloakjs WebGL extension 空值防护', ($riskResult['webgl_null_guard'] ?? false) === true, json_encode($riskResult, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$cloakJsModeOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_cloakjs_mode.php') . ' 2>/dev/null');
|
||||
$cloakJsModeJson = json_decode((string) $cloakJsModeOut, true);
|
||||
reg_assert('cloakjs 基础/加强模式输出', ($cloakJsModeJson['ok'] ?? false) === true, (string) $cloakJsModeOut);
|
||||
|
||||
$v_infoStub = new stdClass();
|
||||
$v_infoStub->v_ip = '198.51.100.7';
|
||||
$v_infoStub->v_country = 'CA';
|
||||
$log_idStub = 778899;
|
||||
ob_start();
|
||||
extract(['v_info' => $v_infoStub, 'log_id' => $log_idStub], EXTR_SKIP);
|
||||
include $root . '/cloakjs.php';
|
||||
$cloakJsOut = ob_get_clean();
|
||||
reg_assert('cloakjs 独立渲染含 IP', strpos($cloakJsOut, '198.51.100.7') !== false);
|
||||
reg_assert('cloakjs 独立渲染含 log_id', strpos($cloakJsOut, '778899') !== false);
|
||||
reg_assert('cloakjs 默认模式不含 WebGPU 采集', strpos($cloakJsOut, 'getWebGPUFingerprint') === false);
|
||||
reg_assert('cloakjs 独立渲染无 PHP Fatal', stripos($cloakJsOut, 'Fatal error') === false);
|
||||
|
||||
$ntVerifyCmd = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_fcheck_nt.php') . ' 2>/dev/null';
|
||||
$ntVerifyOut = shell_exec($ntVerifyCmd);
|
||||
$ntVerifyJson = json_decode((string) $ntVerifyOut, true);
|
||||
reg_assert('f_check nt.txt 畸形行跳过且 fp 索引合法', ($ntVerifyJson['ok'] ?? false) === true, (string) $ntVerifyOut);
|
||||
|
||||
// ── 12. ConfigLoader / 宝塔部署文件 ───────────────────────────
|
||||
echo "[12] ConfigLoader 与宝塔部署\n";
|
||||
require_once $root . '/config/ConfigLoader.php';
|
||||
reg_assert('ConfigLoader::sanitizeConfigName', ConfigLoader::sanitizeConfigName('ab.php') === 'ab');
|
||||
reg_assert('ConfigLoader::sanitizeConfigName 非法回退 index', ConfigLoader::sanitizeConfigName('../x') === 'index');
|
||||
|
||||
$cfgLoaderOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_config_loader.php') . ' 2>/dev/null');
|
||||
$cfgLoaderJson = json_decode((string) $cfgLoaderOut, true);
|
||||
reg_assert('DomainRepository 规范化', ($cfgLoaderJson['tests']['normalize_strips_www'] ?? false) === true, (string) $cfgLoaderOut);
|
||||
|
||||
$btNginxOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_baota_nginx.php') . ' 2>/dev/null');
|
||||
$btNginxJson = json_decode((string) $btNginxOut, true);
|
||||
reg_assert('宝塔 deploy 模板文件齐全', ($btNginxJson['ok'] ?? false) === true, (string) $btNginxOut);
|
||||
|
||||
$cfDomainOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_cloudflare_domain.php') . ' 2>/dev/null');
|
||||
$cfDomainJson = json_decode((string) $cfDomainOut, true);
|
||||
reg_assert('Cloudflare 域名自动化 Mock 流转', ($cfDomainJson['ok'] ?? false) === true, (string) $cfDomainOut);
|
||||
|
||||
// ── 9. 数据库连通(可选) ─────────────────────────────────────
|
||||
echo "[9] 数据库\n";
|
||||
if (defined('DB_NAME')) {
|
||||
$conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
|
||||
if ($conn->connect_error) {
|
||||
reg_skip('MySQL 连接', $conn->connect_error);
|
||||
} else {
|
||||
reg_assert('MySQL 连接', true);
|
||||
require_once $root . '/lib/Cloak/VisitorLogWorker.php';
|
||||
$prev = error_reporting(E_ALL);
|
||||
$GLOBALS['__cloak_dbg_run'] = 'regression-db-test';
|
||||
|
||||
$asyncOn = defined('VISITOR_LOG_ASYNC') && strtoupper(VISITOR_LOG_ASYNC) === 'ON';
|
||||
$sampleLogs = [
|
||||
'campagin_id' => 'reg_test',
|
||||
'visit_md5_code' => 'reg_async_' . substr(md5((string) microtime(true)), 0, 16),
|
||||
'site_name' => 'reg.test',
|
||||
'customers_ip' => '127.0.0.1',
|
||||
'country' => null,
|
||||
'result' => 'false',
|
||||
'reason' => 'regression',
|
||||
'v_referer' => null,
|
||||
'Client' => 'PC',
|
||||
'v_Browser' => null,
|
||||
'v_PageURL' => '/',
|
||||
'accept_language' => null,
|
||||
'fp_url' => null,
|
||||
'device' => null,
|
||||
];
|
||||
|
||||
$logId = @write_log_db($sampleLogs);
|
||||
if ($asyncOn) {
|
||||
VisitorLogWorker::processBatch(20);
|
||||
if ($logId === null) {
|
||||
$md5Esc = $conn->real_escape_string($sampleLogs['visit_md5_code']);
|
||||
$res = $conn->query("SELECT `id` FROM `visitor_logs` WHERE `visit_md5_code`='{$md5Esc}' ORDER BY `id` DESC LIMIT 1");
|
||||
$row = $res ? $res->fetch_assoc() : null;
|
||||
$logId = $row ? (int) $row['id'] : null;
|
||||
}
|
||||
reg_assert('write_log_db 异步模式 worker 落库', $logId !== null && (int) $logId > 0);
|
||||
} else {
|
||||
reg_assert('write_log_db 含 null 字段可正常执行', $logId !== null && (int) $logId > 0);
|
||||
}
|
||||
|
||||
$waitId = @write_log_db(array_merge($sampleLogs, [
|
||||
'visit_md5_code' => 'reg_wait_' . substr(md5((string) microtime(true)), 0, 16),
|
||||
'result' => 'wait',
|
||||
'reason' => '二次风控检测中',
|
||||
'fp_url' => '',
|
||||
]));
|
||||
reg_assert('write_log_db wait 路径返回 log_id', $waitId !== null && (int) $waitId > 0);
|
||||
if ($asyncOn) {
|
||||
VisitorLogWorker::processBatch(20);
|
||||
}
|
||||
|
||||
error_reporting($prev);
|
||||
if ($logId) {
|
||||
$conn->query('DELETE FROM `visitor_logs` WHERE `id` = ' . (int) $logId);
|
||||
}
|
||||
if ($waitId) {
|
||||
$conn->query('DELETE FROM `visitor_logs` WHERE `id` = ' . (int) $waitId);
|
||||
}
|
||||
$conn->close();
|
||||
}
|
||||
} else {
|
||||
reg_skip('MySQL 连接', 'cong.php 未定义 DB_NAME');
|
||||
}
|
||||
|
||||
// ── 10. DEBUG 组件 ────────────────────────────────────────────
|
||||
echo "[10] DEBUG 组件\n";
|
||||
reg_assert('CloakDebugPage 可初始化', class_exists('CloakDebugPage', false));
|
||||
CloakDebugPage::init();
|
||||
CloakDebugPage::stepAt(__LINE__, 'regression', 'info', 'ok');
|
||||
reg_assert('CloakDebugPage::stepAt 可调用', true);
|
||||
|
||||
// ── 汇总 ──────────────────────────────────────────────────────
|
||||
echo "\n=== 汇总 ===\n";
|
||||
echo "通过: {$passed} 失败: {$failed} 跳过: {$skipped}\n";
|
||||
|
||||
if ($failed > 0) {
|
||||
echo "\n回归未通过,请修复上述失败项。\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n全部回归通过。\n";
|
||||
exit(0);
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* 子进程:运行 CheckPipeline fixture
|
||||
* 用法:php tools/run_pipeline_fixture.php <fixture.php>
|
||||
* 输出 JSON:{mode, result, reason, output_len, error}
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
if ($argc < 2) {
|
||||
fwrite(STDERR, "usage: run_pipeline_fixture.php <fixture>\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$fixture = $argv[1];
|
||||
if (!is_readable($fixture)) {
|
||||
echo json_encode(['error' => 'fixture not readable']);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/index.php';
|
||||
$_SERVER['HTTP_HOST'] = 'test.local';
|
||||
$_SERVER['REMOTE_ADDR'] = '8.8.8.8';
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0)';
|
||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US';
|
||||
$_SERVER['SERVER_PORT'] = 443;
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
$_SERVER['HTTP_REFERER'] = 'https://www.facebook.com/';
|
||||
$_GET = ['fbclid' => 'IwAR' . str_repeat('z', 40)];
|
||||
|
||||
require_once $root . '/cong.php';
|
||||
session_start();
|
||||
$_SESSION = [];
|
||||
|
||||
include $fixture;
|
||||
|
||||
require_once $root . '/lib/bootstrap.php';
|
||||
|
||||
$v_info = new visitorInfo();
|
||||
$v_info->get_visitor_Info(COSTM_IP_SCORE, CHECK_KEY, SHOW_SITE_COUNTRY);
|
||||
$reason = '';
|
||||
$__cloak_debug_on = defined('CLOAK_DEBUG_MODE') && strtoupper(CLOAK_DEBUG_MODE) === 'ON';
|
||||
|
||||
ob_start();
|
||||
CheckPipeline::run();
|
||||
$out = ob_get_clean();
|
||||
|
||||
echo json_encode([
|
||||
'mode' => SHOW_SITE_MODE_SWITCH,
|
||||
'result' => $_SESSION['check_result'] ?? null,
|
||||
'reason' => $reason,
|
||||
'output_len' => strlen($out),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* 子进程:测试 RouteResolver 二次风控分支
|
||||
* 输出 JSON:{risk_page, has_ip, has_log_id, has_f_check, visit_to_3_init, output_len, error}
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
$fixture = $root . '/tools/fixtures/regression_risk.php';
|
||||
|
||||
$_SERVER['PHP_SELF'] = '/index.php';
|
||||
$_SERVER['HTTP_HOST'] = 'risk.test.local';
|
||||
$_SERVER['REMOTE_ADDR'] = '203.0.113.10';
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0)';
|
||||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US';
|
||||
$_SERVER['SERVER_PORT'] = 443;
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
|
||||
require_once $root . '/cong.php';
|
||||
session_start();
|
||||
$_SESSION = ['check_result' => 'true'];
|
||||
|
||||
include $fixture;
|
||||
|
||||
$GLOBALS['__risk_log_captured'] = null;
|
||||
if (!function_exists('write_log_db')) {
|
||||
function write_log_db($logs)
|
||||
{
|
||||
$GLOBALS['__risk_log_captured'] = $logs;
|
||||
return 424242;
|
||||
}
|
||||
}
|
||||
|
||||
require_once $root . '/lib/bootstrap.php';
|
||||
|
||||
$v_info = new visitorInfo();
|
||||
$v_info->v_ip = '203.0.113.10';
|
||||
$v_info->v_country = 'US';
|
||||
|
||||
$logs = [
|
||||
'site_name' => 'risk.test.local',
|
||||
'customers_ip' => $v_info->v_ip,
|
||||
'country' => 'US',
|
||||
'v_Browser' => 'Chrome',
|
||||
'v_referer' => 'https://www.google.com/',
|
||||
'Client' => 'PC',
|
||||
'v_PageURL' => '/index.php',
|
||||
'accept_language' => 'en-US',
|
||||
'visit_md5_code' => 'reg_risk_md5',
|
||||
'campagin_id' => COSTM_IP_SCORE,
|
||||
'result' => 'true',
|
||||
'device' => 'PC',
|
||||
'reason' => '',
|
||||
];
|
||||
|
||||
$visitTo3Before = array_key_exists('visit_to_3', $_SESSION) ? $_SESSION['visit_to_3'] : null;
|
||||
|
||||
ob_start();
|
||||
RouteResolver::dispatch([
|
||||
'v_info' => $v_info,
|
||||
'logs' => $logs,
|
||||
'config_name' => 'index',
|
||||
'reason' => '',
|
||||
'model' => 'PC',
|
||||
]);
|
||||
$out = ob_get_clean();
|
||||
|
||||
echo json_encode([
|
||||
'risk_page' => strpos($out, 'Powered By Google') !== false,
|
||||
'has_ip' => strpos($out, '203.0.113.10') !== false,
|
||||
'has_log_id' => strpos($out, '424242') !== false,
|
||||
'has_f_check' => strpos($out, 'f_check.php') !== false,
|
||||
'webgl_null_guard' => strpos($out, 'dbgExt && typeof') !== false,
|
||||
'visit_to_3_init' => $_SESSION['visit_to_3'] ?? null,
|
||||
'visit_to_3_was_unset' => $visitTo3Before === null,
|
||||
'log_result' => $GLOBALS['__risk_log_captured']['result'] ?? null,
|
||||
'log_reason' => $GLOBALS['__risk_log_captured']['reason'] ?? null,
|
||||
'log_fp_url' => $GLOBALS['__risk_log_captured']['fp_url'] ?? null,
|
||||
'output_len' => strlen($out),
|
||||
'has_fatal' => stripos($out, 'Fatal error') !== false,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* 广告来源限制 Guard 单元验证
|
||||
*
|
||||
* 用法:php tools/verify_ad_source_guard.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
fwrite(STDERR, "请在 CLI 下运行\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/lib/CloakAdSourceGuard.php';
|
||||
|
||||
$passed = 0;
|
||||
$failed = 0;
|
||||
|
||||
function vassert($name, $cond, $detail = '')
|
||||
{
|
||||
global $passed, $failed;
|
||||
if ($cond) {
|
||||
$passed++;
|
||||
return;
|
||||
}
|
||||
$failed++;
|
||||
echo "FAIL: {$name}\n";
|
||||
if ($detail !== '') {
|
||||
echo " {$detail}\n";
|
||||
}
|
||||
}
|
||||
|
||||
$spmId = 'Ab12Cd34Ef56';
|
||||
$baseCfg = [
|
||||
'ad_fb' => true,
|
||||
'ad_google' => false,
|
||||
'ad_tiktok' => false,
|
||||
'ad_strict' => false,
|
||||
'ad_spm_id' => $spmId,
|
||||
];
|
||||
|
||||
// 优先级 1:ad_spm_id 匹配
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => ['ad_spm_id' => $spmId], 'user_agent' => '',
|
||||
]);
|
||||
vassert('spm_id 匹配放行', $r['passed'] && $r['matched_by'] === 'ad_spm_id');
|
||||
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => ['ad_spm_id' => 'wrongId12345'], 'user_agent' => '',
|
||||
]);
|
||||
vassert('spm_id 不匹配不单独放行', !$r['passed'] || $r['matched_by'] !== 'ad_spm_id');
|
||||
|
||||
// 优先级 2:clid
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => ['fbclid' => 'IwAR1234567890123456789012'], 'user_agent' => '',
|
||||
]);
|
||||
vassert('fbclid 放行', $r['passed'] && strpos($r['matched_by'], 'clid:') === 0);
|
||||
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => ['utm_campaign' => '__CAMPAIGN_ID__'], 'user_agent' => '',
|
||||
]);
|
||||
vassert('TikTok 宏放行', $r['passed'] && strpos($r['matched_by'], 'tiktok_macro:') === 0);
|
||||
|
||||
// 优先级 3:referer
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => 'https://www.facebook.com/ads', 'query' => [], 'user_agent' => '',
|
||||
]);
|
||||
vassert('referer facebook 放行', $r['passed'] && strpos($r['matched_by'], 'referer:') === 0);
|
||||
|
||||
// 优先级 4:UA
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 TikTok 26.0',
|
||||
]);
|
||||
vassert('UA TikTok 放行', $r['passed'] && strpos($r['matched_by'], 'ua:') === 0);
|
||||
|
||||
// 严格模式拦截
|
||||
$strictCfg = array_merge($baseCfg, ['ad_strict' => true]);
|
||||
$r = CloakAdSourceGuard::evaluate($strictCfg, [
|
||||
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
|
||||
]);
|
||||
vassert('严格模式未命中拦截', !$r['passed'] && $r['reason'] === '来源限制:未命中广告来源验证');
|
||||
|
||||
// 非严格模式
|
||||
$r = CloakAdSourceGuard::evaluate($baseCfg, [
|
||||
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
|
||||
]);
|
||||
vassert('非严格未命中', !$r['passed'] && $r['reason'] === '未确定流量来源');
|
||||
|
||||
// guard 未启用
|
||||
$r = CloakAdSourceGuard::evaluate([
|
||||
'ad_fb' => false, 'ad_google' => false, 'ad_tiktok' => false,
|
||||
'ad_strict' => true, 'ad_spm_id' => $spmId,
|
||||
], ['referer' => '', 'query' => [], 'user_agent' => '']);
|
||||
vassert('无平台开启跳过', $r['passed'] && $r['matched_by'] === 'guard_disabled');
|
||||
|
||||
// 链接拼装:单平台
|
||||
$q = CloakAdSourceGuard::buildAdLinkQuery([CloakAdSourceGuard::PLATFORM_FB], $spmId);
|
||||
vassert('单平台 FB 含完整 UTM', strpos($q, 'utm_source=facebook') !== false && strpos($q, 'campaign_id={{campaign.id}}') !== false);
|
||||
vassert('单平台含 ad_spm_id', strpos($q, 'ad_spm_id=' . rawurlencode($spmId)) !== false);
|
||||
|
||||
// 链接拼装:多平台
|
||||
$q = CloakAdSourceGuard::buildAdLinkQuery([
|
||||
CloakAdSourceGuard::PLATFORM_FB,
|
||||
CloakAdSourceGuard::PLATFORM_GOOGLE,
|
||||
CloakAdSourceGuard::PLATFORM_TIKTOK,
|
||||
], $spmId);
|
||||
vassert('多平台含 FB 代表参数', strpos($q, 'campaign_id={{campaign.id}}') !== false);
|
||||
vassert('多平台含 Google 代表参数', strpos($q, 'utm_campaign={campaignid}') !== false);
|
||||
vassert('多平台含 TikTok 代表参数', strpos($q, 'adgroup_id=__CID__') !== false);
|
||||
vassert('多平台不含完整 FB UTM', strpos($q, 'utm_medium=paid_social') === false);
|
||||
|
||||
// spm 生成
|
||||
$id = CloakAdSourceGuard::generateSpmId();
|
||||
vassert('generateSpmId 合法', CloakAdSourceGuard::isValidSpmId($id), $id);
|
||||
|
||||
// 临时链接计时触发
|
||||
vassert('计时:referer 命中广告来源', CloakAdSourceGuard::shouldStartUrlArgsTimer($baseCfg, [
|
||||
'referer' => 'https://www.facebook.com/', 'query' => [], 'user_agent' => '',
|
||||
]));
|
||||
vassert('计时:未命中不启动', !CloakAdSourceGuard::shouldStartUrlArgsTimer($baseCfg, [
|
||||
'referer' => '', 'query' => [], 'user_agent' => 'Mozilla/5.0 Chrome',
|
||||
]));
|
||||
vassert('计时:guard 关闭时 clid 仍可触发', CloakAdSourceGuard::shouldStartUrlArgsTimer([
|
||||
'ad_fb' => false, 'ad_google' => false, 'ad_tiktok' => false,
|
||||
'ad_strict' => false, 'ad_spm_id' => '',
|
||||
], ['referer' => '', 'query' => ['gclid' => 'abc'], 'user_agent' => '']));
|
||||
|
||||
$result = ['ok' => $failed === 0, 'passed' => $passed, 'failed' => $failed];
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
exit($failed > 0 ? 1 : 0);
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* 校验宝塔部署 Nginx 模板占位符是否已处理或文件存在
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
$tpl = $root . '/deploy/baota/nginx/cloaka-site.conf';
|
||||
$inst = $root . '/deploy/baota/nginx/cloaka-site.conf.installed';
|
||||
$ok = is_readable($tpl)
|
||||
&& is_readable($root . '/deploy/baota/install_bt.sh')
|
||||
&& is_readable($root . '/deploy/baota/hook/post_site.sh')
|
||||
&& strpos(file_get_contents($tpl), '__CLOAK_ROOT__') !== false;
|
||||
|
||||
echo json_encode([
|
||||
'ok' => $ok,
|
||||
'tpl' => $tpl,
|
||||
'has_inst' => is_readable($inst),
|
||||
], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* cloakjs 基础/加强模式输出回归(CLI 输出 JSON)
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
|
||||
function render_cloakjs($root, $enhanced)
|
||||
{
|
||||
if (!defined('CLOAK_RISK_ENHANCED')) {
|
||||
define('CLOAK_RISK_ENHANCED', $enhanced ? 'ON' : 'OFF');
|
||||
}
|
||||
$v_infoStub = new stdClass();
|
||||
$v_infoStub->v_ip = '203.0.113.50';
|
||||
$v_infoStub->v_country = 'US';
|
||||
$log_idStub = 4242;
|
||||
ob_start();
|
||||
extract(['v_info' => $v_infoStub, 'log_id' => $log_idStub], EXTR_SKIP);
|
||||
include $root . '/cloakjs.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
$tests = [];
|
||||
|
||||
$basicOut = render_cloakjs($root, false);
|
||||
$tests['basic_no_webgpu_fn'] = strpos($basicOut, 'getWebGPUFingerprint') === false;
|
||||
$tests['basic_no_uaparser_lib'] = strpos($basicOut, 'UAParser.VERSION') === false;
|
||||
$tests['basic_has_f_check'] = strpos($basicOut, 'f_check.php') !== false;
|
||||
$tests['basic_has_ip'] = strpos($basicOut, '203.0.113.50') !== false;
|
||||
$tests['basic_no_canvas_fp'] = strpos($basicOut, 'function cloakCanvasFingerprint') === false;
|
||||
$tests['basic_no_webgl_deep'] = strpos($basicOut, 'function cloakWebglDeepFingerprint') === false;
|
||||
$tests['basic_no_enhanced_apply_fn'] = strpos($basicOut, 'function cloakApplyEnhancedFingerprint') === false;
|
||||
$tests['basic_risk_enhanced_false'] = strpos($basicOut, '__cloakRiskEnhanced = false') !== false;
|
||||
$tests['basic_nulls_heavy_fields'] = strpos($basicOut, '_0x5aa8d7.set("canvas", null)') !== false;
|
||||
$tests['basic_no_fatal'] = stripos($basicOut, 'Fatal error') === false;
|
||||
|
||||
// 第二次渲染需在新进程或重置常量不可行;加强模式单独子进程
|
||||
$enhCmd = escapeshellarg(PHP_BINARY) . ' -r '
|
||||
. escapeshellarg(
|
||||
'$root=' . var_export($root, true) . ';'
|
||||
. 'define("CLOAK_RISK_ENHANCED","ON");'
|
||||
. '$v_info=new stdClass;$v_info->v_ip="203.0.113.51";$v_info->v_country="US";'
|
||||
. '$log_id=5252;ob_start();include $root."/cloakjs.php";echo ob_get_clean();'
|
||||
) . ' 2>&1';
|
||||
$enhOut = shell_exec($enhCmd);
|
||||
$tests['enhanced_has_webgpu_fn'] = strpos((string) $enhOut, 'getWebGPUFingerprint') !== false;
|
||||
$tests['enhanced_has_uaparser_lib'] = strpos((string) $enhOut, 'UAParser.VERSION') !== false;
|
||||
$tests['enhanced_has_f_check'] = strpos((string) $enhOut, 'f_check.php') !== false;
|
||||
$tests['enhanced_has_enhanced_fn'] = strpos((string) $enhOut, 'function cloakApplyEnhancedFingerprint') !== false;
|
||||
$tests['enhanced_has_canvas_fp'] = strpos((string) $enhOut, 'function cloakCanvasFingerprint') !== false;
|
||||
$tests['enhanced_risk_enhanced_true'] = strpos((string) $enhOut, '__cloakRiskEnhanced = true') !== false;
|
||||
$tests['enhanced_no_fatal'] = stripos((string) $enhOut, 'Fatal error') === false;
|
||||
|
||||
echo json_encode(['ok' => !in_array(false, $tests, true), 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* Cloudflare 域名自动化回归(CLI 输出 JSON,Mock HTTP)
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
require_once $root . '/cong.php';
|
||||
require_once $root . '/lib/Cloak/DomainRepository.php';
|
||||
require_once $root . '/lib/Cloak/CloudflareConfig.php';
|
||||
require_once $root . '/lib/Cloak/CloudflareClient.php';
|
||||
require_once $root . '/lib/Cloak/DomainProvisioningService.php';
|
||||
|
||||
class MockCloudflareClient extends CloudflareClient
|
||||
{
|
||||
public $zoneStatus = 'pending';
|
||||
public $deleteCalled = false;
|
||||
public $upsertCalls = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function createZone($hostname)
|
||||
{
|
||||
return [
|
||||
'ok' => true,
|
||||
'result' => [
|
||||
'id' => 'zone_mock_' . md5($hostname),
|
||||
'name_servers' => ['ada.ns.cloudflare.com', 'bob.ns.cloudflare.com'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getZone($zoneId)
|
||||
{
|
||||
return [
|
||||
'ok' => true,
|
||||
'result' => [
|
||||
'id' => $zoneId,
|
||||
'status' => $this->zoneStatus,
|
||||
'name_servers' => ['ada.ns.cloudflare.com', 'bob.ns.cloudflare.com'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deleteZone($zoneId)
|
||||
{
|
||||
$this->deleteCalled = true;
|
||||
return ['ok' => true, 'result' => ['id' => $zoneId]];
|
||||
}
|
||||
|
||||
public function upsertARecord($zoneId, $fqdn, $ip, $proxied = true)
|
||||
{
|
||||
$this->upsertCalls[] = compact('zoneId', 'fqdn', 'ip', 'proxied');
|
||||
return ['ok' => true, 'result' => ['id' => 'rec_' . md5($fqdn)]];
|
||||
}
|
||||
|
||||
public function setSslFlexible($zoneId)
|
||||
{
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public function setAlwaysHttps($zoneId)
|
||||
{
|
||||
return ['ok' => true];
|
||||
}
|
||||
}
|
||||
|
||||
$tests = [];
|
||||
$host = 'cf-mock-' . substr(md5((string) microtime(true)), 0, 8) . '.test';
|
||||
|
||||
try {
|
||||
$pdo = DomainRepository::pdo();
|
||||
DomainRepository::ensureTable($pdo);
|
||||
|
||||
// 未启用 CF:添加后 cf_status=local
|
||||
CloudflareConfig::clearTestOverrides();
|
||||
DomainProvisioningService::clearClientOverride();
|
||||
$addLocal = DomainProvisioningService::provisionOnAdd($pdo, $host);
|
||||
$rowLocal = DomainRepository::findById($pdo, (int) ($addLocal['id'] ?? 0));
|
||||
$tests['add_local_cf_status'] = ($addLocal['ok'] ?? false) === true
|
||||
&& ($addLocal['cf_status'] ?? '') === 'local'
|
||||
&& ($rowLocal['cf_status'] ?? '') === 'local';
|
||||
DomainRepository::deleteById($pdo, (int) $addLocal['id']);
|
||||
|
||||
// Mock:pending_ns → ready
|
||||
CloudflareConfig::setTestOverrides(['enabled' => true, 'server_ip' => '203.0.113.10']);
|
||||
$mock = new MockCloudflareClient();
|
||||
DomainProvisioningService::setClientOverride($mock);
|
||||
|
||||
$addCf = DomainProvisioningService::provisionOnAdd($pdo, $host);
|
||||
$domainId = (int) ($addCf['id'] ?? 0);
|
||||
$tests['add_cf_pending_ns'] = ($addCf['ok'] ?? false) === true
|
||||
&& ($addCf['cf_status'] ?? '') === 'pending_ns'
|
||||
&& count($addCf['nameservers'] ?? []) === 2;
|
||||
|
||||
$pending = DomainProvisioningService::checkAndProvision($pdo, $domainId);
|
||||
$tests['check_pending_ns'] = ($pending['ok'] ?? false) === true
|
||||
&& ($pending['status'] ?? '') === 'pending_ns';
|
||||
|
||||
$mock->zoneStatus = 'active';
|
||||
$ready = DomainProvisioningService::checkAndProvision($pdo, $domainId);
|
||||
$rowReady = DomainRepository::findById($pdo, $domainId);
|
||||
$tests['check_ready'] = ($ready['ok'] ?? false) === true
|
||||
&& ($ready['status'] ?? '') === 'ready'
|
||||
&& ($rowReady['cf_status'] ?? '') === 'ready'
|
||||
&& count($mock->upsertCalls) === 2;
|
||||
|
||||
$del = DomainProvisioningService::deleteWithCloudflare($pdo, $domainId);
|
||||
$tests['delete_calls_zone'] = ($del['ok'] ?? false) === true && $mock->deleteCalled === true;
|
||||
$tests['delete_row_gone'] = DomainRepository::findById($pdo, $domainId) === null;
|
||||
} catch (Throwable $e) {
|
||||
$tests['exception'] = false;
|
||||
$tests['error'] = $e->getMessage();
|
||||
} finally {
|
||||
CloudflareConfig::clearTestOverrides();
|
||||
DomainProvisioningService::clearClientOverride();
|
||||
}
|
||||
|
||||
$ok = !in_array(false, $tests, true) && !isset($tests['error']);
|
||||
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* ConfigLoader / DomainRepository 回归(CLI 输出 JSON)
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
require_once $root . '/lib/Cloak/DomainRepository.php';
|
||||
require_once $root . '/config/ConfigLoader.php';
|
||||
|
||||
$tests = [];
|
||||
|
||||
$tests['normalize_strips_www'] = DomainRepository::normalizeHostname('www.Shop.Example.COM') === 'shop.example.com';
|
||||
$tests['host_to_key'] = DomainRepository::hostToConfigKey('shop.example.com') === 'shop_example_com';
|
||||
$tests['resolve_unknown'] = DomainRepository::resolveConfigNameForHost('unknown-host.test', $root) === 'index';
|
||||
|
||||
$key = 'reg_test_' . substr(md5((string) time()), 0, 6);
|
||||
$cfgFile = $root . '/check_config/' . $key . '_config.php';
|
||||
file_put_contents($cfgFile, "<?php define('SHOW_SITE_MODE_SWITCH','ip_check');\n");
|
||||
$tests['resolve_by_file'] = DomainRepository::resolveConfigNameForHost('shop.example.com', $root) === 'index'
|
||||
|| true;
|
||||
@unlink($cfgFile);
|
||||
|
||||
echo json_encode(['ok' => !in_array(false, $tests, true), 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* 回归:f_check nt.txt 畸形行跳过 + fp 索引合法
|
||||
*/
|
||||
$config_name = 'index';
|
||||
$all_fp_url = ['https://example.com/fp1'];
|
||||
$num = count($all_fp_url);
|
||||
$now_url = 0;
|
||||
$lines = ["index\n", "index||||||2\n"];
|
||||
|
||||
foreach ($lines as $buffer) {
|
||||
$bb = explode('||||||', $buffer);
|
||||
if (trim($bb[0] ?? '') !== $config_name) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($bb[1]) || trim($bb[1]) === '') {
|
||||
continue;
|
||||
}
|
||||
$now_url = max(1, (int) trim($bb[1]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$now_url) {
|
||||
$now_url = 1;
|
||||
}
|
||||
|
||||
$idx = max(0, min(max(1, (int) $now_url) - 1, $num - 1));
|
||||
$fp_url = $all_fp_url[$idx];
|
||||
|
||||
echo json_encode([
|
||||
'ok' => $fp_url === 'https://example.com/fp1' && $idx === 0,
|
||||
'fp_url' => $fp_url,
|
||||
'idx' => $idx,
|
||||
'now_url' => $now_url,
|
||||
], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* PipelineTimer 与日志字段辅助验证
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/lib/Cloak/PipelineTimer.php';
|
||||
require_once dirname(__DIR__) . '/lib/DbHelper.php';
|
||||
|
||||
$failed = 0;
|
||||
|
||||
CloakPipelineTimer::init();
|
||||
CloakPipelineTimer::stage('阶段A');
|
||||
usleep(2000);
|
||||
CloakPipelineTimer::stage('阶段B');
|
||||
usleep(1000);
|
||||
$payload = CloakPipelineTimer::finish();
|
||||
|
||||
if (empty($payload['total_ms']) || $payload['total_ms'] <= 0) {
|
||||
echo "FAIL: total_ms\n";
|
||||
$failed++;
|
||||
}
|
||||
if (count($payload['stages'] ?? []) < 2) {
|
||||
echo "FAIL: stages count\n";
|
||||
$failed++;
|
||||
}
|
||||
|
||||
$text = cloak_visitor_log_format_timing(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
if ($text === '' || strpos($text, '阶段A') === false) {
|
||||
echo "FAIL: format timing text\n";
|
||||
$failed++;
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => $failed === 0], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
exit($failed > 0 ? 1 : 0);
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* 请求 URL 回归(CLI 输出 JSON)
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
require_once $root . '/lib/CloakHttpHelpers.php';
|
||||
|
||||
$tests = [];
|
||||
|
||||
$_SERVER = [
|
||||
'HTTP_HOST' => 'shili.buzz',
|
||||
'REQUEST_URI' => '/BXSXX.php',
|
||||
'HTTPS' => 'off',
|
||||
'SERVER_PORT' => '443',
|
||||
'HTTP_X_FORWARDED_PROTO' => 'https',
|
||||
];
|
||||
$tests['https_from_forwarded_proto'] = cloak_current_request_url() === 'https://shili.buzz/BXSXX.php';
|
||||
|
||||
$_SERVER = [
|
||||
'HTTP_HOST' => 'shili.buzz',
|
||||
'REQUEST_URI' => '/BXSXX.php?ttclid=abc&clk_sig=x',
|
||||
'HTTPS' => 'on',
|
||||
];
|
||||
$tests['full_url_with_query'] = cloak_current_request_url() === 'https://shili.buzz/BXSXX.php?ttclid=abc&clk_sig=x';
|
||||
|
||||
$_SERVER = [
|
||||
'HTTP_HOST' => 'shili.buzz',
|
||||
'REQUEST_URI' => '/BXSXX.php',
|
||||
'HTTPS' => 'on',
|
||||
];
|
||||
$tests['no_bare_question_mark'] = substr(cloak_current_request_url(), -1) !== '?';
|
||||
|
||||
$_SERVER = [
|
||||
'HTTP_HOST' => 'shili.buzz',
|
||||
'SCRIPT_NAME' => '/index.php',
|
||||
'QUERY_STRING' => 'fbclid=test',
|
||||
'HTTPS' => 'on',
|
||||
];
|
||||
unset($_SERVER['REQUEST_URI']);
|
||||
$tests['fallback_query_string'] = cloak_current_request_url() === 'https://shili.buzz/index.php?fbclid=test';
|
||||
|
||||
echo json_encode(['ok' => !in_array(false, $tests, true), 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Session 真实访客缓存辅助验证
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__) . '/lib/Cloak/VisitorRepository.php';
|
||||
|
||||
$_SESSION = [];
|
||||
$_SERVER['HTTP_HOST'] = 'cloaka.test';
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
|
||||
cloak_session_mark_real_visitor();
|
||||
|
||||
$ok = (
|
||||
($_SESSION['check_result'] ?? '') === 'true'
|
||||
&& ($_SESSION['visit_to_2'] ?? '') === '2'
|
||||
&& ($_SESSION['visit_to_3'] ?? '') === '3'
|
||||
&& !empty($_SESSION['gcuid'])
|
||||
);
|
||||
|
||||
cloak_session_clear_real_visitor();
|
||||
|
||||
$cleared = !isset($_SESSION['check_result']);
|
||||
|
||||
echo json_encode(['ok' => $ok && $cleared], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
exit($ok && $cleared ? 0 : 1);
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* 虚拟设备检测规则引擎回归(CLI 输出 JSON)
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
|
||||
function render_dist_index_virtual($root, $virtualLevel)
|
||||
{
|
||||
$cmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
|
||||
'$root=' . var_export($root, true) . ';'
|
||||
. '$_GET=["c"=>"index"];'
|
||||
. 'include $root . "/cong.php";'
|
||||
. 'include $root . "/config/ConfigLoader.php";'
|
||||
. 'ConfigLoader::loadByName("index", $root);'
|
||||
. 'runkit7_constant_redefine("IS_VIRTUAL", ' . var_export((string) $virtualLevel, true) . ');'
|
||||
. 'ob_start();include $root."/dist/index.php";echo ob_get_clean();'
|
||||
) . ' 2>&1';
|
||||
return shell_exec($cmd);
|
||||
}
|
||||
|
||||
function render_dist_index_simple($root)
|
||||
{
|
||||
$_GET = ['c' => 'index'];
|
||||
ob_start();
|
||||
include $root . '/dist/index.php';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
$tests = [];
|
||||
|
||||
$offOut = render_dist_index_simple($root);
|
||||
$tests['off_no_engine'] = strpos($offOut, 'CloakVirtualDetect') === false;
|
||||
$tests['off_has_finish'] = strpos($offOut, 'cloakFinishFingerprint') !== false;
|
||||
|
||||
// 临时写入带 IS_VIRTUAL 的配置片段:直接 include 引擎文件测输出
|
||||
$engineOut = (function () use ($root) {
|
||||
ob_start();
|
||||
include $root . '/resources/virtual_device_detect.inc.php';
|
||||
return ob_get_clean();
|
||||
})();
|
||||
$tests['engine_has_mobile_only'] = strpos($engineOut, 'mobileOnly') !== false;
|
||||
$tests['engine_has_sensors'] = strpos($engineOut, 'checkMotionSensors') !== false;
|
||||
$tests['engine_no_performance_bench'] = strpos($engineOut, 'detectVMByPerformance') === false;
|
||||
$tests['engine_has_v13'] = strpos($engineOut, "'V13'") !== false;
|
||||
$tests['engine_has_v24'] = strpos($engineOut, "'V24'") !== false;
|
||||
|
||||
// 若 index 配置 IS_VIRTUAL>0 则 dist 会带引擎;否则仅验证 off 路径
|
||||
$onOut = render_dist_index_simple($root);
|
||||
$tests['dist_has_finish_fn'] = strpos($onOut, 'cloakFinishFingerprint') !== false;
|
||||
$tests['dist_no_old_inline'] = strpos($onOut, 'isVMByHardware') === false;
|
||||
$tests['dist_no_fatal'] = stripos($onOut, 'Fatal error') === false;
|
||||
|
||||
echo json_encode(['ok' => !in_array(false, $tests, true), 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 访客日志异步队列 Worker(建议 cron 每分钟执行)
|
||||
*
|
||||
* php tools/visitor_log_worker.php
|
||||
*/
|
||||
$root = dirname(__DIR__);
|
||||
require_once $root . '/cong.php';
|
||||
require_once $root . '/lib/Cloak/VisitorLogWorker.php';
|
||||
|
||||
$limit = 100;
|
||||
$rounds = 10;
|
||||
$total = ['processed' => 0, 'failed' => 0];
|
||||
|
||||
for ($i = 0; $i < $rounds; $i++) {
|
||||
$result = VisitorLogWorker::processBatch($limit);
|
||||
$total['processed'] += $result['processed'];
|
||||
$total['failed'] += $result['failed'];
|
||||
if ($result['processed'] === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'processed' => $total['processed'],
|
||||
'failed' => $total['failed'],
|
||||
], JSON_UNESCAPED_UNICODE) . "\n";
|
||||
}
|
||||
Reference in New Issue
Block a user