From cae96dd654a5d361717d6a7cf9df45f851ad9728 Mon Sep 17 00:00:00 2001 From: yelochick Date: Mon, 8 Jun 2026 16:07:08 +0800 Subject: [PATCH] feat: auto scan plugins --- .../java/com/novitechie/PrivacyPlugin.java | 13 +- .../novitechie/scan/CanaryAutoScanner.java | 119 ++++++++ .../com/novitechie/scan/CanaryScanner.java | 256 ++++++++++++++++++ .../novitechie/scan/JbDirectoryScanner.java | 236 ++++++++++++++++ .../java/com/novitechie/scan/RuleMerger.java | 88 ++++++ .../com/novitechie/utils/PropertyUtil.java | 40 +++ 6 files changed, 750 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/novitechie/scan/CanaryAutoScanner.java create mode 100644 src/main/java/com/novitechie/scan/CanaryScanner.java create mode 100644 src/main/java/com/novitechie/scan/JbDirectoryScanner.java create mode 100644 src/main/java/com/novitechie/scan/RuleMerger.java create mode 100644 src/main/java/com/novitechie/utils/PropertyUtil.java diff --git a/src/main/java/com/novitechie/PrivacyPlugin.java b/src/main/java/com/novitechie/PrivacyPlugin.java index 15a7964..0a22fd1 100644 --- a/src/main/java/com/novitechie/PrivacyPlugin.java +++ b/src/main/java/com/novitechie/PrivacyPlugin.java @@ -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 autoScanClassRules = autoScanResult.getClassRules(); + List autoScanResourceRules = RuleMerger.toResourceRules(autoScanClassRules); + List hideResourceRules = RuleMerger.adjustMarkerResourceRule(config.getBySection("Hide_Resource"), autoScanResult.isMarkerResourceExists()); + List ignoreClassRules = RuleMerger.merge(config.getBySection("Ignore_Class"), autoScanClassRules); + List ignoreResourceRules = RuleMerger.merge(config.getBySection("Ignore_Resource"), autoScanResourceRules); IdeaPluginRule.initRules(config.getBySection("Hide_Plugin")); - LoadClassRule.initRules(config.getBySection("Hide_Package"), 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")); } diff --git a/src/main/java/com/novitechie/scan/CanaryAutoScanner.java b/src/main/java/com/novitechie/scan/CanaryAutoScanner.java new file mode 100644 index 0000000..8aa86c1 --- /dev/null +++ b/src/main/java/com/novitechie/scan/CanaryAutoScanner.java @@ -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 DEFAULT_SCAN_PLUGINS = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.EQUAL, "izhangzhihao.rainbow.brackets"))); + private static final List DEFAULT_SCAN_PACKAGES = Collections.unmodifiableList(Arrays.asList(new FilterRule(RuleType.PREFIX, "com.janetfilter."), new FilterRule(RuleType.PREFIX, "com.novitechie."))); + + private CanaryAutoScanner() { + } + + public static final class AutoScanResult { + private final List classRules; + private final boolean markerResourceExists; + + private AutoScanResult(List classRules, boolean markerResourceExists) { + this.classRules = classRules == null ? Collections.emptyList() : classRules; + this.markerResourceExists = markerResourceExists; + } + + public static AutoScanResult empty() { + return new AutoScanResult(Collections.emptyList(), false); + } + + public List getClassRules() { + return new ArrayList(this.classRules); + } + + public boolean isMarkerResourceExists() { + return this.markerResourceExists; + } + } + + public static List scan(PluginConfig config) { + return scanWithResult(config).getClassRules(); + } + + public static AutoScanResult scanWithResult(PluginConfig config) { + List pluginRules = rules(config, "Auto_Scan_Plugin", DEFAULT_SCAN_PLUGINS); + List packageRules = rules(config, "Auto_Scan_Package", DEFAULT_SCAN_PACKAGES); + List excludeRules = emptyIfNull(config.getBySection("Auto_Scan_Exclude")); + if (pluginRules.isEmpty() || packageRules.isEmpty()) { + DebugInfo.warn("[PRIVACY-SCAN] Auto scan disabled: plugin or package rules are empty"); + return AutoScanResult.empty(); + } + List scanTargets = JbDirectoryScanner.getPluginDir(config); + if (scanTargets.isEmpty()) { + DebugInfo.warn("[PRIVACY-SCAN] No plugin scan targets found"); + return AutoScanResult.empty(); + } + DebugInfo.output("[PRIVACY-SCAN] Plugin targets: " + fileSummary(scanTargets)); + DebugInfo.output("[PRIVACY-SCAN] Scan_Plugin: " + ruleSummary(pluginRules)); + DebugInfo.output("[PRIVACY-SCAN] Scan_Package: " + ruleSummary(packageRules)); + DebugInfo.output("[PRIVACY-SCAN] Exclude_Package: " + ruleSummary(excludeRules)); + CanaryScanner.ScanResult result = CanaryScanner.scan(scanTargets, pluginRules, packageRules, excludeRules); + List classNames = result.getClassNames(); + List classRules = new ArrayList<>(classNames.size()); + DebugInfo.output("[PRIVACY-SCAN] Found " + classNames.size() + " canary classes"); + for (String className : classNames) { + classRules.add(FilterRule.of("EQUAL", className)); + DebugInfo.output("[PRIVACY-SCAN] - " + className); + } + DebugInfo.output("[PRIVACY-SCAN] Marker resource exists: " + result.isMarkerResourceExists()); + return new AutoScanResult(classRules, result.isMarkerResourceExists()); + } + + private static List rules(PluginConfig config, String section, List defaultValue) { + List configRules = config.getBySection(section); + if (hasSection(config, section)) { + return emptyIfNull(configRules); + } + if (defaultValue == null) { + return Collections.emptyList(); + } + return defaultValue; + } + + private static boolean hasSection(PluginConfig config, String section) { + return config.getData() != null && config.getData().containsKey(section); + } + + private static List emptyIfNull(List rules) { + return rules == null ? Collections.emptyList() : rules; + } + + private static String fileSummary(List files) { + StringBuilder summary = new StringBuilder(); + for (File file : files) { + if (summary.length() > 0) { + summary.append(", "); + } + summary.append(file.getAbsolutePath()); + } + return summary.toString(); + } + + private static String ruleSummary(List rules) { + if (rules == null || rules.isEmpty()) { + return "(none)"; + } + StringBuilder summary = new StringBuilder(); + for (FilterRule rule : rules) { + if (summary.length() > 0) { + summary.append(", "); + } + summary.append(rule); + } + return summary.toString(); + } +} diff --git a/src/main/java/com/novitechie/scan/CanaryScanner.java b/src/main/java/com/novitechie/scan/CanaryScanner.java new file mode 100644 index 0000000..01645c3 --- /dev/null +++ b/src/main/java/com/novitechie/scan/CanaryScanner.java @@ -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[^>]*)?>(.*?)", 32); + + private CanaryScanner() { + } + + public static final class ScanResult { + private final Set classNames = new LinkedHashSet(); + private boolean markerResourceExists; + + public List 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 scanTargets, List scanPluginRules, List scanPackageRules, List 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 scanPluginRules, List scanPackageRules, List 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 scanPackageRules, List excludePackageRules, ScanResult result) { + File libDir = new File(pluginDir, LIB_DIR); + if (!libDir.isDirectory()) { + return; + } + scanJars(listJars(libDir), scanPackageRules, excludePackageRules, result); + } + + private static void scanSubFiles(File target, List scanPluginRules, List scanPackageRules, List excludePackageRules, ScanResult result) { + File[] subFiles = target.listFiles(); + if (subFiles == null) { + return; + } + 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 scanPackageRules, List excludePackageRules, ScanResult result) { + if (jars == null) { + return; + } + for (File jar : jars) { + scanJarClasses(jar, scanPackageRules, excludePackageRules, result); + } + } + + private static void processJarIfMatches(File jarFile, List scanPluginRules, List scanPackageRules, List excludePackageRules, ScanResult result) { + try { + JarFile jar = new JarFile(jarFile); + String pluginId = parsePluginId(jar); + if (matchRules(pluginId, scanPluginRules)) { + extractClasses(jar, scanPackageRules, excludePackageRules, result); + } + jar.close(); + } catch (IOException e) { + DebugInfo.warn("[PRIVACY-SCAN] scan jar failed: " + jarFile, e); + } + } + + private static void scanJarClasses(File jarFile, List scanPackageRules, List excludePackageRules, ScanResult result) { + try { + JarFile jar = new JarFile(jarFile); + extractClasses(jar, scanPackageRules, excludePackageRules, result); + jar.close(); + } catch (IOException e) { + DebugInfo.warn("[PRIVACY-SCAN] scan jar classes failed: " + jarFile, e); + } + } + + 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 scanPackageRules, List excludePackageRules, ScanResult result) { + Enumeration 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 rules) { + if (value == null || rules == null || rules.isEmpty()) { + return false; + } + for (FilterRule rule : rules) { + if (rule != null && rule.test(value)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/novitechie/scan/JbDirectoryScanner.java b/src/main/java/com/novitechie/scan/JbDirectoryScanner.java new file mode 100644 index 0000000..05c9dea --- /dev/null +++ b/src/main/java/com/novitechie/scan/JbDirectoryScanner.java @@ -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 getPluginDir(PluginConfig config) { + Class pathManagerClass = loadClass(PATH_MANAGER_CLASS); + List pluginPathRules = config == null ? null : config.getBySection(IDEA_PLUGIN_PATH_SECTION); + if (pluginPathRules == null || pluginPathRules.isEmpty()) { + DebugInfo.warn("[PRIVACY-SCAN] No configured IDEA plugin path rules found, fallback to PathManager"); + return getPathManagerPluginDirs(pathManagerClass); + } + String selector = resolveCurrentSelector(pathManagerClass); + if (isBlank(selector)) { + DebugInfo.warn("[PRIVACY-SCAN] Current IDEA selector not found"); + return Collections.emptyList(); + } + DebugInfo.output("[PRIVACY-SCAN] Current IDEA selector: " + selector); + Set pluginRoots = new LinkedHashSet<>(); + for (FilterRule pluginPathRule : pluginPathRules) { + addConfiguredPluginDir(pluginRoots, selector, pluginPathRule); + } + 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 getPluginDir() { + Class pathManagerClass = loadClass(PATH_MANAGER_CLASS); + return getPathManagerPluginDirs(pathManagerClass); + } + + private static List getPathManagerPluginDirs(Class pathManagerClass) { + if (pathManagerClass == null) { + DebugInfo.warn("[PRIVACY-SCAN] PathManager not found"); + return Collections.emptyList(); + } + Set pluginRoots = new LinkedHashSet<>(); + addDirFromIdeaProperties(pathManagerClass, pluginRoots); + if (pluginRoots.isEmpty()) { + addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsDir")); + addDir(pluginRoots, invokePathManagerDir(pathManagerClass, "getPluginsPath")); + DebugInfo.output("[PRIVACY-SCAN] PathManager plugin roots: " + pluginRoots); + } + return new ArrayList<>(pluginRoots); + } + + private static void addDirFromIdeaProperties(Class pathManagerClass, Set 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 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 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(); + } +} diff --git a/src/main/java/com/novitechie/scan/RuleMerger.java b/src/main/java/com/novitechie/scan/RuleMerger.java new file mode 100644 index 0000000..6ace938 --- /dev/null +++ b/src/main/java/com/novitechie/scan/RuleMerger.java @@ -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 merge(List first, List second) { + List mergedRules = new ArrayList<>(); + addRules(mergedRules, first); + addRules(mergedRules, second); + return mergedRules; + } + + public static List toResourceRules(List classRules) { + List 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 adjustMarkerResourceRule(List hideResourceRules, boolean markerResourceExists) { + List 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 target, List rules) { + if (rules == null) { + return; + } + for (FilterRule rule : rules) { + addRule(target, rule); + } + } + + private static void addRule(List target, FilterRule rule) { + if (rule == null) { + return; + } + for (FilterRule existingRule : target) { + if (sameRule(existingRule, rule)) { + return; + } + } + target.add(rule); + } + + private static void removeRule(List 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()); + } +} diff --git a/src/main/java/com/novitechie/utils/PropertyUtil.java b/src/main/java/com/novitechie/utils/PropertyUtil.java new file mode 100644 index 0000000..da031c3 --- /dev/null +++ b/src/main/java/com/novitechie/utils/PropertyUtil.java @@ -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(); + } +}