feat: auto scan plugins
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.novitechie;
|
||||
|
||||
import com.janetfilter.core.Environment;
|
||||
import com.janetfilter.core.models.FilterRule;
|
||||
import com.janetfilter.core.plugin.MyTransformer;
|
||||
import com.janetfilter.core.plugin.PluginConfig;
|
||||
import com.janetfilter.core.plugin.PluginEntry;
|
||||
@@ -8,6 +9,8 @@ import com.novitechie.rules.IdeaPluginRule;
|
||||
import com.novitechie.rules.LoadClassRule;
|
||||
import com.novitechie.rules.ResourceRule;
|
||||
import com.novitechie.rules.SystemRule;
|
||||
import com.novitechie.scan.CanaryAutoScanner;
|
||||
import com.novitechie.scan.RuleMerger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -16,9 +19,15 @@ public class PrivacyPlugin implements PluginEntry {
|
||||
|
||||
@Override
|
||||
public void init(Environment environment, PluginConfig config) {
|
||||
CanaryAutoScanner.AutoScanResult autoScanResult = CanaryAutoScanner.scanWithResult(config);
|
||||
List<FilterRule> autoScanClassRules = autoScanResult.getClassRules();
|
||||
List<FilterRule> autoScanResourceRules = RuleMerger.toResourceRules(autoScanClassRules);
|
||||
List<FilterRule> hideResourceRules = RuleMerger.adjustMarkerResourceRule(config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists());
|
||||
List<FilterRule> ignoreClassRules = RuleMerger.merge(config.getBySection("Ignore_Class"), autoScanClassRules);
|
||||
List<FilterRule> ignoreResourceRules = RuleMerger.merge(config.getBySection("Ignore_Resource"), autoScanResourceRules);
|
||||
IdeaPluginRule.initRules(config.getBySection("Hide_Plugin"));
|
||||
LoadClassRule.initRules(config.getBySection("Hide_Package"), config.getBySection("Ignore_Class"));
|
||||
ResourceRule.initRules(config.getBySection("Hide_Resource"), config.getBySection("Ignore_Resource"));
|
||||
LoadClassRule.initRules(config.getBySection("Hide_Package"), ignoreClassRules);
|
||||
ResourceRule.initRules(hideResourceRules, ignoreResourceRules);
|
||||
SystemRule.initRules(config.getBySection("Hide_Env"), config.getBySection("Hide_Property"));
|
||||
}
|
||||
|
||||
|
||||
119
src/main/java/com/novitechie/scan/CanaryAutoScanner.java
Normal file
119
src/main/java/com/novitechie/scan/CanaryAutoScanner.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package com.novitechie.scan;
|
||||
|
||||
import com.janetfilter.core.commons.DebugInfo;
|
||||
import com.janetfilter.core.enums.RuleType;
|
||||
import com.janetfilter.core.models.FilterRule;
|
||||
import com.janetfilter.core.plugin.PluginConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final 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 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) {
|
||||
return scanWithResult(config).getClassRules();
|
||||
}
|
||||
|
||||
public static AutoScanResult scanWithResult(PluginConfig config) {
|
||||
List<FilterRule> pluginRules = rules(config, "Auto_Scan_Plugin", DEFAULT_SCAN_PLUGINS);
|
||||
List<FilterRule> packageRules = rules(config, "Auto_Scan_Package", DEFAULT_SCAN_PACKAGES);
|
||||
List<FilterRule> 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<File> 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<String> classNames = result.getClassNames();
|
||||
List<FilterRule> classRules = new ArrayList<>(classNames.size());
|
||||
DebugInfo.output("[PRIVACY-SCAN] Found " + classNames.size() + " canary classes");
|
||||
for (String className : classNames) {
|
||||
classRules.add(FilterRule.of("EQUAL", className));
|
||||
DebugInfo.output("[PRIVACY-SCAN] - " + className);
|
||||
}
|
||||
DebugInfo.output("[PRIVACY-SCAN] Marker resource exists: " + result.isMarkerResourceExists());
|
||||
return new AutoScanResult(classRules, result.isMarkerResourceExists());
|
||||
}
|
||||
|
||||
private static List<FilterRule> rules(PluginConfig config, String section, List<FilterRule> defaultValue) {
|
||||
List<FilterRule> configRules = config.getBySection(section);
|
||||
if (hasSection(config, section)) {
|
||||
return emptyIfNull(configRules);
|
||||
}
|
||||
if (defaultValue == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static boolean hasSection(PluginConfig config, String section) {
|
||||
return config.getData() != null && config.getData().containsKey(section);
|
||||
}
|
||||
|
||||
private static List<FilterRule> emptyIfNull(List<FilterRule> rules) {
|
||||
return rules == null ? Collections.emptyList() : rules;
|
||||
}
|
||||
|
||||
private static String fileSummary(List<File> files) {
|
||||
StringBuilder summary = new StringBuilder();
|
||||
for (File file : files) {
|
||||
if (summary.length() > 0) {
|
||||
summary.append(", ");
|
||||
}
|
||||
summary.append(file.getAbsolutePath());
|
||||
}
|
||||
return summary.toString();
|
||||
}
|
||||
|
||||
private static String ruleSummary(List<FilterRule> rules) {
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
return "(none)";
|
||||
}
|
||||
StringBuilder summary = new StringBuilder();
|
||||
for (FilterRule rule : rules) {
|
||||
if (summary.length() > 0) {
|
||||
summary.append(", ");
|
||||
}
|
||||
summary.append(rule);
|
||||
}
|
||||
return summary.toString();
|
||||
}
|
||||
}
|
||||
256
src/main/java/com/novitechie/scan/CanaryScanner.java
Normal file
256
src/main/java/com/novitechie/scan/CanaryScanner.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package com.novitechie.scan;
|
||||
|
||||
import com.janetfilter.core.commons.DebugInfo;
|
||||
import com.janetfilter.core.models.FilterRule;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class CanaryScanner {
|
||||
private static final String JAR_SUFFIX = ".jar";
|
||||
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 CanaryScanner() {
|
||||
}
|
||||
|
||||
public static final class ScanResult {
|
||||
private final Set<String> classNames = new LinkedHashSet();
|
||||
private boolean markerResourceExists;
|
||||
|
||||
public List<String> getClassNames() {
|
||||
return new ArrayList(this.classNames);
|
||||
}
|
||||
|
||||
public boolean isMarkerResourceExists() {
|
||||
return this.markerResourceExists;
|
||||
}
|
||||
|
||||
public void addClassName(String className) {
|
||||
this.classNames.add(className);
|
||||
}
|
||||
|
||||
public void markResourceExists() {
|
||||
this.markerResourceExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static ScanResult scan(List<File> scanTargets, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules) {
|
||||
ScanResult result = new ScanResult();
|
||||
if (scanTargets == null || scanTargets.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
for (File target : scanTargets) {
|
||||
if (target != null) {
|
||||
scanTarget(target, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void scanTarget(File target, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
||||
if (isJar(target)) {
|
||||
processJarIfMatches(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);
|
||||
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();
|
||||
if (subFiles == null) {
|
||||
return;
|
||||
}
|
||||
for (File subFile : subFiles) {
|
||||
scanTarget(subFile, scanPluginRules, scanPackageRules, excludePackageRules, result);
|
||||
}
|
||||
}
|
||||
|
||||
private static File[] listJars(File dir) {
|
||||
if (dir == null || !dir.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
return dir.listFiles((file, name) -> {
|
||||
return name.endsWith(JAR_SUFFIX);
|
||||
});
|
||||
}
|
||||
|
||||
private static void scanJars(File[] jars, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
||||
if (jars == null) {
|
||||
return;
|
||||
}
|
||||
for (File jar : jars) {
|
||||
scanJarClasses(jar, scanPackageRules, excludePackageRules, result);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processJarIfMatches(File jarFile, List<FilterRule> scanPluginRules, List<FilterRule> scanPackageRules, List<FilterRule> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static void scanJarClasses(File jarFile, List<FilterRule> scanPackageRules, List<FilterRule> 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
jar.close();
|
||||
} catch (IOException e) {
|
||||
DebugInfo.warn("[PRIVACY-SCAN] resolve plugin id failed: " + jarFile, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String parsePluginIdFromXml(File xmlFile) {
|
||||
try {
|
||||
FileInputStream input = new FileInputStream(xmlFile);
|
||||
String strExtractXmlTag = extractXmlTag(readUtf8(input));
|
||||
input.close();
|
||||
return strExtractXmlTag;
|
||||
} catch (IOException e) {
|
||||
DebugInfo.warn("[PRIVACY-SCAN] parse plugin xml failed: " + xmlFile, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String parsePluginId(JarFile jar) throws IOException {
|
||||
JarEntry entry = jar.getJarEntry(PLUGIN_XML);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static void extractClasses(JarFile jar, List<FilterRule> scanPackageRules, List<FilterRule> excludePackageRules, ScanResult result) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
String name = entry.getName();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJar(File file) {
|
||||
return file != null && file.isFile() && file.getName().endsWith(JAR_SUFFIX);
|
||||
}
|
||||
|
||||
private static boolean matchRules(String value, List<FilterRule> rules) {
|
||||
if (value == null || rules == null || rules.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (FilterRule rule : rules) {
|
||||
if (rule != null && rule.test(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
236
src/main/java/com/novitechie/scan/JbDirectoryScanner.java
Normal file
236
src/main/java/com/novitechie/scan/JbDirectoryScanner.java
Normal file
@@ -0,0 +1,236 @@
|
||||
package com.novitechie.scan;
|
||||
|
||||
import com.janetfilter.core.commons.DebugInfo;
|
||||
import com.janetfilter.core.models.FilterRule;
|
||||
import com.janetfilter.core.plugin.PluginConfig;
|
||||
import com.novitechie.utils.PropertyUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public final 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_PLUGINS_PATH_PROPERTY = "idea.plugins.path";
|
||||
|
||||
private JbDirectoryScanner() {
|
||||
}
|
||||
|
||||
public static List<File> getPluginDir(PluginConfig config) {
|
||||
Class<?> pathManagerClass = loadClass(PATH_MANAGER_CLASS);
|
||||
List<FilterRule> pluginPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION);
|
||||
if (pluginPathRules == null || pluginPathRules.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<File> pluginRoots = new LinkedHashSet<>();
|
||||
for (FilterRule pluginPathRule : pluginPathRules) {
|
||||
addConfiguredPluginDir(pluginRoots, selector, pluginPathRule);
|
||||
}
|
||||
if (pluginRoots.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);
|
||||
}
|
||||
|
||||
public static List<File> getPluginDir() {
|
||||
Class<?> pathManagerClass = loadClass(PATH_MANAGER_CLASS);
|
||||
return getPathManagerPluginDirs(pathManagerClass);
|
||||
}
|
||||
|
||||
private static List<File> getPathManagerPluginDirs(Class<?> pathManagerClass) {
|
||||
if (pathManagerClass == null) {
|
||||
DebugInfo.warn("[PRIVACY-SCAN] PathManager not found");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Set<File> 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);
|
||||
}
|
||||
return new ArrayList<>(pluginRoots);
|
||||
}
|
||||
|
||||
private static void addDirFromIdeaProperties(Class<?> pathManagerClass, Set<File> pluginRoots) {
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addConfiguredPluginDir(Set<File> pluginRoots, String selector, FilterRule pluginPathRule) {
|
||||
if (pluginPathRule == null || isBlank(pluginPathRule.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);
|
||||
return;
|
||||
}
|
||||
String selectorRule = ruleValue.substring(0, separatorIndex).trim();
|
||||
String pluginPath = ruleValue.substring(separatorIndex + 1).trim();
|
||||
FilterRule selectorFilter = new FilterRule(pluginPathRule.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());
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveCurrentSelector(Class<?> pathManagerClass) {
|
||||
String selector = getProperty(IDEA_PATHS_SELECTOR_PROPERTY);
|
||||
if (!isBlank(selector)) {
|
||||
return selector;
|
||||
}
|
||||
return invokePathManagerString(pathManagerClass, "getPathsSelector");
|
||||
}
|
||||
|
||||
private static Class<?> loadClass(String className) {
|
||||
ClassLoader[] classLoaders = {Thread.currentThread().getContextClassLoader(), JbDirectoryScanner.class.getClassLoader(), ClassLoader.getSystemClassLoader()};
|
||||
for (ClassLoader classLoader : classLoaders) {
|
||||
Class<?> loadedClass = tryLoadClass(className, classLoader);
|
||||
if (loadedClass != null) {
|
||||
return loadedClass;
|
||||
}
|
||||
}
|
||||
return tryLoadClass(className, null);
|
||||
}
|
||||
|
||||
private static Class<?> tryLoadClass(String className, ClassLoader classLoader) {
|
||||
try {
|
||||
if (classLoader == null) {
|
||||
return Class.forName(className);
|
||||
}
|
||||
return Class.forName(className, false, classLoader);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static File invokePathManagerDir(Class<?> pathManagerClass, String methodName) {
|
||||
try {
|
||||
Method method = pathManagerClass.getMethod(methodName);
|
||||
return asFile(method.invoke(null));
|
||||
} catch (Exception e) {
|
||||
DebugInfo.error("[PRIVACY-SCAN] invokePathManagerDir failed (" + methodName + ")", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String invokePathManagerString(Class<?> pathManagerClass, String methodName) {
|
||||
if (pathManagerClass == null) {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static File asFile(Object value) {
|
||||
if (value instanceof File) {
|
||||
return (File) value;
|
||||
}
|
||||
if (value instanceof Path) {
|
||||
return ((Path) value).toFile();
|
||||
}
|
||||
if ((value instanceof String) && !isBlank((String) value)) {
|
||||
return new File((String) value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addDir(Set<File> dirs, File dir) {
|
||||
if (dir == null || !dir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
dirs.add(dir);
|
||||
}
|
||||
|
||||
private static String stripSurroundingQuotes(String value) {
|
||||
if (value == null || value.length() < 2) {
|
||||
return value;
|
||||
}
|
||||
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||
return value.substring(1, value.length() - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String getProperty(String name) {
|
||||
if (isBlank(name)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return System.getProperty(name);
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
88
src/main/java/com/novitechie/scan/RuleMerger.java
Normal file
88
src/main/java/com/novitechie/scan/RuleMerger.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.novitechie.scan;
|
||||
|
||||
import com.janetfilter.core.commons.DebugInfo;
|
||||
import com.janetfilter.core.models.FilterRule;
|
||||
|
||||
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";
|
||||
private static final String MARKER_RESOURCE_RULE = "/6c81ec87e55d331c267262e892427a3d93d76683.txt";
|
||||
|
||||
private RuleMerger() {
|
||||
}
|
||||
|
||||
public static List<FilterRule> merge(List<FilterRule> first, List<FilterRule> second) {
|
||||
List<FilterRule> mergedRules = new ArrayList<>();
|
||||
addRules(mergedRules, first);
|
||||
addRules(mergedRules, second);
|
||||
return mergedRules;
|
||||
}
|
||||
|
||||
public static List<FilterRule> toResourceRules(List<FilterRule> classRules) {
|
||||
List<FilterRule> resourceRules = new ArrayList<>();
|
||||
if (classRules == null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return resourceRules;
|
||||
}
|
||||
|
||||
public static List<FilterRule> adjustMarkerResourceRule(List<FilterRule> hideResourceRules, boolean markerResourceExists) {
|
||||
List<FilterRule> adjustedRules = new ArrayList<>();
|
||||
addRules(adjustedRules, 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;
|
||||
}
|
||||
addRule(adjustedRules, markerRule);
|
||||
DebugInfo.output("[PRIVACY-SCAN] Marker resource not found, added Hide_Resource rule: /6c81ec87e55d331c267262e892427a3d93d76683.txt");
|
||||
return adjustedRules;
|
||||
}
|
||||
|
||||
private static void addRules(List<FilterRule> target, List<FilterRule> rules) {
|
||||
if (rules == null) {
|
||||
return;
|
||||
}
|
||||
for (FilterRule rule : rules) {
|
||||
addRule(target, rule);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRule(List<FilterRule> target, FilterRule rule) {
|
||||
if (rule == null) {
|
||||
return;
|
||||
}
|
||||
for (FilterRule existingRule : target) {
|
||||
if (sameRule(existingRule, rule)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
target.add(rule);
|
||||
}
|
||||
|
||||
private static void removeRule(List<FilterRule> target, FilterRule rule) {
|
||||
if (target == null || rule == null) {
|
||||
return;
|
||||
}
|
||||
for (int index = target.size() - 1; index >= 0; index--) {
|
||||
if (sameRule(target.get(index), rule)) {
|
||||
target.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean sameRule(FilterRule left, FilterRule right) {
|
||||
return Objects.equals(left.getType(), right.getType()) && Objects.equals(left.getRule(), right.getRule());
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/novitechie/utils/PropertyUtil.java
Normal file
40
src/main/java/com/novitechie/utils/PropertyUtil.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.novitechie.utils;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author YeloChick
|
||||
*/
|
||||
public class PropertyUtil {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)}");
|
||||
|
||||
public static String getProperty(Properties prop, String key) {
|
||||
String value = prop.getProperty(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return resolvePlaceholders(prop, value);
|
||||
}
|
||||
|
||||
private static String resolvePlaceholders(Properties prop, String value) {
|
||||
Matcher matcher = PATTERN.matcher(value);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String placeholderKey = matcher.group(1);
|
||||
String placeholderValue = prop.getProperty(placeholderKey);
|
||||
|
||||
if (placeholderValue != null) {
|
||||
placeholderValue = resolvePlaceholders(prop, placeholderValue);
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(placeholderValue));
|
||||
} else {
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0)));
|
||||
}
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user