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[^>]*)?>(.*?)" + Pattern.quote(ID_TAG) + ">", 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.quote(ID_TAG) + ">",
+ 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 extends ZipEntry> 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 extends ZipEntry> 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);
}
}
}