feat: support plugin update zip scan and .vmoptions read interception
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.novitechie</groupId>
|
<groupId>com.novitechie</groupId>
|
||||||
<artifactId>plugin-privacy</artifactId>
|
<artifactId>plugin-privacy</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>1.1.0</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
|||||||
@@ -43,3 +43,22 @@ EQUAL,com.janetfilter.core.utils.StringUtils
|
|||||||
[Ignore_Resource]
|
[Ignore_Resource]
|
||||||
EQUAL,/com/janetfilter/core/utils/StringUtils.class
|
EQUAL,/com/janetfilter/core/utils/StringUtils.class
|
||||||
# EQUAL,/com/janetfilter/core/utils/DateUtils.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
|
||||||
44
src/main/java/com/novitechie/FileInputStreamTransformer.java
Normal file
44
src/main/java/com/novitechie/FileInputStreamTransformer.java
Normal file
@@ -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 ("<init>".equals(method.name) && "(Ljava/io/File;)V".equals(method.desc)) {
|
||||||
|
insertRedirect(method, "redirectFile", "(Ljava/io/File;)Ljava/io/File;");
|
||||||
|
} else if ("<init>".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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
63
src/main/java/com/novitechie/FilesTransformer.java
Normal file
63
src/main/java/com/novitechie/FilesTransformer.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,10 +5,7 @@ import com.janetfilter.core.models.FilterRule;
|
|||||||
import com.janetfilter.core.plugin.MyTransformer;
|
import com.janetfilter.core.plugin.MyTransformer;
|
||||||
import com.janetfilter.core.plugin.PluginConfig;
|
import com.janetfilter.core.plugin.PluginConfig;
|
||||||
import com.janetfilter.core.plugin.PluginEntry;
|
import com.janetfilter.core.plugin.PluginEntry;
|
||||||
import com.novitechie.rules.IdeaPluginRule;
|
import com.novitechie.rules.*;
|
||||||
import com.novitechie.rules.LoadClassRule;
|
|
||||||
import com.novitechie.rules.ResourceRule;
|
|
||||||
import com.novitechie.rules.SystemRule;
|
|
||||||
import com.novitechie.scan.CanaryAutoScanner;
|
import com.novitechie.scan.CanaryAutoScanner;
|
||||||
import com.novitechie.scan.RuleMerger;
|
import com.novitechie.scan.RuleMerger;
|
||||||
|
|
||||||
@@ -22,13 +19,16 @@ public class PrivacyPlugin implements PluginEntry {
|
|||||||
CanaryAutoScanner.AutoScanResult autoScanResult = CanaryAutoScanner.scanWithResult(config);
|
CanaryAutoScanner.AutoScanResult autoScanResult = CanaryAutoScanner.scanWithResult(config);
|
||||||
List<FilterRule> autoScanClassRules = autoScanResult.getClassRules();
|
List<FilterRule> autoScanClassRules = autoScanResult.getClassRules();
|
||||||
List<FilterRule> autoScanResourceRules = RuleMerger.toResourceRules(autoScanClassRules);
|
List<FilterRule> autoScanResourceRules = RuleMerger.toResourceRules(autoScanClassRules);
|
||||||
List<FilterRule> hideResourceRules = RuleMerger.adjustMarkerResourceRule(config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists());
|
List<FilterRule> hideResourceRules = RuleMerger.adjustMarkerResourceRule(
|
||||||
|
config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists());
|
||||||
List<FilterRule> ignoreClassRules = RuleMerger.merge(config.getBySection("Ignore_Class"), autoScanClassRules);
|
List<FilterRule> ignoreClassRules = RuleMerger.merge(config.getBySection("Ignore_Class"), autoScanClassRules);
|
||||||
List<FilterRule> ignoreResourceRules = RuleMerger.merge(config.getBySection("Ignore_Resource"), autoScanResourceRules);
|
List<FilterRule> ignoreResourceRules = RuleMerger.merge(config.getBySection("Ignore_Resource"), autoScanResourceRules);
|
||||||
|
|
||||||
IdeaPluginRule.initRules(config.getBySection("Hide_Plugin"));
|
IdeaPluginRule.initRules(config.getBySection("Hide_Plugin"));
|
||||||
LoadClassRule.initRules(config.getBySection("Hide_Package"), ignoreClassRules);
|
LoadClassRule.initRules(config.getBySection("Hide_Package"), ignoreClassRules);
|
||||||
ResourceRule.initRules(hideResourceRules, ignoreResourceRules);
|
ResourceRule.initRules(hideResourceRules, ignoreResourceRules);
|
||||||
SystemRule.initRules(config.getBySection("Hide_Env"), config.getBySection("Hide_Property"));
|
SystemRule.initRules(config.getBySection("Hide_Env"), config.getBySection("Hide_Property"));
|
||||||
|
StackTraceRule.initRules(config.getBySection("Trace_Check_Package"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -46,6 +46,8 @@ public class PrivacyPlugin implements PluginEntry {
|
|||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// new CollectionsTransformer(),
|
// new CollectionsTransformer(),
|
||||||
new VMOptionsTransformer(),
|
new VMOptionsTransformer(),
|
||||||
|
new FilesTransformer(),
|
||||||
|
new FileInputStreamTransformer(),
|
||||||
new PluginClassLoaderTransformer(),
|
new PluginClassLoaderTransformer(),
|
||||||
new LicensingFacadeTransformer(),
|
new LicensingFacadeTransformer(),
|
||||||
new PluginManagerCoreTransformer(),
|
new PluginManagerCoreTransformer(),
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ public class SystemTransformer implements MyTransformer {
|
|||||||
list.add(new VarInsnNode(ALOAD, 0));
|
list.add(new VarInsnNode(ALOAD, 0));
|
||||||
list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/SystemRule", "checkProperty", "(Ljava/lang/String;)Z", false));
|
list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/SystemRule", "checkProperty", "(Ljava/lang/String;)Z", false));
|
||||||
list.add(new JumpInsnNode(IFEQ, L0));
|
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(new InsnNode(ARETURN));
|
||||||
list.add(L0);
|
list.add(L0);
|
||||||
m.instructions.insert(list);
|
m.instructions.insert(list);
|
||||||
|
|||||||
@@ -1,19 +1,56 @@
|
|||||||
package com.novitechie.rules;
|
package com.novitechie.rules;
|
||||||
|
|
||||||
import com.janetfilter.core.commons.DebugInfo;
|
import com.janetfilter.core.commons.DebugInfo;
|
||||||
|
import com.janetfilter.core.models.FilterRule;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class StackTraceRule {
|
public class StackTraceRule {
|
||||||
|
|
||||||
private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("\\A\\p{ASCII}*\\z");
|
private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("\\A\\p{ASCII}*\\z");
|
||||||
|
private static List<FilterRule> traceCheckPackageRules = Collections.emptyList();
|
||||||
|
|
||||||
|
public static void initRules(List<FilterRule> traceCheckPackageRules) {
|
||||||
|
StackTraceRule.traceCheckPackageRules = traceCheckPackageRules == null
|
||||||
|
? Collections.emptyList()
|
||||||
|
: traceCheckPackageRules;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean check() {
|
public static boolean check() {
|
||||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.janetfilter.core.commons.DebugInfo;
|
|||||||
import com.janetfilter.core.models.FilterRule;
|
import com.janetfilter.core.models.FilterRule;
|
||||||
import com.novitechie.LogUtil;
|
import com.novitechie.LogUtil;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class SystemRule {
|
public class SystemRule {
|
||||||
|
|
||||||
|
private static final String JB_VM_OPTIONS_FILE = "jb.vmOptionsFile";
|
||||||
|
|
||||||
private static List<FilterRule> hideEnvRules = Collections.emptyList();
|
private static List<FilterRule> hideEnvRules = Collections.emptyList();
|
||||||
|
|
||||||
private static List<FilterRule> hidePropertyRules = Collections.emptyList();
|
private static List<FilterRule> hidePropertyRules = Collections.emptyList();
|
||||||
@@ -31,7 +34,7 @@ public class SystemRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkProperty(String name) {
|
public static boolean checkProperty(String name) {
|
||||||
if (checkHideProperty(name) && StackTraceRule.check()) {
|
if ((checkVMOptionsProperty(name) || checkHideProperty(name)) && StackTraceRule.check()) {
|
||||||
DebugInfo.output("======================Hide Property: " + name);
|
DebugInfo.output("======================Hide Property: " + name);
|
||||||
LogUtil.printStackTrace();
|
LogUtil.printStackTrace();
|
||||||
return true;
|
return true;
|
||||||
@@ -39,6 +42,14 @@ public class SystemRule {
|
|||||||
return false;
|
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) {
|
private static boolean checkHideEnv(String name) {
|
||||||
return hideEnvRules.stream().anyMatch(rule -> rule.test(name));
|
return hideEnvRules.stream().anyMatch(rule -> rule.test(name));
|
||||||
}
|
}
|
||||||
@@ -46,4 +57,8 @@ public class SystemRule {
|
|||||||
private static boolean checkHideProperty(String name) {
|
private static boolean checkHideProperty(String name) {
|
||||||
return hidePropertyRules.stream().anyMatch(rule -> rule.test(name));
|
return hidePropertyRules.stream().anyMatch(rule -> rule.test(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkVMOptionsProperty(String name) {
|
||||||
|
return JB_VM_OPTIONS_FILE.equals(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
src/main/java/com/novitechie/rules/VMOptionsReadRule.java
Normal file
79
src/main/java/com/novitechie/rules/VMOptionsReadRule.java
Normal file
@@ -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<String> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,56 +11,54 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class CanaryAutoScanner {
|
public class CanaryAutoScanner {
|
||||||
private static final List<FilterRule> DEFAULT_SCAN_PLUGINS = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.EQUAL, "izhangzhihao.rainbow.brackets")));
|
|
||||||
private static final List<FilterRule> DEFAULT_SCAN_PACKAGES = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.PREFIX, "com.janetfilter."), new FilterRule(RuleType.PREFIX, "com.novitechie.")));
|
private static final List<FilterRule> DEFAULT_SCAN_PLUGINS = Arrays.asList(
|
||||||
|
new FilterRule(RuleType.EQUAL, "izhangzhihao.rainbow.brackets"));
|
||||||
|
private static final List<FilterRule> DEFAULT_SCAN_PACKAGES = Arrays.asList(
|
||||||
|
new FilterRule(RuleType.PREFIX, "com.janetfilter."),
|
||||||
|
new FilterRule(RuleType.PREFIX, "com.novitechie."));
|
||||||
|
|
||||||
private CanaryAutoScanner() {
|
private CanaryAutoScanner() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class AutoScanResult {
|
|
||||||
private final List<FilterRule> classRules;
|
|
||||||
private final boolean markerResourceExists;
|
|
||||||
|
|
||||||
private AutoScanResult(List<FilterRule> 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<FilterRule> getClassRules() {
|
|
||||||
return new ArrayList(this.classRules);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMarkerResourceExists() {
|
|
||||||
return this.markerResourceExists;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<FilterRule> scan(PluginConfig config) {
|
public static List<FilterRule> scan(PluginConfig config) {
|
||||||
return scanWithResult(config).getClassRules();
|
return scanWithResult(config).getClassRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AutoScanResult scanWithResult(PluginConfig config) {
|
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<FilterRule> pluginRules = rules(config, "Auto_Scan_Plugin", DEFAULT_SCAN_PLUGINS);
|
List<FilterRule> pluginRules = rules(config, "Auto_Scan_Plugin", DEFAULT_SCAN_PLUGINS);
|
||||||
List<FilterRule> packageRules = rules(config, "Auto_Scan_Package", DEFAULT_SCAN_PACKAGES);
|
List<FilterRule> packageRules = rules(config, "Auto_Scan_Package", DEFAULT_SCAN_PACKAGES);
|
||||||
List<FilterRule> excludeRules = emptyIfNull(config.getBySection("Auto_Scan_Exclude"));
|
List<FilterRule> excludeRules = emptyIfNull(config.getBySection("Auto_Scan_Exclude"));
|
||||||
|
|
||||||
if (pluginRules.isEmpty() || packageRules.isEmpty()) {
|
if (pluginRules.isEmpty() || packageRules.isEmpty()) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Auto scan disabled: plugin or package rules are empty");
|
DebugInfo.warn("[PRIVACY-SCAN] Auto scan disabled: plugin or package rules are empty");
|
||||||
return AutoScanResult.empty();
|
return AutoScanResult.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<File> scanTargets = JbDirectoryScanner.getPluginDir(config);
|
List<File> scanTargets = JbDirectoryScanner.getPluginDir(config);
|
||||||
if (scanTargets.isEmpty()) {
|
if (scanTargets.isEmpty()) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] No plugin scan targets found");
|
DebugInfo.warn("[PRIVACY-SCAN] No plugin scan targets found");
|
||||||
return AutoScanResult.empty();
|
return AutoScanResult.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Plugin targets: " + fileSummary(scanTargets));
|
DebugInfo.output("[PRIVACY-SCAN] Plugin targets: " + fileSummary(scanTargets));
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Scan_Plugin: " + ruleSummary(pluginRules));
|
DebugInfo.output("[PRIVACY-SCAN] Scan_Plugin: " + ruleSummary(pluginRules));
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Scan_Package: " + ruleSummary(packageRules));
|
DebugInfo.output("[PRIVACY-SCAN] Scan_Package: " + ruleSummary(packageRules));
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Exclude_Package: " + ruleSummary(excludeRules));
|
DebugInfo.output("[PRIVACY-SCAN] Exclude_Package: " + ruleSummary(excludeRules));
|
||||||
|
|
||||||
CanaryScanner.ScanResult result = CanaryScanner.scan(scanTargets, pluginRules, packageRules, excludeRules);
|
CanaryScanner.ScanResult result = CanaryScanner.scan(scanTargets, pluginRules, packageRules, excludeRules);
|
||||||
List<String> classNames = result.getClassNames();
|
List<String> classNames = result.getClassNames();
|
||||||
List<FilterRule> classRules = new ArrayList<>(classNames.size());
|
List<FilterRule> classRules = new ArrayList<>(classNames.size());
|
||||||
@@ -116,4 +114,26 @@ public final class CanaryAutoScanner {
|
|||||||
}
|
}
|
||||||
return summary.toString();
|
return summary.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class AutoScanResult {
|
||||||
|
private final List<FilterRule> classRules;
|
||||||
|
private final boolean markerResourceExists;
|
||||||
|
|
||||||
|
private AutoScanResult(List<FilterRule> 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<FilterRule> getClassRules() {
|
||||||
|
return new ArrayList<>(classRules);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMarkerResourceExists() {
|
||||||
|
return markerResourceExists;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,42 +10,50 @@ import java.util.jar.JarEntry;
|
|||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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 JAR_SUFFIX = ".jar";
|
||||||
|
private static final String ZIP_SUFFIX = ".zip";
|
||||||
private static final String CLASS_SUFFIX = ".class";
|
private static final String CLASS_SUFFIX = ".class";
|
||||||
private static final String PLUGIN_XML = "META-INF/plugin.xml";
|
private static final String PLUGIN_XML = "META-INF/plugin.xml";
|
||||||
private static final String MARKER_RESOURCE = "6c81ec87e55d331c267262e892427a3d93d76683.txt";
|
private static final String MARKER_RESOURCE = "6c81ec87e55d331c267262e892427a3d93d76683.txt";
|
||||||
private static final String LIB_DIR = "lib";
|
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 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() {
|
private CanaryScanner() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ScanResult {
|
public static class ScanResult {
|
||||||
private final Set<String> classNames = new LinkedHashSet();
|
private final Set<String> classNames = new LinkedHashSet<>();
|
||||||
private boolean markerResourceExists;
|
private boolean markerResourceExists;
|
||||||
|
|
||||||
public List<String> getClassNames() {
|
public List<String> getClassNames() {
|
||||||
return new ArrayList(this.classNames);
|
return new ArrayList<>(classNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMarkerResourceExists() {
|
public boolean isMarkerResourceExists() {
|
||||||
return this.markerResourceExists;
|
return markerResourceExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClassName(String className) {
|
private void addClassName(String className) {
|
||||||
this.classNames.add(className);
|
classNames.add(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markResourceExists() {
|
private void markResourceExists() {
|
||||||
this.markerResourceExists = true;
|
markerResourceExists = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ScanResult scan(List<File> scanTargets, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules) {
|
public static ScanResult scan(List<File> scanTargets, List<FilterRule> scanPluginRules,
|
||||||
|
List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules) {
|
||||||
ScanResult result = new ScanResult();
|
ScanResult result = new ScanResult();
|
||||||
if (scanTargets == null || scanTargets.isEmpty()) {
|
if (scanTargets == null || scanTargets.isEmpty()) {
|
||||||
return result;
|
return result;
|
||||||
@@ -58,45 +66,44 @@ public final class CanaryScanner {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scanTarget(File target, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
private static void scanTarget(File target, List<FilterRule> scanPluginRules,
|
||||||
|
List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules,
|
||||||
|
ScanResult result) {
|
||||||
if (isJar(target)) {
|
if (isJar(target)) {
|
||||||
processJarIfMatches(target, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
processJarIfMatches(target, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isZip(target)) {
|
||||||
|
processZipIfMatches(target, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (target == null || !target.isDirectory()) {
|
if (target == null || !target.isDirectory()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File xmlFile = new File(target, PLUGIN_XML);
|
File xmlFile = new File(target, PLUGIN_XML);
|
||||||
if (xmlFile.isFile()) {
|
if (xmlFile.isFile()) {
|
||||||
String pluginId = parsePluginIdFromXml(xmlFile);
|
String pluginId = parsePluginIdFromXml(xmlFile);
|
||||||
if (matchRules(pluginId, scanPluginRules)) {
|
if (matchRules(pluginId, scanPluginRules)) {
|
||||||
scanPluginDirectoryContent(target, scanPackageRules, excludePackageRules, result);
|
scanPluginDirectoryContent(target, scanPackageRules, excludePackageRules, result);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File libDir = new File(target, LIB_DIR);
|
File libDir = new File(target, LIB_DIR);
|
||||||
File[] jars = listJars(libDir);
|
if (libDir.isDirectory()) {
|
||||||
if (libDir.isDirectory() && jars != null && jars.length > 0) {
|
File[] jars = listJars(libDir);
|
||||||
String pluginId2 = resolvePluginIdFromJars(jars);
|
if (jars != null && jars.length > 0) {
|
||||||
if (matchRules(pluginId2, scanPluginRules)) {
|
String pluginId = resolvePluginIdFromJars(jars);
|
||||||
scanJars(jars, scanPackageRules, excludePackageRules, result);
|
if (matchRules(pluginId, scanPluginRules)) {
|
||||||
|
scanJars(jars, scanPackageRules, excludePackageRules, result);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
scanSubFiles(target, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void scanPluginDirectoryContent(File pluginDir, List<FilterRule> scanPackageRules, List<FilterRule> 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<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
|
||||||
File[] subFiles = target.listFiles();
|
File[] subFiles = target.listFiles();
|
||||||
if (subFiles == null) {
|
if (subFiles == null) {
|
||||||
return;
|
return;
|
||||||
@@ -106,16 +113,21 @@ public final class CanaryScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File[] listJars(File dir) {
|
private static void scanPluginDirectoryContent(File pluginDir, List<FilterRule> scanPackageRules,
|
||||||
if (dir == null || !dir.isDirectory()) {
|
List<FilterRule> excludePackageRules, ScanResult result) {
|
||||||
return null;
|
File libDir = new File(pluginDir, LIB_DIR);
|
||||||
|
if (!libDir.isDirectory()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return dir.listFiles((file, name) -> {
|
scanJars(listJars(libDir), scanPackageRules, excludePackageRules, result);
|
||||||
return name.endsWith(JAR_SUFFIX);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scanJars(File[] jars, List<FilterRule> scanPackageRules, List<FilterRule> 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<FilterRule> scanPackageRules,
|
||||||
|
List<FilterRule> excludePackageRules, ScanResult result) {
|
||||||
if (jars == null) {
|
if (jars == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -124,57 +136,45 @@ public final class CanaryScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processJarIfMatches(File jarFile, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
private static void processJarIfMatches(File jarFile, List<FilterRule> scanPluginRules,
|
||||||
try {
|
List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules,
|
||||||
JarFile jar = new JarFile(jarFile);
|
ScanResult result) {
|
||||||
|
try (JarFile jar = new JarFile(jarFile)) {
|
||||||
String pluginId = parsePluginId(jar);
|
String pluginId = parsePluginId(jar);
|
||||||
if (matchRules(pluginId, scanPluginRules)) {
|
if (matchRules(pluginId, scanPluginRules)) {
|
||||||
extractClasses(jar, scanPackageRules, excludePackageRules, result);
|
extractClasses(jar, scanPackageRules, excludePackageRules, result);
|
||||||
}
|
}
|
||||||
jar.close();
|
} catch (IOException ignored) {
|
||||||
} catch (IOException e) {
|
DebugInfo.warn("[PRIVACY-SCAN] scan jar failed: " + jarFile, ignored);
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] scan jar failed: " + jarFile, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scanJarClasses(File jarFile, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
private static void scanJarClasses(File jarFile, List<FilterRule> scanPackageRules,
|
||||||
try {
|
List<FilterRule> excludePackageRules, ScanResult result) {
|
||||||
JarFile jar = new JarFile(jarFile);
|
try (JarFile jar = new JarFile(jarFile)) {
|
||||||
extractClasses(jar, scanPackageRules, excludePackageRules, result);
|
extractClasses(jar, scanPackageRules, excludePackageRules, result);
|
||||||
jar.close();
|
} catch (IOException ignored) {
|
||||||
} catch (IOException e) {
|
DebugInfo.warn("[PRIVACY-SCAN] scan jar classes failed: " + jarFile, ignored);
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] scan jar classes failed: " + jarFile, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String resolvePluginIdFromJars(File[] jars) {
|
private static String resolvePluginIdFromJars(File[] jars) {
|
||||||
JarFile jar;
|
|
||||||
String pluginId;
|
|
||||||
if (jars == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (File jarFile : jars) {
|
for (File jarFile : jars) {
|
||||||
try {
|
try (JarFile jar = new JarFile(jarFile)) {
|
||||||
jar = new JarFile(jarFile);
|
String id = parsePluginId(jar);
|
||||||
pluginId = parsePluginId(jar);
|
if (id != null) {
|
||||||
if (pluginId != null) {
|
return id;
|
||||||
jar.close();
|
|
||||||
return pluginId;
|
|
||||||
}
|
}
|
||||||
jar.close();
|
} catch (IOException ignored) {
|
||||||
} catch (IOException e) {
|
// 轮询下一个包
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] resolve plugin id failed: " + jarFile, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parsePluginIdFromXml(File xmlFile) {
|
private static String parsePluginIdFromXml(File xmlFile) {
|
||||||
try {
|
try (FileInputStream fis = new FileInputStream(xmlFile)) {
|
||||||
FileInputStream input = new FileInputStream(xmlFile);
|
return extractXmlTag(readUtf8(fis));
|
||||||
String strExtractXmlTag = extractXmlTag(readUtf8(input));
|
|
||||||
input.close();
|
|
||||||
return strExtractXmlTag;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] parse plugin xml failed: " + xmlFile, e);
|
DebugInfo.warn("[PRIVACY-SCAN] parse plugin xml failed: " + xmlFile, e);
|
||||||
return null;
|
return null;
|
||||||
@@ -186,42 +186,29 @@ public final class CanaryScanner {
|
|||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
InputStream input = jar.getInputStream(entry);
|
try (InputStream is = jar.getInputStream(entry)) {
|
||||||
try {
|
return extractXmlTag(readUtf8(is));
|
||||||
String strExtractXmlTag = extractXmlTag(readUtf8(input));
|
|
||||||
input.close();
|
|
||||||
return strExtractXmlTag;
|
|
||||||
} catch (Throwable th3) {
|
|
||||||
if (input != null) {
|
|
||||||
input.close();
|
|
||||||
}
|
|
||||||
throw th3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String readUtf8(InputStream input) throws IOException {
|
private static String readUtf8(InputStream input) throws IOException {
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
|
||||||
byte[] chunk = new byte[1024];
|
byte[] chunk = new byte[1024];
|
||||||
while (true) {
|
int read;
|
||||||
int size = input.read(chunk);
|
while ((read = input.read(chunk)) != -1) {
|
||||||
if (size != -1) {
|
output.write(chunk, 0, read);
|
||||||
output.write(chunk, 0, size);
|
|
||||||
} else {
|
|
||||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractXmlTag(String content) {
|
private static String extractXmlTag(String content) {
|
||||||
String withoutComments = COMMENT_PATTERN.matcher(content).replaceAll("");
|
String withoutComments = COMMENT_PATTERN.matcher(content).replaceAll("");
|
||||||
Matcher matcher = TAG_PATTERN.matcher(withoutComments);
|
Matcher matcher = TAG_PATTERN.matcher(withoutComments);
|
||||||
if (matcher.find()) {
|
return matcher.find() ? matcher.group(1).trim() : null;
|
||||||
return matcher.group(1).trim();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void extractClasses(JarFile jar, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
private static void extractClasses(JarFile jar, List<FilterRule> scanPackageRules,
|
||||||
|
List<FilterRule> excludePackageRules, ScanResult result) {
|
||||||
Enumeration<JarEntry> entries = jar.entries();
|
Enumeration<JarEntry> entries = jar.entries();
|
||||||
while (entries.hasMoreElements()) {
|
while (entries.hasMoreElements()) {
|
||||||
JarEntry entry = entries.nextElement();
|
JarEntry entry = entries.nextElement();
|
||||||
@@ -229,11 +216,12 @@ public final class CanaryScanner {
|
|||||||
if (MARKER_RESOURCE.equals(name)) {
|
if (MARKER_RESOURCE.equals(name)) {
|
||||||
result.markResourceExists();
|
result.markResourceExists();
|
||||||
}
|
}
|
||||||
if (name.endsWith(CLASS_SUFFIX)) {
|
if (!name.endsWith(CLASS_SUFFIX)) {
|
||||||
String className = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.');
|
continue;
|
||||||
if (matchRules(className, scanPackageRules) && !matchRules(className, excludePackageRules)) {
|
}
|
||||||
result.addClassName(className);
|
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);
|
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<FilterRule> scanPluginRules,
|
||||||
|
List<FilterRule> scanPackageRules, List<FilterRule> 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<FilterRule> scanPackageRules,
|
||||||
|
List<FilterRule> 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<FilterRule> rules) {
|
private static boolean matchRules(String value, List<FilterRule> rules) {
|
||||||
if (value == null || rules == null || rules.isEmpty()) {
|
if (value == null || rules == null || rules.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -13,45 +13,49 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
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 PATH_MANAGER_CLASS = "com.intellij.openapi.application.PathManager";
|
||||||
private static final String IDEA_PATHS_SELECTOR_PROPERTY = "idea.paths.selector";
|
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_PLUGIN_PATH_SECTION = "Auto_Scan_Ide_Plugin_Path";
|
||||||
private static final String IDEA_PROPERTIES_ENV_VARIABLE = "IDEA_PROPERTIES";
|
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 IDEA_PLUGINS_PATH_PROPERTY = "idea.plugins.path";
|
||||||
|
private static final String ZIP_SUFFIX = ".zip";
|
||||||
|
|
||||||
private JbDirectoryScanner() {
|
private JbDirectoryScanner() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<File> getPluginDir(PluginConfig config) {
|
public static List<File> getPluginDir(PluginConfig config) {
|
||||||
Class<?> pathManagerClass = loadClass(PATH_MANAGER_CLASS);
|
Class<?> pathManagerClass = loadClass(PATH_MANAGER_CLASS);
|
||||||
List<FilterRule> pluginPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION);
|
List<FilterRule> configuredPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION);
|
||||||
if (pluginPathRules == null || pluginPathRules.isEmpty()) {
|
if (configuredPathRules == null || configuredPathRules.isEmpty()) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path rules found, fallback to PathManager");
|
DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path rules found, fallback to PathManager");
|
||||||
return getPathManagerPluginDirs(pathManagerClass);
|
return getPathManagerPluginDirs(pathManagerClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
String selector = resolveCurrentSelector(pathManagerClass);
|
String selector = resolveCurrentSelector(pathManagerClass);
|
||||||
if (isBlank(selector)) {
|
if (isBlank(selector)) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Current IDEA selector not found");
|
DebugInfo.warn("[PRIVACY-SCAN] Current IDEA selector not found");
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Current IDEA selector: " + selector);
|
DebugInfo.output("[PRIVACY-SCAN] Current IDEA selector: " + selector);
|
||||||
Set<File> pluginRoots = new LinkedHashSet<>();
|
Set<File> pluginDirs = new LinkedHashSet<>();
|
||||||
for (FilterRule pluginPathRule : pluginPathRules) {
|
for (FilterRule rule : configuredPathRules) {
|
||||||
addConfiguredPluginDir(pluginRoots, selector, pluginPathRule);
|
addConfiguredPluginDir(pluginDirs, selector, rule);
|
||||||
}
|
}
|
||||||
if (pluginRoots.isEmpty()) {
|
if (pluginDirs.isEmpty()) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path matched: " + selector);
|
DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path matched: " + selector);
|
||||||
return Collections.emptyList();
|
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<File> getPluginDir() {
|
public static List<File> getPluginDir() {
|
||||||
Class<?> pathManagerClass = loadClass(PATH_MANAGER_CLASS);
|
return getPathManagerPluginDirs(loadClass(PATH_MANAGER_CLASS));
|
||||||
return getPathManagerPluginDirs(pathManagerClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<File> getPathManagerPluginDirs(Class<?> pathManagerClass) {
|
private static List<File> getPathManagerPluginDirs(Class<?> pathManagerClass) {
|
||||||
@@ -59,78 +63,86 @@ public final class JbDirectoryScanner {
|
|||||||
DebugInfo.warn("[PRIVACY-SCAN] PathManager not found");
|
DebugInfo.warn("[PRIVACY-SCAN] PathManager not found");
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
Set<File> pluginRoots = new LinkedHashSet<>();
|
|
||||||
addDirFromIdeaProperties(pathManagerClass, pluginRoots);
|
Set<File> pluginDirs = new LinkedHashSet<>();
|
||||||
if (pluginRoots.isEmpty()) {
|
addDirFromIdeaProperties(pathManagerClass, pluginDirs);
|
||||||
addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsDir"));
|
if (pluginDirs.isEmpty()) {
|
||||||
addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsPath"));
|
addDir(pluginDirs, invokePathManagerDir(pathManagerClass, "getPluginsDir"));
|
||||||
DebugInfo.output("[PRIVACY-SCAN] PathManager plugin roots: " + pluginRoots);
|
addDir(pluginDirs, invokePathManagerDir(pathManagerClass, "getPluginsPath"));
|
||||||
}
|
}
|
||||||
return new ArrayList<>(pluginRoots);
|
|
||||||
|
List<File> targets = new ArrayList<>(pluginDirs);
|
||||||
|
List<File> 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<File> pluginRoots) {
|
private static void addDirFromIdeaProperties(Class<?> pathManagerClass, Set<File> pluginDirs) {
|
||||||
File ideaPropertiesFile;
|
File ideaPropertiesFile;
|
||||||
InputStream in = null;
|
String configuredPath = System.getenv(IDEA_PROPERTIES_ENV_VARIABLE);
|
||||||
try {
|
if (isBlank(configuredPath)) {
|
||||||
String ideaPropertiesEnvVar = System.getenv(IDEA_PROPERTIES_ENV_VARIABLE);
|
File homePath = invokePathManagerDir(pathManagerClass, "getHomePath");
|
||||||
if (isBlank(ideaPropertiesEnvVar)) {
|
DebugInfo.output("[PRIVACY-SCAN] PathManager home path: " + homePath);
|
||||||
File homePath = invokePathManagerDir(pathManagerClass, "getHomePath");
|
if (homePath == null) {
|
||||||
DebugInfo.output("[PRIVACY-SCAN] PathManager home path: " + homePath);
|
return;
|
||||||
ideaPropertiesFile = new File(homePath, IDEA_PROPERTIES_FILE_PATH);
|
|
||||||
} else {
|
|
||||||
DebugInfo.output("[PRIVACY-SCAN] IDEA_PROPERTIES environment variable: " + ideaPropertiesEnvVar);
|
|
||||||
ideaPropertiesFile = new File(ideaPropertiesEnvVar);
|
|
||||||
}
|
}
|
||||||
if (ideaPropertiesFile.exists()) {
|
ideaPropertiesFile = new File(homePath, IDEA_PROPERTIES_FILE_PATH);
|
||||||
DebugInfo.output("[PRIVACY-SCAN] idea.properties file path: " + ideaPropertiesFile);
|
} else {
|
||||||
Properties ideaProperties = new Properties();
|
DebugInfo.output("[PRIVACY-SCAN] IDEA_PROPERTIES environment variable: " + configuredPath);
|
||||||
in = Files.newInputStream(ideaPropertiesFile.toPath());
|
ideaPropertiesFile = new File(configuredPath);
|
||||||
ideaProperties.load(in);
|
}
|
||||||
String ideaPluginsPathProperty = PropertyUtil.getProperty(ideaProperties, IDEA_PLUGINS_PATH_PROPERTY);
|
|
||||||
DebugInfo.output("[PRIVACY-SCAN] IDEA plugins path property: " + ideaPluginsPathProperty);
|
if (!ideaPropertiesFile.exists()) {
|
||||||
if (isBlank(ideaPluginsPathProperty)) {
|
return;
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] IDEA plugins path not found in idea.properties");
|
}
|
||||||
return;
|
|
||||||
}
|
DebugInfo.output("[PRIVACY-SCAN] idea.properties file path: " + ideaPropertiesFile);
|
||||||
addDir(pluginRoots, new File(ideaPluginsPathProperty));
|
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) {
|
} catch (IOException e) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Loading idea.properties failed");
|
DebugInfo.warn("[PRIVACY-SCAN] Loading idea.properties failed", e);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (in != null) {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Closing idea.properties input failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addConfiguredPluginDir(Set<File> pluginRoots, String selector, FilterRule pluginPathRule) {
|
private static void addConfiguredPluginDir(Set<File> pluginDirs, String selector, FilterRule rule) {
|
||||||
if (pluginPathRule == null || isBlank(pluginPathRule.getRule())) {
|
if (rule == null || isBlank(rule.getRule())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ruleValue = pluginPathRule.getRule().trim();
|
String value = rule.getRule().trim();
|
||||||
int separatorIndex = ruleValue.indexOf('=');
|
int separator = value.indexOf('=');
|
||||||
if (separatorIndex <= 0 || separatorIndex >= ruleValue.length() - 1) {
|
if (separator <= 0 || separator >= value.length() - 1) {
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Invalid IDEA plugin path rule: " + ruleValue);
|
DebugInfo.warn("[PRIVACY-SCAN] Invalid IDEA plugin path rule: " + value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String selectorRule = ruleValue.substring(0, separatorIndex).trim();
|
|
||||||
String pluginPath = ruleValue.substring(separatorIndex + 1).trim();
|
String selectorRule = value.substring(0, separator).trim();
|
||||||
FilterRule selectorFilter = new FilterRule(pluginPathRule.getType(), selectorRule);
|
String path = value.substring(separator + 1).trim();
|
||||||
|
FilterRule selectorFilter = new FilterRule(rule.getType(), selectorRule);
|
||||||
if (!selectorFilter.test(selector)) {
|
if (!selectorFilter.test(selector)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File pluginDir = new File(stripSurroundingQuotes(pluginPath));
|
|
||||||
if (!pluginDir.isDirectory()) {
|
File dir = new File(stripSurroundingQuotes(path));
|
||||||
DebugInfo.warn("[PRIVACY-SCAN] Configured plugin path ignored, directory not found: " + pluginDir.getAbsolutePath());
|
if (!dir.isDirectory()) {
|
||||||
} else {
|
DebugInfo.warn("[PRIVACY-SCAN] Configured plugin path ignored, directory not found: "
|
||||||
addDir(pluginRoots, pluginDir);
|
+ dir.getAbsolutePath());
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Configured plugin path matched: " + selectorRule + " -> " + pluginDir.getAbsolutePath());
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDir(pluginDirs, dir);
|
||||||
|
DebugInfo.output("[PRIVACY-SCAN] Configured plugin path matched: " + selectorRule + " -> "
|
||||||
|
+ dir.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String resolveCurrentSelector(Class<?> pathManagerClass) {
|
private static String resolveCurrentSelector(Class<?> pathManagerClass) {
|
||||||
@@ -141,12 +153,58 @@ public final class JbDirectoryScanner {
|
|||||||
return invokePathManagerString(pathManagerClass, "getPathsSelector");
|
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<File> selectPendingUpdateZips(File cacheDir, Set<File> pluginDirs) {
|
||||||
|
List<File> 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<File> 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) {
|
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) {
|
for (ClassLoader classLoader : classLoaders) {
|
||||||
Class<?> loadedClass = tryLoadClass(className, classLoader);
|
Class<?> loaded = tryLoadClass(className, classLoader);
|
||||||
if (loadedClass != null) {
|
if (loaded != null) {
|
||||||
return loadedClass;
|
return loaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tryLoadClass(className, null);
|
return tryLoadClass(className, null);
|
||||||
@@ -158,14 +216,19 @@ public final class JbDirectoryScanner {
|
|||||||
return Class.forName(className);
|
return Class.forName(className);
|
||||||
}
|
}
|
||||||
return Class.forName(className, false, classLoader);
|
return Class.forName(className, false, classLoader);
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File invokePathManagerDir(Class<?> pathManagerClass, String methodName) {
|
private static File invokePathManagerDir(Class<?> pathManagerClass, String methodName) {
|
||||||
|
Method method;
|
||||||
|
try {
|
||||||
|
method = pathManagerClass.getMethod(methodName);
|
||||||
|
} catch (NoSuchMethodException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Method method = pathManagerClass.getMethod(methodName);
|
|
||||||
return asFile(method.invoke(null));
|
return asFile(method.invoke(null));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
DebugInfo.error("[PRIVACY-SCAN] invokePathManagerDir failed (" + methodName + ")", e);
|
DebugInfo.error("[PRIVACY-SCAN] invokePathManagerDir failed (" + methodName + ")", e);
|
||||||
@@ -178,13 +241,10 @@ public final class JbDirectoryScanner {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Method method = pathManagerClass.getMethod(methodName, new Class[0]);
|
Method method = pathManagerClass.getMethod(methodName);
|
||||||
Object value = method.invoke(null, new Object[0]);
|
Object value = method.invoke(null);
|
||||||
if (value == null) {
|
return value == null ? null : String.valueOf(value);
|
||||||
return null;
|
} catch (Exception ignored) {
|
||||||
}
|
|
||||||
return String.valueOf(value);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +256,7 @@ public final class JbDirectoryScanner {
|
|||||||
if (value instanceof Path) {
|
if (value instanceof Path) {
|
||||||
return ((Path) value).toFile();
|
return ((Path) value).toFile();
|
||||||
}
|
}
|
||||||
if ((value instanceof String) && !isBlank((String) value)) {
|
if (value instanceof String && !isBlank((String) value)) {
|
||||||
return new File((String) value);
|
return new File((String) value);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -213,7 +273,8 @@ public final class JbDirectoryScanner {
|
|||||||
if (value == null || value.length() < 2) {
|
if (value == null || value.length() < 2) {
|
||||||
return value;
|
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.substring(1, value.length() - 1);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -225,7 +286,7 @@ public final class JbDirectoryScanner {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return System.getProperty(name);
|
return System.getProperty(name);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,4 +294,5 @@ public final class JbDirectoryScanner {
|
|||||||
private static boolean isBlank(String value) {
|
private static boolean isBlank(String value) {
|
||||||
return value == null || value.trim().isEmpty();
|
return value == null || value.trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,18 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class RuleMerger {
|
public class RuleMerger {
|
||||||
private static final String CLASS_RESOURCE_PREFIX = "/";
|
|
||||||
private static final String CLASS_RESOURCE_SUFFIX = ".class";
|
|
||||||
private static final String MARKER_RESOURCE_RULE = "/6c81ec87e55d331c267262e892427a3d93d76683.txt";
|
private static final String MARKER_RESOURCE_RULE = "/6c81ec87e55d331c267262e892427a3d93d76683.txt";
|
||||||
|
|
||||||
private RuleMerger() {
|
private RuleMerger() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<FilterRule> merge(List<FilterRule> first, List<FilterRule> second) {
|
public static List<FilterRule> merge(List<FilterRule> first, List<FilterRule> second) {
|
||||||
List<FilterRule> mergedRules = new ArrayList<>();
|
List<FilterRule> merged = new ArrayList<>();
|
||||||
addRules(mergedRules, first);
|
addRules(merged, first);
|
||||||
addRules(mergedRules, second);
|
addRules(merged, second);
|
||||||
return mergedRules;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<FilterRule> toResourceRules(List<FilterRule> classRules) {
|
public static List<FilterRule> toResourceRules(List<FilterRule> classRules) {
|
||||||
@@ -28,26 +27,30 @@ public final class RuleMerger {
|
|||||||
return resourceRules;
|
return resourceRules;
|
||||||
}
|
}
|
||||||
for (FilterRule classRule : classRules) {
|
for (FilterRule classRule : classRules) {
|
||||||
if (classRule != null && classRule.getRule() != null) {
|
if (classRule == null || classRule.getRule() == null) {
|
||||||
String resourcePath = CLASS_RESOURCE_PREFIX + classRule.getRule().replace('.', '/') + CLASS_RESOURCE_SUFFIX;
|
continue;
|
||||||
addRule(resourceRules, FilterRule.of("EQUAL", resourcePath));
|
|
||||||
}
|
}
|
||||||
|
String resourcePath = "/" + classRule.getRule().replace('.', '/') + ".class";
|
||||||
|
addRule(resourceRules, FilterRule.of("EQUAL", resourcePath));
|
||||||
}
|
}
|
||||||
return resourceRules;
|
return resourceRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<FilterRule> adjustMarkerResourceRule(List<FilterRule> hideResourceRules, boolean markerResourceExists) {
|
public static List<FilterRule> adjustMarkerResourceRule(List<FilterRule> hideResourceRules,
|
||||||
List<FilterRule> adjustedRules = new ArrayList<>();
|
boolean markerResourceExists) {
|
||||||
addRules(adjustedRules, hideResourceRules);
|
List<FilterRule> adjusted = new ArrayList<>();
|
||||||
|
addRules(adjusted, hideResourceRules);
|
||||||
FilterRule markerRule = FilterRule.of("EQUAL", MARKER_RESOURCE_RULE);
|
FilterRule markerRule = FilterRule.of("EQUAL", MARKER_RESOURCE_RULE);
|
||||||
if (markerResourceExists) {
|
if (markerResourceExists) {
|
||||||
removeRule(adjustedRules, markerRule);
|
removeRule(adjusted, markerRule);
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Marker resource exists, removed Hide_Resource rule: /6c81ec87e55d331c267262e892427a3d93d76683.txt");
|
DebugInfo.output("[PRIVACY-SCAN] Marker resource exists, removed Hide_Resource rule: "
|
||||||
return adjustedRules;
|
+ MARKER_RESOURCE_RULE);
|
||||||
|
return adjusted;
|
||||||
}
|
}
|
||||||
addRule(adjustedRules, markerRule);
|
addRule(adjusted, markerRule);
|
||||||
DebugInfo.output("[PRIVACY-SCAN] Marker resource not found, added Hide_Resource rule: /6c81ec87e55d331c267262e892427a3d93d76683.txt");
|
DebugInfo.output("[PRIVACY-SCAN] Marker resource not found, added Hide_Resource rule: "
|
||||||
return adjustedRules;
|
+ MARKER_RESOURCE_RULE);
|
||||||
|
return adjusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addRules(List<FilterRule> target, List<FilterRule> rules) {
|
private static void addRules(List<FilterRule> target, List<FilterRule> rules) {
|
||||||
@@ -63,8 +66,8 @@ public final class RuleMerger {
|
|||||||
if (rule == null) {
|
if (rule == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (FilterRule existingRule : target) {
|
for (FilterRule existing : target) {
|
||||||
if (sameRule(existingRule, rule)) {
|
if (sameRule(existing, rule)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,9 +78,9 @@ public final class RuleMerger {
|
|||||||
if (target == null || rule == null) {
|
if (target == null || rule == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int index = target.size() - 1; index >= 0; index--) {
|
for (int i = target.size() - 1; i >= 0; i--) {
|
||||||
if (sameRule(target.get(index), rule)) {
|
if (sameRule(target.get(i), rule)) {
|
||||||
target.remove(index);
|
target.remove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user