From 88cbdac735929c4ca0dc9b36a2180a47a366dc9d Mon Sep 17 00:00:00 2001 From: zpj80231 Date: Mon, 8 Jun 2026 16:54:22 +0800 Subject: [PATCH] feat: support plugin update zip scan and .vmoptions read interception --- pom.xml | 2 +- privacy.conf.example | 19 ++ .../FileInputStreamTransformer.java | 44 +++ .../java/com/novitechie/FilesTransformer.java | 63 +++++ .../java/com/novitechie/PrivacyPlugin.java | 12 +- .../com/novitechie/SystemTransformer.java | 3 +- .../com/novitechie/rules/StackTraceRule.java | 39 ++- .../java/com/novitechie/rules/SystemRule.java | 17 +- .../novitechie/rules/VMOptionsReadRule.java | 79 ++++++ .../novitechie/scan/CanaryAutoScanner.java | 70 +++-- .../com/novitechie/scan/CanaryScanner.java | 257 +++++++++++------- .../novitechie/scan/JbDirectoryScanner.java | 226 +++++++++------ .../java/com/novitechie/scan/RuleMerger.java | 51 ++-- 13 files changed, 646 insertions(+), 236 deletions(-) create mode 100644 src/main/java/com/novitechie/FileInputStreamTransformer.java create mode 100644 src/main/java/com/novitechie/FilesTransformer.java create mode 100644 src/main/java/com/novitechie/rules/VMOptionsReadRule.java diff --git a/pom.xml b/pom.xml index 4fbafb6..9071a85 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.novitechie plugin-privacy - 1.0.0 + 1.1.0 8 diff --git a/privacy.conf.example b/privacy.conf.example index 62346e1..5ee12c5 100644 --- a/privacy.conf.example +++ b/privacy.conf.example @@ -43,3 +43,22 @@ EQUAL,com.janetfilter.core.utils.StringUtils [Ignore_Resource] EQUAL,/com/janetfilter/core/utils/StringUtils.class # EQUAL,/com/janetfilter/core/utils/DateUtils.class + +[Trace_Check_Package] +# Short method names are only treated as suspicious when the stack class name matches these rules. +# PREFIX,com.example.plugin. + +[Auto_Scan_Plugin] +# Auto scan matches plugin ids first, then hides matched canary classes from those plugins. +# Leave this section empty to disable default auto scan plugin rules. + +[Auto_Scan_Package] +# Leave this section empty to disable default auto scan package rules. + +[Auto_Scan_Exclude] +# PREFIX,com.example.safe. + +[Auto_Scan_Ide_Plugin_Path] +# Match current idea.paths.selector on the left side, then scan the configured plugin root. +# EQUAL,IntelliJIdea2026.2=D:\Develop\Jetbrains\Settings\.IntelliJIdea\config\plugins +# EQUAL,IntelliJIdea2025.3=D:\Develop\Jetbrains\Settings\.IntelliJIdea2\config\plugins \ No newline at end of file diff --git a/src/main/java/com/novitechie/FileInputStreamTransformer.java b/src/main/java/com/novitechie/FileInputStreamTransformer.java new file mode 100644 index 0000000..e29e3ed --- /dev/null +++ b/src/main/java/com/novitechie/FileInputStreamTransformer.java @@ -0,0 +1,44 @@ +package com.novitechie; + +import com.janetfilter.core.plugin.MyTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import static org.objectweb.asm.Opcodes.*; + +public class FileInputStreamTransformer implements MyTransformer { + + private static final String RULE = "com/novitechie/rules/VMOptionsReadRule"; + + @Override + public String getHookClassName() { + return "java/io/FileInputStream"; + } + + @Override + public byte[] transform(String className, byte[] classBytes, int order) throws Exception { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + for (MethodNode method : node.methods) { + if ("".equals(method.name) && "(Ljava/io/File;)V".equals(method.desc)) { + insertRedirect(method, "redirectFile", "(Ljava/io/File;)Ljava/io/File;"); + } else if ("".equals(method.name) && "(Ljava/lang/String;)V".equals(method.desc)) { + insertRedirect(method, "redirectPath", "(Ljava/lang/String;)Ljava/lang/String;"); + } + } + ClassWriter writer = new SafeClassWriter(reader, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + return writer.toByteArray(); + } + + private static void insertRedirect(MethodNode method, String hookName, String hookDesc) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 1)); + list.add(new MethodInsnNode(INVOKESTATIC, RULE, hookName, hookDesc, false)); + list.add(new VarInsnNode(ASTORE, 1)); + method.instructions.insert(list); + } + +} diff --git a/src/main/java/com/novitechie/FilesTransformer.java b/src/main/java/com/novitechie/FilesTransformer.java new file mode 100644 index 0000000..194ad16 --- /dev/null +++ b/src/main/java/com/novitechie/FilesTransformer.java @@ -0,0 +1,63 @@ +package com.novitechie; + +import com.janetfilter.core.plugin.MyTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import static org.objectweb.asm.Opcodes.*; + +public class FilesTransformer implements MyTransformer { + + private static final String RULE = "com/novitechie/rules/VMOptionsReadRule"; + private static final String PATH_DESC = "(Ljava/nio/file/Path;)Z"; + + @Override + public String getHookClassName() { + return "java/nio/file/Files"; + } + + @Override + public byte[] transform(String className, byte[] classBytes, int order) throws Exception { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + for (MethodNode method : node.methods) { + if ("readAllBytes".equals(method.name) && "(Ljava/nio/file/Path;)[B".equals(method.desc)) { + insertPathGuard(method, "emptyBytes", "(Ljava/nio/file/Path;)[B"); + } else if ("readString".equals(method.name) + && ("(Ljava/nio/file/Path;)Ljava/lang/String;".equals(method.desc) + || "(Ljava/nio/file/Path;Ljava/nio/charset/Charset;)Ljava/lang/String;".equals(method.desc))) { + insertPathGuard(method, "emptyString", "(Ljava/nio/file/Path;)Ljava/lang/String;"); + } else if ("readAllLines".equals(method.name) + && ("(Ljava/nio/file/Path;)Ljava/util/List;".equals(method.desc) + || "(Ljava/nio/file/Path;Ljava/nio/charset/Charset;)Ljava/util/List;".equals(method.desc))) { + insertPathGuard(method, "emptyLines", "(Ljava/nio/file/Path;)Ljava/util/List;"); + } else if ("newInputStream".equals(method.name) + && "(Ljava/nio/file/Path;[Ljava/nio/file/OpenOption;)Ljava/io/InputStream;".equals(method.desc)) { + insertPathGuard(method, "emptyInputStream", "(Ljava/nio/file/Path;)Ljava/io/InputStream;"); + } else if ("newBufferedReader".equals(method.name) + && ("(Ljava/nio/file/Path;)Ljava/io/BufferedReader;".equals(method.desc) + || "(Ljava/nio/file/Path;Ljava/nio/charset/Charset;)Ljava/io/BufferedReader;".equals(method.desc))) { + insertPathGuard(method, "emptyBufferedReader", "(Ljava/nio/file/Path;)Ljava/io/BufferedReader;"); + } + } + ClassWriter writer = new SafeClassWriter(reader, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + return writer.toByteArray(); + } + + private static void insertPathGuard(MethodNode method, String hookName, String hookDesc) { + InsnList list = new InsnList(); + LabelNode continueLabel = new LabelNode(); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKESTATIC, RULE, "shouldHide", PATH_DESC, false)); + list.add(new JumpInsnNode(IFEQ, continueLabel)); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKESTATIC, RULE, hookName, hookDesc, false)); + list.add(new InsnNode(ARETURN)); + list.add(continueLabel); + method.instructions.insert(list); + } + +} diff --git a/src/main/java/com/novitechie/PrivacyPlugin.java b/src/main/java/com/novitechie/PrivacyPlugin.java index 0a22fd1..a0e008d 100644 --- a/src/main/java/com/novitechie/PrivacyPlugin.java +++ b/src/main/java/com/novitechie/PrivacyPlugin.java @@ -5,10 +5,7 @@ import com.janetfilter.core.models.FilterRule; import com.janetfilter.core.plugin.MyTransformer; import com.janetfilter.core.plugin.PluginConfig; import com.janetfilter.core.plugin.PluginEntry; -import com.novitechie.rules.IdeaPluginRule; -import com.novitechie.rules.LoadClassRule; -import com.novitechie.rules.ResourceRule; -import com.novitechie.rules.SystemRule; +import com.novitechie.rules.*; import com.novitechie.scan.CanaryAutoScanner; import com.novitechie.scan.RuleMerger; @@ -22,13 +19,16 @@ public class PrivacyPlugin implements PluginEntry { CanaryAutoScanner.AutoScanResult autoScanResult = CanaryAutoScanner.scanWithResult(config); List autoScanClassRules = autoScanResult.getClassRules(); List autoScanResourceRules = RuleMerger.toResourceRules(autoScanClassRules); - List hideResourceRules = RuleMerger.adjustMarkerResourceRule(config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists()); + List hideResourceRules = RuleMerger.adjustMarkerResourceRule( + config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists()); List ignoreClassRules = RuleMerger.merge(config.getBySection("Ignore_Class"), autoScanClassRules); List ignoreResourceRules = RuleMerger.merge(config.getBySection("Ignore_Resource"), autoScanResourceRules); + IdeaPluginRule.initRules(config.getBySection("Hide_Plugin")); LoadClassRule.initRules(config.getBySection("Hide_Package"), ignoreClassRules); ResourceRule.initRules(hideResourceRules, ignoreResourceRules); SystemRule.initRules(config.getBySection("Hide_Env"), config.getBySection("Hide_Property")); + StackTraceRule.initRules(config.getBySection("Trace_Check_Package")); } @Override @@ -46,6 +46,8 @@ public class PrivacyPlugin implements PluginEntry { return Arrays.asList( // new CollectionsTransformer(), new VMOptionsTransformer(), + new FilesTransformer(), + new FileInputStreamTransformer(), new PluginClassLoaderTransformer(), new LicensingFacadeTransformer(), new PluginManagerCoreTransformer(), diff --git a/src/main/java/com/novitechie/SystemTransformer.java b/src/main/java/com/novitechie/SystemTransformer.java index 66e3bf7..5405053 100644 --- a/src/main/java/com/novitechie/SystemTransformer.java +++ b/src/main/java/com/novitechie/SystemTransformer.java @@ -39,7 +39,8 @@ public class SystemTransformer implements MyTransformer { list.add(new VarInsnNode(ALOAD, 0)); list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/SystemRule", "checkProperty", "(Ljava/lang/String;)Z", false)); list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new InsnNode(ACONST_NULL)); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/SystemRule", "hookProperty", "(Ljava/lang/String;)Ljava/lang/String;", false)); list.add(new InsnNode(ARETURN)); list.add(L0); m.instructions.insert(list); diff --git a/src/main/java/com/novitechie/rules/StackTraceRule.java b/src/main/java/com/novitechie/rules/StackTraceRule.java index 27628ad..613fc46 100644 --- a/src/main/java/com/novitechie/rules/StackTraceRule.java +++ b/src/main/java/com/novitechie/rules/StackTraceRule.java @@ -1,19 +1,56 @@ package com.novitechie.rules; import com.janetfilter.core.commons.DebugInfo; +import com.janetfilter.core.models.FilterRule; import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.regex.Pattern; public class StackTraceRule { private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("\\A\\p{ASCII}*\\z"); + private static List traceCheckPackageRules = Collections.emptyList(); + + public static void initRules(List traceCheckPackageRules) { + StackTraceRule.traceCheckPackageRules = traceCheckPackageRules == null + ? Collections.emptyList() + : traceCheckPackageRules; + } public static boolean check() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { - if (!PACKAGE_NAME_PATTERN.matcher(stackTraceElement.getMethodName()).matches()) { + if (isSuspiciousFrame(stackTraceElement)) { + return true; + } + } + return false; + } + + static boolean isSuspiciousFrame(StackTraceElement stackTraceElement) { + String methodName = stackTraceElement.getMethodName(); + if (!PACKAGE_NAME_PATTERN.matcher(methodName).matches()) { + return true; + } + if (methodName.length() <= 1) { + String className = stackTraceElement.getClassName(); + if (checkTracePackage(className)) { + // DebugInfo.info("short method frame, className: " + className + ", methodName: " + methodName); + return true; + } + } + return false; + } + + private static boolean checkTracePackage(String className) { + if (className == null) { + return false; + } + for (FilterRule rule : traceCheckPackageRules) { + if (rule != null && rule.test(className)) { return true; } } diff --git a/src/main/java/com/novitechie/rules/SystemRule.java b/src/main/java/com/novitechie/rules/SystemRule.java index 40e7f94..a7cc492 100644 --- a/src/main/java/com/novitechie/rules/SystemRule.java +++ b/src/main/java/com/novitechie/rules/SystemRule.java @@ -4,6 +4,7 @@ import com.janetfilter.core.commons.DebugInfo; import com.janetfilter.core.models.FilterRule; import com.novitechie.LogUtil; +import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -12,6 +13,8 @@ import java.util.List; */ public class SystemRule { + private static final String JB_VM_OPTIONS_FILE = "jb.vmOptionsFile"; + private static List hideEnvRules = Collections.emptyList(); private static List hidePropertyRules = Collections.emptyList(); @@ -31,7 +34,7 @@ public class SystemRule { } public static boolean checkProperty(String name) { - if (checkHideProperty(name) && StackTraceRule.check()) { + if ((checkVMOptionsProperty(name) || checkHideProperty(name)) && StackTraceRule.check()) { DebugInfo.output("======================Hide Property: " + name); LogUtil.printStackTrace(); return true; @@ -39,6 +42,14 @@ public class SystemRule { return false; } + public static String hookProperty(String name) { + if (checkVMOptionsProperty(name)) { + Path path = VMOptionsRule.hook(); + return path == null ? null : path.toString(); + } + return null; + } + private static boolean checkHideEnv(String name) { return hideEnvRules.stream().anyMatch(rule -> rule.test(name)); } @@ -46,4 +57,8 @@ public class SystemRule { private static boolean checkHideProperty(String name) { return hidePropertyRules.stream().anyMatch(rule -> rule.test(name)); } + + private static boolean checkVMOptionsProperty(String name) { + return JB_VM_OPTIONS_FILE.equals(name); + } } diff --git a/src/main/java/com/novitechie/rules/VMOptionsReadRule.java b/src/main/java/com/novitechie/rules/VMOptionsReadRule.java new file mode 100644 index 0000000..acf8419 --- /dev/null +++ b/src/main/java/com/novitechie/rules/VMOptionsReadRule.java @@ -0,0 +1,79 @@ +package com.novitechie.rules; + +import com.janetfilter.core.commons.DebugInfo; + +import java.io.*; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class VMOptionsReadRule { + + private static final String VMOPTIONS_SUFFIX = ".vmoptions"; + + public static boolean shouldHide(Path path) { + return isVMOptions(path) && StackTraceRule.check(); + } + + public static File redirectFile(File file) { + if (isVMOptions(file) && StackTraceRule.check()) { + return hookFile(file); + } + return file; + } + + public static String redirectPath(String path) { + if (isVMOptions(path) && StackTraceRule.check()) { + return hookFile(new File(path)).getPath(); + } + return path; + } + + public static byte[] emptyBytes(Path path) { + log(path); + return new byte[0]; + } + + public static String emptyString(Path path) { + log(path); + return ""; + } + + public static List emptyLines(Path path) { + log(path); + return Collections.emptyList(); + } + + public static InputStream emptyInputStream(Path path) { + log(path); + return new ByteArrayInputStream(new byte[0]); + } + + public static BufferedReader emptyBufferedReader(Path path) { + log(path); + return new BufferedReader(new StringReader("")); + } + + private static boolean isVMOptions(Path path) { + return path != null && isVMOptions(path.toString()); + } + + private static boolean isVMOptions(File file) { + return file != null && isVMOptions(file.getPath()); + } + + private static boolean isVMOptions(String path) { + return path != null && path.toLowerCase(Locale.ROOT).endsWith(VMOPTIONS_SUFFIX); + } + + private static File hookFile(File fallback) { + Path path = VMOptionsRule.hook(); + return path == null ? fallback : path.toFile(); + } + + private static void log(Path path) { + DebugInfo.output("======================Hide VMOptions read: " + path); + } + +} diff --git a/src/main/java/com/novitechie/scan/CanaryAutoScanner.java b/src/main/java/com/novitechie/scan/CanaryAutoScanner.java index 8aa86c1..ebc5513 100644 --- a/src/main/java/com/novitechie/scan/CanaryAutoScanner.java +++ b/src/main/java/com/novitechie/scan/CanaryAutoScanner.java @@ -11,56 +11,54 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -public final class CanaryAutoScanner { - private static final List DEFAULT_SCAN_PLUGINS = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.EQUAL, "izhangzhihao.rainbow.brackets"))); - private static final List DEFAULT_SCAN_PACKAGES = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.PREFIX, "com.janetfilter."), new FilterRule(RuleType.PREFIX, "com.novitechie."))); +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 final 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(this.classRules); - } - - public boolean isMarkerResourceExists() { - return this.markerResourceExists; - } - } - 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()); @@ -116,4 +114,26 @@ public final class CanaryAutoScanner { } 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; + } + } } diff --git a/src/main/java/com/novitechie/scan/CanaryScanner.java b/src/main/java/com/novitechie/scan/CanaryScanner.java index 01645c3..dead926 100644 --- a/src/main/java/com/novitechie/scan/CanaryScanner.java +++ b/src/main/java/com/novitechie/scan/CanaryScanner.java @@ -10,42 +10,50 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class CanaryScanner { -public final class CanaryScanner { private static final String JAR_SUFFIX = ".jar"; + private static final String ZIP_SUFFIX = ".zip"; private static final String CLASS_SUFFIX = ".class"; private static final String PLUGIN_XML = "META-INF/plugin.xml"; private static final String MARKER_RESOURCE = "6c81ec87e55d331c267262e892427a3d93d76683.txt"; private static final String LIB_DIR = "lib"; - private static final Pattern COMMENT_PATTERN = Pattern.compile("", 32); private static final String ID_TAG = "id"; - private static final Pattern TAG_PATTERN = Pattern.compile("<" + Pattern.quote(ID_TAG) + "(?:\\s[^>]*)?>(.*?)", 32); + private static final Pattern COMMENT_PATTERN = Pattern.compile("", Pattern.DOTALL); + public static final Pattern TAG_PATTERN = Pattern.compile( + "<" + Pattern.quote(ID_TAG) + "(?:\\s[^>]*)?>(.*?)", + Pattern.DOTALL); private CanaryScanner() { } - public static final class ScanResult { - private final Set classNames = new LinkedHashSet(); + public static class ScanResult { + private final Set classNames = new LinkedHashSet<>(); private boolean markerResourceExists; public List getClassNames() { - return new ArrayList(this.classNames); + return new ArrayList<>(classNames); } public boolean isMarkerResourceExists() { - return this.markerResourceExists; + return markerResourceExists; } - public void addClassName(String className) { - this.classNames.add(className); + private void addClassName(String className) { + classNames.add(className); } - public void markResourceExists() { - this.markerResourceExists = true; + private void markResourceExists() { + markerResourceExists = true; } } - public static ScanResult scan(List scanTargets, List scanPluginRules, List scanPackageRules, List excludePackageRules) { + public static ScanResult scan(List scanTargets, List scanPluginRules, + List scanPackageRules, List excludePackageRules) { ScanResult result = new ScanResult(); if (scanTargets == null || scanTargets.isEmpty()) { return result; @@ -58,45 +66,44 @@ public final class CanaryScanner { return result; } - private static void scanTarget(File target, List scanPluginRules, List scanPackageRules, List excludePackageRules, ScanResult result) { + private static void scanTarget(File target, List scanPluginRules, + List scanPackageRules, List excludePackageRules, + ScanResult result) { if (isJar(target)) { processJarIfMatches(target, scanPluginRules, scanPackageRules, excludePackageRules, result); return; } + + if (isZip(target)) { + processZipIfMatches(target, scanPluginRules, scanPackageRules, excludePackageRules, result); + return; + } + if (target == null || !target.isDirectory()) { return; } + File xmlFile = new File(target, PLUGIN_XML); if (xmlFile.isFile()) { String pluginId = parsePluginIdFromXml(xmlFile); if (matchRules(pluginId, scanPluginRules)) { scanPluginDirectoryContent(target, scanPackageRules, excludePackageRules, result); - return; } return; } + File libDir = new File(target, LIB_DIR); - File[] jars = listJars(libDir); - if (libDir.isDirectory() && jars != null && jars.length > 0) { - String pluginId2 = resolvePluginIdFromJars(jars); - if (matchRules(pluginId2, scanPluginRules)) { - scanJars(jars, scanPackageRules, excludePackageRules, result); + if (libDir.isDirectory()) { + File[] jars = listJars(libDir); + if (jars != null && jars.length > 0) { + String pluginId = resolvePluginIdFromJars(jars); + if (matchRules(pluginId, scanPluginRules)) { + scanJars(jars, scanPackageRules, excludePackageRules, result); + } return; } - return; } - scanSubFiles(target, scanPluginRules, scanPackageRules, excludePackageRules, result); - } - private static void scanPluginDirectoryContent(File pluginDir, List scanPackageRules, List excludePackageRules, ScanResult result) { - File libDir = new File(pluginDir, LIB_DIR); - if (!libDir.isDirectory()) { - return; - } - scanJars(listJars(libDir), scanPackageRules, excludePackageRules, result); - } - - private static void scanSubFiles(File target, List scanPluginRules, List scanPackageRules, List excludePackageRules, ScanResult result) { File[] subFiles = target.listFiles(); if (subFiles == null) { return; @@ -106,16 +113,21 @@ public final class CanaryScanner { } } - private static File[] listJars(File dir) { - if (dir == null || !dir.isDirectory()) { - return null; + private static void scanPluginDirectoryContent(File pluginDir, List scanPackageRules, + List excludePackageRules, ScanResult result) { + File libDir = new File(pluginDir, LIB_DIR); + if (!libDir.isDirectory()) { + return; } - return dir.listFiles((file, name) -> { - return name.endsWith(JAR_SUFFIX); - }); + scanJars(listJars(libDir), scanPackageRules, excludePackageRules, result); } - private static void scanJars(File[] jars, List scanPackageRules, List excludePackageRules, ScanResult result) { + private static File[] listJars(File dir) { + return dir.listFiles((d, name) -> name.endsWith(JAR_SUFFIX)); + } + + private static void scanJars(File[] jars, List scanPackageRules, + List excludePackageRules, ScanResult result) { if (jars == null) { return; } @@ -124,57 +136,45 @@ public final class CanaryScanner { } } - private static void processJarIfMatches(File jarFile, List scanPluginRules, List scanPackageRules, List excludePackageRules, ScanResult result) { - try { - JarFile jar = new JarFile(jarFile); + private static void processJarIfMatches(File jarFile, List scanPluginRules, + List scanPackageRules, List excludePackageRules, + ScanResult result) { + try (JarFile jar = new JarFile(jarFile)) { String pluginId = parsePluginId(jar); if (matchRules(pluginId, scanPluginRules)) { extractClasses(jar, scanPackageRules, excludePackageRules, result); } - jar.close(); - } catch (IOException e) { - DebugInfo.warn("[PRIVACY-SCAN] scan jar failed: " + jarFile, e); + } catch (IOException ignored) { + DebugInfo.warn("[PRIVACY-SCAN] scan jar failed: " + jarFile, ignored); } } - private static void scanJarClasses(File jarFile, List scanPackageRules, List excludePackageRules, ScanResult result) { - try { - JarFile jar = new JarFile(jarFile); + private static void scanJarClasses(File jarFile, List scanPackageRules, + List excludePackageRules, ScanResult result) { + try (JarFile jar = new JarFile(jarFile)) { extractClasses(jar, scanPackageRules, excludePackageRules, result); - jar.close(); - } catch (IOException e) { - DebugInfo.warn("[PRIVACY-SCAN] scan jar classes failed: " + jarFile, e); + } catch (IOException ignored) { + DebugInfo.warn("[PRIVACY-SCAN] scan jar classes failed: " + jarFile, ignored); } } private static String resolvePluginIdFromJars(File[] jars) { - JarFile jar; - String pluginId; - if (jars == null) { - return null; - } for (File jarFile : jars) { - try { - jar = new JarFile(jarFile); - pluginId = parsePluginId(jar); - if (pluginId != null) { - jar.close(); - return pluginId; + try (JarFile jar = new JarFile(jarFile)) { + String id = parsePluginId(jar); + if (id != null) { + return id; } - jar.close(); - } catch (IOException e) { - DebugInfo.warn("[PRIVACY-SCAN] resolve plugin id failed: " + jarFile, e); + } catch (IOException ignored) { + // 轮询下一个包 } } return null; } private static String parsePluginIdFromXml(File xmlFile) { - try { - FileInputStream input = new FileInputStream(xmlFile); - String strExtractXmlTag = extractXmlTag(readUtf8(input)); - input.close(); - return strExtractXmlTag; + try (FileInputStream fis = new FileInputStream(xmlFile)) { + return extractXmlTag(readUtf8(fis)); } catch (IOException e) { DebugInfo.warn("[PRIVACY-SCAN] parse plugin xml failed: " + xmlFile, e); return null; @@ -186,42 +186,29 @@ public final class CanaryScanner { if (entry == null) { return null; } - InputStream input = jar.getInputStream(entry); - try { - String strExtractXmlTag = extractXmlTag(readUtf8(input)); - input.close(); - return strExtractXmlTag; - } catch (Throwable th3) { - if (input != null) { - input.close(); - } - throw th3; + try (InputStream is = jar.getInputStream(entry)) { + return extractXmlTag(readUtf8(is)); } } private static String readUtf8(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(1024); byte[] chunk = new byte[1024]; - while (true) { - int size = input.read(chunk); - if (size != -1) { - output.write(chunk, 0, size); - } else { - return new String(output.toByteArray(), StandardCharsets.UTF_8); - } + int read; + while ((read = input.read(chunk)) != -1) { + output.write(chunk, 0, read); } + return new String(output.toByteArray(), StandardCharsets.UTF_8); } private static String extractXmlTag(String content) { String withoutComments = COMMENT_PATTERN.matcher(content).replaceAll(""); Matcher matcher = TAG_PATTERN.matcher(withoutComments); - if (matcher.find()) { - return matcher.group(1).trim(); - } - return null; + return matcher.find() ? matcher.group(1).trim() : null; } - private static void extractClasses(JarFile jar, List scanPackageRules, List excludePackageRules, ScanResult result) { + private static void extractClasses(JarFile jar, List scanPackageRules, + List excludePackageRules, ScanResult result) { Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); @@ -229,11 +216,12 @@ public final class CanaryScanner { if (MARKER_RESOURCE.equals(name)) { result.markResourceExists(); } - if (name.endsWith(CLASS_SUFFIX)) { - String className = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.'); - if (matchRules(className, scanPackageRules) && !matchRules(className, excludePackageRules)) { - result.addClassName(className); - } + if (!name.endsWith(CLASS_SUFFIX)) { + continue; + } + String className = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.'); + if (matchRules(className, scanPackageRules) && !matchRules(className, excludePackageRules)) { + result.addClassName(className); } } } @@ -242,6 +230,83 @@ public final class CanaryScanner { return file != null && file.isFile() && file.getName().endsWith(JAR_SUFFIX); } + private static boolean isZip(File file) { + return file != null && file.isFile() && file.getName().endsWith(ZIP_SUFFIX); + } + + private static void processZipIfMatches(File zipFile, List scanPluginRules, + List scanPackageRules, List excludePackageRules, + ScanResult result) { + try (ZipFile zip = new ZipFile(zipFile)) { + String pluginId = resolvePluginIdFromZip(zip); + if (!matchRules(pluginId, scanPluginRules)) { + return; + } + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(JAR_SUFFIX)) { + continue; + } + try (InputStream is = zip.getInputStream(entry)) { + extractClassesFromJarStream(is, scanPackageRules, excludePackageRules, result); + } + } + } catch (IOException ignored) { + // 损坏或非标准 zip,忽略 + } + } + + private static String resolvePluginIdFromZip(ZipFile zip) { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(JAR_SUFFIX)) { + continue; + } + try (InputStream is = zip.getInputStream(entry)) { + String id = parsePluginIdFromJarStream(is); + if (id != null) { + return id; + } + } catch (IOException ignored) { + // 跳过损坏的内层 jar + } + } + return null; + } + + private static String parsePluginIdFromJarStream(InputStream jarStream) throws IOException { + ZipInputStream zis = new ZipInputStream(jarStream); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (PLUGIN_XML.equals(entry.getName())) { + return extractXmlTag(readUtf8(zis)); + } + } + return null; + } + + private static void extractClassesFromJarStream(InputStream jarStream, List scanPackageRules, + List excludePackageRules, ScanResult result) + throws IOException { + ZipInputStream zis = new ZipInputStream(jarStream); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String name = entry.getName(); + if (MARKER_RESOURCE.equals(name)) { + result.markResourceExists(); + } + if (!name.endsWith(CLASS_SUFFIX)) { + continue; + } + String className = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.'); + if (matchRules(className, scanPackageRules) && !matchRules(className, excludePackageRules)) { + result.addClassName(className); + } + } + } + private static boolean matchRules(String value, List rules) { if (value == null || rules == null || rules.isEmpty()) { return false; diff --git a/src/main/java/com/novitechie/scan/JbDirectoryScanner.java b/src/main/java/com/novitechie/scan/JbDirectoryScanner.java index 05c9dea..b6b2134 100644 --- a/src/main/java/com/novitechie/scan/JbDirectoryScanner.java +++ b/src/main/java/com/novitechie/scan/JbDirectoryScanner.java @@ -13,45 +13,49 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; -public final class JbDirectoryScanner { +public class JbDirectoryScanner { + private static final String PATH_MANAGER_CLASS = "com.intellij.openapi.application.PathManager"; private static final String IDEA_PATHS_SELECTOR_PROPERTY = "idea.paths.selector"; private static final String IDEA_PLUGIN_PATH_SECTION = "Auto_Scan_Ide_Plugin_Path"; private static final String IDEA_PROPERTIES_ENV_VARIABLE = "IDEA_PROPERTIES"; - private static final String IDEA_PROPERTIES_FILE_PATH = "/bin/idea.properties"; + private static final String IDEA_PROPERTIES_FILE_PATH = "bin/idea.properties"; private static final String IDEA_PLUGINS_PATH_PROPERTY = "idea.plugins.path"; + private static final String ZIP_SUFFIX = ".zip"; private JbDirectoryScanner() { } public static List getPluginDir(PluginConfig config) { Class pathManagerClass = loadClass(PATH_MANAGER_CLASS); - List pluginPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION); - if (pluginPathRules == null || pluginPathRules.isEmpty()) { + List configuredPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION); + if (configuredPathRules == null || configuredPathRules.isEmpty()) { DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path rules found, fallback to PathManager"); return getPathManagerPluginDirs(pathManagerClass); } + String selector = resolveCurrentSelector(pathManagerClass); if (isBlank(selector)) { DebugInfo.warn("[PRIVACY-SCAN] Current IDEA selector not found"); return Collections.emptyList(); } + DebugInfo.output("[PRIVACY-SCAN] Current IDEA selector: " + selector); - Set pluginRoots = new LinkedHashSet<>(); - for (FilterRule pluginPathRule : pluginPathRules) { - addConfiguredPluginDir(pluginRoots, selector, pluginPathRule); + Set pluginDirs = new LinkedHashSet<>(); + for (FilterRule rule : configuredPathRules) { + addConfiguredPluginDir(pluginDirs, selector, rule); } - if (pluginRoots.isEmpty()) { + if (pluginDirs.isEmpty()) { DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path matched: " + selector); return Collections.emptyList(); } - DebugInfo.output("[PRIVACY-SCAN] Configured plugin roots: " + pluginRoots); - return new ArrayList<>(pluginRoots); + + DebugInfo.output("[PRIVACY-SCAN] Configured plugin roots: " + pluginDirs); + return new ArrayList<>(pluginDirs); } public static List getPluginDir() { - Class pathManagerClass = loadClass(PATH_MANAGER_CLASS); - return getPathManagerPluginDirs(pathManagerClass); + return getPathManagerPluginDirs(loadClass(PATH_MANAGER_CLASS)); } private static List getPathManagerPluginDirs(Class pathManagerClass) { @@ -59,78 +63,86 @@ public final class JbDirectoryScanner { DebugInfo.warn("[PRIVACY-SCAN] PathManager not found"); return Collections.emptyList(); } - Set pluginRoots = new LinkedHashSet<>(); - addDirFromIdeaProperties(pathManagerClass, pluginRoots); - if (pluginRoots.isEmpty()) { - addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsDir")); - addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsPath")); - DebugInfo.output("[PRIVACY-SCAN] PathManager plugin roots: " + pluginRoots); + + Set pluginDirs = new LinkedHashSet<>(); + addDirFromIdeaProperties(pathManagerClass, pluginDirs); + if (pluginDirs.isEmpty()) { + addDir(pluginDirs, invokePathManagerDir(pathManagerClass, "getPluginsDir")); + addDir(pluginDirs, invokePathManagerDir(pathManagerClass, "getPluginsPath")); } - return new ArrayList<>(pluginRoots); + + List targets = new ArrayList<>(pluginDirs); + List pendingZips = selectPendingUpdateZips(resolveStagingDir(pathManagerClass), pluginDirs); + targets.addAll(pendingZips); + + DebugInfo.output("[PRIVACY-SCAN] Plugin dirs: " + pluginDirs); + DebugInfo.output("[PRIVACY-SCAN] Pending update zips: " + pendingZips); + return targets; } - private static void addDirFromIdeaProperties(Class pathManagerClass, Set pluginRoots) { + private static void addDirFromIdeaProperties(Class pathManagerClass, Set pluginDirs) { File ideaPropertiesFile; - InputStream in = null; - try { - String ideaPropertiesEnvVar = System.getenv(IDEA_PROPERTIES_ENV_VARIABLE); - if (isBlank(ideaPropertiesEnvVar)) { - File homePath = invokePathManagerDir(pathManagerClass, "getHomePath"); - DebugInfo.output("[PRIVACY-SCAN] PathManager home path: " + homePath); - ideaPropertiesFile = new File(homePath, IDEA_PROPERTIES_FILE_PATH); - } else { - DebugInfo.output("[PRIVACY-SCAN] IDEA_PROPERTIES environment variable: " + ideaPropertiesEnvVar); - ideaPropertiesFile = new File(ideaPropertiesEnvVar); + String configuredPath = System.getenv(IDEA_PROPERTIES_ENV_VARIABLE); + if (isBlank(configuredPath)) { + File homePath = invokePathManagerDir(pathManagerClass, "getHomePath"); + DebugInfo.output("[PRIVACY-SCAN] PathManager home path: " + homePath); + if (homePath == null) { + return; } - if (ideaPropertiesFile.exists()) { - DebugInfo.output("[PRIVACY-SCAN] idea.properties file path: " + ideaPropertiesFile); - Properties ideaProperties = new Properties(); - in = Files.newInputStream(ideaPropertiesFile.toPath()); - ideaProperties.load(in); - String ideaPluginsPathProperty = PropertyUtil.getProperty(ideaProperties, IDEA_PLUGINS_PATH_PROPERTY); - DebugInfo.output("[PRIVACY-SCAN] IDEA plugins path property: " + ideaPluginsPathProperty); - if (isBlank(ideaPluginsPathProperty)) { - DebugInfo.warn("[PRIVACY-SCAN] IDEA plugins path not found in idea.properties"); - return; - } - addDir(pluginRoots, new File(ideaPluginsPathProperty)); + ideaPropertiesFile = new File(homePath, IDEA_PROPERTIES_FILE_PATH); + } else { + DebugInfo.output("[PRIVACY-SCAN] IDEA_PROPERTIES environment variable: " + configuredPath); + ideaPropertiesFile = new File(configuredPath); + } + + if (!ideaPropertiesFile.exists()) { + return; + } + + DebugInfo.output("[PRIVACY-SCAN] idea.properties file path: " + ideaPropertiesFile); + Properties properties = new Properties(); + try (InputStream input = Files.newInputStream(ideaPropertiesFile.toPath())) { + properties.load(input); + String pluginsPath = PropertyUtil.getProperty(properties, IDEA_PLUGINS_PATH_PROPERTY); + DebugInfo.output("[PRIVACY-SCAN] IDEA plugins path property: " + pluginsPath); + if (isBlank(pluginsPath)) { + DebugInfo.warn("[PRIVACY-SCAN] IDEA plugins path not found in idea.properties"); + return; } + addDir(pluginDirs, new File(pluginsPath)); } catch (IOException e) { - DebugInfo.warn("[PRIVACY-SCAN] Loading idea.properties failed"); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - DebugInfo.warn("[PRIVACY-SCAN] Closing idea.properties input failed"); - } + DebugInfo.warn("[PRIVACY-SCAN] Loading idea.properties failed", e); } } - private static void addConfiguredPluginDir(Set pluginRoots, String selector, FilterRule pluginPathRule) { - if (pluginPathRule == null || isBlank(pluginPathRule.getRule())) { + private static void addConfiguredPluginDir(Set pluginDirs, String selector, FilterRule rule) { + if (rule == null || isBlank(rule.getRule())) { return; } - String ruleValue = pluginPathRule.getRule().trim(); - int separatorIndex = ruleValue.indexOf('='); - if (separatorIndex <= 0 || separatorIndex >= ruleValue.length() - 1) { - DebugInfo.warn("[PRIVACY-SCAN] Invalid IDEA plugin path rule: " + ruleValue); + String value = rule.getRule().trim(); + int separator = value.indexOf('='); + if (separator <= 0 || separator >= value.length() - 1) { + DebugInfo.warn("[PRIVACY-SCAN] Invalid IDEA plugin path rule: " + value); return; } - String selectorRule = ruleValue.substring(0, separatorIndex).trim(); - String pluginPath = ruleValue.substring(separatorIndex + 1).trim(); - FilterRule selectorFilter = new FilterRule(pluginPathRule.getType(), selectorRule); + + String selectorRule = value.substring(0, separator).trim(); + String path = value.substring(separator + 1).trim(); + FilterRule selectorFilter = new FilterRule(rule.getType(), selectorRule); if (!selectorFilter.test(selector)) { return; } - File pluginDir = new File(stripSurroundingQuotes(pluginPath)); - if (!pluginDir.isDirectory()) { - DebugInfo.warn("[PRIVACY-SCAN] Configured plugin path ignored, directory not found: " + pluginDir.getAbsolutePath()); - } else { - addDir(pluginRoots, pluginDir); - DebugInfo.output("[PRIVACY-SCAN] Configured plugin path matched: " + selectorRule + " -> " + pluginDir.getAbsolutePath()); + + File dir = new File(stripSurroundingQuotes(path)); + if (!dir.isDirectory()) { + DebugInfo.warn("[PRIVACY-SCAN] Configured plugin path ignored, directory not found: " + + dir.getAbsolutePath()); + return; } + + addDir(pluginDirs, dir); + DebugInfo.output("[PRIVACY-SCAN] Configured plugin path matched: " + selectorRule + " -> " + + dir.getAbsolutePath()); } private static String resolveCurrentSelector(Class pathManagerClass) { @@ -141,12 +153,58 @@ public final class JbDirectoryScanner { return invokePathManagerString(pathManagerClass, "getPathsSelector"); } + private static File resolveStagingDir(Class pathManagerClass) { + File tempPath = invokePathManagerDir(pathManagerClass, "getPluginTempPath"); + if (tempPath != null) { + return tempPath; + } + File systemPath = invokePathManagerDir(pathManagerClass, "getSystemPath"); + if (systemPath != null) { + return new File(systemPath, "plugins"); + } + return null; + } + + private static List selectPendingUpdateZips(File cacheDir, Set pluginDirs) { + List pending = new ArrayList<>(); + if (cacheDir == null || !cacheDir.isDirectory()) { + return pending; + } + File[] zips = cacheDir.listFiles((d, name) -> name.endsWith(ZIP_SUFFIX)); + if (zips == null) { + return pending; + } + for (File zip : zips) { + String name = zip.getName(); + String baseName = name.substring(0, name.length() - ZIP_SUFFIX.length()); + File installed = findInstalledDir(pluginDirs, baseName); + if (installed != null && zip.lastModified() > installed.lastModified()) { + pending.add(zip); + } + } + return pending; + } + + private static File findInstalledDir(Set pluginDirs, String baseName) { + for (File dir : pluginDirs) { + File candidate = new File(dir, baseName); + if (candidate.isDirectory()) { + return candidate; + } + } + return null; + } + private static Class loadClass(String className) { - ClassLoader[] classLoaders = {Thread.currentThread().getContextClassLoader(), JbDirectoryScanner.class.getClassLoader(), ClassLoader.getSystemClassLoader()}; + ClassLoader[] classLoaders = { + Thread.currentThread().getContextClassLoader(), + JbDirectoryScanner.class.getClassLoader(), + ClassLoader.getSystemClassLoader() + }; for (ClassLoader classLoader : classLoaders) { - Class loadedClass = tryLoadClass(className, classLoader); - if (loadedClass != null) { - return loadedClass; + Class loaded = tryLoadClass(className, classLoader); + if (loaded != null) { + return loaded; } } return tryLoadClass(className, null); @@ -158,14 +216,19 @@ public final class JbDirectoryScanner { return Class.forName(className); } return Class.forName(className, false, classLoader); - } catch (Exception e) { + } catch (Exception ignored) { return null; } } private static File invokePathManagerDir(Class pathManagerClass, String methodName) { + Method method; + try { + method = pathManagerClass.getMethod(methodName); + } catch (NoSuchMethodException ignored) { + return null; + } try { - Method method = pathManagerClass.getMethod(methodName); return asFile(method.invoke(null)); } catch (Exception e) { DebugInfo.error("[PRIVACY-SCAN] invokePathManagerDir failed (" + methodName + ")", e); @@ -178,13 +241,10 @@ public final class JbDirectoryScanner { return null; } try { - Method method = pathManagerClass.getMethod(methodName, new Class[0]); - Object value = method.invoke(null, new Object[0]); - if (value == null) { - return null; - } - return String.valueOf(value); - } catch (Exception e) { + Method method = pathManagerClass.getMethod(methodName); + Object value = method.invoke(null); + return value == null ? null : String.valueOf(value); + } catch (Exception ignored) { return null; } } @@ -196,7 +256,7 @@ public final class JbDirectoryScanner { if (value instanceof Path) { return ((Path) value).toFile(); } - if ((value instanceof String) && !isBlank((String) value)) { + if (value instanceof String && !isBlank((String) value)) { return new File((String) value); } return null; @@ -213,7 +273,8 @@ public final class JbDirectoryScanner { if (value == null || value.length() < 2) { return value; } - if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) { + if ((value.startsWith("\"") && value.endsWith("\"")) + || (value.startsWith("'") && value.endsWith("'"))) { return value.substring(1, value.length() - 1); } return value; @@ -225,7 +286,7 @@ public final class JbDirectoryScanner { } try { return System.getProperty(name); - } catch (SecurityException e) { + } catch (SecurityException ignored) { return null; } } @@ -233,4 +294,5 @@ public final class JbDirectoryScanner { private static boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } + } diff --git a/src/main/java/com/novitechie/scan/RuleMerger.java b/src/main/java/com/novitechie/scan/RuleMerger.java index 6ace938..3bcd8b9 100644 --- a/src/main/java/com/novitechie/scan/RuleMerger.java +++ b/src/main/java/com/novitechie/scan/RuleMerger.java @@ -7,19 +7,18 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -public final class RuleMerger { - private static final String CLASS_RESOURCE_PREFIX = "/"; - private static final String CLASS_RESOURCE_SUFFIX = ".class"; +public class RuleMerger { + private static final String MARKER_RESOURCE_RULE = "/6c81ec87e55d331c267262e892427a3d93d76683.txt"; private RuleMerger() { } public static List merge(List first, List second) { - List mergedRules = new ArrayList<>(); - addRules(mergedRules, first); - addRules(mergedRules, second); - return mergedRules; + List merged = new ArrayList<>(); + addRules(merged, first); + addRules(merged, second); + return merged; } public static List toResourceRules(List classRules) { @@ -28,26 +27,30 @@ public final class RuleMerger { return resourceRules; } for (FilterRule classRule : classRules) { - if (classRule != null && classRule.getRule() != null) { - String resourcePath = CLASS_RESOURCE_PREFIX + classRule.getRule().replace('.', '/') + CLASS_RESOURCE_SUFFIX; - addRule(resourceRules, FilterRule.of("EQUAL", resourcePath)); + if (classRule == null || classRule.getRule() == null) { + continue; } + String resourcePath = "/" + classRule.getRule().replace('.', '/') + ".class"; + addRule(resourceRules, FilterRule.of("EQUAL", resourcePath)); } return resourceRules; } - public static List adjustMarkerResourceRule(List hideResourceRules, boolean markerResourceExists) { - List adjustedRules = new ArrayList<>(); - addRules(adjustedRules, hideResourceRules); + public static List adjustMarkerResourceRule(List hideResourceRules, + boolean markerResourceExists) { + List adjusted = new ArrayList<>(); + addRules(adjusted, hideResourceRules); FilterRule markerRule = FilterRule.of("EQUAL", MARKER_RESOURCE_RULE); if (markerResourceExists) { - removeRule(adjustedRules, markerRule); - DebugInfo.output("[PRIVACY-SCAN] Marker resource exists, removed Hide_Resource rule: /6c81ec87e55d331c267262e892427a3d93d76683.txt"); - return adjustedRules; + removeRule(adjusted, markerRule); + DebugInfo.output("[PRIVACY-SCAN] Marker resource exists, removed Hide_Resource rule: " + + MARKER_RESOURCE_RULE); + return adjusted; } - addRule(adjustedRules, markerRule); - DebugInfo.output("[PRIVACY-SCAN] Marker resource not found, added Hide_Resource rule: /6c81ec87e55d331c267262e892427a3d93d76683.txt"); - return adjustedRules; + addRule(adjusted, markerRule); + DebugInfo.output("[PRIVACY-SCAN] Marker resource not found, added Hide_Resource rule: " + + MARKER_RESOURCE_RULE); + return adjusted; } private static void addRules(List target, List rules) { @@ -63,8 +66,8 @@ public final class RuleMerger { if (rule == null) { return; } - for (FilterRule existingRule : target) { - if (sameRule(existingRule, rule)) { + for (FilterRule existing : target) { + if (sameRule(existing, rule)) { return; } } @@ -75,9 +78,9 @@ public final class RuleMerger { if (target == null || rule == null) { return; } - for (int index = target.size() - 1; index >= 0; index--) { - if (sameRule(target.get(index), rule)) { - target.remove(index); + for (int i = target.size() - 1; i >= 0; i--) { + if (sameRule(target.get(i), rule)) { + target.remove(i); } } }