diff --git a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/EnvironmentPlaceholders.java b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/EnvironmentPlaceholders.java index e1cdcfc3b..cb440b508 100644 --- a/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/EnvironmentPlaceholders.java +++ b/quickshop-bukkit/src/main/java/com/ghostchu/quickshop/util/config/EnvironmentPlaceholders.java @@ -1,100 +1,106 @@ package com.ghostchu.quickshop.util.config; -import java.io.File; -import java.io.IOException; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; // Keeps SubstAgent placeholders on disk when the config is written back. -// SubstAgent substitutes while the JVM reads through FileInputStream. -// Bukkit loads configs that way, so the in-memory value is already resolved. -// NIO reads are not intercepted, so the raw tokens are recovered with NIO. +// SubstAgent expands ^{ENV_VAR} while the server reads a config file, so the +// value held in memory is already resolved and saving it would burn the secret +// into the file. The live file cannot be re-read to recover the token, because +// SubstAgent intercepts the stream and the NIO read paths alike, so the token +// names come from the default config inside the plugin jar instead. A jar is +// not a config file, so that copy still carries the raw placeholders. public final class EnvironmentPlaceholders { - // SubstAgent $ENV_VAR form, plus the older ^{ENV_VAR} form. - private static final Pattern PLACEHOLDER = Pattern - .compile("\\$[A-Za-z_][A-Za-z0-9_]*|\\^\\{[A-Za-z_][A-Za-z0-9_]*}"); + // The ^{ENV_VAR} form SubstAgent expands, group 1 is the variable name. + private static final Pattern PLACEHOLDER = Pattern.compile("^\\^\\{([A-Za-z_][A-Za-z0-9_]*)}$"); private EnvironmentPlaceholders() { } - // Reads the placeholder tokens still present in the file on disk. + // Maps each config key the bundled default declares to its variable name. @NotNull - public static Map readTokens(@NotNull File file) { + public static Map readBundledPlaceholders(@NotNull JavaPlugin plugin) { - if (!file.isFile()) { + InputStream resource = plugin.getResource("config.yml"); + if (resource == null) { return Collections.emptyMap(); } - String raw; - try { + YamlConfiguration bundled; + try (InputStreamReader reader = new InputStreamReader(new BufferedInputStream(resource), + StandardCharsets.UTF_8)) + { - // NIO read, a FileInputStream read would be substituted. - raw = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + bundled = YamlConfiguration.loadConfiguration(reader); - } catch (IOException e) { + } catch (Exception e) { return Collections.emptyMap(); } - YamlConfiguration onDisk = new YamlConfiguration(); - try { + Map placeholders = new LinkedHashMap<>(); + for (String key : bundled.getKeys(true)) { - onDisk.loadFromString(raw); + Object value = bundled.get(key); + if (!(value instanceof String string)) { - } catch (InvalidConfigurationException e) { + continue; - return Collections.emptyMap(); + } - } + Matcher matcher = PLACEHOLDER.matcher(string.trim()); + if (matcher.matches()) { - Map tokens = new LinkedHashMap<>(); - for (String key : onDisk.getKeys(true)) { - - Object value = onDisk.get(key); - if (value instanceof String string && PLACEHOLDER.matcher(string).find()) { - - tokens.put(key, string); + placeholders.put(key, matcher.group(1)); } } - return tokens; + return placeholders; } // Saves the config without persisting substituted placeholder values. public static void saveConfig(@NotNull JavaPlugin plugin) { - File file = new File(plugin.getDataFolder(), "config.yml"); - Map tokens = readTokens(file); - if (tokens.isEmpty()) { - - plugin.saveConfig(); - return; - - } - FileConfiguration config = plugin.getConfig(); - Map substituted = new LinkedHashMap<>(); - for (Map.Entry token : tokens.entrySet()) { + Map resolved = new LinkedHashMap<>(); + for (Map.Entry placeholder : readBundledPlaceholders(plugin).entrySet()) { - substituted.put(token.getKey(), config.get(token.getKey())); - config.set(token.getKey(), token.getValue()); + String variable = System.getenv(placeholder.getValue()); + if (variable == null) { + + continue; + + } + + Object current = config.get(placeholder.getKey()); + // Only a value equal to the variable can have come from SubstAgent. + if (!(current instanceof String string) || !string.equals(variable)) { + + continue; + + } + + resolved.put(placeholder.getKey(), current); + config.set(placeholder.getKey(), "^{" + placeholder.getValue() + "}"); } @@ -105,7 +111,7 @@ public final class EnvironmentPlaceholders { } finally { // Restore the resolved values for the running server. - for (Map.Entry value : substituted.entrySet()) { + for (Map.Entry value : resolved.entrySet()) { config.set(value.getKey(), value.getValue()); diff --git a/quickshop-bukkit/src/main/resources/config.yml b/quickshop-bukkit/src/main/resources/config.yml index 0a9046f2a..6d5271fe0 100644 --- a/quickshop-bukkit/src/main/resources/config.yml +++ b/quickshop-bukkit/src/main/resources/config.yml @@ -132,9 +132,9 @@ database: # The database names. (Only required if mysql is true) database: quickshop # The database username. (Only required if mysql is true) - user: $MARIADB_USER + user: ^{MARIADB_USER} # The database password. (Only required if mysql is true) - password: $MARIADB_PASSWORD + password: ^{MARIADB_PASSWORD} # Set prefix to "none" to remove prefix (Both local and remote will be used). prefix: none # Should QuickShop use SSL for database connections? (Only required if mysql is true)