package com.novitechie.scan; import com.janetfilter.core.commons.DebugInfo; import com.janetfilter.core.enums.RuleType; import com.janetfilter.core.models.FilterRule; import com.janetfilter.core.plugin.PluginConfig; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class CanaryAutoScanner { private static final List DEFAULT_SCAN_PLUGINS = Arrays.asList( new FilterRule(RuleType.EQUAL, "izhangzhihao.rainbow.brackets")); private static final List DEFAULT_SCAN_PACKAGES = Arrays.asList( new FilterRule(RuleType.PREFIX, "com.janetfilter."), new FilterRule(RuleType.PREFIX, "com.novitechie.")); private CanaryAutoScanner() { } public static List scan(PluginConfig config) { return scanWithResult(config).getClassRules(); } public static AutoScanResult scanWithResult(PluginConfig config) { try { return doScan(config); } catch (Exception e) { DebugInfo.warn("[PRIVACY-SCAN] Auto scan failed; continue without generated rules", e); return AutoScanResult.empty(); } catch (LinkageError e) { DebugInfo.warn("[PRIVACY-SCAN] Auto scan unavailable; continue without generated rules", e); return AutoScanResult.empty(); } } private static AutoScanResult doScan(PluginConfig config) { List pluginRules = rules(config, "Auto_Scan_Plugin", DEFAULT_SCAN_PLUGINS); List packageRules = rules(config, "Auto_Scan_Package", DEFAULT_SCAN_PACKAGES); List excludeRules = emptyIfNull(config.getBySection("Auto_Scan_Exclude")); if (pluginRules.isEmpty() || packageRules.isEmpty()) { DebugInfo.warn("[PRIVACY-SCAN] Auto scan disabled: plugin or package rules are empty"); return AutoScanResult.empty(); } List scanTargets = JbDirectoryScanner.getPluginDir(config); if (scanTargets.isEmpty()) { DebugInfo.warn("[PRIVACY-SCAN] No plugin scan targets found"); return AutoScanResult.empty(); } DebugInfo.output("[PRIVACY-SCAN] Plugin targets: " + fileSummary(scanTargets)); DebugInfo.output("[PRIVACY-SCAN] Scan_Plugin: " + ruleSummary(pluginRules)); DebugInfo.output("[PRIVACY-SCAN] Scan_Package: " + ruleSummary(packageRules)); DebugInfo.output("[PRIVACY-SCAN] Exclude_Package: " + ruleSummary(excludeRules)); CanaryScanner.ScanResult result = CanaryScanner.scan(scanTargets, pluginRules, packageRules, excludeRules); List classNames = result.getClassNames(); List classRules = new ArrayList<>(classNames.size()); DebugInfo.output("[PRIVACY-SCAN] Found " + classNames.size() + " canary classes"); for (String className : classNames) { classRules.add(FilterRule.of("EQUAL", className)); DebugInfo.output("[PRIVACY-SCAN] - " + className); } DebugInfo.output("[PRIVACY-SCAN] Marker resource exists: " + result.isMarkerResourceExists()); return new AutoScanResult(classRules, result.isMarkerResourceExists()); } private static List rules(PluginConfig config, String section, List defaultValue) { List configRules = config.getBySection(section); if (hasSection(config, section)) { return emptyIfNull(configRules); } if (defaultValue == null) { return Collections.emptyList(); } return defaultValue; } private static boolean hasSection(PluginConfig config, String section) { return config.getData() != null && config.getData().containsKey(section); } private static List emptyIfNull(List rules) { return rules == null ? Collections.emptyList() : rules; } private static String fileSummary(List files) { StringBuilder summary = new StringBuilder(); for (File file : files) { if (summary.length() > 0) { summary.append(", "); } summary.append(file.getAbsolutePath()); } return summary.toString(); } private static String ruleSummary(List rules) { if (rules == null || rules.isEmpty()) { return "(none)"; } StringBuilder summary = new StringBuilder(); for (FilterRule rule : rules) { if (summary.length() > 0) { summary.append(", "); } summary.append(rule); } return summary.toString(); } public static class AutoScanResult { private final List classRules; private final boolean markerResourceExists; private AutoScanResult(List classRules, boolean markerResourceExists) { this.classRules = classRules == null ? Collections.emptyList() : classRules; this.markerResourceExists = markerResourceExists; } public static AutoScanResult empty() { return new AutoScanResult(Collections.emptyList(), false); } public List getClassRules() { return new ArrayList<>(classRules); } public boolean isMarkerResourceExists() { return markerResourceExists; } } }